ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

快速手搓一个MCP服务指南(十一):FastMCP与ASGI应用集成指南——从独立服务到框架融合的全流程实践

快速手搓一个MCP服务指南(十一):FastMCP与ASGI应用集成指南——从独立服务到框架融合的全流程实践 1. 为什么要把 FastMCP 塞进现有 ASGI 应用如果你已经用 FastAPI 或 Starlette 写过线上服务大概率会遇到一个尴尬场景模型侧的工具调用能力想加进来但不想再单独起一个进程、单独配一套鉴权、单独维护一份路由表。FastMCP 与 ASGI 应用集成解决的正是这个问题——它让 MCP 服务不再是孤岛而是你现有 Web 服务里的一个子路由。FastMCP 是什么简单说它是一个把 Python 函数快速暴露成 MCP 工具的服务框架。你写一个普通函数加个mcp.tool装饰器它就变成了模型可以调用的工具。而 ASGI 是 FastAPI、Starlette、uvicorn 共同遵循的异步网关接口标准。把两者结合意味着你的 MCP 工具能和现有 REST API 共享同一个端口、同一套中间件、同一个生命周期。适合谁看三类人最需要一是已经有 FastAPI 项目、想低成本加 MCP 能力的后端二是用 Starlette 做轻量服务、希望统一鉴权和路由的开发者三是想把 MCP 服务从本地独立进程迁移到生产环境、需要反向代理和并发支持的团队。我试过最省事的路径是先让 FastMCP 独立跑起来确认工具能被调用再把它挂到 ASGI 主应用上处理 lifespan 和路由前缀最后统一鉴权把 endpoint 和 Base URL 指向 TaoToken 的接入地址。整个过程不需要重写业务代码改动集中在应用装配层。这一篇会给出可复制的实例化代码、ASGI 挂载配置、uvicorn 启动命令并用 curl 和 MCP 客户端各验证一次调用链路。踩过的坑主要集中在 lifespan 传递和路径拼接上后面会逐个拆开讲。2. TaoToken 前置准备Base URL、Key 与 Model ID 三件套在把 MCP 服务挂到 ASGI 之前先要把模型侧的接入信息准备好。TaoToken 在这里扮演的是统一接入层你的 MCP 工具最终要调用模型而模型请求需要 Base URL、API Key、Model ID 这三个要素。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址不带 UTM 参数。先说 Base URL。很多教程只给一个域名但实际配置时要区分「控制台地址」和「API 请求地址」。控制台用于创建 Key、查看用量API 地址用于代码里的base_url字段。在 OpenAI 兼容的客户端里通常写成https://taotoken.net/api作为根具体路径由 SDK 拼接。如果你用的是 Anthropic 风格的客户端路径会略有不同以接入文档为准。再说 API Key。进入控制台后创建格式通常是一串以特定前缀开头的字符串。这里有个安全习惯不要把 Key 硬编码进代码用环境变量注入。我在本地用.env文件生产环境用容器编排的 secret 管理。Key 一旦泄露别人就能消耗你的额度所以别图省事写死在main.py里。最后是 Model ID。这是最容易被忽略的一环。不同客户端对模型名的写法要求不一样有的要求带厂商前缀有的只认裸名。配置前先去模型对话页面确认当前可用的模型标识再填到代码里。三件套缺一不可Base URL 决定请求打到哪Key 决定能不能进Model ID 决定用哪个模型。把这三样准备好之后你的 FastMCP 工具在需要调用模型时就能通过统一的 endpoint 发起请求。下面进入代码环节先跑通独立服务再做 ASGI 挂载。3. 可复制配置FastMCP 实例化与 ASGI 挂载这一节是全文的核心给出可以直接复制运行的配置。先看独立服务的实例化再看挂载到 Starlette 和 FastAPI 的写法最后给 uvicorn 启动配置。先装依赖。FastMCP 的包名和版本会影响 API2.3.2 以上用http_app()旧版本用streamable_http_app()或sse_app()。建议直接升到较新版本pip install fastmcp2.3.2 uvicorn starlette fastapi独立服务的实例化代码如下。注意http_app()默认把端点挂在/mcp/路径下如果你传了path/mcp-service最终端点会变成/mcp-service/mcp/这个双层路径是很多人第一次配置时踩的坑from fastmcp import FastMCP mcp FastMCP(MyServer) mcp.tool def hello(name: str) - str: return fHello, {name}! # 推荐Streamable HTTP 传输 http_app mcp.http_app() if __name__ __main__: import uvicorn uvicorn.run(http_app, host0.0.0.0, port8000)跑起来之后端点就是http://127.0.0.1:8000/mcp/。这一步先确认独立服务能通再往下做挂载否则出问题不好定位是挂载错了还是服务本身没起来。接下来挂到 Starlette。关键点是lifespan必须从 MCP 子应用传给主应用否则 Streamable HTTP 的会话管理器初始化不了请求会直接失败from starlette.applications import Starlette from starlette.routing import Mount from fastmcp import FastMCP mcp FastMCP(MyServer) mcp.tool def hello(name: str) - str: return fHello, {name}! mcp_app mcp.http_app(path/mcp) app Starlette( routes[ Mount(/mcp-server, appmcp_app), ], lifespanmcp_app.lifespan, )最终端点是/mcp-server/mcp/。挂到 FastAPI 的写法几乎一样只是主应用换成FastAPI并且可以用app.mount()from fastapi import FastAPI from fastmcp import FastMCP mcp FastMCP(MyServer) mcp.tool def hello(name: str) - str: return fHello, {name}! mcp_app mcp.http_app(path/mcp) app FastAPI(lifespanmcp_app.lifespan) app.mount(/mcp-service, mcp_app) app.get(/api/hello) def hello_api(name: str): return {message: fAPI says: Hello, {name}!}这样同一个 FastAPI 应用里/api/hello是原生 REST 路由/mcp-service/mcp/是 MCP 端点共享同一个 uvicorn 进程和端口。uvicorn 启动配置。开发环境直接命令行uvicorn main:app --host 0.0.0.0 --port 8000 --reload生产环境建议加 worker 数并配合反向代理处理 SSLuvicorn main:app --host 0.0.0.0 --port 8000 --workers 4如果你要把模型请求指向 TaoToken在工具函数内部调用模型客户端时把base_url设为https://taotoken.net/apiKey 从环境变量读取Model ID 按控制台确认的值填。这样 MCP 工具和模型调用就走通了同一条链路。4. 验证请求curl 与 MCP 客户端各跑一次配置写完必须验证否则你不知道是路由没挂上、lifespan 没传、还是鉴权拦住了。这一节用两种方式各验证一次。先启动服务uvicorn main:app --host 0.0.0.0 --port 8000第一种用 curl 验证端点可达。Streamable HTTP 的握手需要特定的请求头和 body最简单的探活是发一个初始化请求curl -i -X POST http://127.0.0.1:8000/mcp-service/mcp/ \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -d {jsonrpc:2.0,id:1,method:initialize,params:{protocolVersion:2024-11-05,capabilities:{},clientInfo:{name:curl-test,version:1.0}}}如果返回里能看到result字段和serverInfo说明端点通了、lifespan 正常。如果返回 404多半是路径拼错了回去检查Mount前缀和http_app(path...)的组合。如果返回 500 且日志里提到 session manager那就是lifespan没传。第二种用 MCP 客户端验证工具调用。以 Python 客户端为例import asyncio from fastmcp import Client async def main(): async with Client(http://127.0.0.1:8000/mcp-service/mcp/) as client: tools await client.list_tools() print(可用工具:, [t.name for t in tools]) result await client.call_tool(hello, {name: ASGI}) print(调用结果:, result) asyncio.run(main())预期输出里能看到hello工具以及Hello, ASGI!的返回。这一步通了说明整条链路——客户端到 ASGI 主应用、到 MCP 子应用、到工具函数——全部打通。如果你在工具函数里调用了模型并且把 Base URL 指向了 TaoToken那么这次call_tool会真实触发一次模型请求。验证模型侧是否正常可以去模型对话页面看调用记录确认请求确实到达。这一步能帮你区分「MCP 链路问题」和「模型接入问题」两者排查方向完全不同。5. 本篇常见报错排查401、lifespan 与路径拼接这一节按真实报错来对照都是我在集成过程中实际遇到的。第一个401 Unauthorized。如果你在中间件里加了鉴权但 curl 或客户端没带 token就会 401。排查顺序先确认请求头里有没有Authorization再确认中间件的白名单是否把 MCP 端点排除了。常见错误是 CORS 中间件和认证中间件顺序写反导致预检请求被拦。正确顺序是 CORS 在外层认证在内层。第二个RuntimeError: Task group is not initialized或日志里出现 session manager 相关报错。这是 lifespan 没传的典型症状。Streamable HTTP 模式依赖子应用的 lifespan 来初始化会话管理器如果你只写了Mount没写lifespanmcp_app.lifespan启动时不会报错但第一个请求进来就崩。修复方式就是挂载时显式传 lifespan嵌套路由时逐层往上传。第三个404 Not Found。九成是路径拼接问题。记住规则Mount(/a, appmcp_app)加上mcp.http_app(path/b)最终端点是/a/b/mcp/。那个末尾的/mcp/是 FastMCP 自己加的不要以为传了path就覆盖了它。如果你想要干净的/a/端点得用http_app(path/)再配合 Mount 前缀但这样容易和其他路由冲突建议保留默认。第四个ImportError: cannot import name http_app。这是版本问题你的 FastMCP 低于 2.3.2。要么升级要么改用streamable_http_app()。升级前先看 changelog确认没有破坏性变更。第五个OAuth 相关报错。如果你接的是需要 OAuth 的模型服务客户端配置里要带全三件套Base URL、Key、Model ID。缺任何一个都会在鉴权阶段失败。特别是 Model ID写错了不会报「模型不存在」而是报鉴权失败容易误导排查方向。第六个local proxy failed或连接被拒。这通常是 uvicorn 没起来或者 host 绑成了127.0.0.1而客户端在容器外访问。生产环境绑0.0.0.0并用反向代理转发。排查时养成习惯先看服务端日志再看客户端报错。MCP 的报错经常在两端表现不一致服务端日志才是真相。6. 统一鉴权与路由后的接入收尾把 MCP 挂进 ASGI 之后鉴权和路由就统一了这是这套方案最大的收益。你可以在主应用上加一个认证中间件让 REST API 和 MCP 端点共用同一套 token 校验逻辑不用维护两份。如果你还在本地独立跑 MCP 服务建议尽快迁到 ASGI 挂载模式。独立进程在开发时方便但生产环境要多开端口、多配一套反向代理、多维护一份鉴权长期看是负担。挂载模式下一个 uvicorn 进程搞定worker 扩展也简单。模型接入侧把 endpoint 和 Base URL 统一到 TaoToken 之后你的 MCP 工具调用模型时不用再关心底层是哪个厂商。需要创建 Key 或查看接入细节去 API Keys 页面和接入文档想先验证模型是否可用用模型对话页面快速试一次如果是长期编码或 Agent 场景Coding Plan 更适合按量使用。最后给一个实用技巧在mcp.custom_route里加一个健康检查端点返回服务状态和当前挂载的 MCP 路径。这样部署后第一件事就是打这个端点能快速确认服务活着、路由没配错。健康检查不要走鉴权否则探活工具会被 401 拦住。代码跑通之后把main.py里的路由前缀、中间件顺序、lifespan 传递这三处固定成模板下次新项目直接复制能省掉大半排查时间。
返回列表