MCP Client
Model Context Protocol (MCP) client — connect Delphi apps to any MCP server (resources, tools, prompts).
Model Context Protocol (MCP) client — connect Delphi apps to any MCP server (resources, tools, prompts).
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.
TsgcWSAPIClient_MCPMCPOptions.ProtocolEra: Auto, Modern or Legacy).server/discover, per-request _meta and the MCP-Protocol-Version, Mcp-Method, Mcp-Name and Mcp-Param-* headers sent automatically.OnMCPInputRequired; legacy server-initiated requests are answered the same way through OnMCPListRoots, OnMCPSamplingCreateMessage and OnMCPElicitationCreate.MCPOptions.Tasks.Enabled with automatic polling (AutoPoll), or TasksGet, TasksUpdate and TasksCancel called manually.subscriptions/listen for list changes and resource updates.application_type, PKCE with resource indicators, RFC 9207 iss validation and per-issuer credentials.| Standards & specs | Model Context Protocol 2026-07-28 specification · MCP 2025-11-25 · MCP — introduction |
| Component class | TsgcWSAPIClient_MCP (unit sgcAI_MCP_Client) |
| Frameworks | VCL, FireMonkey, Lazarus / FPC, .NET |
| Platforms | Windows, macOS, Linux, iOS, Android |
The principal published / public properties used to configure and drive the component. Consult the online help for the full list.
MCPOptions | Centralises every runtime option for the MCP client (endpoint URL, TLS, authentication, client identity, heartbeat, transport, tasks). |
Version | Read-only semantic version of the MCP client component. |
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. |
The component exposes the following published events; consult the online help for full event-handler signatures.
OnMCPDiscover | Fires when a 2026-07-28 server answers server/discover; set Accept := False to abort. |
OnMCPInputRequired | Fires when a 2026-07-28 server needs elicitation, sampling or roots to finish a call (Multi Round-Trip Requests). |
OnMCPListChanged | Delivers tools, prompts or resources list_changed notifications received on a subscription. |
OnMCPElicitationCreate | Fires when the server asks the user for additional input (elicitation/create). |
OnMCPInitialize | Fires when the server replies to the initialize handshake; inspect capabilities and set Accept := False to abort. |
OnMCPListPrompts | Receives the prompt templates catalogue returned by prompts/list. |
OnMCPListResources | property OnMCPListResources: TsgcAI_MCP_Client_OnListResourcesEvent; // TsgcAI_MCP_Client_OnListResourcesEvent = procedure(Sender: TObject; const aRequest: TsgcAI_MCP_Request_ResourcesList; const aRes... |
OnMCPListRoots | Fires when the server asks the client for its filesystem roots (roots/list). |
OnMCPListTools | property OnMCPListTools: TsgcAI_MCP_Client_OnListToolsEvent; // TsgcAI_MCP_Client_OnListToolsEvent = procedure(Sender: TObject; const aRequest: TsgcAI_MCP_Request_ToolsList; const aResponse: TsgcAI_MC... |
OnMCPPing | Fires when the server acknowledges a ping. Useful for round-trip telemetry. |
OnMCPResponsePrompt | Delivers the rendered prompt content returned by prompts/get. |
OnMCPResponseResource | Delivers the payload returned by resources/read, including streamed chunks. |
OnMCPResponseTool | property OnMCPResponseTool: TsgcAI_MCP_Client_OnResponseToolEvent; // TsgcAI_MCP_Client_OnResponseToolEvent = procedure(Sender: TObject; const aRequest: TsgcAI_MCP_Request_ToolsCall; const aResponse: ... |
OnMCPSamplingCreateMessage | TsgcWSAPIClient_MCP › Events › OnMCPSamplingCreateMessage |
OnMCPStreamMessage | Fires while streaming responses are being read; inspect the raw JSON fragment and set Cancel := True to abort the stream. |
OnMCPTaskCreated | Fires once a tools/call reply returns a task handle (Tasks extension). |
OnMCPTaskStatus | Fires on every poll while a task is still running. |
OnMCPTaskCompleted | Fires when a task reaches a terminal state; the result is available on the task object. |
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.
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;
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()); } } }
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"); } } }
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.
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;
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.
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;
SubscriptionsListen opens one stream for the notifications you choose. List changes arrive in OnMCPListChanged and resource updates in OnMCPResourcesUpdated.
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;
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.
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;
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.
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;
Every external claim links back to a primary source. The online-help references decode the canonical deep-link the company maintains for this component.
Demos\15.AI\03.MCP\02.MCP_Client
.net\demos\15.AI\03.MCP\02.MCP_Client