MCPApp

TsgcHTMLComponent_MCPApp: an MCP tool that answers with a page instead of text, so the same Delphi components that render any web page render inside ChatGPT, Claude and VS Code (MCP Apps).

TsgcHTMLComponent_MCPApp

An MCP App is a tool whose result the host renders instead of only reading. The component publishes the tool, the resource that carries the page and a companion tool the page itself calls, on the MCP server you already have, and the page is built from the same sgcHTML components as any other. Needs the Enterprise or All-Access edition of sgcWebSockets, or the sgcAI pack, because it runs on the MCP server.

Component class

TsgcHTMLComponent_MCPApp, with the TsgcHTMLMCPApps collection of TsgcHTMLMCPApp items

Renders

One HTML document that the host shows in a sandboxed frame, with its assets inline

Family

Chat & AI

Languages

Delphi, C++ Builder, .NET

Declare an app, answer three events

Give the component the MCPServer of a TsgcWSAPIServer_MCP, add an app with a tool name and a description, answer OnRender, OnToolCall and OnFragment, then call RegisterApps.

uses
  Classes, SysUtils, StrUtils,
  // sgc
  sgcWebSocket_Server, sgcAI, sgcHTML_Nodes, sgcHTML_Nodes_Bootstrap,
  sgcHTML_MCPApp;

// The MCP endpoint a host connects to
FMCP := TsgcWSAPIServer_MCP.Create(nil);
FMCP.Server := FHTTPServer;
FMCP.EndpointOptions.Endpoint := '/mcp';

// The apps, declared on the MCP server the endpoint owns
FApps := TsgcHTMLComponent_MCPApp.Create(nil);
FApps.MCPServer := FMCP.MCPServer;
FApps.OnRender := DoRender;
FApps.OnToolCall := DoToolCall;
FApps.OnFragment := DoFragment;

with FApps.Apps.Add do
begin
  ToolName := 'orders.board';
  Title := 'Orders board';
  Description := 'Shows the orders of the company as a board that ' +
    'can be filtered by status.';
  InputSchema.Text := '{"type":"object","properties":{"status":' +
    '{"type":"string","description":"open, shipped or closed"}}}';
  PreferredWidth := 720;
  PreferredHeight := 460;
end;
FApps.RegisterApps;   // the tool, the fragment tool and the resource

// What the model reads, and what the page reads
procedure TMyLogic.DoToolCall(Sender: TObject; aApp: TsgcHTMLMCPApp;
  const aArguments: string; var aText, aStructured: string);
begin
  aText := '3 open orders, 1420.50 in total.';
  aStructured := '{"orders":3,"total":1420.5}';
end;

// The page: ordinary sgcHTML components
procedure TMyLogic.DoRender(Sender: TObject; aApp: TsgcHTMLMCPApp;
  const aArguments: string; var aHTML: string);
var
  oCard: TsgcHTMLCard;
  oButton: TsgcHTMLContainer;
begin
  oCard := TsgcHTMLCard.Create;
  try
    oCard.Body.Add(TsgcHTMLHeading.Create('Orders', 5));

    oButton := TsgcHTMLContainer.Create('button');
    oButton.CSSClass := 'btn btn-outline-secondary';
    // inside a host, the bridge turns this hx-get into a call of the fragment tool
    oButton.Attributes := 'type="button" hx-get="orders?status=open" ' +
      'hx-target="#orders" hx-swap="outerHTML"';
    oButton.AddText('Open');
    oCard.Body.Add(oButton);

    oCard.Body.AddRaw(BuildOrdersTable(''));   // your markup: the element whose id is orders
    aHTML := oCard.HTML;
  finally
    oCard.Free;
  end;
end;

// The markup a control of the page asked for
procedure TMyLogic.DoFragment(Sender: TObject; aApp: TsgcHTMLMCPApp;
  const aTarget, aArguments: string; var aHTML: string);
begin
  // aTarget is what the element asked for, for instance orders?status=open
  if StartsText('orders', aTarget) then
    aHTML := BuildOrdersTable(StatusOf(aTarget));
end;
// includes: sgcAI.hpp, sgcHTML_MCPApp.hpp

// The MCP endpoint a host connects to
FMCP = new TsgcWSAPIServer_MCP(NULL);
FMCP->Server = FHTTP;
FMCP->EndpointOptions->Endpoint = "/mcp";

// The apps, declared on the MCP server the endpoint owns
FApps = new TsgcHTMLComponent_MCPApp(NULL);
FApps->MCPServer = FMCP->MCPServer;
FApps->OnRender = DoRender;
FApps->OnToolCall = DoToolCall;
FApps->OnFragment = DoFragment;

TsgcHTMLMCPApp *app = FApps->Apps->Add();
app->ToolName = "orders.board";
app->Title = "Orders board";
app->Description = "Shows the orders of the company as a board that can be filtered by status.";
app->InputSchema->Text = "{\"type\":\"object\",\"properties\":{\"status\":{\"type\":\"string\"}}}";
app->PreferredWidth = 720;
app->PreferredHeight = 460;
FApps->RegisterApps();   // the tool, the fragment tool and the resource

// What the model reads, and what the page reads
void __fastcall TMyLogic::DoToolCall(TObject *Sender, TsgcHTMLMCPApp *aApp,
  const String aArguments, String &aText, String &aStructured)
{
  aText = "3 open orders, 1420.50 in total.";
  aStructured = "{\"orders\":3,\"total\":1420.5}";
}

// The markup a control of the page asked for
void __fastcall TMyLogic::DoFragment(TObject *Sender, TsgcHTMLMCPApp *aApp,
  const String aTarget, const String aArguments, String &aHTML)
{
  // aTarget is what the element asked for, for instance orders?status=open
  if (aTarget.Pos("orders") == 1)
    aHTML = BuildOrdersTable(StatusOf(aTarget));
}

// DoRender answers the page with sgcHTML components, as in the Delphi tab.
using esegece.sgcWebSockets;

// An MCP server over stdio, for a host that starts your program as a process
var host = new TsgcAI_MCP_Server_Stdio();

var apps = new TsgcHTMLComponent_MCPApp();
apps.MCPServer = host.MCPServer;

var app = apps.Apps.Add();
app.ToolName = "orders.board";
app.Title = "Orders board";
app.Description = "Shows the orders of the company as a board that can be filtered by status.";
app.InputSchema.Add("{\"type\":\"object\",\"properties\":{\"status\":{\"type\":\"string\"}}}");
app.PreferredWidth = 720;
app.PreferredHeight = 460;

// What the model reads, and what the page reads
apps.OnToolCall += (object sender, TsgcHTMLMCPApp a, string args,
    ref string text, ref string structured) =>
{
    text = "3 open orders, 1420.50 in total.";
    structured = "{\"orders\":3,\"total\":1420.5}";
};

// The page: ordinary sgcHTML components
apps.OnRender += (object sender, TsgcHTMLMCPApp a, string args, ref string html) =>
{
    var card = new TsgcHTMLCard();
    card.Body.Add(new TsgcHTMLHeading("Orders", 5));

    var button = new TsgcHTMLContainer("button");
    button.CSSClass = "btn btn-outline-secondary";
    // inside a host, the bridge turns this hx-get into a call of the fragment tool
    button.Attributes = "type=\"button\" hx-get=\"orders?status=open\" "
        + "hx-target=\"#orders\" hx-swap=\"outerHTML\"";
    button.AddText("Open");
    card.Body.Add(button);

    card.Body.AddRaw(BuildOrdersTable(""));   // your markup: the element whose id is orders
    html = card.HTML;
};

// The markup a control of the page asked for
apps.OnFragment += (object sender, TsgcHTMLMCPApp a, string target,
    string args, ref string html) =>
{
    // target is what the element asked for, for instance orders?status=open
    if (target.StartsWith("orders"))
        html = BuildOrdersTable(StatusOf(target));
};

apps.RegisterApps();   // the tool, the fragment tool and the resource
host.Run();

Key properties & methods

The members you reach for most often.

Apps

Apps is a TsgcHTMLMCPApps collection of TsgcHTMLMCPApp items. ToolName is required: an app without one registers nothing. Description is what the model reads to decide whether to call the tool, and InputSchema is the JSON schema of its arguments. Title, ResourceURI, PageBuilder, Enabled, PreferredWidth and PreferredHeight complete an app, the last two being the size the host is asked to open the frame at.

What RegisterApps declares

Three things per app on the server: the tool, named by ToolName, whose _meta.ui.resourceUri tells the host which resource carries the page; the companion tool <ToolName>.fragment, which the page calls and the model never does; and the resource ui://sgchtml/<toolname>, served as text/html;profile=mcp-app. In Delphi, RegisterApps runs from Loaded and code that adds an app calls it again; in .NET, call it yourself. UnregisterApps removes them.

Two halves of a result

A tool result has two audiences. In OnToolCall, aText is the sentence the model reads and aStructured is the JSON the page reads. Both are optional, and when the text is empty the app Title is used. Answer both when they differ, which they usually do: the model wants a sentence and the page wants numbers.

The page

OnRender answers the page as ordinary sgcHTML markup. aHTML arrives holding the page that the PageBuilder of the app builds, when one is assigned, so a handler can replace it or leave it. GetAppHTML returns the whole document of one app, which is what the resource answers and what a test reads.

Fragments and the bridge

A sandboxed frame has no HTTP server to answer an hx-get. Every document carries window.sgcMCPApp: it sends ui/initialize when the page is ready, applies the theme the host answers with, reports the height of the page so the host can size the frame, and registers an htmx extension that turns every hx-get and hx-post into a tools/call of the fragment tool. OnFragment answers it: aTarget is what the element asked for, aArguments the JSON of the form or the query, and aHTML the markup swapped in. The same markup served over HTTP is an ordinary htmx request.

One document

The frame is sandboxed and usually has no network of its own, so nothing can be linked. InlineAssets is on by default and makes the stylesheet, the scripts and the page travel in one file. Switch it off only for a host on the same machine that can serve the assets.

The MCP server

MCPServer is the TsgcAI_MCP_Server the apps are declared on. TsgcWSAPIServer_MCP exposes it as a public property, so FApps.MCPServer := FMCP.MCPServer puts the apps on the endpoint of a web server. The component subscribes to the tool and resource events and chains the handlers that were there, so the tools you already publish keep working. A host that handles those events itself calls ProcessToolsCall and ProcessResourcesRead, which answer False when the request was not an app's. In .NET, give it the TsgcAI_MCP_Server you have, for instance the one owned by TsgcAI_MCP_Server_Stdio.

No state

The component keeps no state of its own. What a tool call means, who may call it and what a fragment request may return are decisions of your application, exactly as they are for a route in a web application. What the model can trigger is a tool you registered, answering with markup you built.

See it without an assistant

Demos\60.HTML\01.RunTime\19.MCPApp is a console server on port 5725 that publishes two apps, orders.board and sales.summary, on one endpoint, and ships a reference host. Open http://localhost:5725/ and the left panel does what a real host does: it calls the tool, reads the resource, renders it in a sandboxed frame and forwards the page's calls. Point Claude, ChatGPT or VS Code at http://localhost:5725/mcp for the real thing.

Availability

The unit compiles where both SGC_HTML and SGC_AI_MCP are defined. SGC_AI_MCP comes with the Enterprise and All-Access editions and with the sgcAI pack. SGC_HTML comes with sgcHTML, which is sold independently of sgcWebSockets, on every platform except Android and iOS.

Keep exploring

Online HelpUsage guide for MCP Apps: the tool, the resource, the fragment tool and the bridge.
All sgcHTML ComponentsBrowse the full feature matrix of 80+ components.
Download Free TrialThe 30-day trial ships the 60.HTML demo projects.
PricingSingle, Team and Site licenses with full source code.
Best value: All-AccessEvery eSeGeCe product, Premium Support included, from €1,059/year.
See All-Access pricing

Ready to Get Started?

Download the free trial and start building web UIs in Delphi, C++ Builder and .NET.