拓冰建站拓冰建站
首页 / 资讯中心 / 正文

MCP Server 开发实战:从零编写并测试你的第一个 Server(TaoToken 统一 Key 接入)

1. 为什么你的第一个 MCP Server 值得认真写MCP Server 开发这件事很多人卡在第一步概念看懂了协议也翻了几页但真到动手写的时候不知道从哪一行代码开始。我见过不少开发者把 MCP 当成一个配置项来理解结果写出来的 Server 要么工具注册不上要么 Inspector 里连不上要么调用返回一堆看不懂的报错。先把话说清楚MCPModel Context ProtocolServer 本质上就是一个暴露工具Tool给模型调用的本地服务。它不神秘你可以把它理解成一个函数注册中心——你写好函数声明参数和说明模型在需要的时候就会来调它。适合谁适合已经知道 MCP 是什么、准备写第一个能跑通的 Server 的开发者。这篇文章的目标很明确从零建项目、写工具、本地测试、验证调用全程可复现。我试过用最朴素的方式搭一个查询用户信息的 Server整个过程踩了几个坑也总结出一套比较顺的流程。下面按环境准备 → 写代码 → 本地测试 → 接入统一 Key → 排错的顺序展开每一步都给可复制的命令和配置。你跟着做最后应该能在 Inspector 里看到工具被正确列出并返回结果。需要提前说明的是MCP Server 本身是本地进程测试阶段完全不需要联网。但当你后续想把它接到真实的模型对话里做端到端验证时就需要一个统一的 API 通道。这里我会用 TaoToken 作为统一 Key/API 通道在接入环节出现一次官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 它的作用是让你不用为每个模型单独管理 Key一个通道走通对话和工具调用。2. 用 uv 初始化 MCP Server 项目并安装 mcp[cli] 依赖2.1 环境准备Python 与 uvMCP 的 Python SDK 对版本有要求建议 Python 3.10 及以上。我本地用的是 3.10.9实测没问题。包管理用 uv它比 pip 快很多而且能自动管理虚拟环境。如果你还没装 uv先装# Windows PowerShell powershell -ExecutionPolicy ByPass -c irm https://astral.sh/uv/install.ps1 | iex # macOS / Linux curl -LsSf https://astral.sh/uv/install.sh | sh装完验证一下uv --version能打印出版本号就说明装好了。这一步别跳过后面所有命令都依赖它。2.2 初始化项目进入你的工作目录执行初始化uv init MCPServer1 cd MCPServer1uv init会生成一个基础的项目结构包括pyproject.toml和main.py。接着创建并激活虚拟环境uv venv # Windows .venv\Scripts\activate # macOS / Linux source .venv/bin/activate激活后命令行前面会出现(MCPServer1)之类的提示。确认 Python 版本python -V2.3 安装依赖核心依赖就一个uv add mcp[cli]这里有个坑要提醒默认源在国内可能很慢甚至超时。你可以配置镜像加速在pyproject.toml里加上[[tool.uv.index]] url https://pypi.tuna.tsinghua.edu.cn/simple default true或者临时用环境变量指定。装完之后pyproject.toml的dependencies里应该能看到mcp这一项。这一步是整个项目的地基依赖没装好后面mcp dev会直接报模块找不到。2.4 项目结构确认初始化完成后目录大概长这样MCPServer1/ ├── .venv/ ├── main.py ├── pyproject.toml └── uv.lockmain.py是入口文件我们接下来要改的就是它。uv.lock锁定了依赖版本保证换台机器也能复现同样的环境——这点对团队协作很重要。3. 编写 FastMCP Server 骨架与工具注册配置3.1 最小可运行骨架打开main.py替换成下面的内容。这是整个 Server 的核心我把它拆成三块初始化、工具定义、启动。from mcp.server.fastmcp import FastMCP from pydantic import Field # 初始化 FastMCP server mcp FastMCP(hello-mcp-server, log_levelERROR) # 模拟数据库 user_database { 001: {name: 张三, age: 30, city: 北京}, 002: {name: 李四, age: 25, city: 上海}, 003: {name: 王五, age: 35, city: 武汉}, } mcp.tool() async def get_user_info(user_id: str Field(description用户ID)) - str: 查询用户信息。当用户需要根据ID查询用户信息时调用此工具 Args: user_id: 用户ID Returns: 用户信息的字符串描述 user_info user_database.get(user_id, None) if user_info: return ( f用户ID{user_id}\n f姓名{user_info[name]}\n f年龄{user_info[age]}\n f城市{user_info[city]} ) else: return 未找到该用户的信息 def main(): print(Hello from mcpserver1!) mcp.run() if __name__ __main__: main()3.2 关键点拆解FastMCP(hello-mcp-server)里的名字是 Server 标识Inspector 里会显示。log_levelERROR是为了减少噪音调试阶段可以改成DEBUG。mcp.tool()装饰器是核心。被它标记的函数会自动注册成工具模型能看到函数名、参数说明和 docstring。docstring 不是可选项——模型靠它判断什么时候该调这个工具。我见过有人把 docstring 写成一行查询用户结果模型经常不调用因为信息量不够。参数用Field(description...)声明这个描述会出现在工具的 input schema 里。user_id是字符串类型模型传参时会按这个约束来。3.3 工具注册的配置语义如果你想把工具配置抽出来做成 JSON 或 TOML方便多环境切换可以这样组织。比如一个mcp-config.json{ server: { name: hello-mcp-server, log_level: ERROR }, tools: [ { name: get_user_info, description: 根据用户ID查询用户信息, parameters: { user_id: { type: string, description: 用户ID例如 001 } } } ] }然后在代码里读取这份配置来动态注册。这样做的好处是工具描述和代码解耦改描述不用动 Python 逻辑。对于工具数量多的 Server这种组织方式会清爽很多。注意FastMCP 的装饰器方式是静态注册动态注册需要走底层Server类。第一个 Server 建议先用装饰器跑通别一上来就上动态注册容易绕晕。4. 用 MCP Inspector 本地测试并验证工具调用4.1 启动 Inspector代码写好后用mcp dev启动调试工具mcp dev main.py第一次运行会提示安装modelcontextprotocol/inspector输入y确认。安装完成后终端会打印一个本地地址通常是http://localhost:5173之类。在浏览器打开它。4.2 连接与列出工具打开页面后点击Connect按钮连接你的 Server。连接成功后切到Tools标签页点击List Tools。如果一切正常你会看到get_user_info出现在列表里点开能看到它的参数 schema。这一步是验证工具注册是否成功的关键。如果列表是空的说明mcp.tool()没生效或者 Server 启动时抛了异常但被吞掉了。4.3 发起一次调用在工具详情里填入user_id为001点击Run。预期返回用户ID001 姓名张三 年龄30 城市北京再试一个不存在的 ID比如999应该返回未找到该用户的信息。这两次调用覆盖了命中和不命中两条路径说明工具逻辑是通的。4.4 用命令行方式验证除了 Inspector你也可以直接用 stdio 方式测试。写一个简单的客户端脚本import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): params StdioServerParameters( commandpython, args[main.py], ) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools() print(可用工具:, [t.name for t in tools.tools]) result await session.call_tool(get_user_info, {user_id: 002}) print(调用结果:, result.content[0].text) asyncio.run(main())运行后应该打印出工具列表和李四的信息。这种方式更接近真实调用链路适合写自动化测试。4.5 接入统一 Key 做端到端验证本地工具跑通后如果你想验证模型真的会调用这个工具就需要把 Server 接到一个支持工具调用的模型通道上。这里用 TaoToken 作为统一 Key/API 通道一个 Key 走通对话和工具调用不用为每个模型单独配。先拿到 API Key在控制台创建https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content然后在你的客户端配置里填三件套{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: claude-3-5-sonnet }Base URL 是https://taotoken.net/api注意不要加 UTM 参数。Model ID 按你实际要用的填。配置好后发起一次带工具定义的对话请求观察模型是否返回了tool_use类型的响应块。如果返回了说明整条链路是通的。想直接体验模型对话效果可以用模型对话页https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content5. 常见报错排查401、local proxy failed 与 reading choices5.1 401 Unauthorized这是最常见的接入报错。原因通常是 Key 没填对、Key 过期或者 Base URL 写错了。检查顺序第一确认api_key字段填的是完整 Key没有多余空格。第二确认base_url是https://taotoken.net/api不要写成带路径的完整接口地址。第三如果用的是环境变量确认变量名和代码里读取的一致。# 快速验证 Key 是否有效 curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的Key \ -H Content-Type: application/json \ -d {model:claude-3-5-sonnet,messages:[{role:user,content:hi}]}如果返回 401就是 Key 的问题返回 200 说明 Key 没问题问题在客户端配置。5.2 local proxy failed这个报错通常出现在客户端尝试走本地代理时。检查你的环境变量里有没有HTTP_PROXY/HTTPS_PROXY之类的设置如果有先清掉再试。另外确认客户端配置里没有多余的代理字段。MCP Server 本身是本地 stdio 进程不需要任何代理。5.3 reading choices 相关报错这类报错一般出现在解析模型响应时提示reading choices或类似字段缺失。原因通常是响应体不是预期的 JSON 结构——可能是请求被拦截返回了 HTML或者模型名写错了导致接口返回错误对象。排查方法把原始响应打印出来看。在客户端里加一行日志或者在 curl 里加-v看完整返回。如果返回的是 HTML 页面说明请求根本没到 API如果返回的是{error: ...}按错误信息定位。5.4 OAuth 相关报错如果你在配置里看到了 OAuth 相关的提示说明客户端在尝试走 OAuth 流程。对于 API Key 接入方式不需要 OAuth。检查配置里有没有误开 OAuth 选项关掉它改用 Key 认证。5.5 工具调用返回空如果模型没有调用工具先检查工具的 docstring 是否足够清晰。模型靠描述判断何时调用描述太模糊它就不调。其次检查参数 schema 是否正确类型不匹配会导致调用失败。最后确认客户端是否把工具定义正确传给了模型——有些客户端需要显式开启工具调用功能。6. 把 Server 接到长期编码与 Agent 工作流第一个 Server 跑通之后下一步通常是把它接到实际的编码或 Agent 工作流里。这时候你会遇到几个新问题多个 Server 怎么管理、Key 怎么统一、模型怎么切换。统一 Key 的价值在这里体现出来。如果你有多个工具 Server每个都要配一套认证管理成本会很高。用一个通道统一走配置只需要维护一份。TaoToken 的 Coding Plan 就是为这种长期编码场景准备的适合需要持续调用模型和工具的开发流https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content如果你用的是 Claude Code 这类工具接入文档在这里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI Key 管理页https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content最后给一个实用建议写 MCP Server 时先把工具逻辑写成纯函数单独测试通过后再包上mcp.tool()。这样调试的时候不用每次都启动 Inspector直接跑单元测试就行。工具描述要写得像给同事解释一样具体模型不是人它只能靠你写的文字来判断。我踩过的坑是 docstring 写太简略结果模型十次有八次不调用改成详细描述后命中率明显上来了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门