CopilotKit MCP Apps 实战:.NET Agent 零工具声明,MCP 服务器 UI 自动内嵌聊天
【免费下载链接】CopilotKitThe Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit
本文基于仓库中 MS Agent Framework(.NET 后端)集成示例的 MCP Apps 演示文档,完整拆解「CopilotKit 聊天 + 带 UI 资源的 MCP 服务器」的接线方式:一个不声明任何自定义工具的 .NET Agent,如何通过 Runtime 侧的mcpApps配置自动获得远程 MCP 工具,并在工具调用后把 MCP 服务器提供的 UI(Excalidraw 画布)以沙箱 iframe 形式渲染进聊天流,且前端一行渲染器注册代码都不用写。读完本文,你可以复现「后端零工具、前端零渲染器」的 MCP Apps 集成链路,并理解serverId固定、专用 runtime 路由等关键设计决策的原因。
一、MCP Apps 演示要解决的问题
MCP Apps 指的是一类特殊的 MCP(Model Context Protocol)服务器:它的每个工具都携带一个关联的UI 资源。传统做法下,要展示工具产出的 UI,前端必须注册对应的渲染器;而 MCP Apps 的机制是——当 Agent 调用这类工具时,CopilotKit Runtime 的中间件会抓取该工具关联的 UI 资源,发出一个activity事件,由 CopilotKit 内置的MCPAppsActivityRenderer把 UI 作为沙箱 iframe直接渲染在聊天消息流里,应用侧无需注册任何渲染器(见 演示说明文档)。
本演示指向公开的 Excalidraw MCP 应用(默认https://mcp.excalidraw.com)。仓库建议的交互方式(对应 suggestions.ts 中配置的两条建议提示):
- "Use Excalidraw to draw a simple flowchart with three steps."
- "Open Excalidraw and sketch a system diagram with a client, server, and database."
Agent 会调用 MCP 服务器的绘图工具create_view,随后聊天中内联出现可交互的 Excalidraw 画布。
二、整体链路:四个环节如何协作
从 演示文档 的 "Technical Details" 一节可以提炼出这条链路的四个环节,每个环节在仓库中都有对应实现:
- .NET Agent 后端(无自定义工具):
agent/McpAppsAgent.cs暴露一个"裸"的ChatClientAgent,tools为空数组;所有工具都来自远程 MCP 服务器,经 Runtime 的 MCP Apps 中间件注入。 - Runtime 配置:前端 API 路由创建专用
CopilotRuntime,其中mcpApps.servers声明 HTTP 类型的 MCP 服务器;Runtime 会自动把 MCP Apps 中间件应用到所有注册的 Agent 上。 - 中间件行为:每次请求时,中间件拉取远程 MCP 服务器的工具列表并暴露给 Agent;当 Agent 调用其中某个工具时,中间件抓取关联的 UI 资源并发出携带该资源的
activity事件。 - 内置渲染器:
CopilotKitProvider(<CopilotKit>组件)自动注册MCPAppsActivityRenderer,消费activity事件并把 UI 资源渲染为聊天内联的沙箱 iframe。
这套设计把「工具从哪来、UI 怎么画」全部收敛到 MCP 服务器与 Runtime 配置两个位置,前端只剩一个<CopilotChat />。
三、后端:零工具的 .NET Agent
3.1 Agent 工厂:ChatClientAgent + 空工具集
后端 Agent 定义在 McpAppsAgent.cs,核心创建逻辑非常克制:
public AIAgent CreateMcpAppsAgent() { // gpt-4o-mini for speed — Excalidraw element emission is simple JSON // and we bias hard toward sub-30s generation. var chatClient = _openAiClient.GetChatClient("gpt-4o-mini").AsIChatClient(); return new ChatClientAgent( chatClient, name: "McpAppsAgent", instructions: SystemPrompt, tools: []); // 关键:不声明任何 bespoke 工具 }要点:
tools: []是刻意为之。源码注释明确写道:"This agent has no bespoke tools — the CopilotKit runtime is wired withmcpApps: { servers: [...] }... The runtime auto-applies the MCP Apps middleware which exposes the remote MCP server's tools to this agent at request time"。即工具集完全由中间件在请求期从远程服务器动态补齐,后端不应重复声明同一批工具。SystemPrompt是一份针对 Excalidraw 场景高度约束的系统提示词(要求一次性create_view调用、3–5 个元素、每个元素唯一id、末尾附带一个cameraUpdate取景、禁止调用read_me等),目的是让轻量模型gpt-4o-mini在数秒内产出"够用"的图,而非精雕细琢。- 模型与 API 凭证通过
ApiKeyResolver(见 ApiKeyResolver.cs)从配置解析,模型名硬编码为gpt-4o-mini,注释解释了选型理由:Excalidraw 元素输出就是简单 JSON,速度优先。
3.2 挂载 AG-UI 端点
Agent 在 Program.cs 中通过MapAGUI挂载为 AG-UI 兼容端点:
// MCP Apps demo. var mcpAppsFactory = new McpAppsAgentFactory(builder.Configuration, loggerFactory); app.MapAGUI("/mcp-apps", mcpAppsFactory.CreateMcpAppsAgent());这对应文档中"mounted at/mcp-appsviaProgram.cs"的说明:.NET进程(默认基址http://localhost:8000)在/mcp-apps路径上以 AG-UI 协议对外提供该 Agent。前端 Runtime 通过HttpAgent客户端按 URL 寻址它。
四、Runtime 侧:专用路由与 mcpApps 配置
4.1 路由文件与单路由模式
Runtime 配置位于 route.ts(注意:实际路径是[[...slug]]可选 catch-all 目录,而非普通单文件route.ts)。文件头部注释解释了为什么需要 catch-all:
MCP Apps resource proxy requests are addressed below
/api/copilotkit-mcp-apps, so a plainroute.tsat the parent segment handles the chat POST but misses those subpath requests.
也就是说,MCP Apps 的资源代理请求(iframe 加载 UI 资源时的回源请求)落在/api/copilotkit-mcp-apps的子路径下;只用父级的普通route.ts只能接住聊天的 POST,会漏掉这些子路径请求。catch-all 写法同时兼容两种请求,这也是该演示对齐 langgraph-python 参考实现的细节。
路由处理函数使用单路由模式:
export const POST = async (req: NextRequest) => { try { const copilotHandler = createCopilotRuntimeHandler({ runtime, basePath: "/api/copilotkit-mcp-apps", mode: "single-route", }); return await copilotHandler(req); } catch (error: unknown) { // 错误以 JSON 返回(含 message 与 stack),状态码 500 return NextResponse.json({ error: e.message, stack: e.stack }, { status: 500 }); } };basePath与前端<CopilotKit runtimeUrl="/api/copilotkit-mcp-apps">一一对应。
4.2 mcpApps.servers 是服务端唯一必需配置
完整 Runtime 构造如下(源码中带@region[runtime-mcpapps-config]标记的核心段):
const AGENT_URL = process.env.AGENT_URL || "http://localhost:8000"; const mcpAppsAgent = new HttpAgent({ url: `${AGENT_URL}/mcp-apps/` }); const runtime = new CopilotRuntime({ agents: { "mcp-apps": mcpAppsAgent, "headless-complete": headlessCompleteAgent, // 共享同一 runtime(见下文说明) }, mcpApps: { servers: [ { type: "http", url: process.env.MCP_SERVER_URL || "https://mcp.excalidraw.com", // Keep the server id 1:1 with langgraph-python so persisted MCP Apps // and fixture-backed resource calls use the same identity. serverId: "excalidraw", }, ], }, });逐项说明:
| 配置项 | 取值 | 说明 |
|---|---|---|
mcpApps.servers[].type | "http" | MCP 服务器传输类型,此处为 HTTP |
mcpApps.servers[].url | MCP_SERVER_URL,默认https://mcp.excalidraw.com | HTTP MCP 服务器地址,可通过环境变量指向自建 MCP Apps 服务器 |
mcpApps.servers[].serverId | "excalidraw"(代码实际值) | 稳定的服务器身份标识,原因见下节 |
agents["mcp-apps"] | HttpAgent指向${AGENT_URL}/mcp-apps/ | 以 AG-UI over HTTP 方式接入 .NET 后端 Agent |
AGENT_URL | 默认http://localhost:8000 | .NET Agent 进程基址 |
basePath/mode | /api/copilotkit-mcp-apps/single-route | 单路由模式,所有请求经同一 POST 入口分发 |
需要指出一个细节差异:演示 README 文中写的是serverId: "mcp_apps_server",而当前仓库代码中实际值为"excalidraw",注释说明这是为了与 langgraph-python 参考实现(mcp_apps_agent 参考 注释中提到的 parity 目标)保持 1:1,使持久化的 MCP Apps 状态和基于 fixture 的资源调用使用同一身份。以源码为准即可。
该路由同时注册了headless-completeAgent(指向${AGENT_URL}/headless-complete)。源码注释解释了共享原因:headless-complete 演示单元也要验证 MCP Apps activity 渲染(其 "Sketch a diagram" 建议经由同一中间件触发 Excalidraw MCP 服务器),因此复用此 runtime,而不是另建端点。
五、前端:零渲染器接线的三行式实现
5.1 页面:一个 Provider 加一个 Chat
page.tsx 的页面组件全部逻辑只有两行核心代码:
export default function MCPAppsDemo() { // @region[no-frontend-renderer-needed] // No `renderActivityMessages`, no `useRenderActivityMessage` — the // CopilotKitProvider auto-registers the built-in `MCPAppsActivityRenderer` // for the "mcp-apps" activity type. A plain <CopilotChat /> is enough. return ( <CopilotKit runtimeUrl="/api/copilotkit-mcp-apps" agent="mcp-apps"> <div className="flex justify-center items-center h-screen w-full"> <div className="h-full w-full max-w-4xl"> <Chat /> </div> </div> </CopilotKit> ); // @endregion[no-frontend-renderer-needed] }文件顶部注释把机制说得很直白:<CopilotKit>指向专用 runtime,agent属性选中 runtime 中注册的"mcp-apps"Agent;不需要renderActivityMessages、不需要useRenderActivityMessage,因为 Provider 已为"mcp-apps"活动类型自动注册内置渲染器。
5.2 聊天组件与建议提示
chat.tsx 保持最小形态:
export function Chat() { useMcpAppsSuggestions(); return <CopilotChat agentId="mcp-apps" className="h-full rounded-2xl" />; }suggestions.ts 通过useConfigureSuggestions注入两条固定建议(available: "always"),即第一节提到的两条 Excalidraw 绘图提示。建议消息就是触发 MCP 工具调用的自然语言入口。
六、三条可复用的设计决策
演示文档的 "Building With This" 一节给出了三条实践准则,结合源码可以进一步落实为具体做法:
- 前端不需要渲染器。
MCPAppsActivityRenderer由CopilotKitProvider自动注册,基础 MCP Apps 场景下useRenderActivityMessage和任何自定义 activity 处理器都不需要——这一点在 page.tsx 的@region[no-frontend-renderer-needed]注释块中有明示。只有当你要定制 iframe 外层样式或行为时,才考虑介入活动消息渲染。 - 给 MCP Apps 配专用 runtime 路由。MCP Apps 配置挂在 Runtime 上而非 Agent 上,因此应给它独立的
/api/copilotkit-*路由(本例为/api/copilotkit-mcp-apps),使其能与主聊天 runtime 独立演进。注意落地时要用 catch-all([[...slug]])形式,否则资源代理的子路径请求会漏接。 - 后端 Agent 工具集保持为空。工具经由中间件从 MCP 服务器注入——不要把 MCP 工具再复制成 bespoke agent 工具。McpAppsAgent.cs 的
tools: []就是这条准则的直接体现。
关于serverId固定(pinned server ID)的原理:README 指出,若不显式提供serverId,CopilotKit 会对 URL 做哈希生成身份;一旦 URL 变化,历史会话线程中持久化的 MCP Apps 状态将无法恢复,且这种破坏是"静默"发生的。因此代码中固定serverId: "excalidraw",并同时保持与 langgraph-python 实现一致,保证跨技术栈的身份可比。
七、配置参数与验证方式
环境变量
| 变量 | 默认值 | 作用 |
|---|---|---|
MCP_SERVER_URL | https://mcp.excalidraw.com | 指向任意 HTTP MCP Apps 服务器 |
AGENT_URL | http://localhost:8000 | .NET Agent 进程基址,Runtime 据此拼出/mcp-apps/端点 |
两个变量均在 route.ts 中以process.env.X || 默认值的写法读取,未设置时演示可开箱运行(前提是 .NET Agent 进程与默认端点可用)。
端到端验证
该演示配有 Playwright E2E 用例 mcp-apps.spec.ts(Playwright 配置见 playwright.config.ts)。从源码结构看,E2E 验证覆盖"发送绘图提示 → Agent 调用create_view→ 聊天中出现 Excalidraw iframe"这条完整链路;若仓库内启用了 aimock 录制回放(参见 AimockHeaderContext.cs 等中间件),该链路也可在无真实 LLM 的环境下回放验证。
八、小结
这套 MCP Apps 接线的分工非常清晰:MCP 服务器提供"工具 + UI 资源",Runtime通过mcpApps.servers一条配置声明服务器并自动应用中间件(请求期注入工具、工具调用后发射activity事件),前端仅以<CopilotKit runtimeUrl agent>+<CopilotChat agentId>完成接线。仓库中 ms-agent-dotnet 集成目录 下的 agent/McpAppsAgent.cs、Program.cs、copilotkit-mcp-apps 路由 与 演示页面 共同构成一个可直接参照的最小完整实现;若需扩展到其他 MCP Apps 服务器,只需替换MCP_SERVER_URL并视情况调整serverId与系统提示词即可。
【免费下载链接】CopilotKitThe Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考