主題: AI agents
MCP Apps 把 UI 放進對話,但不是每個 Tool 都要變成小網站
MCP Apps 能在 AI 對話裡顯示 dashboard、表單與視覺工具。真正好用的邊界是:tool 執行、View 呈現、host 管理信任。
動態迷因(展開/收合)
MCP tool 很會回傳文字與 structured data。遇到使用者需要檢查 200 筆資料、調整六個互相影響的選項、在 canvas 拖動物件,或逐段確認 visual diff 時,就沒那麼好用了。
2026 年 1 月,MCP Apps 成為第一個正式 MCP extension。Tool 現在可以指向一份互動式 HTML resource,讓支援的 host 直接在對話裡顯示。daily.dev 的整理舉了 dashboard、diff viewer 與設定精靈等例子,背後則透過 JSON-RPC bridge 溝通。
看起來很像「把 web app 塞進聊天室」,這句話不算錯,但少了最重要的邊界:
Tool 負責執行 domain work,View 負責呈現互動,host 負責管理兩者之間的信任邊界。
少了這層分工,MCP App 很快就會變成一個 state 不清楚、API 重複、權限也說不明白的脆弱小網站。
MCP Apps 不是每次都讓模型生成 UI
MCP App 通常不是請模型替每次回應臨時發明一份 HTML。它的介面是 MCP server 預先宣告、自己維護並提供的 resource。
穩定版 MCP Apps 規格定義了四個主要部分:
- Tool 透過
_meta.ui.resourceUri宣告 View。 - URI 使用
ui://scheme,指向一份 HTML resource。 - Host 讀取 resource,放進 sandboxed iframe 顯示。
- Iframe 與 host 透過
postMessage傳遞 JSON-RPC 訊息。
最小的 tool 宣告大概長這樣:
{
"name": "visualize_orders",
"description": "Show orders for a selected period",
"inputSchema": {
"type": "object",
"properties": {
"from": { "type": "string", "format": "date" },
"to": { "type": "string", "format": "date" }
}
},
"_meta": {
"ui": {
"resourceUri": "ui://orders/dashboard"
}
}
}
Server 再另外註冊 ui://orders/dashboard,並使用 MCP Apps 的 HTML MIME type。官方 quickstart 的總結很精準:MCP App = Tool + UI Resource。
這個拆分很重要。Dashboard 不是訂單服務本身,只是 tool result 的一種呈現方式。
Host 不是只負責把 iframe 貼上去
Host 做的事比在 chat message 裡插入 iframe 多很多。
它要確認雙方是否支援 io.modelcontextprotocol/ui extension、讀取指定 resource、套用 sandbox 與 Content Security Policy、把 tool result 傳給 View,再把允許的呼叫代理回 MCP server。
整個流程可以簡化成:
Model 或使用者
↓ 選擇操作
Server 上的 MCP tool
↓ 回傳文字/structured data
Host
↓ 透過可稽核的 bridge 傳遞結果
Sandboxed View
↓ 使用者篩選、編輯、確認或要求下一步
Host
↓ 驗證並代理允許的 tool call
MCP server
每一層只負責一件事:
| 層級 | 應該負責 | 不該負責 |
|---|---|---|
| MCP tool | 驗證、授權、domain action、權威結果 | 排版與 click state |
| View | 畫面、區域互動、accessible controls | secrets 或最終授權 |
| Host | capability negotiation、隔離、同意、訊息仲介 | 產品 business rules |
如果按鈕的意思是「刪除正式環境資料」,View 不會因為畫出了按鈕就自動取得權限。Server 仍要驗證請求,host 也可以在轉送前要求使用者明確同意。
直接操作比再問一句 prompt 更快時,UI 才值得
官方 overview提到複雜資料探索、相互依賴的設定、視覺創作、文件審查與 multi-step workflow。它們有一個共同點:使用者需要操作或檢查 state,不只是讀一句話。
適合 MCP Apps 的情況包括:
- 能篩選、比較、drill down 的圖表。
- 後續欄位會隨 environment、region 改變的部署表單。
- 能逐段核准的 PDF 或 code diff。
- 位置本身有意義的 canvas、地圖、播放器或 timeline。
- 有上一筆、下一筆、接受與拒絕的批次 review queue。
不太需要 MCP Apps 的情況包括:
- 回傳目前時間。
- 說明 deployment 是否成功。
- 做一次搜尋並顯示三筆簡短結果。
- 詢問單純的 yes/no confirmation。
- 顯示原本用純文字更好複製、引用與搜尋的內容。
第二份清單不會因為多了 card、gradient 與 loading spinner 就比較好用。普通 MCP tool 開發更快、測試更簡單,也能在更多 host 運作。
動態迷因(展開/收合)
先設計文字 fallback,再做 iframe
MCP Apps 是 optional extension。不同 host 的支援程度不一,穩定版規格要求先做 capability negotiation,不能假設每個 client 都能顯示 View。
它的 graceful degradation 規則很實用:帶 UI 的 tool 仍應回傳有意義的文字內容。Host 不支援 Apps 時,它就退回普通 tool,而不是只留下一句「請開啟 widget 查看結果」。
以訂單 dashboard 為例,result 可以包含:
{
"content": [
{
"type": "text",
"text": "42 orders, NT$183,400 total; 3 require review."
}
],
"structuredContent": {
"count": 42,
"total": 183400,
"needsReview": 3,
"orders": []
}
}
文字讓 model 與不支援 UI 的 client 仍有可用答案;structured data 則給 View 一份穩定的輸入。兩邊都不需要把 prose 重新 parse 回 application state。
測試也會簡單很多:先驗證 tool contract,再拿已知 result 驗證 View。
Sandbox 限制破壞範圍,不代表陌生程式碼自動可信
MCP Apps 在 sandboxed iframe 裡執行。Host 會阻止 View 讀取 parent DOM、cookies 與 storage,通訊則經過可稽核的訊息。這是一條真的安全邊界,不只是宣傳用語。
但 server 送來的仍是可執行 HTML、CSS 與 JavaScript。規格的 security model要求 host 檢查預先宣告的 resource、驗證訊息並強制套用 CSP。
實作時可以從這些預設開始:
connectDomains與resourceDomains只宣告必要來源;沒寫的 domain 維持封鎖。- Camera、microphone、geolocation 與 clipboard permission 只在功能真的需要時申請。
- Credentials 與授權決策留在 server。
- 每個 View 發起的 tool call 都當成不可信輸入。
- Destructive action 必須能被 host 看見並要求同意。
規格也能透過 UI visibility 定義 app-only tool。例如 refresh 或 local form submit helper 可以只讓 View 呼叫,不必塞滿 model 的 tool list。不過它仍不能繞過 server authorization。
權威 state 留在 server
2026 年 1 月的穩定版規格仍把 state persistence 與 restoration 列為未來功能;不同 client 提供的 App capabilities 也不完全相同。近期 Reddit 實作討論裡,開發者就提到把 state 存在 server,再同步回 iframe,以避開 host 行為差異。
因此可以採用一條很無聊、但可靠的規則:View 可以暫存互動 state,server 才擁有可復原的 state。
如果關閉對話再打開就會遺失重要決策,那份決策從一開始就不該只住在 iframe memory。透過 tool 保存 domain change,再讓 View 從新 result 重建畫面。
這條邊界也讓同一個 MCP server 能服務 text-only client、未來 host 與獨立 web interface。UI 可以替換,business state 不行。
我會照這個順序做
要替既有 MCP server 加入 Apps,我的最小路徑是:
- 先讓底層 MCP tool 沒有 View 也實用。
- 找出一個目前需要多輪 prompt,或會失去視覺脈絡的互動。
- 同時回傳簡潔文字與穩定 structured data。
- 只為這個互動加入一份
ui://resource。 - 縮小 View-side call;純 UI helper 適合時隱藏於 model。
- 宣告最小 CSP 與 permission set。
- 同時測試支援的 host 與 text-only fallback。
- 測試 keyboard、窄 container、loading、empty 與 error state。
- 像普通 API 一樣記錄並檢查每個 View 發起的 mutation。
不要從「把整個產品改成 MCP App」開始。一個 tool 加上一個高摩擦 workflow,已足夠判斷 embedded UI 值不值得長期維護。
六月一篇討論 MCP Apps 為何還沒像 MCP 與 Skills 一樣普及的 Reddit thread 正好呈現兩面:有人在圖表與確認流程得到好結果,也有人遇到不同 surface 行為不一致。這不是否定標準的理由,而是保留 fallback、測試使用者真正採用 host 的理由。
結論:增加一個 View,不要增加第二份真相
MCP Apps 解決了真的介面問題。純文字不適合直接操作;要使用者用一輪又一輪 prompt 代替篩選表格、審查文件或編輯視覺物件,也很彆扭。
它最好用的時候,邊界反而很無聊:
- Tool 執行並驗證。
- View 呈現並收集互動。
- Host 隔離、仲介並取得同意。
- Server 保留權威 state。
- Text fallback 持續可用。
互動本身有意義時,用 MCP Apps。只要一句話就能完成工作時,讓普通 tool 繼續普通。