用 CopilotKit 构建 Open MCP Client:Mastra Agent + E2B 沙箱驱动的 MCP App 生成器实战
【免费下载链接】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
本篇文章基于仓库中的 open-mcp-client 演示项目 展开,完整讲解一个可运行的MCP App 生成器(MCP App Builder)参考实现:Next.js 前端如何通过 CopilotKit v2 聊天界面驱动 Mastra Agent,后者如何在 E2B 云沙箱中按需装配运行mcp-use-server模板,并最终把生成的带 UI 的 MCP 工具以可点击、可预览、可下载的形式交付给用户。读完本文,你将掌握这个三端闭环的架构设计、环境配置、构建脚本、动态 MCP 侧边栏渲染机制以及 Render 云端部署的完整方案。
架构总览:从 Web UI 到 E2B 沙箱的完整链路
这个 monorepo 演示的是 CopilotKit 生态中"渲染 MCP Apps 与创建 MCP Apps"的完整闭环。核心组件共有三个:
apps/web:Next.js 前端,即MCP App Builder的 Web UI。它内置了 CopilotKit v2 聊天面板、MCP 服务器管理侧边栏、工具列表与工具预览模态框。apps/mcp-use-server:运行在E2B沙箱里的 MCP 服务模板。Agent 通过预构建的 E2B 镜像快速启动沙箱,在沙箱内编写工具(tool)与小组件(widget),最终对外暴露一个标准的 MCP endpoint。apps/threejs-server:本地可选的 Three.js MCP 示例(在本地运行整个项目时,它作为侧边栏的默认 MCP 服务器出现)。
三者通过一个后端路由串联:Web UI 的聊天请求发往/api/mastra-agent(实现见 apps/web/app/api/mastra-agent/route.ts),Mastra Agent 负责编排:既可以直接调用已连接的 MCP 工具,也可以调用一组内置的"工作区工具"(provision/read/write/edit/exec/restart/download)去 E2B 沙箱里生成新的 MCP 工具。
环境准备与快速开始
前置条件
- Node.js 20+;
- pnpm:workspace 必需(monorepo 的包管理统一用 pnpm);
- OpenAI API Key:
OPENAI_API_KEY必填,供聊天与 Agent 使用;可选OPENAI_MODEL指定模型。注意默认值存在两处差异:route.ts中代码默认值为gpt-5.2(见 route.ts),而.env.example与 render.yaml 中建议使用gpt-5.4-2026-03-05,部署或本地运行时以你实际设置的环境变量为准。
关于 Lockfile:
pnpm-lock.yaml是被提交到版本库的,应当始终保留在版本控制中,以保证安装的可复现性(配合--frozen-lockfile)。本仓库的.gitignore只排除了package-lock.json、yarn.lock和bun.lockb,并不排除 pnpm 的 lockfile——这意味着不要用 npm/yarn/bun 生成自己的锁文件去污染这个 workspace。
安装与启动
从仓库根目录(即examples/showcases/open-mcp-client/)执行:
pnpm i Copy-Item .env.example .env # 编辑 .env:至少设置 OPENAI_API_KEY=sk-proj-...;如需沙箱供给再加 E2B_* 变量(见下文) pnpm devpnpm dev实际调用的是Turbo的turbo run dev,会并行启动 workspace 下所有配置了dev任务的包(主要是 Next.js 应用,见根 package.json 与 turbo.json)。启动完成后打开 Next 输出的地址(通常是http://localhost:3000)。
单独启动某个应用
| 目标 | 命令 |
|---|---|
| 只启动 Web 应用 | pnpm --filter web dev(从仓库根目录)或cd apps/web && pnpm dev |
| 启动 Three.js MCP 示例(本地侧边栏默认) | cd apps/threejs-server && pnpm dev |
启动mcp-use-server(本地 MCP,而非 E2B 镜像) | cd apps/mcp-use-server && pnpm dev |
注意三个子应用各自维护独立的package.json(apps/web/package.json、apps/mcp-use-server/package.json),依赖互不相同:Web 侧依赖@copilotkit/react-core、@copilotkit/runtime、@mastra/core、@mastra/mcp、@modelcontextprotocol/sdk与e2b;而mcp-use-server侧则依赖mcp-use、@openai/apps-sdk-ui与 React 19。
脚本参考
根工作区(package.json)
| 脚本 | 说明 |
|---|---|
pnpm dev | Turbo 并发执行所有包的dev |
pnpm build | Turbo 并发执行所有包的build(对web而言会先跑prebuild,见下) |
pnpm lint | Turbo 执行各包 lint |
pnpm clean/pnpm fresh | 清理 installs / lockfile 的辅助脚本(fresh等价于clean && pnpm i) |
apps/web
| 脚本 | 说明 |
|---|---|
pnpm dev | Next.js 开发服务器(Turbopack) |
pnpm build | 依次执行prebuild→pack-download-kit(生成.download-kit/base.tar.gz,供"完整 app kit"下载使用)→next build |
pnpm pack-download-kit | 无需完整 Next 构建,单独重新生成.download-kit/base.tar.gz |
pnpm start | 生产模式 Next 服务器 |
pnpm lint | ESLint |
pnpm run test:download-kit | 集成测试:Next + E2B +POST /api/workspace/download |
pnpm run test:e2b-download | 冒烟测试:仅验证 E2B tarball 产物 |
pnpm run dev:mcp | 从apps/threejs-server启动 Three.js 示例 MCP(本地 MCP 与 Web 同跑时使用) |
其中pack-download-kit的实现在 scripts/pack-download-kit.mjs:它会以mcp-apps-starter为根目录名,把仓库外壳(显式跳过node_modules、.next、.turbo、dist、.vercel、.git等目录)打包为base.tar.gz。
E2B 沙箱模板(apps/mcp-use-server)
Agent 用于供给沙箱的 E2B模板定义在 template.ts。当你在该模板中修改了依赖、工具或小组件后,需要重建镜像:
| 脚本 | 用途 | 命令(仓库根目录执行) |
|---|---|---|
Dev 模板(mcp-use-server-dev) | 日常迭代 | cd apps/mcp-use-server && npx tsx --env-file=../../.env build.dev.ts |
Prod 模板(mcp-use-server) | 生产环境的稳定快照 | cd apps/mcp-use-server && npx tsx --env-file=../../.env build.prod.ts |
两个构建脚本(build.dev.ts、build.prod.ts)都以 2 CPU / 2048MB 的规格调用Template.build,构建结束后会在控制台打印一个BuildInfo对象,其中templateId需要抄到.env的E2B_TEMPLATE(以及你的托管面板)中。注意:模板的名称(如mcp-use-server-dev)并不等于templateId,二者不要混淆。
E2B 沙箱模板:把 mcp-use-server 烘焙成镜像
模板的核心价值在于把依赖和构建产物提前烘焙进镜像,让Sandbox.create(templateId)在几秒内就能启动一个已经装好依赖、跑起服务的 MCP 服务器。从 template.ts 可以看到完整的构建链:
import { Template, waitForPort } from "e2b"; export const template = Template() .fromNodeImage("lts") // Node.js LTS 基础镜像 .setWorkdir("/home/user/workspace") // 工作目录 .copy(".", "/home/user/workspace") // 拷贝项目文件 .runCmd("npm install --no-audit --no-fund") // 把 node_modules 烤进镜像 .runCmd("npm run build") // 预构建 mcp-use 小组件 .setStartCmd( "npx tsx index.ts", // 沙箱启动时拉起服务 waitForPort(3109), // 等待 3109 端口就绪 );mcp-use-server的入口 index.ts 会在 3109 端口启动一个MCPServer,并通过register()注册各个工具;新增小组件时按文件头注释的三步走:写resources/<widget-name>/widget.tsx、写tools/<tool-name>.ts、然后在index.ts两个标记段(// ADD NEW TOOL IMPORTS HERE与// ADD NEW TOOL REGISTRATIONS HERE)补上 import 与注册调用。
当E2B_TEMPLATE为空时,e2b.ts 会退回冷启动路径:git clone --depth 1克隆E2B_REPO_URL指定的仓库,再执行npm install与npm run dev,冷启动通常需要 60~90 秒。无论哪种路径,最终都会通过betaGetMcpUrl()(E2B 托管的 MCP URL)或回退到https://<sandbox-host>:3109/mcp拿到 endpoint,并默认给沙箱设置 60 分钟的生命周期上限(SANDBOX_TIMEOUT_MS)。
Agent 与 UI 的协作机制
聊天与建议(ChatSuggestions)
Starter prompts通过useCopilotChatSuggestions注册,实现见 ChatSuggestions.tsx,与 v2 的CopilotChat组件搭配使用。默认提供了四个建议条目(定义在 chatStarters.ts):Tic tac toe、Tip calculator、Dice roller 三个"有界的小组件构建演示",外加一个"Try Excalidraw"测试。可通过NEXT_PUBLIC_CHAT_STARTER_PROMPTS覆盖,JSON 数组格式为[{"title":"...","message":"..."}]。
构建后的测试芯片(show_mcp_test_prompts)
当 Agent 在沙箱里构建完一个新的 MCP 工具后,会调用一个纯前端 actionshow_mcp_test_prompts(实现见 McpTestPromptsAction.tsx)。该 action 接收一个{ label, message }[]的 JSON 字符串,前端解析后渲染成可点击的芯片;用户点击芯片时,通过appendMessage把对应 message 追加进同一条聊天线程,从而就地测试新生成的 MCP 工具(最多渲染 8 个芯片)。
下载:MCP-only 与 full app kit
restart_server/ 侧边栏下载能够返回完整 app kit(.tar.gz):当apps/web/.download-kit/base.tar.gz存在时(由pnpm build/prebuild生成),后端会把 E2B 工作区合并进mcp-apps-starter/骨架,产出"monorepo + 你的沙箱代码"的完整项目包;否则下载产物只包含MCP-only的工作区代码。合并逻辑实现在 merge-download-kit.ts:它把 E2B 导出的workspace/目录树替换进基础 kit 的apps/mcp-use-server位置,重新打成 gzip 流返回。下载入口路由为apps/web/app/api/workspace/download/route.ts,E2B 侧打包时会在 e2b.ts 的prepareDownload中先清理node_modules、dist、.agent等大目录,再以tar -czf归档(注释明确说明 E2B 环境常缺 GNUzip,故用 tar)。README 中关于合并细节的完整说明指向docs/HANDOFF.md。
调试 Agent 流量
在.env中设置MASTRA_AGENT_DEBUG=1,/api/mastra-agent就会输出逐请求级别的详细日志(每次请求的 MCP 加载情况、发现的 UI 工具、Agent 就绪状态等),实现在 route.ts 中,用mastraLog统一收口。
动态 MCP UI(侧边栏)
MCP 服务器管理
- MCP servers:支持按 URL 添加/移除 MCP 服务器(可选
serverId);前端维护的服务器列表会通过x-mcp-serversHTTP 头在每次请求时传给后端(/api/mastra-agent用它动态加载工具)。服务端缺省解析逻辑见 mcp-defaults.ts:无该头时回退到DEFAULT_MCP_SERVERS,再回退到内置默认Excalidraw(https://mcp.excalidraw.com,serverId: "excalidraw")。前端初始列表则来自 mcpServers.ts,由NEXT_PUBLIC_DEFAULT_MCP_SERVERS覆盖(注意这是客户端变量,必须带NEXT_PUBLIC_前缀;生产环境无托管 MCP 时可设为[],让用户自行在 UI 中添加)。 - Tools:侧边栏以紧凑列表展示当前连接服务器发现到的全部工具;点击某个工具会在模态框中打开详情与预览(桌面与移动端共用一套
ToolDetailModal,而非移动端第三个 Tab)。 - Chat:CopilotKit v2 聊天,带建议芯片。
移动端布局
页面主布局在 page.tsx 中实现:
- 移动端(<768px):两个 Tab——Chat与Tools(含服务器与工具列表);工具预览/详情在模态框打开;
- 桌面端(md+):340px 固定宽度侧边栏 + 弹性聊天列(
gridTemplateColumns: "340px minmax(0,1fr)"); - 移动/桌面两套布局通过
window.matchMedia("(min-width: 768px)")互斥挂载,避免同时渲染两份CopilotChat导致重复请求/api/mastra-agent; - 聊天 UX:专门处理了 spacing 与底部 padding,确保输入框不会遮挡最新消息;
- 页面挂载时会尝试从
localStorage(mcp_active_workspace)恢复上次的 E2B 工作区,调用/api/workspace/info校验后自动重新连上,避免刷新页面就要重新供给沙箱。
后端实现细节:/api/mastra-agent与/api/mcp-introspect
x-mcp-servers头解析与默认服务器
route.ts 的readMcpServersFromHeader解析x-mcp-servers,解析失败或缺失时回退到getDefaultMcpServers()。每个服务器配置形如{ type: "http" | "sse", url, serverId? },且会基于type + url计算一个 MD5 的serverHash用于后续工具归属定位。
MCP UI 元数据发现
fetchUIToolMetadata会对每个服务器发起 MCP 握手(SSE 用SSEClientTransport,其余用StreamableHTTPClientTransport),调用listTools()扫描工具的_meta["ui/resourceUri"],凡是声明了 UI 资源的工具都会登记为"UI 工具",并按 Mastra 的命名习惯把工具名规范为${serverId}_${tool.name}。这些信息构成了 AG-UI 中间件判断"哪个工具结果需要渲染成 App"的依据。
AG-UI 中间件与 ACTIVITY_SNAPSHOT
这是整个演示最关键的一层。createMcpUIMiddleware注册在 AG-UI 的Observable 层(而非 SSE 层),因此事件能顺利流经 CopilotKit v2 管线并触发内置的MCPAppsActivityRenderer。当某个工具返回TOOL_CALL_RESULT且该工具命中 UI 工具表时,中间件会拦截结果、把工具入参(toolInput)与结果包装成MCPAppsActivityContentSchema兼容的结构,并发射一条ACTIVITY_SNAPSHOT事件(activityType: "mcp-apps",携带resourceUri、serverHash、serverId),前端据此渲染小组件 iframe。
由于 CopilotKit runtime 在runAgent()前会调用registeredAgent.clone(),而MastraAgent.clone()会丢失.use()注册的中间件,源码中特意重写了clone()方法,让克隆体重新挂载同一份中间件——这是运行期最容易踩坑、也最值得复用的修复模式。
代理 MCP 请求与 HTML 重写
当MCPAppsActivityRenderer需要拉取小组件 HTML 时,会通过__proxiedMCPRequest把请求交给中间件代执行(支持tools/call、resources/read、notifications/message、ping四种方法)。executeProxiedMcpRequest在resources/read分支里还做了一轮CSP 安全的 HTML 修复:
- 从
<base href="...">标签提取小组件内部的源站(如http://localhost:3109); - 移除
<base>标签(它会被 CSP 的base-uri 'self'拦截,且在--inline构建下本无必要); - 把剩余的内部源站引用统一改写为外部 endpoint 的源站,保证 sandboxed iframe 内的图片、
window.__mcpPublicUrl、window.__getFile等都能正确解析。
同样的改写逻辑也完整出现在 mcp-introspect/route.ts 中,用于离线预览。
工作区工具集
Agent 除了 MCP 工具外,还挂载了一组服务端工作区工具(全部在 route.ts 中用 zod 声明 schema):
| 工具 | 作用 |
|---|---|
provision_workspace | 从预构建模板创建 E2B 沙箱(有模板约 3 秒),并自动清理模板默认的product-search工具、重启服务器,返回workspaceId与endpoint |
read_file/write_file/edit_file | 在沙箱内读写/精准替换文件(edit_file支持一次多个 search/replace,逐条顺序应用) |
exec | 在沙箱工作区执行 shell 命令(支持后台运行与超时;注意沙箱内没有fuser/lsof,查端口要用ss) |
restart_server | 杀掉 3109 端口旧服务、后台重启npm run dev、每 5 秒轮询tools/list直到健康(最多 30 秒),失败则返回构建日志 |
get_workspace_info | 查询沙箱状态与 endpoint |
download_workspace | 打包工作区为.tar.gz并返回签名下载 URL(约 1 小时有效) |
Agent 配置上把maxSteps提到 25(默认 10),并设置了reasoningEffort: "minimal",以适配快速构建类任务。整个POST路由声明了export const maxDuration = 300,允许最长 5 分钟的 Agent 循环。
Agent 系统提示与工作流
内置的AGENT_SYSTEM_PROMPT(route.ts)为模型固化了三条典型工作流:
- WORKFLOW A(构建新工具):
provision_workspace→add_mcp_server→set_active_workspace→ 写 widget → 写 tool →edit_file注册到index.ts→restart_server→refresh_mcp_tools→show_mcp_test_prompts→ 告知用户; - WORKFLOW B(编辑/追加工具):跳过供给步骤,直接改文件后走
restart_server → refresh_mcp_tools → show_mcp_test_prompts; - WORKFLOW C(使用已有 MCP 工具):直接调用工具即可,无需沙箱。
系统提示还明确约束了生成边界:优先"单工具 + 单小组件",不新增 npm 依赖,避免流程图/节点图/无限画布等重度需求(除非用户明确要求),并在需求模糊时先问一句澄清。工具文件的模板要求每个 widget 工具都必须带上_meta["ui/previewData"](对象形状需与小组件 props 一致),否则 Studio 没有演示预览。
消息 ID 去重
由于 Mastra 会复用同一个messageId同时作为TOOL_CALL_START.parentMessageId与TEXT_MESSAGE_*的messageId,CopilotKit 会根据两套事件各建一条消息,导致 React 侧出现重复 key。中间件用三张映射表(usedAsParentId、currentTextRemap、parentRemap)对冲突的messageId/parentMessageId重新生成 UUID,从事件流源头消除重复。
mcp-introspect:工具内省与 UI 预览
apps/web/app/api/mcp-introspect/route.ts 提供一个独立的POST { endpoint }接口,供前端钩子 useMcpIntrospect.ts 使用:
- 连接优先尝试Streamable HTTP,失败自动回退SSE;
- 分页调用
listTools(),提取每个工具的name、description、inputSchema、_meta、hasUI、uiResourceUri、uiPreviewData; - 对有 UI 的工具调用
readResource拉取 HTML,并应用与代理层相同的<base>标签/内部源站改写; - 最后列出全部原始资源,返回
{ tools, resources }。
该接口的超时上限同样是 300 秒,因为工具枚举与 HTML 拉取可能较慢。侧边栏的工具列表、UI/Local/Modified徽标(见 page.tsx 中的渲染逻辑)都来自这条内省链路。
在 Render 上托管
Blueprint 部署步骤
- 把仓库推送到 GitHub/GitLab;
- 在 Render 控制台进入Blueprints,选择你的仓库——Render 会自动识别根目录的 render.yaml;
- 在面板中设置机密环境变量:至少
OPENAI_API_KEY;需要沙箱时再加E2B_API_KEY与E2B_TEMPLATE; - 部署。Blueprint 会自动配置构建/启动命令、
NODE_VERSION与HOSTNAME。
从 render.yaml 可以看到:服务类型为web、runtime 为docker、plan 为standard,E2B_REPO_URL与OPENAI_MODEL(gpt-5.4-2026-03-05)带有默认值,其余均为sync: false的机密变量,需要在面板单独注入。此外还有一个配套的 Dockerfile 可供自托管。
长期运行进程说明
Render 运行的是常驻 Node.js 进程而非 serverless 函数,因此不存在逐函数超时限制——这也与maxDuration = 300的长 Agent 循环设计相匹配:在 serverless 平台上 5 分钟上限是硬约束,而 Render 上可以跑更久。
总结
Open MCP Client 演示项目把 CopilotKit 的前端 Agent 编排能力、Mastra 的工具调用生态、E2B 的云端沙箱供给和mcp-use-server的 UI 工具运行时整合在了一个可运行的 monorepo 里。从本仓库源码可以清晰看到一条可复用的工程链路:前端x-mcp-servers头声明服务器列表 → Mastra Agent 动态加载 MCP 工具并执行工作区工具 → AG-UI 中间件把工具结果转换为ACTIVITY_SNAPSHOT→ CopilotKit v2 渲染小组件 → 一键打包下载完整项目。若要在自己的项目中复刻这套模式,建议按顺序吃透三个文件:route.ts(Agent 与中间件)、template.ts(沙箱镜像)与 page.tsx(前端布局与状态管理)。
【免费下载链接】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),仅供参考