MCP详解:10分钟快速入门MCP开发,用TaoToken统一Key打通LLM工具链
1. 为什么要在本地手写一个 MCP 客户端MCP 全称 Model Context Protocol是一个开源协议用来标准化 LLM 与外部数据源、工具之间的交互方式。你可以把它理解成 AI 应用世界的 USB-C 接口不管对面是 DeepSeek、Claude 还是别的模型只要双方都按 MCP 说话工具就能即插即用。它解决的问题很具体——过去每接一个外部能力查时间、算 BMI、读数据库、调地图都要为每个模型写一套适配代码有了 MCP工具方只写一次 Server模型方只写一次 Client中间靠协议对齐。这篇面向的是想快速跑通 MCP 服务的开发者尤其是习惯用 DeepSeek 这类 LLM 做工具调用的人。网上大多数 MCP 教程停留在 Claude Desktop、Cursor、Cline 这些现成客户端里点几下但真实开发中你往往需要在自己的代码里构建客户端把工具调用嵌进业务逻辑。所以本文从零开始用 uv 初始化项目、写第一个 MCP Server、配置 TaoToken 统一 Key 与 API 通道、再写一个能真正发起工具调用的客户端最后验证一次请求确实经 TaoToken 正常返回。适合谁会一点 Python、听说过 function calling 但没动手写过 MCP 的人手里有多个模型 Key、想统一管理入口的人想把本地函数暴露给 LLM 调用的人。全程大约 10 分钟命令和配置都可直接复制。我试过把同一套 Server 分别接到不同模型上只要客户端里的 Base URL 和 Key 换一下就能跑这也是后面要引入 TaoToken 统一 Key 的原因——省去每个模型单独配 Key 的麻烦。先明确一个概念边界MCP Server 负责“提供能力”MCP Client 负责“连接模型与 Server”。Server 里用mcp.tool()装饰的函数会被自动解析成工具描述包括函数名、注释、参数类型、返回类型这些信息会拼进系统提示交给 LLM。LLM 判断该调哪个工具、填什么参数返回一段 JSON客户端解析后真正执行函数再把结果回传给 LLM 生成自然语言回答。整条链路里模型只负责“决策”执行发生在你本地安全边界清晰。2. 用 uv 初始化项目并接入 TaoToken 统一 Key环境管理这块MCP 官方推荐 uv。uv 是用 Rust 写的高性能 Python 包安装器和虚拟环境管理器目标是统一替代 pip、pip-tools、venv、virtualenv。它主要维护两个文件pyproject.toml定义项目依赖和元信息uv.lock记录完整依赖树且跨平台一致后者由 uv 自动管理别手动改。安装 uv 一条命令pip install uv然后创建项目并进入目录uv init mcp-server-demo cd mcp-server-demo把 MCP 的 Python SDK 加进依赖uv add mcp[cli]如果你用的是纯 pip 项目等价写法是pip install mcp[cli]。日常几个高频命令记一下uv add 包名添加依赖并更新配置uv pip install 包名只装不写配置uv run 文件名.py在项目环境里跑代码。后面所有脚本都用uv run启动避免手动激活虚拟环境。接下来是统一 Key 的部分。开发时最烦的是每个模型一个 Key、一个 Base URL散落在各处。TaoToken 提供统一入口把模型通道收敛成一套配置。你需要在控制台创建一个 API Key然后所有请求都走同一个 Base URL。控制台地址是 https://taotoken.net/console 创建 Key 的页面在 https://taotoken.net/api-keys 。拿到 Key 后建议放进.env文件而不是硬编码# .env TAOTOKEN_API_KEYsk-你的key TAOTOKEN_BASE_URLhttps://taotoken.net/api客户端里用AsyncOpenAI指向这个 Base URL 即可模型名按你实际要用的填比如deepseek-chat。这样 Server 端完全不用关心模型是谁Client 端只认一个入口。如果你更习惯用现成的编码工具TaoToken 也提供 Coding Plan 通道适合长期跑 Agent 场景入口在 https://taotoken.net/coding-plan 。模型对话调试可以在 https://taotoken.net/models 里先验证通道是否通接入文档在 https://taotoken.net/doc 。这里给一份可直接复制的客户端配置片段路径和字段名保持原样方便你对照修改{ base_url: https://taotoken.net/api, api_key: sk-替换成你的key, model: deepseek-chat, timeout: 60 }注意Base URL 只写到/api不要在后面拼/v1之类的路径具体以接入文档为准。Key 不要提交到 Git.env记得加进.gitignore。3. 编写第一个 MCP Server 并暴露两个工具Server 是提供能力的地方。MCP 能提供的东西有四类资源 Resources文件内容、数据库记录、图像等、提示 Prompt可复用的提示模板和工作流、工具 ToolsLLM 可直接调用的函数、采样 Sampling让 Server 反向请求 LLM 生成结果。入门阶段先聚焦 Tools因为它最直观。在项目根目录新建server.pyfrom mcp.server.fastmcp import FastMCP import datetime mcp FastMCP() mcp.tool() def get_time() - str: 获取当前系统时间 return str(datetime.datetime.now()) mcp.tool() def calculate_bmi(weight_kg: float, height_m: float) - float: 根据体重kg和身高m计算BMI return weight_kg / (height_m ** 2) if __name__ __main__: mcp.run(transportstdio)写工具时有三个细节决定 LLM 能不能正确调用。第一函数注释必须写清楚MCP 会自动解析注释作为工具描述注释含糊模型就选错工具。第二参数类型要标全weight_kg: float这种写法会被解析成参数描述模型输出的字符串也会被自动转成对应类型。第三返回值类型也标上- float让协议知道返回结构。这三点做到位工具描述就完整了。transportstdio表示用标准输入输出通信适合本地开发客户端通过子进程方式拉起 Server。跑起来后它不会打印什么安静等待客户端连接这是正常的。你可以先用uv run server.py确认没有语法错误能启动就说明 Server 侧没问题。4. 写客户端连接 Server、解析工具、发起一次真实调用客户端是用户与 LLM 交互的地方流程分五步连接 Server、列出可用工具、把工具描述拼进系统提示、用户输入后让 LLM 决策、若返回工具调用则执行并把结果回传。新建client.pyimport asyncio import sys import json from typing import Optional from contextlib import AsyncExitStack from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from dotenv import load_dotenv from openai import AsyncOpenAI load_dotenv() def format_tools_for_llm(tool) - str: args_desc [] if properties in tool.inputSchema: for param_name, param_info in tool.inputSchema[properties].items(): arg_desc f- {param_name}: {param_info.get(description, No description)} if param_name in tool.inputSchema.get(required, []): arg_desc (required) args_desc.append(arg_desc) return fTool: {tool.name}\nDescription: {tool.description}\nArguments:\n{chr(10).join(args_desc)} class MCPClient: def __init__(self): self.session: Optional[ClientSession] None self.exit_stack AsyncExitStack() self.client AsyncOpenAI( base_urlhttps://taotoken.net/api, api_keysk-替换成你的key, ) self.model deepseek-chat self.messages [] async def connect_to_server(self, server_script_path: str): server_params StdioServerParameters( commandpython, args[server_script_path], envNone ) self.stdio, self.write await self.exit_stack.enter_async_context( stdio_client(server_params)) self.session await self.exit_stack.enter_async_context( ClientSession(self.stdio, self.write)) await self.session.initialize() response await self.session.list_tools() tools response.tools print(\n服务器中可用的工具, [tool.name for tool in tools]) tools_description \n.join([format_tools_for_llm(tool) for tool in tools]) system_prompt ( You are a helpful assistant with access to these tools:\n\n f{tools_description}\n Choose the appropriate tool based on the users question. If no tool is needed, reply directly.\n\n IMPORTANT: When you need to use a tool, you must ONLY respond with the exact JSON object format below, nothing else:\n {\n tool: tool-name,\n arguments: {\n argument-name: value\n }\n }\n\n After receiving a tools response:\n 1. Transform the raw data into a natural, conversational response\n 2. Keep responses concise but informative\n 3. Focus on the most relevant information\n 4. Use appropriate context from the users question\n 5. Avoid simply repeating the raw data\n\n Please use only the tools that are explicitly defined above. ) self.messages.append({role: system, content: system_prompt}) async def chat(self, prompt, roleuser): self.messages.append({role: role, content: prompt}) response await self.client.chat.completions.create( modelself.model, messagesself.messages, ) return response.choices[0].message.content async def execute_tool(self, llm_response: str): try: tool_call json.loads( llm_response.replace(json\n, ).replace(, )) if tool in tool_call and arguments in tool_call: response await self.session.list_tools() tools response.tools if any(tool.name tool_call[tool] for tool in tools): try: print([提示]正在执行函数) result await self.session.call_tool( tool_call[tool], tool_call[arguments]) print(f[执行结果]: {result}) return fTool execution result: {result} except Exception as e: error_msg fError executing tool: {str(e)} print(error_msg) return error_msg return fNo server found with tool: {tool_call[tool]} return llm_response except json.JSONDecodeError: return llm_response async def chat_loop(self): print(MCP 客户端启动) print(输入 /bye 退出) while True: prompt input( ).strip() if prompt.lower() /bye: break llm_response await self.chat(prompt) print(llm_response) result await self.execute_tool(llm_response) if result ! llm_response: self.messages.append( {role: assistant, content: llm_response}) final_response await self.chat(result, system) print(final_response) self.messages.append( {role: assistant, content: final_response}) else: self.messages.append( {role: assistant, content: llm_response}) async def main(): if len(sys.argv) 2: print(Usage: uv run client.py path_to_server_script) sys.exit(1) client MCPClient() await client.connect_to_server(sys.argv[1]) await client.chat_loop() if __name__ __main__: asyncio.run(main())启动命令uv run client.py ./server.py启动后你会看到“服务器中可用的工具 [get_time, calculate_bmi]”说明客户端成功连上 Server 并解析出工具。接着输入“现在几点了”模型会返回一段 JSON{ tool: get_time, arguments: {} }客户端识别到工具调用打印“[提示]正在执行函数”执行后回传结果模型再生成“现在的时间是……”这样的自然语言。再试“身高180体重80”模型会自动把厘米转成米调用calculate_bmi返回 BMI 约 24.69 并判断属于正常范围。整个过程中模型请求全部经 TaoToken 的 Base URL 发出你可以在控制台看到调用记录确认通道正常。5. 常见报错排查401、local proxy failed、reading choices跑不通时先别怀疑代码八成是配置或环境问题。下面按真实报错对照排查。401 Unauthorized最常见。原因通常是 Key 没填、填错、或者.env没被加载。检查client.py里api_key是否替换成了真实 Key.env是否在项目根目录且被load_dotenv()读到。如果 Key 是从控制台复制的注意别带多余空格。还有一种情况是 Base URL 写错比如多写了/v1导致鉴权路径不匹配也会返回 401。确认 Base URL 就是https://taotoken.net/api。local proxy failed / connection error这类报错一般是网络层没通。先确认本机能否正常访问外网再确认 Base URL 拼写无误。如果你在公司网络里可能有出口限制换一个网络环境试试。另外timeout设太短也会表现为连接失败把超时调到 60 秒以上。reading choices 相关报错典型信息是NoneType object has no attribute choices或读取response.choices[0]时报错。这通常意味着 API 返回结构不是预期的 chat completion 格式可能是模型名写错、通道不支持该模型、或者返回了错误对象。先打印完整response看结构再核对模型名是否与通道支持的列表一致。模型列表可以在 https://taotoken.net/models 里查。OAuth / 鉴权跳转类报错如果你用的是某些需要 OAuth 的客户端工具报错提示授权失败通常是回调地址或 token 过期。这类场景建议改用 API Key 方式接入避免 OAuth 流程。TaoToken 的 API Key 方式在 https://taotoken.net/api-keys 创建接入文档在 https://taotoken.net/doc 有完整说明。工具没被调用模型直接回答而不返回 JSON。检查系统提示里工具描述是否完整函数注释是否为空。注释为空时工具描述就是空的模型无从判断。另外确认format_tools_for_llm正确解析了inputSchema参数描述缺失也会让模型犹豫。Server 启动即退出uv run server.py一闪而过。确认mcp.run(transportstdio)在__main__里且没有其他阻塞代码。stdio 模式下 Server 靠标准输入等待客户端没连上时它安静挂着是正常的不是卡死。排查顺序建议先单独跑 Server 确认能启动再跑 Client 看能否列出工具最后发一次对话看模型是否返回 JSON。每一步的输出都打印出来定位会快很多。6. 把统一 Key 用起来从 Demo 到可扩展的工具链跑通上面这套之后你已经有了一个最小可用的 MCP 工具链。接下来可以做的扩展方向很多把get_time换成查数据库、把calculate_bmi换成调内部 APIServer 侧只改函数Client 侧几乎不用动。这就是 MCP 的价值——工具和模型解耦。统一 Key 的好处在这个阶段会越来越明显。当你同时接多个模型做对比、或者在不同项目里复用同一套工具时不用再维护一堆 Key 和 Base URL。所有请求走同一个入口调用记录集中排查问题也方便。如果你要长期跑编码类 AgentCoding Plan 通道在 https://taotoken.net/coding-plan 有更合适的配额方案日常调试模型对话用 https://taotoken.net/models 就够。最后留一个实用技巧把.env里的 Key 和 Base URL 抽成配置类Client 初始化时统一读取这样换环境只改一处。Server 脚本路径也建议用绝对路径避免uv run client.py ./server.py在不同目录下找不到文件。工具函数的注释尽量写成人能看懂的一句话模型选工具的准确率会明显提升。

相关新闻

Expo Skills的25个技能地图全解析:从导航到云模拟器的速查指南

Expo Skills的25个技能地图全解析:从导航到云模拟器的速查指南

Expo Skills的25个技能地图全解析:从导航到云模拟器的速查指南 【免费下载链接】skills A collection of AI agent skills for working with Expo projects and Expo Application Services 项目地址: https://gitcode.com/gh_mirrors/skills9/skills Expo Sk…

2026/10/4 12:56:14 阅读更多 →
工业数据采集采样频率实战指南:从奈奎斯特到Modbus与MQTT的工程避坑

工业数据采集采样频率实战指南:从奈奎斯特到Modbus与MQTT的工程避坑

1. 采样频率的底层逻辑:为什么拍脑袋定频率一定会翻车1.1 从奈奎斯特说起,但别被它框死搞工业数据采集的人,几乎都听过奈奎斯特定理——采样频率必须大于信号最高频率的2倍,才能无失真地还原原始信号。这个定理本身没错&#xff0…

2026/10/3 6:43:33 阅读更多 →
外贸获客AI靠谱吗?3个维度说清楚

外贸获客AI靠谱吗?3个维度说清楚

这情况确实是很可靠的。在当今这个时间做外贸工作的团队, 有非常高的比例已经在使用人工智能技术去开展客户获取的业务了, 这种应用并不是停留在宣传展示里的抽象概念, 而是每天都在实实在在地产生价值并收获成果的。 对于大家来说, 重点问题不在于是否具备使用的能力, 而在于使…

2026/10/3 6:42:33 阅读更多 →

最新新闻

基于Python的网络入侵检测与防御系统:从实时流量分析到自动封禁的完整闭环

基于Python的网络入侵检测与防御系统:从实时流量分析到自动封禁的完整闭环

简介:这是一份基于Python构建的网络入侵检测与防御系统源码,面向毕业设计、课程设计及网络安全方向学习者,可解决实时流量分析、恶意攻击识别、自动防御与可视化监控等需求。系统采用Flask、Flask-SocketIO与Scapy实现后端数据捕获与检测&…

2026/10/4 12:56:25 阅读更多 →
PLONK与Groth16怎么选?从信任模型到性能开销的完整对比

PLONK与Groth16怎么选?从信任模型到性能开销的完整对比

在密码学社区里被问得最多的问题之一,就是“做ZK证明到底选PLONK还是Groth16?”。无论你是做Layer 2、隐私交易、还是链上验证,几乎都会在某个时刻站在这两个名字前面犹豫。Groth16以极小证明和极低验证成本著称,PLONK以通用可信设…

2026/10/4 12:56:25 阅读更多 →
Mac M5部署Qwen3.8-27B:GGUF+Unsloth实战避坑指南

Mac M5部署Qwen3.8-27B:GGUF+Unsloth实战避坑指南

1. 这不是“跑通就行”的玩具项目:Mac M5芯片上硬刚Qwen3.8-27B的真实战场你搜到这篇记录,大概率正卡在某个报错页面上——比如终端里赫然一行红字:no lm runtime found for model format gguf!,或者OSError: dlopen(libllama.dyl…

2026/10/4 12:56:25 阅读更多 →
C/C++ const关键字全解析:指针、成员函数与constexpr区别及面试实战

C/C++ const关键字全解析:指针、成员函数与constexpr区别及面试实战

1. 面试官为什么要问const:它检验的不是语法,而是代码契约意识先说个比较扎心的观察。C/C 的面试题里,const 出现的频率高得离谱,但它很少作为独立考点出现。我在面试别人的时候,问 const 的真正目的从来不是看对方背没…

2026/10/4 12:56:25 阅读更多 →
深度学习量化投资策略实战:从数据、模型到回测的完整指南

深度学习量化投资策略实战:从数据、模型到回测的完整指南

简介:这份资源是面向高校学生与量化投资初学者的一套完整项目源码,适用于毕业设计、期末大作业或人工智能与金融交叉方向的实践练习。项目以深度学习技术构建量化投资策略,涵盖数据预处理、模型搭建、训练调优与回测评估等核心环节&#xff0…

2026/10/4 12:56:25 阅读更多 →
自动扶梯智能监控系统:AI图像识别与功能安全实战解析

自动扶梯智能监控系统:AI图像识别与功能安全实战解析

扶梯旁边贴满了“请站稳扶好”,但真正能管住乘客行为的,从来不是标语。去年我开始做自动扶梯智能监控系统,第一个要回答的问题是:AI图像识别到底能在这个场景里解决什么。传统机械安全回路能在故障发生后触发制动,却没…

2026/10/4 12:55:24 阅读更多 →

日新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 11:40:45 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 9:43:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 9:42:36 阅读更多 →