MCP Apps 扩展实战:用 python-sdk 为工具打造交互式界面

发布时间:2026/9/21 23:09:40

MCP Apps 扩展实战:用 python-sdk 为工具打造交互式界面 MCP Apps 扩展实战用 python-sdk 为工具打造交互式界面【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdkMCP Apps 是 Model Context Protocol 官方 Python SDKpython-sdk内置的扩展能力扩展标识io.modelcontextprotocol/ui它让一个普通工具额外携带一份 HTML 文档由宿主Host在沙箱 iframe 中渲染成交互界面。读完本文你将掌握Apps扩展的完整使用链路如何注册 UI 绑定的工具与ui://资源、如何优雅降级兼容纯文本客户端、如何用 CSP 与浏览器权限锁定 iframe以及 SDK 在启动期强制校验的规则。本页面对应英文原文档 docs/advanced/apps.md并辅以源码与测试作证据支撑。什么是 MCP App一个有脸的工具MCP App 是一个带有脸UI的工具除了像普通工具一样返回数据外它还指向一份 HTML 文档宿主将其渲染为可交互的表面。这个概念始终由两部分组成缺一不可一个工具tool——负责干活并返回数据与任何其他工具无异一个ui://资源resource——包含宿主为该工具展示的 HTML。两者如何关联工具上携带_meta.ui.resourceUri引用指向该资源。宿主的完整动作是用resources/read获取该资源在**沙箱 iframesandboxed iframe**中渲染它通过postMessage把工具的执行结果推送到 iframe 内。一个关键设计是你的服务端从不发送或接收任何ui/*消息——这部分流量完全发生在宿主与 iframe 之间。你只需提供一个工具和一份 HTML 文档其余演出theater由宿主负责。在 SDK 中这一切以内置的Apps扩展形式提供扩展标识为io.modelcontextprotocol/ui。扩展机制遵循 SEP-2133 契约扩展默认关闭off by default服务端通过向MCPServer(extensions[...])传入实例显式启用启用后服务端会在capabilities.extensions下公告该扩展。如果你还不熟悉扩展机制建议先阅读 扩展机制总览或英文版 docs/advanced/extensions.md再回到本页。扩展在构造时固定没有运行期追加的add_extension因为连接期间能力表不应变化。最小示例一个有脸的时钟以官方教程 docs_src/apps/tutorial001.py 为例完整代码如下from mcp.server.apps import Apps, client_supports_apps from mcp.server.mcpserver import MCPServer from mcp.server.mcpserver.context import Context CLOCK_HTML \ !doctype html titleClock/title h1 idnow.../h1 script window.addEventListener(message, (event) { const text event.data?.result?.content?.[0]?.text; if (text) document.getElementById(now).textContent text; }); /script apps Apps() apps.tool(resource_uriui://clock/app.html, descriptionThe current time.) def get_time(ctx: Context) - str: now 2026-06-26T12:00:00Z if not client_supports_apps(ctx): return fThe time is {now}. return now apps.add_html_resource(ui://clock/app.html, CLOCK_HTML, titleClock) mcp MCPServer(clock, extensions[apps])四个关键动作Apps()创建一个实例它同时持有你 UI 绑定的工具和它们对应的资源。从源码src/mcp/server/apps.py可以看到实例内部用两个列表分别存放(ToolBinding, resource_uri)元组与ResourceBinding。apps.tool(resource_uriui://clock/app.html)一个普通工具外加_meta.ui.resourceUri印记。mcp.tool()接受的一切参数name、title、description、annotations等都原样透传tool_kwargs会被转发给MCPServer.add_tool。apps.add_html_resource(ui://clock/app.html, CLOCK_HTML)注册匹配的资源以text/html;profilemcp-app这一 MIME 类型提供。正是这个精确的 MIME 类型告诉宿主这是一个应用请渲染它。该方法还支持name、title、description、csp、permissions、domain、prefers_border等参数详见下文 iframe 安全小节。MCPServer(clock, extensions[apps])显式启用。服务端随即在capabilities.extensions下公告io.modelcontextprotocol/ui。HTML 本身监听宿主的postMessage事件并展示结果。对于真实应用建议在 HTML 内使用官方modelcontextprotocol/ext-apps浏览器 SDK它替你封装了原始消息事件直接提供ontoolresult、callServerTool、getHostContext、onhostcontextchanged等高层 API。服务端是两件套tools()方法会在服务器消费扩展时校验每个工具的resource_uri是否已注册了匹配资源详见下文SDK 强制执行的规则resources()则返回注册的资源绑定。优雅降级一个工具两种回答并非每个客户端都会渲染应用。规范对此非常直白这是必须遵守的硬性要求即使 UI 可用工具**也必须MUST**返回有意义的content数组。原因在于模型读的是contentiframe 是给人类看的。支持 UI 的宿主仍会把文本结果喂给模型而纯文本客户端只能拿到文本。因此规范范式是一个工具两种回答。再看一眼get_timeapps.tool(resource_uriui://clock/app.html, descriptionThe current time.) def get_time(ctx: Context) - str: now 2026-06-26T12:00:00Z if not client_supports_apps(ctx): return fThe time is {now}. return nowclient_supports_apps(ctx)的判定条件是客户端声明了io.modelcontextprotocol/ui扩展并且在其mimeTypes设置中列出了text/html;profilemcp-app。注意mimeTypes是必填字段——省略它的客户端不计入支持。从源码src/mcp/server/apps.py可以看到该函数读取客户端能力中的extensions[EXTENSION_ID]然后检查mimeTypes是否为 list/tuple 且包含APP_MIME_TYPE。它在高层Context与底层ServerRequestContext两种形态下都可用_client_capabilities内部做了分支处理。协商的客户端一侧docs_src/apps/tutorial001_client.py如下import anyio from mcp import Client from mcp.client import advertise from mcp.server.apps import APP_MIME_TYPE, EXTENSION_ID from mcp.types import TextContent APPS_SUPPORT advertise(EXTENSION_ID, {mimeTypes: [APP_MIME_TYPE]}) async def main() - None: async with Client(http://localhost:8000/mcp, extensions[APPS_SUPPORT]) as client: result await client.call_tool(get_time, {}) for block in result.content: if isinstance(block, TextContent): print(block.text) if __name__ __main__: anyio.run(main)通过 HTTP 提供server.py然后在第二个终端运行客户端uv run mcp run server.py --transport streamable-httppython client.py2026-06-26T12:00:00Z富结果返回了。如果把Client调用中的extensions[APPS_SUPPORT]参数去掉同一个程序会改而打印The time is 2026-06-26T12:00:00Z.——这正是纯文本客户端能看到的一切。测试 tests/docs_src/test_apps.py 在进程内同时驱动两种客户端验证了同一工具的两种回答路径。!!! warning 永远不要把[Rendered UI]之类的占位符作为唯一内容返回。如果后备文本没用这个工具对每个纯文本客户端、对模型本身都没用。把那句人话写出来。关于协商的边界测试 tests/server/test_apps.py 用参数化用例钉死了client_supports_apps的判定矩阵列出mimeTypeslist 或 tuple 皆可返回True扩展未声明、MIME 不是text/html;profilemcp-app、或mimeTypes键缺失均返回False。另外需要注意扩展能力表经由server/discover2026-07-28 协议路径传递legacyinitialize握手无处安放它因此 legacy 客户端看不到该扩展——这也是设计上扩展必须优雅降级、不能成为服务唯一可用方式的原因见 tests/server/test_apps.py。锁住 iframeCSP 与权限声明安全元数据放在资源侧iframe 能加载什么、想要哪些浏览器权限、希望如何被框定。官方教程 docs_src/apps/tutorial002.py 演示了完整形态from mcp.server.apps import Apps, ResourceCsp, ResourcePermissions from mcp.server.mcpserver import MCPServer DASHBOARD_HTML !doctype htmltitleDashboard/titlecanvas idchart/canvas apps Apps() apps.tool(resource_uriui://dashboard/app.html, visibility[app]) def refresh_dashboard() - str: Refresh the dashboard data. return refreshed apps.add_html_resource( ui://dashboard/app.html, DASHBOARD_HTML, titleDashboard, cspResourceCsp(connect_domains[https://api.example.com]), permissionsResourcePermissions(clipboard_write{}), domaindashboard.example.com, prefers_borderTrue, ) mcp MCPServer(dashboard, extensions[apps])要澄清一个根本定位csp和permissions是对宿主的请求requests不是服务端行为。宿主根据它们构建 iframe 的 Content-Security-Policy 和 Permissions-Policy并且可以拒绝。所以在你的 JS 代码里要做特性检测feature-detect而不要假设授权一定被批准。ResourceCsp逐字段说明Python 名称 → 传输键 → 宿主用它控制什么Python传输键_meta.ui.csp控制内容connect_domainsconnectDomainsconnect-srcfetch/XHR 可以访问哪些域resource_domainsresourceDomainsimg-src、style-src等静态资源来源frame_domainsframeDomainsframe-src嵌套 iframe 来源base_uri_domainsbaseUriDomainsbase-uribase可以指向哪里ResourcePermissions的每个字段都向宿主请求一项 iframe 浏览器权限Python传输键_meta.ui.permissionscameracameramicrophonemicrophonegeolocationgeolocationclipboard_writeclipboardWrite从源码src/mcp/server/apps.py可见两个模型类都基于 pydantic通过alias_generatorto_camel自动生成驼峰式传输键model_dump(by_aliasTrue)序列化。add_html_resource会把csp、permissions、domain、prefers_border一并汇入资源的_meta.ui测试 tests/server/test_apps.py 验证了这些字段同时落在resources/list条目与resources/read内容项的_meta.ui中便于宿主读取。!!! note CSP 和权限属于资源resource绝不属于工具tool。规范的工具元数据中没有它们的槽位宿主也会忽略放在工具上的值。SDK 直接让这种错误不可表达apps.tool()根本没有csp参数。可见性Visibility工具上的visibility[app]表示这个工具是给 iframe 用的不是给模型用的。可选值model模型可以调用它appiframe 可以调用它通过callServerTool省略两者皆可这是默认值。过滤是宿主的职责。你的服务端会在tools/list中像列其他工具一样列出 app-only 工具测试 tests/docs_src/test_apps.py 验证了它仍可被调用并返回refreshed由宿主把它们对模型隐藏。不要在服务端做过滤。visibility会被写入_meta.ui.visibility见 src/mcp/server/apps.py 与 tests/server/test_apps.py。SDK 强制执行的规则启动期报错而不是生产期以下所有规则在启动时startup报错而不是在生产运行期失败非ui://...的resource_uri或资源 URI在装饰/注册那一刻抛出ValueError。_require_ui_scheme用uri.startswith(ui://)校验src/mcp/server/apps.py测试 tests/server/test_apps.py 分别覆盖了apps.tool()与add_html_resource()两个入口。绑定了无匹配注册资源的 URI当MCPServer(extensions[apps])消费该扩展时抛出ValueError。一个公告的 HTML 在resources/read上 404 属于配置错误因此拒绝构造。tools()方法在返回绑定前遍历比对已注册资源集合src/mcp/server/apps.py错误消息形如Apps tool _widget binds resource_uri ui://missing/app.html, but no such resource is registered; add it with add_html_resource() or add_resource()见 tests/server/test_apps.py。apps.tool()上传递meta{ui: ...}抛出ValueError。装饰器独享_meta[ui]的所有权请用resource_uri和visibility表达其他meta键可以与之和平共存地合并测试 tests/server/test_apps.py 验证了meta{com.example/k: 1}与ui条目共存于最终元数据。如果允许用户直接传入ui键它会被静默覆盖所以 SDK 在装饰期就拒绝src/mcp/server/apps.py。目前无论是 TypeScript 的 ext-apps SDK 还是 FastMCP 都不会捕获上述任何一条SDK 希望你在宿主发现之前就暴露问题。超越内联 HTML用add_resource自行构建资源add_html_resource覆盖了常见场景一段 HTML 字符串。其余情况——磁盘上的 HTML 文件、程序生成的内容——需要你自己构建资源并交出去docs_src/apps/tutorial003.pyfrom pathlib import Path from mcp.server.apps import Apps from mcp.server.mcpserver import MCPServer from mcp.server.mcpserver.resources import FileResource REPORT_HTML Path(__file__).parent / report.html apps Apps() apps.tool(resource_uriui://report/app.html) def refresh_report() - str: Refresh the report data. return report refreshed apps.add_resource(FileResource(uriui://report/app.html, namereport, pathREPORT_HTML)) mcp MCPServer(report, extensions[apps])add_resource的补全与校验逻辑src/mcp/server/apps.py资源未显式指定mime_type时自动填入text/html;profilemcp-app测试 tests/server/test_apps.py显式指定了其他MIME 类型则直接拒绝任何其他 MIME 类型下的ui://资源都没有宿主会渲染错误消息MCP Apps resources are served as text/html;profilemcp-app, got text/html见 tests/server/test_apps.py资源 URI 同样必须使用ui://协议tests/server/test_apps.py。!!! tip 还在兼容读取废弃扁平键_meta[ui/resourceUri]的 GA 前宿主自己手动合并即可apps.tool(resource_uriui://x, meta{ui/resourceUri: ui://x})。嵌套的ui对象才是规范形态扁平键正在被淘汰的路上。实战运行直接体验可运行的 Apps 示例examples/stories/apps/中的appsstory 是本页内容的可运行成对实现一个带 UI 绑定时钟工具的服务端examples/stories/apps/server.py和一个完整走完协商流程的客户端examples/stories/apps/client.py。客户端会协商 Apps 支持 → 读取工具的_meta.ui.resourceUri→ 拉取 HTML 资源 → 调用工具。# stdio默认——客户端以子进程方式拉起服务端 uv run python -m stories.apps.client # HTTP——客户端在空闲端口自托管服务端运行后自动拆除 uv run python -m stories.apps.client --httpstories 的 READMEexamples/stories/apps/README.md还指出了值得关注的实现细节MCPServer(apps-example, extensions[apps])中MCPServer本身完全不知道 ui 的存在它只是应用一套封闭的扩展贡献工具 资源 能力公告apps.tool(resource_uri...)负责盖章_meta.ui.resourceUriadd_html_resource负责注册text/html;profilemcp-app资源client_supports_apps(ctx)驱动 SEP-2133 优雅降级。story 客户端还断言了能力表extensions {EXTENSION_ID: {}}与资源 MIME 类型与 tests/server/test_apps.py 中的端到端断言遥相呼应。小结MCP Apps 把工具返回数据升级为工具返回数据 宿主渲染的交互界面。在 python-sdk 中这一切收敛为Apps扩展的几个核心 APIapps.tool(resource_uri..., visibility...)绑定 UI、add_html_resource/add_resource注册text/html;profilemcp-app资源、client_supports_apps(ctx)实现优雅降级、ResourceCsp/ResourcePermissions向宿主声明 iframe 安全边界。记住四条铁律工具永远返回有意义的文本内容CSP 与权限只放资源不放工具可见性过滤交给宿主ui://协议与 MIME 类型的错误在启动期就会被 SDK 拦截。【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/21 23:04:40

航空实验班源码图解:3步解决复制代码跑不通痛点

航空实验班源码图解:3步解决复制代码跑不通痛点 复制来的“航空实验班”调度代码,直接运行就报错,看着满屏的红字,心里是不是有点慌?别急,这种“代码能跑但不稳定,或者干脆跑不通”的情况,在工程化落地中太常见了。很多人以为这是玄学,其实核心在于…

2026/9/21 23:04:40

Maven插件避坑指南:3个痛点让你从入门到精通的保姆级教程

Maven插件避坑指南:3个痛点让你从入门到精通的保姆级教程 上周刚结束一场Java后端面试,面试官问得特别刁钻:“Maven的插件执行顺序底层原理是什么?为什么有时候改了pom.xml里的plugin顺序,打包出来的jar包结构还是不对?…

2026/9/22 0:14:50

怎样记住英语单词的底层逻辑与新手避坑指南

怎样记住英语单词的底层逻辑与新手避坑指南 满屏红字报错,StackTrace 长到拉不完,新手避坑的第一步其实是看懂它。 很多人觉得英语单词是语文问题,但在编程圈,它往往意味着你连基本的错误日志都读不懂。当…

2026/9/22 0:14:50

手机从视频里提取音乐:新手避坑指南与底层原理图解

手机从视频里提取音乐:新手避坑指南与底层原理图解 刚装好 Python 环境,跑第一行代码就报错?配置 ffmpeg 路径折腾了半小时,结果还是提示“找不到音频流”?别慌,这是绝大多数初学者在尝试 手机从视频里提取音乐…

2026/9/22 0:14:50

3个冰点下载器官方下载原理拆解面试必问避坑指南

3个冰点下载器官方下载原理拆解面试必问避坑指南 面试被问原理答不上来?别慌。很多转岗的开发者,简历上写满了项目,但一碰到底层机制就露怯。今天把【冰点下载器官方下载】这类工具背后的技术逻辑,结合【面试必问】的高频考点,给你拆得明明白白。…

2026/9/22 0:14:50

lol游戏商城手写实现:版本升级API全变?3招搞定

lol游戏商城手写实现:版本升级API全变?3招搞定 版本升级后 API 全变了,接口文档一夜之间失效,联调环境直接报 404,这种绝望感相信做过后端或全栈的同行都懂。很多团队在应对像 lol游戏商城…

2026/9/22 0:14:50

baidui性能优化实战:源码解析教你避开查询下载卡顿坑

baidui性能优化实战:源码解析教你避开查询下载卡顿坑 官方文档里那些长篇大论的架构描述,读得人头大,核心痛点往往被淹没在细节里。很多人卡在 baidui 电子证书查询接口响应慢、报名材料上传失败这两个死结上,明明网络通畅,系统就是卡。…

2026/9/22 0:09:49

为什么酷狗下载歌要钱图解原理

3步搞定酷狗下载卡顿:图解原理让代码跑通 复制来的代码跑不通不知道怎么调,这感觉太熟了。就像你拿到一套复杂的机械图纸,零件都在,但就是装不进去,急得抓耳挠腮。今天咱们不聊虚的,直接拆解【为什么酷狗下载歌要钱】背后的技术逻辑,用【图解原理】的…

2026/9/21 3:28:31

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/21 3:33:19

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/22 0:04:49

输电线路在线监测高频面试题拆解 3秒抓住官方文档重点

输电线路在线监测高频面试题拆解 3秒抓住官方文档重点 官方文档几百页翻到头还是懵?面试问到 输电线路在线监测 的数据链路时,脑子一片空白?别慌,这种 高频面试题 我整理了10年,专门治各种“文档太长抓不住重点”的毛病。…

2026/9/22 0:04:49

中介房源管理系统重构避坑:3个关键步骤搞定API变更

中介房源管理系统重构避坑:3个关键步骤搞定API变更 版本升级后 API 全变了,这种痛只有真做过的人懂。 很多团队在接手老旧房产项目时,最崩溃的不是代码烂,而是底层框架升级后,原本熟悉的接口调用方式彻底失效。 这份 保姆级教程…

2026/9/22 0:04:49

3个坑点带你一文搞懂55gg小游戏源码

3个坑点带你一文搞懂55gg小游戏源码 盯着控制台满屏的红色报错,看着那一长串 StackTrace ,是不是脑子瞬间宕机?别急,这种时候最忌讳的就是盲目改代码。很多刚入行的前端同学,面对 55gg 小游戏这类轻量级 H5…

2026/9/20 4:54:47

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/21 18:32:12

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/21 10:29:02

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码