Funktionsweise

Wie der MCP-Server technisch aufgebaut ist: Endpunkt, Transport, Authentifizierung, interner Aufbau und eine bekannte Einschränkung.

Endpunkt

FunctionMcp (DataBridge/Functions/McpApi.cs)
RoutePOST /api/mcp (JSON-RPC-Austausch); GET /api/mcp liefert bewusst 405 — siehe Bekannte Einschränkung
Auth-LevelFunction (Function Key als ?code=-Query-Parameter)
ProtokollMCP über Streamable HTTP, stateless — jede POST-Anfrage ist ein eigenständiger JSON-RPC-Austausch, es gibt keine langlebige Server-Session

Der Server ist damit direkt an einer laufenden Function App (lokal oder in Azure) ansprechbar, ohne eigenen Prozess oder zusätzliche Infrastruktur. Da die Authentifizierung über den normalen Azure-Functions-Mechanismus läuft (Function Key), lässt sich der Server ohne zusätzliche Identity-Provider-Konfiguration einrichten — der Key kann als Query-Parameter oder als Header übergeben werden, je nachdem, was der jeweilige Client unterstützt (siehe Nutzung).


Interner Aufbau

Der Mcp-Function-Trigger baut pro HTTP-Anfrage einen eigenen StreamableHttpServerTransport auf und delegiert die eigentliche Tool-Logik an den IMcpToolService (DataBridge.Core/Services/Mcp/), der wiederum dieselben Service-Interfaces (IJobService, IMappingService, IServiceBusService, IConnectionService) aufruft wie die klassischen HTTP-Funktionen — es gibt keine internen Selbstaufrufe zwischen den Functions. McpApi.cs bleibt dadurch auf die reine Protokoll-Anbindung beschränkt (Tool-Registrierung, Transport), analog zum dünnen Zuschnitt von JobApi/MappingApi. Die eigentliche Tool-Logik (Init-Guard, Job-/Mapping-Lesezugriff, Service-Bus-Delegation, Connector-Auflösung) liegt in IMcpToolService, testbar unabhängig vom Protokoll-Layer. Details zur Service-Schicht: Architektur.

Die [Description]-Attribute und Standardwerte für die Tool-Parameter liegen bewusst auf den Methoden von IMcpToolService selbst (reines System.ComponentModel) — McpApi.cs bindet die Tools direkt auf diese Service-Methoden, ohne zusätzliche Wrapper-Methoden. Der Transport-Teil (ModelContextProtocol.AspNetCore) bleibt im Host; DataBridge.Core referenziert nur ModelContextProtocol.Core, und das ausschließlich für die McpException (siehe unten).

Fehlerweitergabe an den Client

Der MCP-C#-SDK ersetzt jede unbehandelte Exception eines Tools durch den generischen Text „An error occurred invoking ‘…’.“ und hängt nur bei einer McpException deren Message an. Alle anderen Exceptions landen zwar im Log, der Client sieht aber nicht, warum der Aufruf fehlgeschlagen ist. Ein CallToolFilter hilft hier nicht, weil der Catch im SDK innerhalb des Tool-Aufrufs sitzt. McpToolService wirft deshalb bei den beiden Connector-Tools (list_connector_tables, get_connector_table_schema) Connector-Fehler und unbekannte Connection-Namen als McpException weiter (SurfaceConnectorErrors), sodass z. B. die angefragte $metadata-URL und der HTTP-Status beim Client ankommen. Der Test McpApiTests.ToolsCall_ListConnectorTables_ConnectorFails_ErrorResultCarriesTheCause sichert dieses Verhalten durch die echte SDK-Pipeline ab.


Bekannte Einschränkung: GET auf /api/mcp

Manche MCP-Clients (typischerweise Remote-Clients, die den Endpunkt über eine echte Azure-Deployment-URL statt lokal ansprechen) versuchen nach dem initialize-POST zusätzlich einen GET auf denselben Endpunkt, um einen optionalen Server-zu-Client-SSE-Stream zu öffnen (Teil der Streamable-HTTP-Spezifikation).

Da dieser Server stateless ist (keine Session, kein Server-Push), unterstützt er diesen Stream bewusst nicht. Die Route ist deshalb zusätzlich zum POST-Handler mit einem eigenen GET-Handler registriert, der spezifikationskonform mit 405 Method Not Allowed (statt einem nicht aussagekräftigen, von Azure Functions automatisch erzeugten 404) antwortet:

[Function("McpGet")]
public IActionResult HandleMcpGetRequest(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "mcp")] HttpRequest req)
{
    req.HttpContext.Response.Headers.Append("Allow", "POST");
    return new StatusCodeResult(StatusCodes.Status405MethodNotAllowed);
}

Manche Clients interpretieren 405 als „kein SSE-Support, aber Server ist erreichbar" und fallen automatisch auf reines POST zurück; ein 404 liest sich dagegen wie „Endpunkt existiert nicht" und lässt die Verbindung fehlschlagen. Ohne diesen Handler tritt das Problem nur bei Remote-/Azure-Deployments zuverlässig auf — lokal (Claude Code über .mcp.json) sendet der Client i. d. R. keinen GET-Probe-Request, daher blieb es dort unbemerkt.