Delphi MCP-Server und -Client: die neue MCP-2026-07-28-Spezifikation

· Komponenten
Delphi MCP-Server und -Client: die neue MCP-2026-07-28-Spezifikation

Das Model Context Protocol hat eine neue Spezifikation, MCP 2026-07-28. Es ist die größte Änderung am Protokoll seit Streamable HTTP: Der Session-Handshake entfällt, jeder Request trägt alles, was der Server zur Beantwortung braucht, und lang laufende oder interaktive Aufgaben werden erstklassig unterstützt. sgcWebSockets 2026.10.0 implementiert dies sowohl im MCP-Server als auch im MCP-Client für Delphi und C++Builder.

Das Wichtigste für alle, die bereits einen Delphi-MCP-Server betreiben: Nichts bricht. Der Server spricht beide Epochen. Clients, die 2025-11-25 sprechen, etwa VS Code oder Claude, rufen weiterhin initialize auf und erhalten eine Session genau wie bisher, während 2026-07-28-Clients auf demselben Endpunkt das neue zustandslose Modell nutzen. Du brauchst weder einen Schalter noch einen zweiten Server.

Was sich in MCP 2026-07-28 geändert hat

Kurz gesagt sind das die Änderungen, die beim Bau eines Servers oder Clients zählen:

Ein MCP-Server für beide Epochen in Delphi

Die Komponente TsgcWSAPIServer_MCP erkennt die Epoche jedes Requests. Ein Request, dessen _meta 2026-07-28 angibt, nimmt den zustandslosen Pfad, während initialize und die älteren Versionen den bereits bekannten Session-Pfad nehmen. Deine Tool-, Prompt- und Resource-Handler werden von beiden gemeinsam genutzt. Die neuen Optionen füllen nur das, was 2026-07-28-Clients über server/discover und die Cache-Hinweise lesen.

procedure TMainForm.FormCreate(Sender: TObject);
begin
  MCPServer.MCPOptions.ServerInfo.Name := 'tickets-mcp';
  // returned by server/discover to 2026-07-28 clients
  MCPServer.MCPOptions.Instructions := 'Use search_tickets before opening a ticket.';
  // cache hints returned with 2026-07-28 results (ttlMs, cacheScope)
  MCPServer.MCPOptions.Cache.TTLMs := 60000;
  MCPServer.MCPOptions.Cache.Scope := aimcpcsPublic;
  MCPServer.Active := True;
end;

Wenn ServerInfo.Name gesetzt ist, tragen 2026-07-28-Ergebnisse die Server-Info zusätzlich in _meta. Protokollfehler verwenden die neuen Codes: Ein Header, der nicht zum Body passt, beantwortet -32020, eine fehlende Client-Capability -32021 und eine nicht unterstützte Version -32022, mit der Liste der unterstützten Versionen in den Error-Daten.

Subscriptions mit subscriptions/listen

Subscriptions sind standardmäßig aktiv. Ein 2026-07-28-Client öffnet einen subscriptions/listen-Stream, und die Notifications, die du bereits mit SendNotificationToolsListChanged oder SendNotificationResourcesUpdated sendest, erreichen diese Listener ebenso wie die 2025-11-25-Sessions. Der Server sendet auf inaktiven Streams einen Keep-Alive-Kommentar, und wenn der Server deaktiviert wird, wird jede offene Subscription sauber mit ihrem Endergebnis geschlossen.

procedure TMainForm.FormCreate(Sender: TObject);
begin
  MCPServer.MCPOptions.Subscriptions.Enabled := True;
  MCPServer.MCPOptions.Subscriptions.KeepAliveInterval := 15000; // 0 disables it
end;

procedure TMainForm.ToolsChanged;
begin
  // delivered to legacy sessions and to every subscriptions/listen stream
  MCPServer.SendNotificationToolsListChanged;
end;

Multi-Round-Trip-Requests: den Benutzernamen erfragen

Mit MRTR fragt ein Handler nach weiteren Eingaben, indem er aResponse.InputRequired befüllt und zurückkehrt. Der Client sammelt die Antworten und ruft das Tool erneut auf. In der zweiten Runde werden die Antworten mit aRequest.InputResponse gelesen. Das folgende Beispiel ist das Tool ask_name aus der Server-Demo.

procedure TMainForm.MCPServerMCPRequestTool(Sender: TObject;
  const aSession: TsgcAI_MCP_Session;
  const aRequest: TsgcAI_MCP_Request_ToolsCall;
  const aResponse: TsgcAI_MCP_Response_ToolsCall);
begin
  if aRequest.Params.Name = 'ask_name' then
  begin
    if not aRequest.HasInputResponse('name') then
    begin
      // first round: ask the client for the user's name
      aResponse.InputRequired.AddElicitation('name', 'What is your name?',
        '{"type":"object","properties":{"name":{"type":"string"}},' +
        '"required":["name"]}');
      Exit;
    end;
    // second round: the answer arrives as raw JSON
    aResponse.Result.Content.AddText('Hello, ' + aRequest.InputResponse('name'));
  end;
end;

Der requestState reist signiert mit HMAC-SHA256 unter Verwendung von MCPOptions.MRTR.Secret und läuft nach MCPOptions.MRTR.StateTTL Sekunden ab, sodass ein Client ihn nicht manipulieren kann. URL-Elicitation (AddElicitationURL), Sampling (AddSampling) und Roots (AddRoots) folgen demselben Muster. Bevor der Server input_required beantwortet, prüft er, dass der Client die passende Capability deklariert hat.

Die Tasks-Erweiterung: ein long_job-Tool

Aktiviere Tasks in MCPOptions.Tasks und rufe CreateTask aus dem Tool-Handler auf. Wenn der Client bei diesem Request die Erweiterung io.modelcontextprotocol/tasks deklariert hat, antwortet der Aufruf sofort mit einer Task-ID, und dein Code erledigt die Arbeit in einem eigenen Thread. CreateTask liefert nil, wenn Tasks deaktiviert sind, der Request 2025-11-25 ist oder der Client die Erweiterung nicht deklariert hat, also behalte für diese Clients einen synchronen Pfad.

procedure TMainForm.FormCreate(Sender: TObject);
begin
  MCPServer.MCPOptions.Tasks.Enabled := True;
  MCPServer.MCPOptions.Tasks.TTL := 3600000;
  MCPServer.MCPOptions.Tasks.PollInterval := 1000;
end;

procedure TMainForm.MCPServerMCPRequestTool(Sender: TObject;
  const aSession: TsgcAI_MCP_Session;
  const aRequest: TsgcAI_MCP_Request_ToolsCall;
  const aResponse: TsgcAI_MCP_Response_ToolsCall);
var
  oTask: TsgcAI_MCP_Task;
begin
  if aRequest.Params.Name = 'long_job' then
  begin
    oTask := MCPServer.CreateTask(aSession, aResponse);
    if Assigned(oTask) then
      TLongJobThread.Create(oTask) // runs the job, see below
    else
      aResponse.Result.Content.AddText(RunLongJob);
  end;
end;

procedure TLongJobThread.Execute;
var
  i: Integer;
  oResponse: TsgcAI_MCP_Response_ToolsCall;
begin
  for i := 1 to 3 do
  begin
    if FTask.IsCancelled then
      Break;
    DoStep(i);
    FTask.SetStatusMessage(Format('long_job step %d of 3', [i]));
  end;
  if FTask.IsCancelled then
    FTask.Fail(CS_AI_MCP_INTERNAL_ERROR, 'Cancelled by the client')
  else
  begin
    oResponse := TsgcAI_MCP_Response_ToolsCall.Create;
    try
      oResponse.Result.Content.AddText('long_job finished after 3 steps');
      FTask.Complete(oResponse);
    finally
      oResponse.Free;
    end;
  end;
end;

Das Task-Objekt ist thread-sicher. OnMCPTaskCancel feuert, wenn der Client tasks/cancel aufruft, und OnMCPTaskUpdate, wenn er eine auf Eingabe wartende Task beantwortet. Tasks sind an den Principal gebunden, der sie erstellt hat, und laufen nach TTL Millisekunden ab.

Der MCP-Client: ProtocolEra und Discover

Die Komponente TsgcWSAPIClient_MCP erhält eine neue Eigenschaft MCPOptions.ProtocolEra. Lässt du sie auf aimcpeAuto, prüft der Client den Server mit server/discover, verwendet 2026-07-28, wenn der Server es unterstützt, und fällt andernfalls auf den initialize-Handshake zurück. Die ausgehandelte Epoche wird pro Endpunkt zwischengespeichert. Setze sie auf aimcpeModern oder aimcpeLegacy, um eine davon zu erzwingen.

procedure TMainForm.Connect;
begin
  MCPClient.MCPOptions.ProtocolEra := aimcpeAuto;
  if MCPClient.Initialize then
  begin
    MemoLog.Lines.Add('protocol: ' + MCPClient.NegotiatedProtocolVersion);
    if MCPClient.NegotiatedEra = aimcpeModern then
      MCPClient.Discover; // result in OnMCPDiscover and ServerDiscover
    MCPClient.ListTools;
  end;
end;

Auf dem 2026-07-28-Pfad schreibt der Client bei jedem Request _meta, sendet die Routing-Header, einschließlich Mcp-Param-* für Tools, die x-mcp-header deklarieren, und öffnet Subscriptions mit SubscriptionsListen. Listenänderungen kommen in OnMCPListChanged an und Resource-Updates in OnMCPResourcesUpdated. Mit MCPOptions.Tasks.Enabled deklariert der Client die Tasks-Erweiterung und pollt Tasks eigenständig, wobei er OnMCPTaskCreated, OnMCPTaskStatus und OnMCPTaskCompleted feuert.

MRTR-Fragen beantworten mit OnMCPInputRequired

Wenn ein Server input_required beantwortet, feuert der Client OnMCPInputRequired mit den offenen Fragen. Beantworte jeden Key und lass Accept auf True. Der Client wiederholt den Aufruf dann mit den Antworten und dem signierten requestState, bis zu MCPOptions.MRTR.MaxRounds Runden. Setze MCPOptions.MRTR.Elicitation, Sampling oder Roots, um diese Capabilities zu deklarieren.

procedure TMainForm.MCPClientMCPInputRequired(Sender: TObject;
  const aMethod: string;
  const aInputRequests: TsgcAI_MCP_Client_InputRequests;
  var Accept: Boolean);
var
  i: Integer;
begin
  for i := 0 to aInputRequests.Count - 1 do
    if aInputRequests.Methods[i] = 'elicitation/create' then
      aInputRequests.SetElicitationAccept(aInputRequests.Keys[i],
        '{"name":"Sergio"}');
  Accept := True;
end;

Dasselbe Ereignis deckt auch Tasks ab, die auf Eingaben warten, wobei aMethod dann tasks/update ist. Legacy-Server, die roots-, sampling- oder elicitation-Requests weiterhin direkt senden, erhalten ihre Antworten über OnMCPListRoots, OnMCPSamplingCreateMessage und OnMCPElicitationCreate.

Client-Autorisierung mit MCPOptions.Authorization

Setze MCPOptions.Authorization.Enabled, und ein 401 oder 403 vom Server startet den MCP-Autorisierungsflow: Protected Resource Metadata Discovery, Registrierung (eine vorregistrierte ClientId, ein Client ID Metadata Document von ClientMetadataURL oder dynamische Registrierung), PKCE mit dem Resource-Indicator, die Browser-Anmeldung auf der Loopback-RedirectURL, iss-Validierung und der Token-Request. Der ursprüngliche Request wird danach wiederholt, und Tokens werden erneuert, bevor sie ablaufen.

procedure TMainForm.FormCreate(Sender: TObject);
begin
  MCPClient.MCPOptions.Authorization.Enabled := True;
  MCPClient.MCPOptions.Authorization.ClientMetadataURL :=
    'https://app.example.com/oauth/client.json';
  MCPClient.MCPOptions.Authorization.AllowDynamicRegistration := True;
  MCPClient.MCPOptions.Authorization.RedirectURL := 'http://127.0.0.1:33418/callback';
  MCPClient.OnMCPAuthorizationURL := MCPClientAuthorizationURL;
end;

procedure TMainForm.MCPClientAuthorizationURL(Sender: TObject;
  const aURL: string; var Handled: Boolean);
begin
  MemoLog.Lines.Add('Sign in at ' + aURL);
  Handled := False; // False: the default browser is opened
end;

Credentials werden pro Issuer aufbewahrt. Behandle OnMCPAuthorizationCredentials, um sie zu speichern und beim nächsten Lauf zu laden, sodass sich der Benutzer nur einmal anmeldet.

Auch in sgcWebSockets .NET

Alles in diesem Artikel ist auch in sgcWebSockets .NET verfügbar, mit denselben Klassen-, Eigenschafts- und Ereignisnamen: TsgcWSAPIServer_MCP und TsgcWSAPIClient_MCP stellen MCPOptions.Subscriptions, MCPOptions.MRTR, MCPOptions.Tasks, ProtocolEra, Discover und MCPOptions.Authorization bereit, sodass eine C#-Anwendung mit einem Delphi-MCP-Server in beiden Epochen sprechen kann, und umgekehrt.

Demos und Dokumentation

Die Demos in Demos\15.AI\03.MCP zeigen jede Funktion. Die Server-Demo (01.MCP_Server) protokolliert die Epoche jedes Requests, hat Schalter für Tasks und Subscriptions und implementiert die Tools ask_name und long_job. Die Client-Demo (02.MCP_Client) lässt dich die Epoche wählen, Discover aufrufen, Notifications abonnieren, MRTR-Fragen beantworten und Tasks ausführen.

Die vollständige Referenz findest du in der MCP-Server-Dokumentation und der MCP-Client-Dokumentation, und die Seite MCP-Komponenten für Delphi gibt einen Überblick über alles, was die Komponenten leisten. Du kannst sgcWebSockets 2026.10.0 von der sgcWebSockets-Downloadseite herunterladen.

Fragen, Feedback oder Hilfe bei der Migration? Nimm Kontakt auf, du bekommst eine Antwort von den Leuten, die den Code geschrieben haben.

Verwandte Themen