作業(yè):用 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」章節(jié)的 TypeScript 作業(yè)解決方案講解如何將一個普通的 MCP Server 升級為能同時返回數據 用戶界面的 MCP App通過registerAppTool注冊工具、registerAppResource注冊組件資源并用resourceUri把二者綁定前端則用純 HTML 定義界面、用modelcontextprotocol/ext-apps提供的App類完成事件綁定與工具調用。讀完本文你將掌握 MCP Apps 的完整開發(fā)鏈路——從服務端工具/資源注冊、前端事件接線到本地、VS Code 與外部 Host 三種方式的運行驗證。一、作業(yè)目標讓 MCP 不只返回數據還返回 UI在 15-mcp-apps 課程主文檔 中給出了本次作業(yè)的明確要求創(chuàng)建一個石頭剪刀布游戲包含兩部分UI 部分一個下拉列表供選擇石頭/剪刀/布、一個提交按鈕、一個用于顯示雙方出拳及勝負結果的標簽Server 部分一個名為石頭剪刀布的 MCP 工具接收choice作為輸入服務端隨機生成電腦出拳并判定勝負。這正是 MCP Apps 的核心范式——工具調用結果不再只是純文本數據而是可以附帶一段自包含的、可直接渲染的 UI 組件。本文對應的解決方案位于 03-GettingStarted/15-mcp-apps/assignment/typescript/完整可運行的參考代碼則位于 03-GettingStarted/15-mcp-apps/code/typescript/。二、解決方案結構三個文件各司其職作業(yè)說明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 }, ], }; }, );這段代碼有幾個值得注意的細節(jié)輸入模式inputSchema使用zod定義choice是一個枚舉類型[rock, paper, scissors]服務端會據此校驗前端傳來的參數非法值會被拒絕勝負判定先隨機生成電腦出拳再按石頭剪刀、剪刀布、布石頭的規(guī)則判定平局/玩家勝/服務端勝最終把結果以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 元素嚴格對應作業(yè)要求下拉列表#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 })創(chuàng)建一個 MCP App 實例其職責是與宿主頁面Host建立通信通道app.ontoolresult處理服務端工具調用結果。這里特意強調要在app.connect()之前賦值以避免錯過最初的工具結果app.callServerTool({ name: play-rps, arguments: { choice: userChoice } })點擊按鈕時把下拉框的值作為arguments傳給服務端工具play-rps。它的底層機制是前端向父窗口發(fā)送消息由父窗口Host代為調用 MCP Server再把結果回傳——這正是在 IFrame 中運行 MCP App 的通信方式app.connect()最后連接 Host正式開始接收與發(fā)送消息。五、運行方式從安裝到三種驗證途徑作業(yè)說明指出具體運行步驟參考 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開發(fā)與并行啟動工具。5.2 啟動后端應用分為**后端backend與宿主host**兩部分。先啟動后端npm start后端會監(jiān)聽在http://localhost:3001/mcp。npm start實際執(zhí)行的是注意 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或作業(yè)場景下的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[...]環(huán)境變量指定多個服務器 URL。5.5 測試應用在渲染出的界面中點擊Call Tool按鈕即可看到工具調用結果——下拉選擇、提交、顯示勝負結果全流程跑通六、源碼級原理補充MCP App 是如何跑起來的6.1 服務端Streamable HTTP 與 stdio 雙傳輸解決方案的服務端入口 code/typescript/my-app/main.ts 支持兩種傳輸方式默認以Streamable HTTP無狀態(tài)模式啟動監(jiān)聽PORT環(huán)境變量缺省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 雙向校驗與轉發(fā)。這意味著即使工具 UI 代碼是惡意的也無法訪問宿主應用的 DOM、Cookie 或 JavaScript 上下文——這正是課程主文檔中強調的MCP Apps 出于安全原因運行在 IFrame 中的落地實現。七、總結通過本次作業(yè)你可以完整掌握 MCP Apps 的構建套路服務端用registerAppTool注冊工具并用_meta.ui.resourceUri關聯 UI 組件用registerAppResource注冊攜帶完整 HTML 文本的組件資源前端用純 HTML 定義界面用App.callServerTool()通過消息機制調用后端工具。再配合 Vite 單文件打包、Streamable HTTP 服務端以及 VS Code / 外部 Host 兩種測試途徑你就能在自己現有的 Web 應用或 MCP 工作流中交付數據與界面同時送達的自包含交互組件。課程的下一站是 04-PracticalImplementation實戰(zhàn)落地。贊分享教程文檔人工智能【免費下載鏈接】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 實戰(zhàn)在 mcp-for-beginners 中用 TypeScript 構建帶交互 UI 的石頭剪刀布 MCP 應用MCP Apps 實戰(zhàn)在 mcp for beginners 中用 TypeScript 構建帶交互 UI 的石頭剪刀布 MCP 應用 導讀 本文圍繞 mcp教程文檔人工智能在 TypeScript 中構建剪刀石頭布 MCP AppregisterAppTool 與 registerAppResource 實戰(zhàn)在 TypeScript 中構建剪刀石頭布 MCP AppregisterAppTool 與 registerAppResource 實戰(zhàn) MCP Apps教程文檔人工智能python-mini-projects 實戰(zhàn)用 Python 實現一個命令行版的石頭剪刀布Rock Paper Scissors游戲python mini projects 實戰(zhàn)用 Python 實現一個命令行版的石頭剪刀布Rock Paper Scissors游戲 導讀 本文基于開源示例工程上一篇從性能瓶頸到毫秒級優(yōu)化jsPerf.com完全使用指南下一篇MenubarX 開源項目教程創(chuàng)作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考