ARTICLE DETAIL

资讯详情

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

手把手MCP教学:客户端连接多服务器时把Base URL改到TaoToken

手把手MCP教学:客户端连接多服务器时把Base URL改到TaoToken 1. 多 MCP 服务器接入时Base URL 和鉴权入口为什么会散成一团MCP 客户端同时接入多个 MCP 服务器是很多人从「跑通一个 demo」走向「日常真用」时绕不开的一步。MCP 全称 Model Context Protocol它做的事情说白一点就是让大模型能通过一套统一协议去调用外部工具——查天气、读写文件、查数据库、调内部接口。单个服务器时你在.env里写一个BASE_URL、一个API_KEY、一个MODEL跑起来就完事可一旦服务器变成两个、三个问题立刻冒出来。我遇到过的典型混乱是这样的天气服务器用硅基流动的地址文件服务器想换成另一家代码助手又想走第三个入口。结果.env里塞了BASE_URL_1、BASE_URL_2、WEATHER_KEY、FS_KEY客户端代码里到处os.getenv改一个地址要翻三个文件。更麻烦的是鉴权每个服务器背后如果各自对接不同的大模型供应商Key 的格式、额度、限流策略都不一样排查一次 401 要挨个试。这篇要解决的就是这个场景MCP 客户端连接多服务器时把 Base URL 统一改到 TaoToken让所有服务器共用同一个鉴权入口。TaoToken 是一个兼容 OpenAI 接口规范的模型调用入口官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。它的价值在于你不需要为每个 MCP 服务器单独维护一套供应商配置客户端只认一个 Base URL、一个 Key模型 ID 按需切换即可。适合谁看已经跟着基础篇跑通过单个 MCP 服务器、手里有 Python 和 uv 环境、想让 LLM 一次调用多个工具的人。如果你还没搭过服务器建议先把天气或文件服务器跑起来再回来因为这篇的重点是「多服务器 统一入口」不是从零讲 MCP 是什么。下面我会先给出多服务器的目录结构和配置片段再把 Base URL 统一改到 TaoToken最后用一次请求同时触发两个服务器的工具验证连通性和鉴权是否都生效。整个过程可以照着敲。2. TaoToken 前置准备一个 Key 管住所有 MCP 服务器在动手改客户端之前先把 TaoToken 这边的入口准备好。这一步不复杂但顺序别搞反——先有 Key再去改配置否则你改完代码发现没地方填 Key还得回头。2.1 拿到 API Key 和确认 Base URL打开 TaoToken 的控制台进入 API Keys 页面创建一个 Key。地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。创建后复制那串以sk-开头的字符串先存到记事本里后面要填进.env。Base URL 这一项要特别注意TaoToken 的 API 根地址是https://taotoken.net/api但在 OpenAI 兼容客户端里通常要写成带/v1的形式也就是https://taotoken.net/api/v1。这一点和很多兼容入口一致写错了会直接 404 或者local proxy failed。我建议你先在模型对话页面确认一下当前可用的模型 ID地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 把你要用的模型名记下来比如某个支持 function calling 的模型。注意MCP 的工具调用依赖模型的 function calling 能力。选模型时优先挑明确支持工具调用的否则客户端把tools传过去模型不认会一直返回普通文本看起来像「工具没生效」其实是模型不支持。2.2 为什么统一入口能解决多服务器鉴权分散原来的做法是每个服务器一套配置。天气服务器读WEATHER_BASE_URL文件服务器读FS_BASE_URL客户端初始化时分别OpenAI(api_key..., base_url...)。服务器一多MCPClient.__init__里就堆满分支。统一到 TaoToken 后客户端只创建一个OpenAI实例base_url固定指向 TaoTokenapi_key也只有一个。多服务器的差异被收敛到「连哪个服务器脚本」这一层模型调用层完全共用。这样带来的直接好处有三个改地址只改一处Key 轮换只换一个排查 401 时只需要确认一个 Key 是否有效。如果你后面要长期跑编码类 Agent可以考虑 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它更适合高频、长时间的调用场景。这篇先用按量 Key 把流程跑通。2.3 目录结构先摆清楚我沿用基础篇的结构服务器放在server/下客户端放在client/mcp-client/下。多服务器就是多几个子目录project/ ├── server/ │ ├── weather/ │ │ ├── weather.py │ │ └── .venv/ │ └── filesystem/ │ ├── filesystem.py │ └── .venv/ └── client/ └── mcp-client/ ├── client_tools.py ├── .env └── .venv/两个服务器各自独立运行、独立虚拟环境客户端通过 stdio 分别启动它们。这样即使某个服务器依赖冲突也不会互相影响。文件服务器我沿用基础篇里的create_file、read_file、write_file三个工具天气服务器保留查询工具这里不重复贴服务器代码重点放在客户端如何统一入口。3. 可复制配置把 Base URL 统一改到 TaoToken这一节是全文的核心给出可以直接复制的.env和客户端配置片段。路径和字段名我会写清楚你照着改就行。3.1 .env 文件只留一套模型配置在client/mcp-client/下新建或覆盖.env# TaoToken 统一入口 BASE_URLhttps://taotoken.net/api/v1 API_KEYsk-你的TaoToken密钥 MODEL你的模型ID # 多服务器脚本路径相对 client_tools.py 所在目录 WEATHER_SERVER../../server/weather/weather.py FILESYSTEM_SERVER../../server/filesystem/filesystem.py对比基础篇这里最大的变化是不再有WEATHER_BASE_URL、FS_BASE_URL这类分服务器字段。所有服务器共用BASE_URL、API_KEY、MODEL三项。服务器路径单独抽出来方便增删服务器时只改这一处。提示.env不要提交到公开仓库。如果你用 git把.env加进.gitignore只提交一份.env.example作为模板。3.2 客户端初始化一个 OpenAI 实例服务所有服务器下面是client_tools.py的关键部分。我保留了多服务器连接、工具映射、循环调用工具的逻辑但把模型客户端收敛成单例import asyncio import os import json from contextlib import AsyncExitStack from openai import OpenAI from dotenv import load_dotenv from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client load_dotenv() class MCPClient: def __init__(self): self.exit_stack AsyncExitStack() self.api_key os.getenv(API_KEY) self.base_url os.getenv(BASE_URL) self.model os.getenv(MODEL) if not self.api_key: raise ValueError(未找到 API_KEY请在 .env 中配置) if not self.base_url: raise ValueError(未找到 BASE_URL请在 .env 中配置) # 统一入口所有 MCP 服务器共用这一个客户端 self.client OpenAI(api_keyself.api_key, base_urlself.base_url) self.sessions {} self.tools_map {} async def connect_to_server(self, server_id: str, server_script_path: str): if server_id in self.sessions: raise ValueError(f服务器 {server_id} 已连接) is_python server_script_path.endswith(.py) is_js server_script_path.endswith(.js) if not (is_python or is_js): raise ValueError(服务器脚本必须是 Python 或 JavaScript 文件) command python if is_python else node server_params StdioServerParameters( commandcommand, args[server_script_path], envNone, ) stdio_transport await self.exit_stack.enter_async_context( stdio_client(server_params) ) stdio, write stdio_transport session await self.exit_stack.enter_async_context( ClientSession(stdio, write) ) await session.initialize() self.sessions[server_id] {session: session} print(f已连接到 MCP 服务器: {server_id}) response await session.list_tools() for tool in response.tools: self.tools_map[tool.name] server_id关键点在于self.client OpenAI(api_keyself.api_key, base_urlself.base_url)这一行。它只创建一次后面无论连多少个服务器模型调用都走它。tools_map记录「工具名 → 服务器 ID」这样模型返回某个工具调用时客户端知道该去哪个 session 执行。3.3 工具列表整合与循环调用继续补上list_tools和process_queryasync def list_tools(self): if not self.sessions: print(没有已连接的服务器) return print(已连接的服务器工具列表:) for tool_name, server_id in self.tools_map.items(): print(f工具: {tool_name}, 来源服务器: {server_id}) async def process_query(self, query: str) - str: messages [{role: user, content: query}] available_tools [] for tool_name, server_id in self.tools_map.items(): session self.sessions[server_id][session] response await session.list_tools() for tool in response.tools: if tool.name tool_name: available_tools.append({ type: function, function: { name: tool.name, description: tool.description, parameters: tool.inputSchema, }, }) while True: response self.client.chat.completions.create( modelself.model, messagesmessages, toolsavailable_tools, ) choice response.choices[0] if choice.finish_reason tool_calls: for tool_call in choice.message.tool_calls: tool_name tool_call.function.name tool_args json.loads(tool_call.function.arguments) server_id self.tools_map.get(tool_name) if not server_id: raise ValueError(f未找到工具 {tool_name} 对应的服务器) session self.sessions[server_id][session] result await session.call_tool(tool_name, tool_args) print(f[调用工具 {tool_name} {server_id}] 参数: {tool_args}) messages.append({ role: tool, content: result.content[0].text, tool_call_id: tool_call.id, }) else: return choice.message.content注意parameters字段。有些教程写的是input_schema但 OpenAI 兼容接口要求的是parameters。写错的话模型看不到工具参数定义会报参数校验错误或者干脆不调用工具。这是我在多服务器场景里踩过的坑之一。3.4 主函数一次连接多个服务器async def main(): client MCPClient() try: await client.connect_to_server( weather, os.getenv(WEATHER_SERVER) ) await client.connect_to_server( filesystem, os.getenv(FILESYSTEM_SERVER) ) await client.list_tools() await client.chat_loop() finally: await client.clean() if __name__ __main__: asyncio.run(main())chat_loop和clean沿用基础篇即可这里不重复。到这里配置部分就完成了一个.env、一个统一客户端、两个服务器连接。接下来验证。4. 验证请求一次提问同时触发两个服务器的工具配置改完最怕的是「看起来连上了其实鉴权没生效」。所以验证要设计成一次请求同时用到两个服务器的工具这样连通性和鉴权一起验。4.1 启动客户端在client/mcp-client/目录下uv venv .venv\Scripts\activate uv add openai python-dotenv mcp uv run client_tools.py启动后应该看到已连接到 MCP 服务器: weather 已连接到 MCP 服务器: filesystem 已连接的服务器工具列表: 工具: get_weather, 来源服务器: weather 工具: create_file, 来源服务器: filesystem 工具: read_file, 来源服务器: filesystem 工具: write_file, 来源服务器: filesystem MCP 客户端已启动输入 exit 退出如果这里就报 401 或者local proxy failed说明 Base URL 或 Key 有问题先别往下走去看第 5 节。4.2 设计一个跨服务器的提问在交互提示符下输入问: 帮我查询广东省东莞市未来七天的天气预报并且在当前目录创建一个 weather.txt 文件把查询到的天气内容写入文件中。这个提问会触发两次工具调用先调get_weather天气服务器拿到结果后再调create_file或write_file文件服务器。如果两个服务器都正常、鉴权都生效你会看到类似输出[调用工具 get_weather weather] 参数: {city: 东莞, days: 7} [调用工具 create_file filesystem] 参数: {file_name: weather.txt, content: ...} AI回复: 已为你查询东莞未来七天天气并写入 weather.txt。4.3 怎么判断鉴权真的生效了光看工具被调用还不够因为工具执行是本地 stdio不经过 TaoToken。真正走 TaoToken 的是self.client.chat.completions.create这一步。判断方法有两个第一看有没有报错。如果 Key 无效这一步会抛AuthenticationError客户端会打印发生错误: ...。没有报错说明鉴权通过。第二看模型是否真的返回了tool_calls。如果模型不支持工具调用finish_reason会是stop直接返回文本工具永远不会被触发。所以「工具被调用」本身就证明了两件事鉴权生效、模型支持 function calling。提示如果你想让验证更彻底可以在.env里故意把API_KEY改错一位重启客户端再问一次。这时应该看到 401 报错改回来再跑一次恢复正常。这样你就确认了鉴权链路是真的在起作用而不是碰巧。4.4 用模型对话页面交叉验证如果客户端这边一直调不通可以先绕开 MCP直接在模型对话页面发一条消息确认 Key 和模型本身可用。地址是 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 。如果那边正常、客户端不正常问题就在客户端配置如果那边也报错问题在 Key 或模型 ID。这个交叉验证能帮你快速定位问题在哪一层。5. 本篇常见错误排查401、local proxy failed、reading choices、OAuth多服务器 统一入口的组合报错往往集中在几个固定位置。我把真实遇到过的几类列出来对照着查。5.1 401 Unauthorized最常见。原因通常是三个Key 复制时带了空格或换行.env里API_KEY没被load_dotenv()读到Key 已失效或被删除。排查顺序先print(os.getenv(API_KEY))看读到的值对不对注意别把完整 Key 打印到日志里再确认.env和client_tools.py在同一目录或者load_dotenv()指定了正确路径最后去控制台确认 Key 状态。如果 Key 是从网页复制的注意前后不要有引号以外的字符。5.2 local proxy failed这个报错通常和 Base URL 写法有关。https://taotoken.net/api和https://taotoken.net/api/v1是两个不同的路径OpenAI 兼容客户端需要后者。如果你写成了前者请求会打到错误的路由上表现为连接失败或代理错误。另一个可能是网络环境问题。这里不展开只提醒确认你的运行环境能正常访问https://taotoken.net/api/v1可以用curl简单测一下curl https://taotoken.net/api/v1/models \ -H Authorization: Bearer sk-你的密钥返回模型列表就说明入口通。5.3 reading choices 相关报错response.choices[0]报IndexError或者reading choices之类的错误一般是返回体结构不符合预期。可能原因Base URL 指向了一个不兼容 OpenAI 格式的地址或者请求被中间层拦截返回了 HTML 错误页客户端却按 JSON 解析。排查方法在create调用外面包一层 try把原始响应打出来try: response self.client.chat.completions.create(...) except Exception as e: print(原始错误:, repr(e)) raise看到具体错误信息基本就能定位是地址问题还是 Key 问题。5.4 OAuth 相关报错如果你用的是某些需要 OAuth 授权的客户端比如 Claude Code 这类可能会遇到 OAuth 流程相关的报错。这类客户端通常有自己的配置文件比如settings.json或auth.json。以 Claude Code 为例配置里需要写全三件套Base URL、API Key、Model ID。缺任何一项都可能触发 OAuth 回退或者鉴权失败。如果你用的是 CC Switch、Cline MCP 或 Codex 的auth.json同样要保证这三项齐全{ baseUrl: https://taotoken.net/api/v1, apiKey: sk-你的密钥, model: 你的模型ID }字段名各客户端略有差异但核心就是这三项。Base URL 统一指向 TaoTokenKey 用同一个Model ID 按客户端要求填。配置文档可以参考 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。5.5 工具被调用但结果不对这类不是鉴权问题而是工具映射问题。表现是模型调用了工具但执行时报「未找到工具对应的服务器」。原因通常是tools_map里工具名重复——两个服务器有同名工具后连接的覆盖了先连接的。解决办法在connect_to_server里检测工具名冲突冲突时给工具名加服务器前缀或者在tools_map里存成server_id::tool_name的形式。多服务器场景下工具命名冲突是迟早要处理的早点加上检测能省很多事。6. 把统一入口用顺手的几个实操建议跑通之后有几个习惯能让这套配置更耐用。第一把服务器路径也放进.env就像 3.1 节那样。增删服务器时只改环境变量不动代码。第二给tools_map加冲突检测避免同名工具互相覆盖。第三Key 轮换时只改.env一处所有服务器自动生效这正是统一入口最大的价值。如果你后面要接入更多服务器比如数据库查询、内部 API 调用思路是一样的服务器脚本各自独立客户端只维护一份模型配置。需要看更多接入示例的话接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite API Keys 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。长期跑编码类 Agent 的话Coding Plan 在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。最后留一个我自己的习惯每次改完.env先跑一次「查天气 写文件」的跨服务器提问。这一个请求能同时验证鉴权、模型工具调用能力、两个服务器的连通性。跑通了再去做别的改动。
返回列表