☰
FastMCP之Tools:用TaoToken统一Key跑通MCP工具调用链
2026/9/28 18:23:23 网站建设 项目流程

1. 为什么要在 FastMCP 里统一 Key

FastMCP 是 Python 生态里把 MCP 工具服务写起来最省事的框架之一,一个@mcp.tool装饰器就能把普通函数变成 LLM 可调用的工具。但真正落到本地开发场景,问题往往不在工具函数本身,而在“调用链上的凭证怎么管”。你本地可能同时跑着计算器服务、文件检索服务、数据库查询服务,每个服务如果各自维护一套模型通道配置,改一次 Key 就要翻好几个文件,调试时还容易把 A 服务的 Key 填到 B 服务里。

我这次要解决的就是这件事:用 TaoToken 的统一 Key 和 API 通道,把 FastMCP 的 Tools 注册与调用链路串起来。TaoToken 在这里扮演的是“统一模型入口”的角色,你只需要在配置里写一份 Key 和 base_url,本地所有 MCP 服务共享同一套通道,工具调用时需要的模型能力都从这里走。适合谁?适合正在本地写 MCP Server、想让 Tools 调用链稳定可复现、又不想被多套凭证搞乱的同学。

整篇会按“配置骨架 → 工具注册 → 调用验证 → 排障”的顺序走,配置部分给出config.toml和settings.json两份可复制骨架,验证部分会真的发一次 Tools 调用请求并看返回结构。你跟着做完,应该能独立跑通一个从配置到调用的闭环。

2. TaoToken 前置:Key 与通道准备

在写 FastMCP 代码之前,先把统一通道准备好。TaoToken 的官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api 。注意 API 地址后面不加任何查询参数,配置里就写这个干净的基础地址。

你需要拿到一个 API Key。进入控制台后创建 Key,建议按项目命名,比如fastmcp-local,方便后面排查是哪个服务在用。创建入口在 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=fastmcp_tools_console&utm_campaign=rewrite ,Key 列表页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=fastmcp_tools_apikeys&utm_campaign=rewrite 。拿到 Key 之后不要直接硬编码进 Python 文件,先放进环境变量或配置文件,后面 FastMCP 服务启动时读取。

这里有个容易踩的点:MCP 的 Tools 调用本身是走你本地 HTTP 服务的,模型通道是工具内部需要调用 LLM 时才用。所以统一 Key 的意义在于,当你的工具函数里需要ctx.sample()做 LLM 抽样,或者需要调用外部模型能力时,所有服务都从同一份配置取通道,不用每个工具单独配。

如果你后面要做长期编码或 Agent 类任务,可以了解下 Coding Plan,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=fastmcp_tools_codingplan&utm_campaign=rewrite 。单纯验证模型连通性的话,模型对话页在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=fastmcp_tools_chat&utm_campaign=rewrite ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=fastmcp_tools_doc&utm_campaign=rewrite 。

3. 可复制配置:config.toml 与 settings.json

先给config.toml骨架。这份配置放在项目根目录,FastMCP 服务启动时读取。字段含义我在注释里写清楚,你按自己的 Key 替换即可。

# config.toml [taotoken] # 统一 API 通道,注意不要带查询参数 base_url = "https://taotoken.net/api" # 从控制台创建的 Key,建议用环境变量注入 api_key = "${TAOTOKEN_API_KEY}" # 默认模型,工具内部需要 LLM 时使用 default_model = "claude-3-5-sonnet" [mcp.server] name = "FastMCPToolsDemo" host = "127.0.0.1" port = 8000 transport = "http" [mcp.tools] # 严格输入验证,避免 LLM 传字符串数字被静默转换 strict_input_validation = true # 重复工具名策略:error / warn / replace / ignore on_duplicate_tools = "error"

再给settings.json骨架。有些团队习惯用 JSON 管理运行时配置,这份和上面的 toml 是等价的,二选一即可。

{ "taotoken": { "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY", "default_model": "claude-3-5-sonnet" }, "mcp": { "server": { "name": "FastMCPToolsDemo", "host": "127.0.0.1", "port": 8000, "transport": "http" }, "tools": { "strict_input_validation": true, "on_duplicate_tools": "error" } } }

环境变量这样设置,Linux/macOS 用export TAOTOKEN_API_KEY="你的Key",Windows PowerShell 用$env:TAOTOKEN_API_KEY="你的Key"。配置文件里只写变量名,不写明文,这样提交到仓库也不会泄露。

注意:base_url一定写https://taotoken.net/api,不要在后面拼/v1之类的路径,具体路径由 SDK 或请求库自己拼。

4. 注册 Tools 并接入统一通道

配置好了,开始写服务端。先装依赖:pip install fastmcp anyio。下面这个服务端注册了三个工具,分别演示同步、异步和带 Context 的场景,同时从配置读取 TaoToken 通道。

# server.py import os import tomllib import anyio from fastmcp import FastMCP, Context # 读取 config.toml with open("config.toml", "rb") as f: cfg = tomllib.load(f) api_key = os.environ.get("TAOTOKEN_API_KEY") base_url = cfg["taotoken"]["base_url"] default_model = cfg["taotoken"]["default_model"] mcp = FastMCP( name=cfg["mcp"]["server"]["name"], strict_input_validation=cfg["mcp"]["tools"]["strict_input_validation"], on_duplicate_tools=cfg["mcp"]["tools"]["on_duplicate_tools"], ) @mcp.tool def add(a: int, b: int) -> int: """Adds two integer numbers together.""" return a + b def cpu_intensive_task(data: str) -> str: # 模拟耗时计算,避免阻塞事件循环 return data.upper() @mcp.tool async def wrapped_cpu_task(data: str) -> str: """CPU-intensive task wrapped to prevent blocking.""" return await anyio.to_thread.run_sync(cpu_intensive_task, data) @mcp.tool async def summarize_with_llm(text: str, ctx: Context) -> dict: """Summarize text using the unified TaoToken channel.""" await ctx.info(f"Summarizing via {base_url}, model={default_model}") # 这里通过统一通道调用模型,api_key 从环境变量取 # 实际调用逻辑按你的 SDK 封装,核心是 base_url 和 api_key 统一 summary = await ctx.sample(f"Summarize in 10 words: {text[:200]}") return {"summary": summary.text, "model": default_model} if __name__ == "__main__": mcp.run( transport=cfg["mcp"]["server"]["transport"], host=cfg["mcp"]["server"]["host"], port=cfg["mcp"]["server"]["port"], )

关键点说明。strict_input_validation=True时,LLM 传"2"给int参数会直接报ToolError,不会静默转换,这在调试调用链时能帮你快速定位参数类型问题。on_duplicate_tools="error"保证同名工具注册时立刻抛错,避免动态注册场景下旧工具被悄悄覆盖。

summarize_with_llm里用到了ctx.sample(),这是 FastMCP 的 LLM 抽样能力,底层走的就是你配置的 TaoToken 通道。ctx.info()会把日志推给客户端,方便你在调用端看到通道信息。注意带ctx: Context的工具函数必须是async def,调用时不要手动传ctx参数,框架会自己注入。

5. 验证请求:发一次 Tools 调用看结果

服务端起好后,另开一个终端写客户端。客户端同样从配置读地址,避免硬编码。

# client.py import asyncio import tomllib from fastmcp import Client with open("config.toml", "rb") as f: cfg = tomllib.load(f) host = cfg["mcp"]["server"]["host"] port = cfg["mcp"]["server"]["port"] url = f"http://{host}:{port}/mcp" client = Client(url) async def call_tool(name: str, args: dict): async with client: result = await client.call_tool(name, args) print(f"tool={name} result={result}") async def main(): await call_tool("add", {"a": 2, "b": 3}) await call_tool("wrapped_cpu_task", {"data": "apple"}) await call_tool("summarize_with_llm", {"text": "FastMCP tools are easy to register."}) if __name__ == "__main__": asyncio.run(main())

先启动服务端python server.py,看到监听 8000 端口的日志后,再跑python client.py。预期输出里,add返回CallToolResult(content=[TextContent(type='text', text='5')], structured_content={'result': 5}, ...),注意structured_content里 key 是result,这是 FastMCP 对基础类型返回值的自动包装。wrapped_cpu_task返回APPLE,summarize_with_llm返回一个 dict,里面带summary和model字段。

如果你在客户端加了message_handler,还能看到服务端推来的notifications/tools/list_changed和ctx.info的日志。这一步验证通过,说明从配置读取、工具注册、统一通道调用到结果返回的链路是通的。

6. 本篇常见错排查

第一个高频错误是ToolError: Input validation error: '3' is not of type 'integer'。这是strict_input_validation=True的正常行为,LLM 传了字符串数字。解决办法有两个:要么在客户端调用时确保传int,要么把服务端改成strict_input_validation=False让 Pydantic 做宽松转换。我建议开发阶段开严格模式,能暴露参数类型问题。

第二个是ValueError: Tool 'xxx' already exists。这是on_duplicate_tools="error"触发的,说明你注册了同名工具。检查是不是动态注册时重复调用了mcp.add_tool(),或者两个模块定义了同名函数。改成replace或ignore可以绕过,但更推荐改工具名。

第三个是连接失败Connection refused。先确认服务端host和客户端url一致,本地调试用127.0.0.1,需要外部访问才改0.0.0.0。再确认端口没被占用,netstat -ano | findstr 8000可以查。

第四个是ctx.sample()报通道错误。检查TAOTOKEN_API_KEY环境变量是否在当前终端生效,base_url是否写成了带查询参数的地址。环境变量在子进程里不继承是常见坑,重启终端或重新export一次。

第五个是带ctx: Context的工具报参数错误。确认函数是async def,且调用时没手动传ctx。框架注入的ctx参数不会出现在工具 schema 里,LLM 也看不到它。

7. 继续把链路用起来

到这里,FastMCP 的 Tools 注册、统一 Key 配置、调用验证和排障都走了一遍。如果你要接着做接入层的调试,建议先去 API Keys 页面确认 Key 状态和额度,入口在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=fastmcp_tools_apikeys2&utm_campaign=rewrite ,接入细节看文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=fastmcp_tools_doc2&utm_campaign=rewrite 。想快速验证模型通道是否正常,用模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=fastmcp_tools_chat2&utm_campaign=rewrite 发一条消息即可。如果你要把这套链路用到长期编码或 Agent 任务上,Coding Plan 在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=fastmcp_tools_codingplan2&utm_campaign=rewrite 。

最后留一个实用技巧:把config.toml里的default_model做成可覆盖的,启动时用TAOTOKEN_MODEL环境变量优先,这样同一份代码在调试和正式跑时能切不同模型,不用改文件。工具函数里所有需要模型能力的地方都从这一个配置取,统一 Key 的价值就体现在这——改一处,全链路生效。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询