Show HN:别再让 MCP 服务器返回原始 JSON,构建丰富内联 UI
速览
该帖子建议 MCP 服务器停止返回原始 JSON 数据,转而构建丰富的内联用户界面。这种做法能够显著提升人机交互的直观性和用户体验。通过内联 UI,用户可以更直接地查看和操作数据,而无需解析复杂的 JSON 结构。
AI 深度解读
Show HN:停止从 MCP 服务器返回原始 JSON,构建丰富的内联 UI
背景
回顾人工智能工具集成的早期阶段,许多开发者都经历过类似的震撼与失落。当首次将简单的天气工具接入 Claude 时,看着模型主动发起调用、获取实时数据并将其编织进回复中,这种体验确实令人着迷。然而,当终端中显示出一大段原始 JSON 数据时,这种兴奋感往往迅速消退。
尽管模型能够准确报告当前温度和天气状况,但它无法展示预报卡片、地图或实时图表。一切仍局限于纯文本。这种局限性在直觉上可能并不显著,但在实际应用中却至关重要:在一个界面至关重要的世界里,一个孤立的数字与一个能让人一眼看出趋势的图表有着天壤之别。
MCP(Model Context Protocol,模型上下文协议)为 AI 代理提供了一套标准化的方式来连接外部世界,但这个世界长期被困在纯文本接口中。直到 MCP Apps 的出现,才打破了这一僵局。如今,通过 Spotify 的 MCP App 向 Claude 请求音乐时,用户获得的是可直接交互的小部件,而非 Markdown 格式的数字列表。
核心内容
MCP Apps 是 MCP 协议在 2026 年初标准化的一项正式扩展。其核心理念是:MCP 服务器不仅可以声明工具,还可以声明 UI 资源。当支持该扩展的主机(如 Claude)调用工具时,它可以在对话中内联渲染完整的交互式 HTML 小部件,而不仅仅是以文本形式打印结果。
架构角色
在 MCP Apps 的实现中,主要涉及三个角色:
- Server(服务器):开发者编写的代码。它声明工具、提供数据,并在
ui://URI 处注册 UI 布局。 - Host(主机):用户与之聊天的客户端(如 Claude Desktop、Cursor、Copilot 等)。
- View(视图):在 iframe 中运行的 HTML 和 JavaScript。它负责渲染工具结果,并允许用户在交互时请求主机再次调用工具。
对于不支持该扩展的主机,它们会忽略 UI 元数据,继续返回纯文本工具输出,从而保证了向后兼容性。完整规范位于 apps.extensions.modelcontextprotocol.io。
工作流程解析
以天气查询为例,整个交互循环如下:
- 用户询问天气预报。
- 主机调用服务器上的
show_weather工具。 - 服务器返回数据以及工具元数据中的
ui://链接。 - 主机获取 HTML 资源并挂载视图(View)。
- 主机将工具结果推送到 iframe 中。
- 用户切换摄氏度/华氏度或点击刷新。
- 视图请求主机调用
fetch_weather(注意:这是应用级工具,而非模型工具)。 - 服务器返回新数据,卡片就地更新。
关键技术细节
实现这一机制有两个关键细节:
- 元数据链接:将工具与其 UI 链接只需一个元数据字段:
"_meta": { "ui": { "resourceUri": "ui://weather-mcp-app/forecast-card.html", "visibility": ["model", "app"] } } - 可见性隔离:并非所有工具都属于模型的“工具列表”。刷新按钮或单位切换等功能应使用
visibility: ["app"]。这意味着模型永远看不到这些工具。这一洞察揭示了 MCP Apps 的本质:它们不是聊天中更漂亮的 JSON,而是一个拥有自己工具集的“第二个客户端”,生活在对话内部。
案例演示:天气卡片
作者通过一个 Python 示例展示了这一过程。服务器端代码使用 FastMCP 库,主要执行三项操作:将工具链接到视图、获取天气数据、并将交互式工具对模型隐藏。
from mcp.server.fastmcp import FastMCP
from mcp import types
VIEW_URI = "ui://weather-mcp-app/forecast-card.html"
mcp = FastMCP("Weather Card", stateless_http=True)
# 主工具:对模型可见,链接到 UI
@mcp.tool(meta={"ui": {"resourceUri": VIEW_URI}, "ui/resourceUri": VIEW_URI})
def show_weather(city: str = "London", units: str = "celsius") -> types.CallToolResult:
payload = _lookup(city, units) # 调用 Open-Meteo
return types.CallToolResult(
content=[types.TextContent(type="text", text=_text_summary(payload))],
structuredContent=payload,
)
# 应用级工具:仅对 UI 可见,模型不可见
@mcp.tool(meta={"ui": {"visibility": ["app"]}})
def fetch_weather(city: str, latitude: float, longitude: float, units: str = "celsius") -> types.CallToolResult:
"""刷新或切换单位。仅限 UI 使用,对模型隐藏。"""
payload = _fetch_weather(city=city, latitude=latitude, longitude=longitude, units=units)
return types.CallToolResult(
content=[types.TextContent(type="text", text=_text_summary(payload))],
structuredContent=payload,
)
# 注册 UI 资源
@mcp.resource(VIEW_URI, mime_type="text/html;profile=mcp-app",
meta={"ui": {"csp": {"resourceDomains": ["https://unpkg.com"]}}})
def forecast_card_view() -> str:
return EMBEDDED_VIEW_HTML
在此架构中,外部 API 调用保留在服务器端的 Python 中,而视图仅与主机通信。
进阶应用:LangMCP 调试
除了演示性的天气卡片,作者正在将 MCP Apps 应用于更复杂的场景:LangMCP。这是一个用于检查 LangGraph 检查点、线程状态和长期记忆的只读 MCP 服务器。
LangMCP 的文本工具已经能够服务于代理,而 MCP App 则是叠加在其上的视图层。其模式与天气卡片相同:show_inspector 供模型使用,__ui_* 辅助函数处理 iframe 内的点击事件,底层共享相同的 Python 路径。这使得在 MCP 主机内部实现图遍历器的调试体验成为可能,相比纯文本,它能更好地处理序列化和分支的检查点历史。
关键要点
- 从文本到交互:MCP Apps 允许 MCP 服务器在对话中内联渲染交互式 HTML 小部件,突破了纯文本输出的限制。
- 双向通信机制:通过
ui://URI 和元数据,服务器可以将工具结果与特定的 UI 视图绑定。 - 工具可见性分离:引入了
visibility概念,区分“模型可见”工具和“应用可见”工具。后者(如刷新、切换单位)由 UI 直接触发,不经过 LLM 推理,提高了效率和安全性。 - 向后兼容:不支持 MCP Apps 的主机会自动忽略 UI 元数据,继续以传统方式处理工具输出。
- 实际应用场景:
- 天气卡片:展示实时数据,支持用户直接切换单位或刷新,无需重新询问模型。
- LangGraph 调试:通过可视化界面检查复杂的图状态和检查点历史,比阅读嵌套 JSON 更直观。
意义与影响
MCP Apps 标志着 AI 工具集成的一个重要转折点。最强大的 AI 工具将不再是那些能生成最长回答的工具,而是那些能让答案易于理解并便于操作的工具。
- 提升认知效率:一个可以切换单位的天气预报、一个一目了然的日程表、一个允许逐步跟踪图运行而无需阅读原始 JSON 的调试视图,这些都不是表面的美化,而是显著改变了人类理解信息和做出决策的速度。
- 确立 UI 层标准:数据层已经存在,协议已经运行。MCP Apps 的加入填补了 UI 层的空白,为 AI 代理与人类交互提供了更丰富、更原生的体验。
- 开发者体验优化:对于开发者而言,这意味着可以更精细地控制 AI 代理的输出展示方式,将复杂的结构化数据转化为直观的用户界面,从而提升最终用户的生产力。
总之,MCP Apps 不仅仅是一个
