sgcWebSockets · Technical Document

MCP Client

Model Context Protocol (MCP) client — connect Delphi apps to any MCP server (resources, tools, prompts).

Overview

TsgcWSAPIClient_MCP implements a Model Context Protocol (MCP) consumer on top of sgcWebSockets networking components. It takes care of session negotiation, JSON-RPC request/response marshalling and convenience events so Delphi & Cbuilder applications can discover prompts, resources and tools exposed by any MCP-compliant server.

The client speaks MCP 2026-07-28 and falls back automatically to 2025-11-25 when the server is older. With the default MCPOptions.ProtocolEra (Auto) it probes server/discover first, and uses the classic initialize handshake only when the server does not answer the new protocol. The negotiated era is cached per endpoint. Available from sgcWebSockets 2026.10.0 for Delphi 7 to Delphi 13, C++ Builder and the .NET package.

At a glance

Component class
TsgcWSAPIClient_MCP
Standards / spec
Model Context Protocol 2026-07-28 (2025-11-25 fallback)
Transports
TCP, TLS
Platforms
Windows, macOS, Linux, iOS, Android
Frameworks
VCL, FireMonkey, Lazarus / FPC, .NET
Edition
Enterprise

Features

Technical specification

Standards & specsModel Context Protocol 2026-07-28 specification · MCP 2025-11-25 · MCP — introduction
Component classTsgcWSAPIClient_MCP (unit sgcAI_MCP_Client)
FrameworksVCL, FireMonkey, Lazarus / FPC, .NET
PlatformsWindows, macOS, Linux, iOS, Android

Main properties

The principal published / public properties used to configure and drive the component. Consult the online help for the full list.

MCPOptionsCentralises every runtime option for the MCP client (endpoint URL, TLS, authentication, client identity, heartbeat, transport, tasks).
VersionRead-only semantic version of the MCP client component.

Main methods

The principal public methods exposed by the component.

Ping()Issues a JSON-RPC ping request to keep the session alive.
RequestTool()Invokes a named tool on the server (tools/call) with optional JSON arguments.
RequestPrompt()Obtains the rendered content of a prompt template (prompts/get).
RequestResource()Reads the contents of a resource identified by URI (resources/read).
SubscribeResource()Subscribes to change notifications for a specific resource (resources/subscribe).
UnsubscribeResource()Cancels a previous resource subscription (resources/unsubscribe).
ListTools()Requests the catalogue of tools published by the server (tools/list).
ListPrompts()Retrieves the available prompt templates from the server (prompts/list).
ListResources()Enumerates resource descriptors published by the server (resources/list).
ListResourceTemplates()Enumerates resource template descriptors (resources/templates/list).
Discover()Calls server/discover on a 2026-07-28 server; the result is available in ServerDiscover.
SubscriptionsListen()Opens a subscriptions/listen stream for list changes and resource updates and returns its subscription id.
SubscriptionsCancel()Closes a subscription opened with SubscriptionsListen.
TasksGet()Fetches the current status of a task created by a tools/call reply (tasks/get).
TasksUpdate()Sends input requested by a running task back to the server (tasks/update).
TasksCancel()Cancels a running task and stops the client from polling it.

Public events

The component exposes the following published events; consult the online help for full event-handler signatures.

OnMCPDiscoverFires when a 2026-07-28 server answers server/discover; set Accept := False to abort.
OnMCPInputRequiredFires when a 2026-07-28 server needs elicitation, sampling or roots to finish a call (Multi Round-Trip Requests).
OnMCPListChangedDelivers tools, prompts or resources list_changed notifications received on a subscription.
OnMCPElicitationCreateFires when the server asks the user for additional input (elicitation/create).
OnMCPInitializeFires when the server replies to the initialize handshake; inspect capabilities and set Accept := False to abort.
OnMCPListPromptsReceives the prompt templates catalogue returned by prompts/list.
OnMCPListResourcesproperty OnMCPListResources: TsgcAI_MCP_Client_OnListResourcesEvent; // TsgcAI_MCP_Client_OnListResourcesEvent = procedure(Sender: TObject; const aRequest: TsgcAI_MCP_Request_ResourcesList; const aRes...
OnMCPListRootsFires when the server asks the client for its filesystem roots (roots/list).
OnMCPListToolsproperty OnMCPListTools: TsgcAI_MCP_Client_OnListToolsEvent; // TsgcAI_MCP_Client_OnListToolsEvent = procedure(Sender: TObject; const aRequest: TsgcAI_MCP_Request_ToolsList; const aResponse: TsgcAI_MC...
OnMCPPingFires when the server acknowledges a ping. Useful for round-trip telemetry.
OnMCPResponsePromptDelivers the rendered prompt content returned by prompts/get.
OnMCPResponseResourceDelivers the payload returned by resources/read, including streamed chunks.
OnMCPResponseToolproperty OnMCPResponseTool: TsgcAI_MCP_Client_OnResponseToolEvent; // TsgcAI_MCP_Client_OnResponseToolEvent = procedure(Sender: TObject; const aRequest: TsgcAI_MCP_Request_ToolsCall; const aResponse: ...
OnMCPSamplingCreateMessageTsgcWSAPIClient_MCP › Events › OnMCPSamplingCreateMessage
OnMCPStreamMessageFires while streaming responses are being read; inspect the raw JSON fragment and set Cancel := True to abort the stream.
OnMCPTaskCreatedFires once a tools/call reply returns a task handle (Tasks extension).
OnMCPTaskStatusFires on every poll while a task is still running.
OnMCPTaskCompletedFires when a task reaches a terminal state; the result is available on the task object.

Quick Start

Drop the component on a form, configure the properties below and activate it. The snippet that follows shows the typical Sample code configuration sourced from the online help.

About this scenario. The example below loads the prompt list, invokes a prompt, and prints the returned messages.

Delphi (VCL / FireMonkey)

procedure TForm1.FormCreate(Sender: TObject);
begin
 MCP.OnMCPListPrompts := MCPListPrompts;
 MCP.OnMCPResponsePrompt := MCPResponsePrompt;
end;
procedure TForm1.LoadPrompts;
begin
 MCP.ListPrompts;
end;
procedure TForm1.MCPListPrompts(Sender: TObject;
 const ARequest: TsgcAI_MCP_Request_PromptsList;
 const AResponse: TsgcAI_MCP_Response_PromptsList);
var
 LIndex: Integer;
 LPrompt: TsgcAI_MCP_Prompt;
begin
 for LIndex := 0 to AResponse.Prompts.Count - 1 do
 begin
  LPrompt := TsgcAI_MCP_Prompt(AResponse.Prompts.Item[LIndex]);
  Memo1.Lines.Add(Format('%s: %s', [LPrompt.Name, LPrompt.Description]));
 end;
end;
procedure TForm1.ExecutePrompt;
var
 LArgs: TsgcJSON;
begin
 LArgs := TsgcJSON.Create(nil);
 try
  LArgs.AddPair('code', 'ShowMessage(''Hello World'')');
  MCP.RequestPrompt('CodeReview', LArgs);
 finally
  LArgs.Free;
 end;
end;
procedure TForm1.MCPResponsePrompt(Sender: TObject;
 const ARequest: TsgcAI_MCP_Request_PromptsGet;
 const AResponse: TsgcAI_MCP_Response_PromptsGet);
var
 LIndex: Integer;
 LMessage: TsgcAI_MCP_Response_PromptsGet_Result_Message;
begin
 Memo1.Lines.Add('Prompt description: ' + AResponse.Result.Description);
 for LIndex := 0 to AResponse.Result.Messages.Count - 1 do
 begin
  LMessage := TsgcAI_MCP_Response_PromptsGet_Result_Message(AResponse.Result.Messages.Item[LIndex]);
  if LMessage is TsgcAI_MCP_Response_PromptsGet_Result_Message_Text then
   Memo1.Lines.Add(LMessage.Role + ': ' +
    TsgcAI_MCP_Response_PromptsGet_Result_Message_Text(LMessage).Content.Text)
  else
   Memo1.Lines.Add(LMessage.Role + ': ' + LMessage.Write);
 end;
end;

C++ Builder

void __fastcall TForm1::FormCreate(TObject *Sender)
{
  MCP->OnMCPListPrompts = MCPListPrompts;
  MCP->OnMCPResponsePrompt = MCPResponsePrompt;
}
void __fastcall TForm1::LoadPrompts()
{
  MCP->ListPrompts();
}
void __fastcall TForm1::MCPListPrompts(TObject *Sender,
  const TsgcAI_MCP_Request_PromptsList &ARequest,
  const TsgcAI_MCP_Response_PromptsList &AResponse)
{
  for (int LIndex = 0; LIndex < AResponse.Prompts->Count; LIndex++)
  {
    TsgcAI_MCP_Prompt *LPrompt = (TsgcAI_MCP_Prompt*)AResponse.Prompts->Item[LIndex];
    Memo1->Lines->Add(Format("%s: %s", ARRAYOFCONST((LPrompt->Name, LPrompt->Description))));
  }
}
void __fastcall TForm1::ExecutePrompt()
{
  TsgcJSON *LArgs = new TsgcJSON(nullptr);
  try
  {
    LArgs->AddPair("code", "ShowMessage('Hello World')");
    MCP->RequestPrompt("CodeReview", LArgs);
  }
  __finally
  {
    delete LArgs;
  }
}
void __fastcall TForm1::MCPResponsePrompt(TObject *Sender,
  const TsgcAI_MCP_Request_PromptsGet &ARequest,
  const TsgcAI_MCP_Response_PromptsGet &AResponse)
{
  Memo1->Lines->Add("Prompt description: " + AResponse.Result->Description);
  for (int LIndex = 0; LIndex < AResponse.Result->Messages->Count; LIndex++)
  {
    TsgcAI_MCP_Response_PromptsGet_Result_Message *LMessage =
      (TsgcAI_MCP_Response_PromptsGet_Result_Message*)AResponse.Result->Messages->Item[LIndex];
    if (dynamic_cast<TsgcAI_MCP_Response_PromptsGet_Result_Message_Text*>(LMessage))
    {
      auto *MsgText = (TsgcAI_MCP_Response_PromptsGet_Result_Message_Text*)LMessage;
      Memo1->Lines->Add(LMessage->Role + ": " + MsgText->Content.Text);
    }
    else
    {
      Memo1->Lines->Add(LMessage->Role + ": " + LMessage->Write());
    }
  }
}

.NET (C#)

private void Form1_Load(object sender, EventArgs e)
{
  MCP.OnMCPListPrompts += MCP_ListPrompts;
  MCP.OnMCPResponsePrompt += MCP_RequestPrompt;
}
private void LoadPrompts()
{
  MCP.ListPrompts();
}
private void MCP_ListPrompts(object sender, MCPResponsePromptsList request, MCPResponsePromptsList response)
{
  foreach (var prompt in response.Prompts)
  {
    memo1.AppendText($"{prompt.Name}: {prompt.Description}\r\n");
  }
}
private void ExecutePrompt()
{
  using (var args = new TsgcJSON())
  {
    args.AddPair("code", "ShowMessage('Hello World')");
    MCP.RequestPrompt("CodeReview", args);
  }
}
private void MCP_RequestPrompt(object sender, MCPResponsePromptsGet request, MCPResponsePromptsGet response)
{
  memo1.AppendText("Prompt description: " + response.Result.Description + "\r\n");
  foreach (var message in response.Result.Messages)
  {
    if (message is MCPResponsePromptsGetResultMessageText msgText)
    {
      memo1.AppendText($"{message.Role}: {msgText.Content.Text}\r\n");
    }
    else
    {
      memo1.AppendText($"{message.Role}: {message.Write()}\r\n");
    }
  }
}

MCP 2026-07-28 and automatic fallback

Leave MCPOptions.ProtocolEra on Auto to talk to both new and older servers. Set it to Modern or Legacy to force one era. After Initialize, NegotiatedEra and NegotiatedProtocolVersion tell you what the server speaks.

Delphi (VCL / FireMonkey)
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;
    MCPClient.ListTools;
  end;
end;

Multi Round-Trip Requests

When a 2026-07-28 server needs more input to complete tools/call, prompts/get or resources/read, OnMCPInputRequired fires with the pending requests. Answer each key and leave Accept True; the client retries the call with the answers and the signed requestState, up to MCPOptions.MRTR.MaxRounds rounds. Set MCPOptions.MRTR.Elicitation, Sampling or Roots to declare those capabilities.

A legacy (2025-11-25 or earlier) server that sends roots, sampling or elicitation requests directly, outside a tool call, is answered the same way: assign OnMCPListRoots, OnMCPSamplingCreateMessage or OnMCPElicitationCreate and the client sends the reply back automatically once the handler returns.

Delphi (VCL / FireMonkey)
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],
        '{"ok":true}');
  Accept := True;
end;

Subscriptions (subscriptions/listen)

SubscriptionsListen opens one stream for the notifications you choose. List changes arrive in OnMCPListChanged and resource updates in OnMCPResourcesUpdated.

Delphi (VCL / FireMonkey)
procedure TMainForm.Listen;
begin
  MCPClient.OnMCPListChanged := MCPClientListChanged;
  FSubscriptionId := MCPClient.SubscriptionsListen(True, True, True);
end;

procedure TMainForm.MCPClientListChanged(Sender: TObject;
  aKind: TsgcAI_MCP_Client_ListChangedKind; const aNotification: string);
begin
  if aKind = aimcplckTools then
    MCPClient.ListTools;
end;

procedure TMainForm.StopListening;
begin
  MCPClient.SubscriptionsCancel(FSubscriptionId);
end;

Tasks extension

When a tool call is expected to run long, a 2026-07-28 server can reply with a task handle instead of the result. Set MCPOptions.Tasks.Enabled to declare support; with AutoPoll True the client polls the task on its own and fires OnMCPTaskCreated, OnMCPTaskStatus on every poll and OnMCPTaskCompleted once it reaches a terminal state. Call TasksGet, TasksUpdate or TasksCancel to drive a task manually.

Delphi (VCL / FireMonkey)
procedure TMainForm.FormCreate(Sender: TObject);
begin
  MCPClient.MCPOptions.Tasks.Enabled := True;
  MCPClient.MCPOptions.Tasks.AutoPoll := True;
  MCPClient.OnMCPTaskStatus := MCPClientTaskStatus;
  MCPClient.OnMCPTaskCompleted := MCPClientTaskCompleted;
end;

procedure TMainForm.MCPClientTaskStatus(Sender: TObject;
  const aTask: TsgcAI_MCP_Client_Task);
begin
  MemoLog.Lines.Add(aTask.TaskId + ': ' + aTask.StatusMessage);
end;

procedure TMainForm.MCPClientTaskCompleted(Sender: TObject;
  const aTask: TsgcAI_MCP_Client_Task);
begin
  MemoLog.Lines.Add('result: ' + aTask.Text);
end;

Authorization (OAuth)

With MCPOptions.Authorization.Enabled, a 401 or 403 from the server starts the MCP authorization flow: protected resource metadata discovery, registration (pre-registered ClientId, Client ID Metadata Document from ClientMetadataURL, or dynamic registration), PKCE with the resource indicator, the browser login on the loopback RedirectURL, RFC 9207 iss validation and the token request. The request is then retried. Credentials are kept per issuer and can be stored with OnMCPAuthorizationCredentials.

Delphi (VCL / FireMonkey)
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;

Sources used to build this document

Every external claim links back to a primary source. The online-help references decode the canonical deep-link the company maintains for this component.

Document scope. This document covers the publicly-documented surface of the MCP Client component shipped with sgcWebSockets. For full property, method and event reference consult the online help linked above.