MCPApp
TsgcHTMLComponent_MCPApp:一个以页面而不是文本作答的 MCP 工具,因此渲染任意网页的同一套 Delphi 组件,也可以在 ChatGPT、Claude 和 VS Code 中渲染(MCP Apps)。
TsgcHTMLComponent_MCPApp:一个以页面而不是文本作答的 MCP 工具,因此渲染任意网页的同一套 Delphi 组件,也可以在 ChatGPT、Claude 和 VS Code 中渲染(MCP Apps)。
MCP App 是一种工具,其结果由宿主渲染,而不只是被读取。该组件会在您已有的 MCP 服务器上发布这个工具、承载页面的资源,以及页面自身调用的配套工具,页面则由与其他页面相同的 sgcHTML 组件构建。需要 sgcWebSockets 的 Enterprise 或 All-Access 版本,或者 sgcAI 包,因为它运行在 MCP 服务器上。
TsgcHTMLComponent_MCPApp,带有 TsgcHTMLMCPApps 集合,其条目为 TsgcHTMLMCPApp
宿主在沙盒框架中显示的单个 HTML 文档,其资源全部内联
Delphi, C++ Builder, .NET
把 MCPServer 设为某个 TsgcWSAPIServer_MCP,添加一个带有工具名称和描述的应用,响应 OnRender、OnToolCall 和 OnFragment,然后调用 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();
您最常使用的成员。
Apps 是一个 TsgcHTMLMCPApps 集合,由 TsgcHTMLMCPApp 条目组成。ToolName 是必需的:没有它的应用不会注册任何内容。Description 是模型用来决定是否调用该工具的内容,InputSchema 是其参数的 JSON schema。Title、ResourceURI、PageBuilder、Enabled、PreferredWidth 和 PreferredHeight 构成一个应用的其余部分,最后两个是请求宿主打开框架时使用的尺寸。
服务器上每个应用有三样东西:工具,以 ToolName 命名,其 _meta.ui.resourceUri 告诉宿主由哪个资源承载页面;配套工具 <ToolName>.fragment,由页面调用,模型从不调用;以及资源 ui://sgchtml/<toolname>,以 text/html;profile=mcp-app 提供。在 Delphi 中,RegisterApps 由 Loaded 运行,添加应用的代码会再次调用它;在 .NET 中,需要您自己调用。UnregisterApps 会移除它们。
工具结果有两类读者。在 OnToolCall 中,aText 是模型读取的句子,aStructured 是页面读取的 JSON。两者都是可选的,文本为空时会使用应用的 Title。两者不同时(通常都是如此)请分别提供:模型需要一句话,页面需要数字。
OnRender 以普通 sgcHTML 标记的形式返回页面。aHTML 到达时已包含应用的 PageBuilder 所构建的页面(如果已指定的话),因此处理程序可以替换它,也可以保持原样。GetAppHTML 返回一个应用的完整文档,也就是资源所返回的内容,以及测试所读取的内容。
沙盒框架没有 HTTP 服务器来响应 hx-get。每个文档都带有 window.sgcMCPApp:页面就绪时它发送 ui/initialize,套用宿主回复的主题,报告页面高度以便宿主调整框架大小,并注册一个 htmx 扩展,把每个 hx-get 和 hx-post 转换为对片段工具的 tools/call。OnFragment 负责响应:aTarget 是元素请求的目标,aArguments 是表单或查询的 JSON,aHTML 是换入的标记。同样的标记通过 HTTP 提供时,就是普通的 htmx 请求。
框架处于沙盒中,通常没有自己的网络,因此无法链接任何外部内容。InlineAssets 默认开启,使样式表、脚本和页面合并在一个文件中传输。仅当宿主在同一台机器上、能够提供这些资源时,才将它关闭。
MCPServer 是声明这些应用所在的 TsgcAI_MCP_Server。TsgcWSAPIServer_MCP 将它公开为公共属性,因此 FApps.MCPServer := FMCP.MCPServer 会把这些应用放到 Web 服务器的端点上。组件会订阅工具和资源事件,并把原有的处理程序串联起来,因此您已发布的工具仍然可以继续工作。自行处理这些事件的宿主可以调用 ProcessToolsCall 和 ProcessResourcesRead,当请求不属于某个应用时,它们返回 False。在 .NET 中,请传入您已有的 TsgcAI_MCP_Server,例如 TsgcAI_MCP_Server_Stdio 所拥有的那一个。
组件自身不保存任何状态。工具调用意味着什么、谁可以调用它、片段请求可以返回什么,都由您的应用决定,与 Web 应用中的路由完全一样。模型能够触发的,是您注册的某个工具,它返回的是您构建的标记。
Demos\60.HTML\01.RunTime\19.MCPApp 是一个监听 5725 端口的控制台服务器,在同一个端点上发布两个应用 orders.board 和 sales.summary,并附带一个参考宿主。打开 http://localhost:5725/,左侧面板会执行真实宿主所做的事情:调用工具,读取资源,在沙盒框架中渲染它,并转发页面的调用。要体验真实效果,请将 Claude、ChatGPT 或 VS Code 指向 http://localhost:5725/mcp。
该单元在同时定义了 SGC_HTML 和 SGC_AI_MCP 时编译。SGC_AI_MCP 随 Enterprise 和 All-Access 版本以及 sgcAI 包提供。SGC_HTML 随 sgcHTML 提供,后者独立于 sgcWebSockets 单独销售,适用于除 Android 和 iOS 之外的所有平台。