MCP Apps 实战作业:用 TypeScript 构建带 UI 的石头剪刀布(Rock-Paper-Scissors)MCP App 教程文档人工智能【免费下载链接】mcp-for-beginnersThis open-source curriculum introduces the fundamentals of Model Context Protocol (MCP) through real-world, cross-language examples in .NET, Java, TypeScript, JavaScript, Rust and Python. Designed for developers, it focuses on practical techniques for building modular, scalable, and secure AI workflows from session setup to service orchestration.项目地址https://gitcode.com/GitHub_Trending/mc/mcp-for-beginners点击查看免费下载本篇文章基于 mcp-for-beginners 开源课程中「MCP Apps」章节的 TypeScript 作业解决方案讲解如何将一个普通的 MCP Server 升级为能同时返回数据 用户界面的 MCP App通过registerAppTool注册工具、registerAppResource注册组件资源并用resourceUri把二者绑定前端则用纯 HTML 定义界面、用modelcontextprotocol/ext-apps提供的App类完成事件绑定与工具调用。读完本文你将掌握 MCP Apps 的完整开发链路——从服务端工具/资源注册、前端事件接线到本地、VS Code 与外部 Host 三种方式的运行验证。一、作业目标让 MCP 不只返回数据还返回 UI在 15-mcp-apps 课程主文档 中给出了本次作业的明确要求创建一个石头剪刀布游戏包含两部分UI 部分一个下拉列表供选择石头/剪刀/布、一个提交按钮、一个用于显示双方出拳及胜负结果的标签Server 部分一个名为石头剪刀布的 MCP 工具接收choice作为输入服务端随机生成电脑出拳并判定胜负。这正是 MCP Apps 的核心范式——工具调用结果不再只是纯文本数据而是可以附带一段自包含的、可直接渲染的 UI 组件。本文对应的解决方案位于 03-GettingStarted/15-mcp-apps/assignment/typescript/完整可运行的参考代码则位于 03-GettingStarted/15-mcp-apps/code/typescript/。二、解决方案结构三个文件各司其职作业说明assignment/typescript/README.md给出了解决方案的骨架它只保留了真正关键的代码——标记markup、事件接线event wire up与服务端特性server features项目结构如下my-app server.ts -- the server functionality服务端功能注册工具与 UI 组件资源 src mcp-app.ts -- UI, event wire up前端逻辑事件绑定与工具调用 mcp-app.html -- UI markup界面标记对照仓库中实际的解决方案源码03-GettingStarted/15-mcp-apps/assignment/typescript/my-app/三个文件的职责清晰对应课程主文档中描述的 MCP App 架构server.ts负责在 MCP Server 上注册工具play-rps和组件资源HTML UI两者通过resourceUri关联mcp-app.html是纯 HTML 的用户界面src/mcp-app.ts负责把界面元素与事件按钮点击、下拉选择接线并通过App.callServerTool()与后端通信。三、服务端实现工具 组件资源的两半合一3.1 用 registerAppTool 注册游戏工具在解决方案的 server.ts 中首先用registerAppTool注册石头剪刀布工具。与普通 MCP 工具注册不同的是这里通过_meta.ui.resourceUri把工具与它的 UI 组件资源绑定起来registerAppTool( server, play-rps, { title: Play Rock-Paper-Scissors, description: Play a game of rock-paper-scissors with the server., inputSchema: zod.object({ choice: zod.enum([rock, paper, scissors]), }), _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async ({ choice }) { const options [rock, paper, scissors] as const; const serverChoice options[Math.floor(Math.random() * options.length)]; let result: string; if (choice serverChoice) { result Its a tie! We both chose ${choice}.; } else if ( (choice rock serverChoice scissors) || (choice paper serverChoice rock) || (choice scissors serverChoice paper) ) { result You win! You chose ${choice} and I chose ${serverChoice}.; } else { result I win! You chose ${choice} and I chose ${serverChoice}.; } return { content: [ { type: text, text: result }, ], }; }, );这段代码有几个值得注意的细节输入模式inputSchema使用zod定义choice是一个枚举类型[rock, paper, scissors]服务端会据此校验前端传来的参数非法值会被拒绝胜负判定先随机生成电脑出拳再按石头剪刀、剪刀布、布石头的规则判定平局/玩家胜/服务端胜最终把结果以content文本形式返回UI 关联_meta: { ui: { resourceUri } }是 MCP Apps 的关键——Host 拿到工具后会读取resourceUri去获取对应的 UI 组件来渲染。3.2 用 registerAppResource 注册组件资源在同一个文件中用registerAppResource注册组件资源。该资源的回调负责读取打包后的 HTML 文件并以RESOURCE_MIME_TYPE的 MIME 类型返回const resourceUri ui://get-time/mcp-app.html; // Register the resource, which returns the bundled HTML/JavaScript for the UI. registerAppResource( server, resourceUri, resourceUri, { mimeType: RESOURCE_MIME_TYPE }, async () { const html await fs.readFile(path.join(DIST_DIR, mcp-app.html), utf-8); return { contents: [ { uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html, _meta: { ui: {} }, }, ], }; }, );可以看到registerAppResource返回的contents中直接携带了mcp-app.html的完整文本——这正是 Host 端将要注入并渲染的 UI。DIST_DIR指向 Vite 的构建输出目录dist意味着前端代码会先被打包成单个 HTML 文件再由服务端以资源形式对外提供见下文 vite.config.ts 的说明。四、前端实现纯 HTML 界面 事件接线4.1 界面标记mcp-app.html解决方案的 mcp-app.html 是一个不依赖任何框架的纯 HTML 页面!DOCTYPE html html langen head meta charsetUTF-8 / titleRock paper scissor/title /head body div classrock-paper-scissors h1Rock Paper Scissors/h1 select idrps-options valuerock option valuerockRock/option option valuepaperPaper/option option valuescissorsScissors/option /select button classselect idrps-button Select/button pResult: code idrps-result.../code/p /div script typemodule src/src/mcp-app.ts/script /body /htmlUI 元素严格对应作业要求下拉列表#rps-options三个选项 rock/paper/scissors、提交按钮#rps-button、结果标签#rps-result。4.2 事件接线mcp-app.tssrc/mcp-app.ts 是前端逻辑的核心全程使用modelcontextprotocol/ext-apps提供的App类import { App } from modelcontextprotocol/ext-apps; // Get element references const serverTimeEl document.getElementById(server-time)!; const getRpsBtn document.getElementById(rps-button)!; const rpsResponseEl document.getElementById(rps-result)!; const rpsOptions document.getElementById(rps-options) as HTMLSelectElement; // Create app instance const app new App({ name: Get Time App, version: 1.0.0 }); // Handle tool results from the server. Set before app.connect() to avoid // missing the initial tool result. app.ontoolresult (result) { const time result.content?.find((c) c.type text)?.text; serverTimeEl.textContent time ?? [ERROR]; }; getRpsBtn.addEventListener(click, async () { const userChoice rpsOptions.value; const result await app.callServerTool({ name: play-rps, arguments: { choice: userChoice } }); const rpsResult result.content?.find((c) c.type text)?.text; rpsResponseEl.textContent rpsResult ?? [ERROR]; }); // Connect to host app.connect();关键点如下new App({ name, version })创建一个 MCP App 实例其职责是与宿主页面Host建立通信通道app.ontoolresult处理服务端工具调用结果。这里特意强调要在app.connect()之前赋值以避免错过最初的工具结果app.callServerTool({ name: play-rps, arguments: { choice: userChoice } })点击按钮时把下拉框的值作为arguments传给服务端工具play-rps。它的底层机制是前端向父窗口发送消息由父窗口Host代为调用 MCP Server再把结果回传——这正是在 IFrame 中运行 MCP App 的通信方式app.connect()最后连接 Host正式开始接收与发送消息。五、运行方式从安装到三种验证途径作业说明指出具体运行步骤参考 code/typescript/README.md并把解决方案文件内容逐一填入对应文件即可。参考运行流程如下。5.1 安装依赖与编译检查npm install这会同时安装前端与后端的依赖。随后用以下命令验证后端可以编译通过npx tsc --noEmit一切正常时该命令不会有任何输出。项目的package.json见 code/typescript/my-app/package.json要求 Node.js 20核心依赖包括modelcontextprotocol/ext-appsMCP App 运行时含前端App类与服务端registerAppTool/registerAppResourcemodelcontextprotocol/sdkMCP 协议 SDKexpresscors承载 Streamable HTTP 传输层vitevite-plugin-singlefile把前端打包成单个 HTML 文件tsx、concurrently、cross-env开发与并行启动工具。5.2 启动后端应用分为**后端backend与宿主host**两部分。先启动后端npm start后端会监听在http://localhost:3001/mcp。npm start实际执行的是注意 Windows 下concurrently需要替代方案start: concurrently \cross-env NODE_ENVdevelopment INPUTmcp-app.html vite build --watch\ \tsx watch main.ts\即一边用 Vite 以 watch 模式把mcp-app.html打包进dist一边用tsx watch热重载运行服务端 main.ts。如果你在 Codespace 中运行可能需要把端口可见性设为 public并通过https://Codespace 名称.app.github.dev/mcp在浏览器中确认端点可达。5.3 途径一在 Visual Studio Code 中测试VS Code 是测试 MCP Apps 最便捷的方式之一。向mcp.json添加一个服务器条目{ servers: { my-mcp-server-7178eca7: { url: http://localhost:3001/mcp, type: http } }, inputs: [] }然后点击mcp.json中的 start 按钮启动服务器在聊天窗口中输入get-faq或作业场景下的play-rps即可看到 MCP App 以 UI 形式渲染5.4 途径二用外部 Host 测试本地或 Codespace也可以使用ext-apps仓库提供的宿主应用来测试 MCP Apps。仓库中内置了参考实现 ext-apps/examples/basic-host它展示了如何构建一个连接 MCP Server 并在安全沙箱中渲染工具 UI 的宿主应用。本地机器进入ext-apps目录运行npm install安装依赖在另一个终端进入ext-apps/examples/basic-host若使用 Codespace需修改 serve.ts 中默认的服务器地址例如把http://localhost:3001/mcp替换为https://psychic-xylophone-657rpjgvxpc5g64-3001.app.github.dev/mcp这类 Codespace 专属 URL运行npm start启动 Host它即会连接后端并在浏览器中渲染出应用界面Codespace同样进入examples/basic-host先npm install再npm start即可。Host 默认会连接http://localhost:3001/mcp也可通过SERVERS[...]环境变量指定多个服务器 URL。5.5 测试应用在渲染出的界面中点击Call Tool按钮即可看到工具调用结果——下拉选择、提交、显示胜负结果全流程跑通六、源码级原理补充MCP App 是如何跑起来的6.1 服务端Streamable HTTP 与 stdio 双传输解决方案的服务端入口 code/typescript/my-app/main.ts 支持两种传输方式默认以Streamable HTTP无状态模式启动监听PORT环境变量缺省3001路径/mcp传入--stdio参数则改用stdio传输。HTTP 模式下每个请求都会新建McpServer实例与StreamableHTTPServerTransport并在响应关闭时清理资源同时通过cors中间件允许跨域访问并允许MCP-Protocol-Version等请求头。6.2 构建Vite 单文件打包是 UI 资源化的前提vite.config.ts 使用vite-plugin-singlefile把INPUT指定的入口即mcp-app.html连同其 TypeScript 逻辑内联打包成单个 HTML 文件输出到dist目录。这正是前面registerAppResource回调能通过fs.readFile直接读取完整 HTML 文本的前提——UI 组件以自包含的单文件形式存在才能作为资源被 Host 获取并注入渲染。6.3 Host 侧双 IFrame 沙箱保证安全从 ext-apps/examples/basic-host/README.md 可以看到MCP App 的 UI 并非直接塞进宿主页面而是采用双 IFrame 沙箱模式Host (port 8080) └── Outer iframe (port 8081) - sandbox proxy沙箱代理 └── Inner iframe (srcdoc) - untrusted tool UI不可信的工具 UI外层 iframe 运行在独立端口独立源上防止直接访问宿主 DOM内层 iframe 通过srcdoc接收 HTML 并受 sandbox 属性约束消息由外层 iframe 双向校验与转发。这意味着即使工具 UI 代码是恶意的也无法访问宿主应用的 DOM、Cookie 或 JavaScript 上下文——这正是课程主文档中强调的MCP Apps 出于安全原因运行在 IFrame 中的落地实现。七、总结通过本次作业你可以完整掌握 MCP Apps 的构建套路服务端用registerAppTool注册工具并用_meta.ui.resourceUri关联 UI 组件用registerAppResource注册携带完整 HTML 文本的组件资源前端用纯 HTML 定义界面用App.callServerTool()通过消息机制调用后端工具。再配合 Vite 单文件打包、Streamable HTTP 服务端以及 VS Code / 外部 Host 两种测试途径你就能在自己现有的 Web 应用或 MCP 工作流中交付数据与界面同时送达的自包含交互组件。课程的下一站是 04-PracticalImplementation实战落地。赞分享教程文档人工智能【免费下载链接】mcp-for-beginnersThis open-source curriculum introduces the fundamentals of Model Context Protocol (MCP) through real-world, cross-language examples in .NET, Java, TypeScript, JavaScript, Rust and Python. Designed for developers, it focuses on practical techniques for building modular, scalable, and secure AI workflows from session setup to service orchestration.项目地址https://gitcode.com/GitHub_Trending/mc/mcp-for-beginners点击查看免费下载相关推荐MCP Apps 实战在 mcp-for-beginners 中用 TypeScript 构建带交互 UI 的石头剪刀布 MCP 应用MCP Apps 实战在 mcp for beginners 中用 TypeScript 构建带交互 UI 的石头剪刀布 MCP 应用 导读 本文围绕 mcp教程文档人工智能在 TypeScript 中构建剪刀石头布 MCP AppregisterAppTool 与 registerAppResource 实战在 TypeScript 中构建剪刀石头布 MCP AppregisterAppTool 与 registerAppResource 实战 MCP Apps教程文档人工智能python-mini-projects 实战用 Python 实现一个命令行版的石头剪刀布Rock Paper Scissors游戏python mini projects 实战用 Python 实现一个命令行版的石头剪刀布Rock Paper Scissors游戏 导读 本文基于开源示例工程上一篇从性能瓶颈到毫秒级优化jsPerf.com完全使用指南下一篇MenubarX 开源项目教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考