基于 FastMCP 构建带交互界面的 QR Code MCP App:工具、`ui://` 资源与 CSP 配置实战
基于 FastMCP 构建带交互界面的 QR Code MCP App工具、ui://资源与 CSP 配置实战【免费下载链接】fastmcp The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp导读本文以仓库中的 examples/apps/qr_server 为完整示例讲解如何在 FastMCP 中构建一个MCP App带交互式 UI 的 MCP 服务器通过AppConfig把一个工具链接到ui://协议的资源由 HTML 资源借助modelcontextprotocol/ext-appsJS SDK 在沙箱 iframe 中渲染工具结果并通过ResourceCSP声明 CDN 域名以配合宿主设置 Content-Security-Policy。读完本文你将掌握mcp.tool/mcp.resource的app元数据、ImageContent二进制返回、stdio 与 HTTP 双模式运行以及使用fastmcp install将应用安装进 MCP 客户端的完整流程。示例概览QR Code MCP App 是什么qr_server是一个移植自 ext-apps 社区示例的最小可运行 MCP App 服务器它只做两件事注册一个generate_qr工具把任意文本/URL 编码为 base64 PNG 二维码图片注册一个ui://qr-server/view.html资源返回一段内嵌 HTML页面通过 MCP Apps JS SDK 接收工具结果并在界面中展示二维码。它重点演示了 FastMCP 对 MCP Apps 扩展io.modelcontextprotocol/ui的四个核心能力见 README.md通过AppConfig将工具链接到ui://资源通过 CDN 加载modelcontextprotocol/ext-appsJS SDK服务内嵌 HTML通过ResourceCSP声明资源允许的 CSP 域名工具返回ImageContentbase64 PNG二进制内容。从源码结构看见 qr_server.py整个应用只有约 170 行是理解 FastMCP Apps 机制的最小闭环样本。环境准备与依赖清单依赖声明pyproject.tomlqr_server项目的依赖声明在 pyproject.toml[project] name fastmcp-app-examples version 0.1.0 description MCP App examples for FastMCP requires-python 3.10 dependencies [ fastmcp, qrcode[pil]8.0, ] [build-system] requires [hatchling] build-backend hatchling.build注意两点qrcode[pil]8.0二维码生成依赖qrcode库[pil]extra 会同时引入pillow用于渲染 PNG 图像requires-python 3.10需要 Python 3.10 及以上版本。fastmcp.json面向客户端安装的声明式配置仓库还提供了 fastmcp.json这是 FastMCP 的服务器配置文件供fastmcp install等 CLI 读取{ $schema: https://gofastmcp.com/public/schemas/fastmcp.json/v1.json, source: { path: qr_server.py }, environment: { dependencies: [ fastmcp, qrcode[pil]8.0, pillow ] } }它声明了服务器入口qr_server.py以及运行所需依赖这样 MCP 客户端如 Claude Desktop在安装时可以自动解析入口并准备环境。初始化项目cd examples/apps/qr_server uv syncuv sync会根据pyproject.toml创建虚拟环境并安装fastmcp、qrcode[pil]等全部依赖。运行方式HTTP 模式与 stdio 模式qr_server.py在__main__中直接调用mcp.run()并支持两种传输模式见 qr_server.pyif __name__ __main__: mcp.run()方式一HTTP 模式默认端口 3001uv run python qr_server.py # HTTP mode (port 3001)在 HTTP 模式下FastMCP(QR Code Server)默认暴露一个流式 HTTP 端点端口 3001浏览器或 MCP 客户端可以通过 HTTP 传输访问该服务器这也是 MCP Apps 交互 UI 最自然的运行方式——宿主可以在 iframe 中加载ui://资源并调用工具。方式二stdio 模式面向 MCP 客户端uv run python qr_server.py --stdio # stdio mode for MCP clients--stdio切换为进程内标准输入/输出通信适合 Claude Desktop、Cursor 等通过子进程启动服务器的 MCP 客户端。方式三安装进 MCP 客户端fastmcp install stdio fastmcp.json该命令读取fastmcp.json把qr_server.py作为 stdio 服务器注册到 MCP 客户端配置中之后客户端即可直接发现generate_qr工具与ui://qr-server/view.html资源。核心实现逐行拆解1. 常量与入口from fastmcp import FastMCP from fastmcp.apps import AppConfig, ResourceCSP from fastmcp.tools import ToolResult VIEW_URI: str ui://qr-server/view.html mcp: FastMCP FastMCP(QR Code Server)VIEW_URI使用ui://scheme这是 MCP Apps 扩展约定io.modelcontextprotocol/ui见 apps/config.py 中的UI_EXTENSION_ID下标识 UI 资源的 URI 格式AppConfig、ResourceCSP从fastmcp.apps导入二者都是 Pydantic 模型负责承载工具/资源的 UI 元数据。2. 工具generate_qr与AppConfig(resource_uri...)mcp.tool(appAppConfig(resource_uriVIEW_URI)) def generate_qr( text: str https://gofastmcp.com, box_size: int 10, border: int 4, error_correction: str M, fill_color: str black, back_color: str white, ) - ToolResult: Generate a QR code from text.appAppConfig(resource_uriVIEW_URI)是关键的一行它告诉宿主——当用户需要以应用形态使用这个工具时应渲染ui://qr-server/view.html这个资源。从 apps/config.py 可以看到AppConfig的完整字段字段别名wire 格式作用resource_uriresourceUri工具关联的 UI 资源 URI仅工具使用通常为ui://visibility—工具可见范围app、model或两者csp—应用 iframe 的 Content-Security-Policypermissions—iframe 沙箱权限摄像头、麦克风等domain—iframe 的域名prefers_borderprefersBorderUI 是否偏好显示可见边框所有字段序列化时使用exclude_none别名遵循 MCP Apps wire 格式camelCase只会把显式设置的值放到线上。参数校验与错误处理工具内部对参数做了完整校验qr_server.pyerror_levels { L: qrcode.constants.ERROR_CORRECT_L, M: qrcode.constants.ERROR_CORRECT_M, Q: qrcode.constants.ERROR_CORRECT_Q, H: qrcode.constants.ERROR_CORRECT_H, } if box_size 0: raise ValueError(box_size must be 0) if border 0: raise ValueError(border must be 0) error_key error_correction.upper() if error_key not in error_levels: raise ValueError(ferror_correction must be one of: {, .join(error_levels)})参数含义与 docstring 一致text要编码的文本或 URL默认https://gofastmcp.combox_size每个模块box的像素尺寸默认 10必须大于 0border边框模块数默认 4必须不小于 0error_correction容错级别L(7%)、M(15%)、Q(25%)、H(30%)大小写不敏感fill_color/back_color前景/背景色支持十六进制#FF0000或颜色名red。生成二维码并以ImageContent返回qr qrcode.QRCode( version1, error_correctionerror_levels[error_key], box_sizebox_size, borderborder, ) qr.add_data(text) qr.make(fitTrue) img qr.make_image(fill_colorfill_color, back_colorback_color) buffer io.BytesIO() img.save(buffer, formatPNG) b64 base64.b64encode(buffer.getvalue()).decode() return ToolResult( content[ImageContent(typeimage, datab64, mime_typeimage/png)] )这里演示了 FastMCP 工具返回二进制内容的正确姿势用qrcode生成 PIL 图像后写入io.BytesIO以 PNG 格式编码为 base64 字符串返回ToolResult(content[ImageContent(...)])其中mime_typeimage/png明确图像类型。从 tools/base.py 看ToolResult是 FastMCP 工具结果的统一载体包含content内容块列表、structured_content结构化内容、meta与is_error等字段content必须非空且会经过类型适配转换后映射为 MCP 协议的CallToolResult。3. 资源ui://qr-server/view.html与ResourceCSPmcp.resource( VIEW_URI, appAppConfig(cspResourceCSP(resource_domains[https://unpkg.com])), ) def view() - str: Interactive QR code viewer — renders tool results as images. return EMBEDDED_VIEW_HTML与工具不同资源上的AppConfig不设置resource_uri资源本身就是 UI而是声明csp。ResourceCSP的字段定义见 apps/config.py字段别名wire 格式对应 CSP 指令connect_domainsconnectDomainsconnect-srcfetch/XHR/WebSocketresource_domainsresourceDomainsscript-src等脚本、图片、样式、字体frame_domainsframeDomainsframe-src嵌套 iframebase_uri_domainsbaseUriDomainsbase-uri示例中resource_domains[https://unpkg.com]是因为内嵌 HTML 需要从 unpkg CDN 加载modelcontextprotocol/ext-appsSDK——宿主MCP Apps 客户端会依据这些声明为沙箱 iframe 生成对应的Content-Security-Policy头从而允许加载该域名下的脚本。4. 内嵌 HTML 与 MCP Apps JS SDKEMBEDDED_VIEW_HTMLqr_server.py是完整的自包含 HTML 页面核心逻辑如下从 CDN 导入 SDK 并连接script typemodule import { App } from https://unpkg.com/modelcontextprotocol/ext-apps0.4.0/app-with-deps; const app new App({ name: QR View, version: 1.0.0 });页面以 ES Module 方式导入modelcontextprotocol/ext-apps0.4.0的App类创建名为 QR View 的应用实例。接收工具结果并渲染图片app.ontoolresult ({ content }) { const img content?.find(c c.type image); if (img) { const qrDiv document.getElementById(qr); qrDiv.innerHTML ; const allowedTypes [image/png, image/jpeg, image/gif]; const mimeType allowedTypes.includes(img.mimeType) ? img.mimeType : image/png; const image document.createElement(img); image.src data:${mimeType};base64,${img.data}; image.alt QR Code; qrDiv.appendChild(image); } };app.ontoolresult是 SDK 提供的回调当宿主调用generate_qr工具并把结果即ImageContent投递给 UI 后前端从content中取出type image的块用data:image/png;base64,...数据 URI 动态创建img展示。allowedTypes白名单做了 MIME 兜底防止非法类型注入。适配宿主安全区域safe areafunction handleHostContextChanged(ctx) { if (ctx.safeAreaInsets) { document.body.style.paddingTop ${ctx.safeAreaInsets.top}px; document.body.style.paddingRight ${ctx.safeAreaInsets.right}px; document.body.style.paddingBottom ${ctx.safeAreaInsets.bottom}px; document.body.style.paddingLeft ${ctx.safeAreaInsets.left}px; } } app.onhostcontextchanged handleHostContextChanged; await app.connect(); const ctx app.getHostContext(); if (ctx) { handleHostContextChanged(ctx); }通过app.onhostcontextchanged监听宿主上下文变化如移动端刘海屏的安全区域再在app.connect()后主动读取一次getHostContext()做初始适配。页面样式还设置了background: transparent、overflow: hidden保证在宿主 iframe 中呈现为干净的浮层效果。背后的协议机制AppConfig 如何变成线上元数据从源码结构可以还原出完整的实现链路mcp.tool(app...)/mcp.resource(..., app...)把AppConfig实例挂到组件的meta上apps/config.py 中的app_config_to_meta_dict()将AppConfig通过model_dump(by_aliasTrue, exclude_noneTrue)转换为 wire 格式的meta[ui]字典——只序列化显式设置的字段键名使用 camelCase服务端在tools/list、resources/list响应中携带该meta[ui]支持 MCP Apps 的宿主客户端解析meta[ui]将工具与resourceUri对应的 UI 资源关联为 iframe 按csp声明设置 CSP并处理visibilityapp/model决定工具是否暴露给模型。也就是说AppConfig是声明式配置——服务端只负责宣告意图实际的过滤与渲染行为由宿主执行这与 apps/config.py 中is_model_visible对 visibility 语义的说明一致。运行效果与验证启动 HTTP 模式后访问http://localhost:3001或通过支持 MCP Apps 的客户端连接工具侧调用generate_qr传入任意 URL 或文本得到ImageContentbase64 PNGUI 侧宿主渲染ui://qr-server/view.html内嵌页面通过 SDK 接收工具结果在 300×300 的圆角卡片中实时显示二维码安全侧由于声明了resource_domains[https://unpkg.com]宿主可以为沙箱 iframe 正确放行 unpkg CDN 的脚本加载页面因此能顺利初始化 SDK。如需在 MCP 客户端如 Claude Desktop中体验完整流程直接执行cd examples/apps/qr_server uv sync fastmcp install stdio fastmcp.json然后重启客户端即可在工具列表中看到generate_qr并以应用视图打开二维码生成界面。小结qr_server是一个麻雀虽小、五脏俱全的 FastMCP Apps 参考实现。通过这一个示例你可以掌握三条可复用的模式工具↔UI 绑定mcp.tool(appAppConfig(resource_uriui://...))把任意工具挂到交互界面安全声明ResourceCSP的connect_domains/resource_domains/frame_domains/base_uri_domains对应 CSP 各指令让宿主在沙箱 iframe 中安全放行所需外部域名二进制内容返回工具统一以ToolResult(content[ImageContent(...)])返回 base64 编码的图片前端 SDK 通过app.ontoolresult接收并渲染。更完整的 AppConfig/ResourceCSP/ResourcePermissions 字段定义可继续阅读 fastmcp_slim/fastmcp/apps/config.py本仓库的其他 App 示例如 examples/apps/approval、examples/apps/form、examples/apps/file_upload则展示了审批、表单、文件上传等更复杂的交互形态可作为下一步的进阶参考。【免费下载链接】fastmcp The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考