戰(zhàn)作業(yè):用 TypeScript 構(gòu)建帶 UI 的石頭剪刀布(Rock-Paper-Scissors)MCP App)
教程文檔人工智能【免費(fèi)下載鏈接】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.項(xiàng)目地址https://gitcode.com/GitHub_Trending/mc/mcp-for-beginners點(diǎn)擊查看免費(fèi)下載本篇文章基于 mcp-for-beginners 開源課程中「MCP Apps」章節(jié)的 TypeScript 作業(yè)解決方案講解如何將一個(gè)普通的 MCP Server 升級為能同時(shí)返回?cái)?shù)據(jù) 用戶界面的 MCP App通過registerAppTool注冊工具、registerAppResource注冊組件資源并用resourceUri把二者綁定前端則用純 HTML 定義界面、用modelcontextprotocol/ext-apps提供的App類完成事件綁定與工具調(diào)用。讀完本文你將掌握 MCP Apps 的完整開發(fā)鏈路——從服務(wù)端工具/資源注冊、前端事件接線到本地、VS Code 與外部 Host 三種方式的運(yùn)行驗(yàn)證。一、作業(yè)目標(biāo)讓 MCP 不只返回?cái)?shù)據(jù)還返回 UI在 15-mcp-apps 課程主文檔 中給出了本次作業(yè)的明確要求創(chuàng)建一個(gè)石頭剪刀布游戲包含兩部分UI 部分一個(gè)下拉列表供選擇石頭/剪刀/布、一個(gè)提交按鈕、一個(gè)用于顯示雙方出拳及勝負(fù)結(jié)果的標(biāo)簽Server 部分一個(gè)名為石頭剪刀布的 MCP 工具接收choice作為輸入服務(wù)端隨機(jī)生成電腦出拳并判定勝負(fù)。這正是 MCP Apps 的核心范式——工具調(diào)用結(jié)果不再只是純文本數(shù)據(jù)而是可以附帶一段自包含的、可直接渲染的 UI 組件。本文對應(yīng)的解決方案位于 03-GettingStarted/15-mcp-apps/assignment/typescript/完整可運(yùn)行的參考代碼則位于 03-GettingStarted/15-mcp-apps/code/typescript/。二、解決方案結(jié)構(gòu)三個(gè)文件各司其職作業(yè)說明assignment/typescript/README.md給出了解決方案的骨架它只保留了真正關(guān)鍵的代碼——標(biāo)記markup、事件接線event wire up與服務(wù)端特性server features項(xiàng)目結(jié)構(gòu)如下my-app server.ts -- the server functionality服務(wù)端功能注冊工具與 UI 組件資源 src mcp-app.ts -- UI, event wire up前端邏輯事件綁定與工具調(diào)用 mcp-app.html -- UI markup界面標(biāo)記對照倉庫中實(shí)際的解決方案源碼03-GettingStarted/15-mcp-apps/assignment/typescript/my-app/三個(gè)文件的職責(zé)清晰對應(yīng)課程主文檔中描述的 MCP App 架構(gòu)server.ts負(fù)責(zé)在 MCP Server 上注冊工具play-rps和組件資源HTML UI兩者通過resourceUri關(guān)聯(lián)mcp-app.html是純 HTML 的用戶界面src/mcp-app.ts負(fù)責(zé)把界面元素與事件按鈕點(diǎn)擊、下拉選擇接線并通過App.callServerTool()與后端通信。三、服務(wù)端實(shí)現(xiàn)工具 組件資源的兩半合一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 }, ], }; }, );這段代碼有幾個(gè)值得注意的細(xì)節(jié)輸入模式inputSchema使用zod定義choice是一個(gè)枚舉類型[rock, paper, scissors]服務(wù)端會據(jù)此校驗(yàn)前端傳來的參數(shù)非法值會被拒絕勝負(fù)判定先隨機(jī)生成電腦出拳再按石頭剪刀、剪刀布、布石頭的規(guī)則判定平局/玩家勝/服務(wù)端勝最終把結(jié)果以content文本形式返回UI 關(guān)聯(lián)_meta: { ui: { resourceUri } }是 MCP Apps 的關(guān)鍵——Host 拿到工具后會讀取resourceUri去獲取對應(yīng)的 UI 組件來渲染。3.2 用 registerAppResource 注冊組件資源在同一個(gè)文件中用registerAppResource注冊組件資源。該資源的回調(diào)負(fù)責(zé)讀取打包后的 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 的構(gòu)建輸出目錄dist意味著前端代碼會先被打包成單個(gè) HTML 文件再由服務(wù)端以資源形式對外提供見下文 vite.config.ts 的說明。四、前端實(shí)現(xiàn)純 HTML 界面 事件接線4.1 界面標(biāo)記mcp-app.html解決方案的 mcp-app.html 是一個(gè)不依賴任何框架的純 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 元素嚴(yán)格對應(yīng)作業(yè)要求下拉列表#rps-options三個(gè)選項(xiàng) rock/paper/scissors、提交按鈕#rps-button、結(jié)果標(biāo)簽#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();關(guān)鍵點(diǎn)如下new App({ name, version })創(chuàng)建一個(gè) MCP App 實(shí)例其職責(zé)是與宿主頁面Host建立通信通道app.ontoolresult處理服務(wù)端工具調(diào)用結(jié)果。這里特意強(qiáng)調(diào)要在app.connect()之前賦值以避免錯(cuò)過最初的工具結(jié)果app.callServerTool({ name: play-rps, arguments: { choice: userChoice } })點(diǎn)擊按鈕時(shí)把下拉框的值作為arguments傳給服務(wù)端工具play-rps。它的底層機(jī)制是前端向父窗口發(fā)送消息由父窗口Host代為調(diào)用 MCP Server再把結(jié)果回傳——這正是在 IFrame 中運(yùn)行 MCP App 的通信方式app.connect()最后連接 Host正式開始接收與發(fā)送消息。五、運(yùn)行方式從安裝到三種驗(yàn)證途徑作業(yè)說明指出具體運(yùn)行步驟參考 code/typescript/README.md并把解決方案文件內(nèi)容逐一填入對應(yīng)文件即可。參考運(yùn)行流程如下。5.1 安裝依賴與編譯檢查npm install這會同時(shí)安裝前端與后端的依賴。隨后用以下命令驗(yàn)證后端可以編譯通過npx tsc --noEmit一切正常時(shí)該命令不會有任何輸出。項(xiàng)目的package.json見 code/typescript/my-app/package.json要求 Node.js 20核心依賴包括modelcontextprotocol/ext-appsMCP App 運(yùn)行時(shí)含前端App類與服務(wù)端registerAppTool/registerAppResourcemodelcontextprotocol/sdkMCP 協(xié)議 SDKexpresscors承載 Streamable HTTP 傳輸層vitevite-plugin-singlefile把前端打包成單個(gè) HTML 文件tsx、concurrently、cross-env開發(fā)與并行啟動(dòng)工具。5.2 啟動(dòng)后端應(yīng)用分為**后端backend與宿主host**兩部分。先啟動(dòng)后端npm start后端會監(jiān)聽在http://localhost:3001/mcp。npm start實(shí)際執(zhí)行的是注意 Windows 下concurrently需要替代方案start: concurrently \cross-env NODE_ENVdevelopment INPUTmcp-app.html vite build --watch\ \tsx watch main.ts\即一邊用 Vite 以 watch 模式把mcp-app.html打包進(jìn)dist一邊用tsx watch熱重載運(yùn)行服務(wù)端 main.ts。如果你在 Codespace 中運(yùn)行可能需要把端口可見性設(shè)為 public并通過https://Codespace 名稱.app.github.dev/mcp在瀏覽器中確認(rèn)端點(diǎn)可達(dá)。5.3 途徑一在 Visual Studio Code 中測試VS Code 是測試 MCP Apps 最便捷的方式之一。向mcp.json添加一個(gè)服務(wù)器條目{ servers: { my-mcp-server-7178eca7: { url: http://localhost:3001/mcp, type: http } }, inputs: [] }然后點(diǎn)擊mcp.json中的 start 按鈕啟動(dòng)服務(wù)器在聊天窗口中輸入get-faq或作業(yè)場景下的play-rps即可看到 MCP App 以 UI 形式渲染5.4 途徑二用外部 Host 測試本地或 Codespace也可以使用ext-apps倉庫提供的宿主應(yīng)用來測試 MCP Apps。倉庫中內(nèi)置了參考實(shí)現(xiàn) ext-apps/examples/basic-host它展示了如何構(gòu)建一個(gè)連接 MCP Server 并在安全沙箱中渲染工具 UI 的宿主應(yīng)用。本地機(jī)器進(jìn)入ext-apps目錄運(yùn)行npm install安裝依賴在另一個(gè)終端進(jìn)入ext-apps/examples/basic-host若使用 Codespace需修改 serve.ts 中默認(rèn)的服務(wù)器地址例如把http://localhost:3001/mcp替換為https://psychic-xylophone-657rpjgvxpc5g64-3001.app.github.dev/mcp這類 Codespace 專屬 URL運(yùn)行npm start啟動(dòng) Host它即會連接后端并在瀏覽器中渲染出應(yīng)用界面Codespace同樣進(jìn)入examples/basic-host先npm install再npm start即可。Host 默認(rèn)會連接http://localhost:3001/mcp也可通過SERVERS[...]環(huán)境變量指定多個(gè)服務(wù)器 URL。5.5 測試應(yīng)用在渲染出的界面中點(diǎn)擊Call Tool按鈕即可看到工具調(diào)用結(jié)果——下拉選擇、提交、顯示勝負(fù)結(jié)果全流程跑通六、源碼級原理補(bǔ)充MCP App 是如何跑起來的6.1 服務(wù)端Streamable HTTP 與 stdio 雙傳輸解決方案的服務(wù)端入口 code/typescript/my-app/main.ts 支持兩種傳輸方式默認(rèn)以Streamable HTTP無狀態(tài)模式啟動(dòng)監(jiān)聽PORT環(huán)境變量缺省3001路徑/mcp傳入--stdio參數(shù)則改用stdio傳輸。HTTP 模式下每個(gè)請求都會新建McpServer實(shí)例與StreamableHTTPServerTransport并在響應(yīng)關(guān)閉時(shí)清理資源同時(shí)通過cors中間件允許跨域訪問并允許MCP-Protocol-Version等請求頭。6.2 構(gòu)建Vite 單文件打包是 UI 資源化的前提vite.config.ts 使用vite-plugin-singlefile把INPUT指定的入口即mcp-app.html連同其 TypeScript 邏輯內(nèi)聯(lián)打包成單個(gè) HTML 文件輸出到dist目錄。這正是前面registerAppResource回調(diào)能通過fs.readFile直接讀取完整 HTML 文本的前提——UI 組件以自包含的單文件形式存在才能作為資源被 Host 獲取并注入渲染。6.3 Host 側(cè)雙 IFrame 沙箱保證安全從 ext-apps/examples/basic-host/README.md 可以看到MCP App 的 UI 并非直接塞進(jìn)宿主頁面而是采用雙 IFrame 沙箱模式Host (port 8080) └── Outer iframe (port 8081) - sandbox proxy沙箱代理 └── Inner iframe (srcdoc) - untrusted tool UI不可信的工具 UI外層 iframe 運(yùn)行在獨(dú)立端口獨(dú)立源上防止直接訪問宿主 DOM內(nèi)層 iframe 通過srcdoc接收 HTML 并受 sandbox 屬性約束消息由外層 iframe 雙向校驗(yàn)與轉(zhuǎn)發(fā)。這意味著即使工具 UI 代碼是惡意的也無法訪問宿主應(yīng)用的 DOM、Cookie 或 JavaScript 上下文——這正是課程主文檔中強(qiáng)調(diào)的MCP Apps 出于安全原因運(yùn)行在 IFrame 中的落地實(shí)現(xiàn)。七、總結(jié)通過本次作業(yè)你可以完整掌握 MCP Apps 的構(gòu)建套路服務(wù)端用registerAppTool注冊工具并用_meta.ui.resourceUri關(guān)聯(lián) UI 組件用registerAppResource注冊攜帶完整 HTML 文本的組件資源前端用純 HTML 定義界面用App.callServerTool()通過消息機(jī)制調(diào)用后端工具。再配合 Vite 單文件打包、Streamable HTTP 服務(wù)端以及 VS Code / 外部 Host 兩種測試途徑你就能在自己現(xiàn)有的 Web 應(yīng)用或 MCP 工作流中交付數(shù)據(jù)與界面同時(shí)送達(dá)的自包含交互組件。課程的下一站是 04-PracticalImplementation實(shí)戰(zhàn)落地。贊分享教程文檔人工智能【免費(fèi)下載鏈接】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.項(xiàng)目地址https://gitcode.com/GitHub_Trending/mc/mcp-for-beginners點(diǎn)擊查看免費(fèi)下載相關(guān)推薦MCP Apps 實(shí)戰(zhàn)在 mcp-for-beginners 中用 TypeScript 構(gòu)建帶交互 UI 的石頭剪刀布 MCP 應(yīng)用MCP Apps 實(shí)戰(zhàn)在 mcp for beginners 中用 TypeScript 構(gòu)建帶交互 UI 的石頭剪刀布 MCP 應(yīng)用 導(dǎo)讀 本文圍繞 mcp教程文檔人工智能在 TypeScript 中構(gòu)建剪刀石頭布 MCP AppregisterAppTool 與 registerAppResource 實(shí)戰(zhàn)在 TypeScript 中構(gòu)建剪刀石頭布 MCP AppregisterAppTool 與 registerAppResource 實(shí)戰(zhàn) MCP Apps教程文檔人工智能python-mini-projects 實(shí)戰(zhàn)用 Python 實(shí)現(xiàn)一個(gè)命令行版的石頭剪刀布Rock Paper Scissors游戲python mini projects 實(shí)戰(zhàn)用 Python 實(shí)現(xiàn)一個(gè)命令行版的石頭剪刀布Rock Paper Scissors游戲 導(dǎo)讀 本文基于開源示例工程上一篇從性能瓶頸到毫秒級優(yōu)化jsPerf.com完全使用指南下一篇MenubarX 開源項(xiàng)目教程創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考