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 컴포넌트로 구성됩니다. MCP 서버에서 실행되므로 sgcWebSockets의 Enterprise 또는 All-Access 에디션, 또는 sgcAI 팩이 필요합니다.
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 스키마입니다. 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은 한 앱의 문서 전체를 반환하며, 이것이 리소스가 응답하는 내용이자 테스트가 읽는 내용입니다.
샌드박스 프레임에는 hx-get에 응답할 HTTP 서버가 없습니다. 모든 문서는 window.sgcMCPApp을 갖습니다. 이것은 페이지가 준비되면 ui/initialize를 보내고, 호스트가 응답한 테마를 적용하며, 호스트가 프레임 크기를 정할 수 있도록 페이지 높이를 보고하고, hx-get과 hx-post를 모두 프래그먼트 도구의 tools/call로 바꾸는 htmx 확장을 등록합니다. OnFragment가 이에 응답합니다. aTarget은 요소가 요청한 것, aArguments는 폼 또는 쿼리의 JSON, aHTML은 교체되어 들어가는 마크업입니다. HTTP로 제공되는 동일한 마크업은 일반 htmx 요청입니다.
프레임은 샌드박스 안에 있고 보통 자체 네트워크가 없으므로 아무것도 링크할 수 없습니다. InlineAssets는 기본적으로 켜져 있으며, 스타일시트, 스크립트, 페이지가 하나의 파일로 함께 전달되게 합니다. 같은 컴퓨터에서 자산을 제공할 수 있는 호스트에서만 끄십시오.
MCPServer는 앱이 선언되는 TsgcAI_MCP_Server입니다. TsgcWSAPIServer_MCP가 이를 공개 속성으로 노출하므로, FApps.MCPServer := FMCP.MCPServer로 웹 서버의 엔드포인트에 앱을 올릴 수 있습니다. 컴포넌트는 도구와 리소스 이벤트를 구독하고 기존에 있던 핸들러를 이어서 호출하므로, 이미 게시한 도구는 계속 동작합니다. 이 이벤트를 직접 처리하는 호스트는 ProcessToolsCall과 ProcessResourcesRead를 호출하며, 이들은 요청이 앱의 것이 아니면 False를 반환합니다. .NET에서는 이미 가지고 있는 TsgcAI_MCP_Server를 지정하십시오. 예를 들어 TsgcAI_MCP_Server_Stdio가 소유한 것을 사용할 수 있습니다.
컴포넌트는 자체 상태를 전혀 갖지 않습니다. 도구 호출이 무엇을 의미하는지, 누가 호출할 수 있는지, 프래그먼트 요청이 무엇을 반환할 수 있는지는 웹 애플리케이션의 라우트와 마찬가지로 애플리케이션이 결정할 사항입니다. 모델이 실행할 수 있는 것은 사용자가 등록한 도구이며, 그 도구는 사용자가 만든 마크업으로 응답합니다.
Demos\60.HTML\01.RunTime\19.MCPApp은 하나의 엔드포인트에 orders.board와 sales.summary 두 개의 앱을 게시하고 참조 호스트를 함께 제공하는, 포트 5725의 콘솔 서버입니다. 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은 sgcWebSockets와 별도로 판매되는 sgcHTML에 포함되며, Android와 iOS를 제외한 모든 플랫폼에서 사용할 수 있습니다.