Serveur et client MCP pour Delphi : la nouvelle spécification MCP 2026-07-28

· Composants
Serveur et client MCP pour Delphi : la nouvelle spécification MCP 2026-07-28

Le Model Context Protocol a une nouvelle spécification, MCP 2026-07-28. C'est le plus grand changement du protocole depuis Streamable HTTP : la poignée de main de session disparaît, chaque requête transporte ce dont le serveur a besoin pour y répondre, et les travaux longs ou interactifs bénéficient d'un support de premier ordre. sgcWebSockets 2026.10.0 l'implémente à la fois dans le serveur MCP et dans le client MCP pour Delphi et C++Builder.

Le point important pour quiconque exploite déjà un serveur MCP Delphi : rien ne casse. Le serveur est bi-ère. Les clients qui parlent 2025-11-25, comme VS Code ou Claude, continuent d'appeler initialize et obtiennent une session exactement comme avant, tandis que les clients 2026-07-28 utilisent le nouveau modèle sans état sur le même point de terminaison. Vous n'avez besoin ni d'un commutateur ni d'un second serveur.

Ce qui a changé dans MCP 2026-07-28

En termes simples, voici les changements qui comptent quand vous construisez un serveur ou un client :

Un serveur MCP bi-ère en Delphi

Le composant TsgcWSAPIServer_MCP détecte l'ère de chaque requête. Une requête dont le _meta indique 2026-07-28 suit le chemin sans état, tandis que initialize et les versions plus anciennes suivent le chemin de session que vous connaissez déjà. Vos gestionnaires d'outils, de prompts et de ressources sont partagés par les deux. Les nouvelles options ne font que remplir ce que les clients 2026-07-28 lisent depuis server/discover et depuis les indications de cache.

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;

Quand ServerInfo.Name est défini, les résultats 2026-07-28 transportent aussi les informations du serveur dans _meta. Les erreurs de protocole utilisent les nouveaux codes : un en-tête qui ne correspond pas au corps répond -32020, une capacité client manquante -32021 et une version non prise en charge -32022, avec la liste des versions prises en charge dans les données d'erreur.

Les abonnements avec subscriptions/listen

Les abonnements sont activés par défaut. Un client 2026-07-28 ouvre un flux subscriptions/listen, et les notifications que vous envoyez déjà avec SendNotificationToolsListChanged ou SendNotificationResourcesUpdated atteignent ces auditeurs aussi bien que les sessions 2025-11-25. Le serveur envoie un commentaire de maintien de connexion sur les flux inactifs, et quand le serveur est désactivé, chaque abonnement ouvert est fermé proprement avec son résultat final.

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;

Requêtes à plusieurs allers-retours : demander un nom à l'utilisateur

Avec MRTR, un gestionnaire demande une saisie supplémentaire en remplissant aResponse.InputRequired puis en retournant. Le client recueille les réponses et rappelle l'outil. Au second tour, les réponses sont lues avec aRequest.InputResponse. L'exemple ci-dessous est l'outil ask_name de la démo serveur.

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;

Le requestState voyage signé avec HMAC-SHA256 en utilisant MCPOptions.MRTR.Secret et expire après MCPOptions.MRTR.StateTTL secondes, si bien qu'un client ne peut pas le falsifier. L'élicitation par URL (AddElicitationURL), l'échantillonnage (AddSampling) et les racines (AddRoots) suivent le même schéma. Avant de répondre à input_required, le serveur vérifie que le client a déclaré la capacité correspondante.

L'extension Tasks : un outil long_job

Activez les tâches dans MCPOptions.Tasks et appelez CreateTask depuis le gestionnaire d'outil. Quand le client a déclaré l'extension io.modelcontextprotocol/tasks sur cette requête, l'appel répond immédiatement avec un identifiant de tâche et votre code termine le travail sur son propre thread. CreateTask renvoie nil quand les tâches sont désactivées, que la requête est 2025-11-25 ou que le client n'a pas déclaré l'extension, alors gardez un chemin synchrone pour ces clients.

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;

L'objet tâche est thread-safe. OnMCPTaskCancel se déclenche quand le client appelle tasks/cancel, et OnMCPTaskUpdate quand il répond à une tâche en attente d'une saisie. Les tâches sont liées au principal qui les a créées et expirent après TTL millisecondes.

Le client MCP : ProtocolEra et Discover

Le composant TsgcWSAPIClient_MCP reçoit une nouvelle propriété MCPOptions.ProtocolEra. Laissez-la sur aimcpeAuto et le client sonde le serveur avec server/discover, utilise 2026-07-28 quand le serveur le prend en charge et retombe sur la poignée de main initialize sinon. L'ère négociée est mise en cache par point de terminaison. Réglez-la sur aimcpeModern ou aimcpeLegacy pour en forcer une.

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;

Sur le chemin 2026-07-28, le client écrit _meta sur chaque requête, envoie les en-têtes de routage, y compris Mcp-Param-* pour les outils qui déclarent x-mcp-header, et ouvre les abonnements avec SubscriptionsListen. Les changements de liste arrivent dans OnMCPListChanged et les mises à jour de ressources dans OnMCPResourcesUpdated. Avec MCPOptions.Tasks.Enabled, le client déclare l'extension Tasks et interroge les tâches par lui-même, en déclenchant OnMCPTaskCreated, OnMCPTaskStatus et OnMCPTaskCompleted.

Répondre aux questions MRTR avec OnMCPInputRequired

Quand un serveur répond input_required, le client déclenche OnMCPInputRequired avec les questions en attente. Répondez à chaque clé et laissez Accept à True. Le client relance alors l'appel avec les réponses et le requestState signé, jusqu'à MCPOptions.MRTR.MaxRounds tours. Réglez MCPOptions.MRTR.Elicitation, Sampling ou Roots pour déclarer ces capacités.

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;

Le même événement couvre les tâches en attente d'une saisie, auquel cas aMethod vaut tasks/update. Les serveurs anciens qui envoient encore directement des requêtes de racines, d'échantillonnage ou d'élicitation reçoivent leurs réponses depuis OnMCPListRoots, OnMCPSamplingCreateMessage et OnMCPElicitationCreate.

Autorisation côté client avec MCPOptions.Authorization

Définissez MCPOptions.Authorization.Enabled et un 401 ou 403 du serveur déclenche le flux d'autorisation MCP : découverte des métadonnées de ressource protégée, enregistrement (un ClientId préenregistré, un Client ID Metadata Document depuis ClientMetadataURL, ou un enregistrement dynamique), PKCE avec l'indicateur de ressource, la connexion dans le navigateur sur le RedirectURL local, la validation iss et la requête de jeton. La requête d'origine est alors relancée, et les jetons sont renouvelés avant leur expiration.

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;

Les identifiants sont conservés par émetteur. Gérez OnMCPAuthorizationCredentials pour les stocker et les recharger au prochain lancement, afin que l'utilisateur ne se connecte qu'une seule fois.

Également dans sgcWebSockets .NET

Tout ce qui est décrit dans cet article est aussi disponible dans sgcWebSockets .NET, avec les mêmes noms de classes, de propriétés et d'événements : TsgcWSAPIServer_MCP et TsgcWSAPIClient_MCP exposent MCPOptions.Subscriptions, MCPOptions.MRTR, MCPOptions.Tasks, ProtocolEra, Discover et MCPOptions.Authorization, si bien qu'une application C# peut dialoguer avec un serveur MCP Delphi dans l'une ou l'autre ère, et inversement.

Démos et documentation

Les démos dans Demos\15.AI\03.MCP montrent chaque fonctionnalité. La démo serveur (01.MCP_Server) journalise l'ère de chaque requête, dispose de commutateurs pour les tâches et les abonnements, et implémente les outils ask_name et long_job. La démo client (02.MCP_Client) vous permet de choisir l'ère, d'appeler Discover, de vous abonner aux notifications, de répondre aux questions MRTR et d'exécuter des tâches.

La référence complète se trouve dans la documentation du serveur MCP et la documentation du client MCP, et la page composants MCP pour Delphi présente un aperçu de tout ce que font les composants. Vous pouvez télécharger sgcWebSockets 2026.10.0 depuis la page de téléchargement de sgcWebSockets.

Des questions, des retours ou besoin d'aide pour la migration ? Contactez-nous, vous obtiendrez une réponse des personnes qui ont écrit le code.

Sur le même thème