ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

手动实现一个简单的MCP Server及原理(Cursor版):用 TaoToken 统一 Key 打通本地工具调用

2026/10/3 16:20:09 拓冰建站 浏览量
手动实现一个简单的MCP Server及原理(Cursor版):用 TaoToken 统一 Key 打通本地工具调用 1. 从零手写 MCP Server 到底难在哪Cursor 工具调用链路拆解MCP Server 这个词最近出现频率很高但真正动手写一个最小可跑版本的人并不多。原因不是代码复杂而是链路里藏着几个容易卡住的点stdio 通信怎么建立、工具函数怎么被模型发现、Cursor 怎么知道去启动你的进程、模型请求又该走哪个 Base URL。这几个问题任何一个没打通表现都是「工具列表空的」或者「模型不调用工具」。我这次的目标很明确在 Cursor 里从零手写一个最小 MCP Server用 Python uv 搭工程把 stdio 通信和工具注册原理讲清楚最后把模型请求的 Base URL 指向 TaoToken用一次真实的工具调用验证整条链路是否跑通。适合已经用过 Cursor、想搞明白 MCP 底层机制、又不想被一堆抽象概念绕晕的开发者。先说清楚 MCP 是什么。MCPModel Context Protocol本质是一套约定客户端把用户的 Prompt 和当前可用的工具列表一起发给 LLMLLM 判断是否需要调用工具如果需要就返回工具名和参数客户端再通过统一接口去执行这个工具把结果塞回对话。整个过程里Server 负责「提供工具」Client 负责「编排调用」LLM 负责「决策」。那为什么要在 Cursor 里手写而不是直接用现成的因为现成的 Server 你看不到工具注册的细节也看不到 stdio 上到底传了什么。手写一遍你会清楚mcp.tool()装饰器做了什么、mcp.run(transportstdio)启动后进程在等什么、Cursor 的mcp.json里那几个字段分别对应什么。这些搞懂了后面接任何模型、任何工具都是同一套逻辑。还有一个现实问题模型请求走哪里。Cursor 默认走它自己的通道但如果你想统一管理 Key、想换模型、想看请求到底发了什么就需要把 Base URL 改到一个可控的入口。TaoToken 在这里的角色就是统一入口——一个 Key 打通模型对话和工具调用Base URL 填https://taotoken.net/api模型 ID 按需选。这样你在 Cursor 里调工具时模型侧的请求和工具侧的本地进程是两条独立但协同的链路排障时能分开定位。下面按「装环境 → 写 Server → 配 Cursor → 验证 → 排错」的顺序走每一步都给可复制的命令和配置。技术部分会占大头拿 Key 的部分放在前面快速带过。2. TaoToken 前置准备统一 Key 与 Base URL 配置在动手写 Server 之前先把模型侧的入口准备好。这一步很快但顺序不能反——因为后面 Cursor 里验证工具调用时模型请求要能正常返回否则你分不清是 Server 没起来还是模型没通。TaoToken 的定位是一个统一的 API 入口把模型对话、编码计划、API Key 管理放在同一个控制台里。对这次实验来说你只需要三样东西Base URL、API Key、Model ID。这三样凑齐Cursor 里任何需要填模型配置的地方都能用。先拿 Key。打开控制台页面https://taotoken.net/console登录后在 API Keys 区域创建一个新 Key。建议命名带上用途比如cursor-mcp-test方便后面区分。创建后立刻复制保存页面刷新后通常不再完整显示。Base URL 固定填https://taotoken.net/api注意不要带结尾斜杠也不要加 UTM 参数——API 调用路径和官网推广链接是两回事混了会 404。Model ID 按你实际要用的填比如claude-3-5-sonnet-20241022这类具体以控制台模型列表为准。如果你后面打算长期在 Cursor 里做编码和 Agent 任务可以顺带看一下 Coding Plan 页面https://taotoken.net/coding-plan它把常用编码模型的额度打包比单次调用更省心。这次实验用按量 Key 就够。把这三样记在一个临时文本里配置项值Base URLhttps://taotoken.net/apiAPI Key控制台创建后复制Model ID控制台模型列表里选注意Base URL 和官网地址不要混用。官网是https://taotoken.net/API 是https://taotoken.net/api前者用于浏览文档和控制台后者用于代码里的请求地址。模型对话的调试入口在https://taotoken.net/models接入文档在https://taotoken.net/doc。这两个页面在你排错时会用到前者可以快速验证 Key 是否有效后者能查到不同模型对应的参数格式。这一步做完模型侧就通了。接下来进入正题写 Server。3. 可复制配置pyproject.toml、weather.py 与 Cursor mcp.json这一节是全文的核心所有代码和配置都可以直接复制。我按「建工程 → 写 Server → 配 Cursor」三步走每步都给完整文件内容。3.1 用 uv 初始化工程uv 是目前比较顺手的 Python 包管理器装依赖快虚拟环境管理也干净。Windows 下用 PowerShell 装powershell -ExecutionPolicy ByPass -c irm https://astral.sh/uv/install.ps1 | iexmacOS 或 Linux 用curl -LsSf https://astral.sh/uv/install.sh | sh装完后新开一个终端验证uv --version然后建工程目录并初始化uv init weather cd weather uv venv激活虚拟环境。Windows.venv\Scripts\activatemacOS / Linuxsource .venv/bin/activate装依赖uv add mcp[cli] httpx这一步会生成pyproject.toml和uv.lock。pyproject.toml里会自动写入依赖你不需要手动改但可以打开确认一下mcp和httpx都在。如果后面 Cursor 启动 Server 报「找不到模块」多半是这个文件里的依赖没装全回到这一步重跑uv add即可。3.2 写 Server 入口 weather.py在工程根目录新建weather.py内容如下。这段代码定义了两个工具查某州天气预警、查某坐标的天气预报。工具本身调用的是公开天气 API重点不在业务逻辑而在于展示mcp.tool()怎么注册、mcp.run(transportstdio)怎么启动。from typing import Any import httpx from mcp.server.fastmcp import FastMCP # 初始化 FastMCP 服务器命名为 weather mcp FastMCP(weather) # 常量 NWS_API_BASE https://api.weather.gov USER_AGENT weather-app/1.0 async def make_nws_request(url: str) - dict[str, Any] | None: 向 NWS API 发请求带错误处理。 headers { User-Agent: USER_AGENT, Accept: application/geojson, } async with httpx.AsyncClient() as client: try: response await client.get(url, headersheaders, timeout30.0) response.raise_for_status() return response.json() except Exception: return None def format_alert(feature: dict) - str: 把单条预警格式化成可读字符串。 props feature[properties] return f Event: {props.get(event, Unknown)} Area: {props.get(areaDesc, Unknown)} Severity: {props.get(severity, Unknown)} Description: {props.get(description, No description available)} Instructions: {props.get(instruction, No specific instructions provided)} mcp.tool() async def get_alerts(state: str) - str: Get weather alerts for a US state. Args: state: Two-letter US state code (e.g. CA, NY) url f{NWS_API_BASE}/alerts/active/area/{state} data await make_nws_request(url) if not data or features not in data: return Unable to fetch alerts or no alerts found. if not data[features]: return No active alerts for this state. alerts [format_alert(feature) for feature in data[features]] return \n---\n.join(alerts) mcp.tool() async def get_forecast(latitude: float, longitude: float) - str: Get weather forecast for a location. Args: latitude: Latitude of the location longitude: Longitude of the location points_url f{NWS_API_BASE}/points/{latitude},{longitude} points_data await make_nws_request(points_url) if not points_data: return Unable to fetch forecast data for this location. forecast_url points_data[properties][forecast] forecast_data await make_nws_request(forecast_url) if not forecast_data: return Unable to fetch detailed forecast. periods forecast_data[properties][periods] forecasts [] for period in periods[:5]: forecast f {period[name]}: Temperature: {period[temperature]}°{period[temperatureUnit]} Wind: {period[windSpeed]} {period[windDirection]} Forecast: {period[detailedForecast]} forecasts.append(forecast) return \n---\n.join(forecasts) if __name__ __main__: mcp.run(transportstdio)几个关键点解释一下。FastMCP(weather)里的名字是 Server 标识Cursor 里显示的就是它。mcp.tool()装饰器把函数注册进工具列表函数的 docstring 会作为工具描述发给模型——所以 docstring 要写清楚参数含义模型靠它判断该不该调、怎么传参。mcp.run(transportstdio)表示这个 Server 通过标准输入输出和客户端通信不监听端口进程由 Cursor 拉起。3.3 配 Cursor 的 mcp.json打开 Cursor右上角设置 → MCP Servers → Add new global MCP Server。填入{ mcpServers: { weather: { command: uv, args: [ --directory, /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather, run, weather.py ] } } }把/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather换成你工程目录的绝对路径。Windows 下形如C:\\Users\\you\\projects\\weather注意 JSON 里反斜杠要转义。macOS / Linux 形如/Users/you/projects/weather。保存后回到 MCP Servers 列表应该能看到一个叫weather的条目状态是绿色展开能看到get_alerts和get_forecast两个工具。如果显示红色或没有工具先看下一节的排错。3.4 把模型请求指向 TaoTokenCursor 里模型配置的位置在 Settings → Models。把 Base URL 改成https://taotoken.net/apiAPI Key 填你在控制台创建的那个Model ID 填你要用的模型。保存后 Cursor 的模型请求就走 TaoToken 了。这里有个容易混的点MCP Server 的启动和模型请求是两条链路。Server 由 Cursor 本地拉起走 stdio模型请求走网络到 TaoToken。两条链路都通工具调用才能完整跑完。排错时先确认 Server 是绿的再确认模型能正常对话最后才测工具调用。4. 验证请求一次工具调用跑通整条链路配置完成后新建一个 Cursor 对话模型选你刚配好的那个。先做一次普通对话确认模型通你好简单介绍一下你自己。如果这句能正常返回说明 Base URL 和 Key 没问题。接下来测工具调用。在对话里输入帮我查一下 CA 州当前的天气预警。预期行为是Cursor 把这句话和工具列表一起发给模型模型判断需要调用get_alerts参数stateCACursor 收到工具调用指令后通过 stdio 把请求发给本地 weather 进程进程调用天气 API 拿到数据返回给 CursorCursor 再把结果交给模型组织成自然语言回复。如果一切正常你会看到回复里包含 CA 州的预警信息同时在 Cursor 的工具调用记录里能看到get_alerts被调用了一次。再测一个带坐标的帮我查一下纬度 37.7749、经度 -122.4194 的天气预报。这次应该触发get_forecast返回未来几天的预报。两个工具都调通说明 Server 注册、stdio 通信、模型决策、结果回传整条链路是通的。实测下来模型选择会影响工具调用成功率。有些模型对工具调用的支持更稳有些会忽略工具直接编答案。如果发现模型不调用工具而是自己瞎编先换一个工具调用能力强的模型再试。Cursor 里切换模型很快不用改代码。验证通过后你可以打开 TaoToken 的模型对话页面https://taotoken.net/models对照看请求是否正常计费、返回是否完整。这一步不是必须但能帮你确认请求确实走了 TaoToken 而不是别的地方。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节按真实报错来。MCP 链路涉及本地进程、stdio、网络请求、模型 API 四层报错信息往往指向其中一层但表述很模糊。下面几个是我和身边人踩过的坑。401 Unauthorized。这个基本是模型侧的问题不是 Server 的问题。检查三处API Key 是否复制完整前后有没有空格、Base URL 是否写成https://taotoken.net/api不要带斜杠、不要带 UTM、Key 是否已过期或被删。如果 Key 没问题去https://taotoken.net/api-keys重新生成一个再试。注意 401 不会影响 MCP Server 的绿色状态因为 Server 本身不校验模型 Key。local proxy failed / connection refused。这个通常出现在模型请求走本地代理的场景。如果你之前配过本地代理Cursor 可能还在用旧配置。检查 Settings → Models 里的 Base URL 是否被改回了本地地址。另外确认没有其他工具占用同名端口。这个报错和 MCP Server 无关Server 走的是 stdio不经过网络代理。reading choices 或 undefined is not an object。这是模型返回格式不符合预期时的典型报错多半是 Model ID 填错了或者该模型不支持当前请求格式。回到https://taotoken.net/doc查一下你用的模型 ID 拼写确认它支持工具调用。有些模型只支持纯对话传了 tools 参数会返回异常结构Cursor 解析时就报这个错。OAuth 相关报错。如果你在 Cursor 里登录过某些账号可能会残留 OAuth token和 API Key 冲突。表现是请求被重定向到登录页或返回 403。解决办法是在 Cursor 设置里退出相关账号登录只用 API Key 认证。MCP Server 本身不涉及 OAuth它只认 stdio。Server 显示红色 / 工具列表为空。先看mcp.json里的路径是不是绝对路径相对路径 Cursor 解析不了。再看uv是否在系统 PATH 里Cursor 启动子进程时用的是系统环境终端里能跑不代表 Cursor 能跑。最后确认pyproject.toml里依赖装全了在工程目录下手动跑一次uv run weather.py如果这行能启动且不报错会卡住等待 stdio 输入这是正常的说明 Server 本身没问题问题在 Cursor 配置。按 CtrlC 退出即可。工具被调用但返回空。检查天气 API 是否可达以及state参数是不是两位州代码。get_forecast对经纬度格式敏感传字符串会失败。这些是工具内部逻辑问题和 MCP 链路无关看 Server 进程的 stderr 输出能定位。排错时记住一个原则先分层再定位。Server 绿不绿看本地进程模型通不通看普通对话工具调不调用看模型能力工具返回对不对看业务代码。四层分开测比盯着一个报错猜要快得多。6. 把这条链路用起来从天气工具到自己的业务工具天气工具只是载体真正有价值的是这套模式可以复制到任何本地工具上。你把手写的weather.py换成查数据库、读本地文件、调内部接口注册方式完全一样Cursor 那边的mcp.json也只需要改路径和文件名。具体做法是新建一个my_tool.py用FastMCP(my-tool)初始化把你要暴露的能力写成带mcp.tool()的异步函数docstring 写清楚参数最后mcp.run(transportstdio)。然后在mcp.json里加一个条目command还是uvargs指向新文件。重启 Cursor新工具就出现在列表里。模型侧继续用 TaoToken 统一入口Base URL 保持https://taotoken.net/api换模型只改 Model ID。这样你的本地工具和模型请求是解耦的工具在本地跑数据不出机器模型请求走统一入口Key 和额度集中管理。想验证某个模型对工具调用的支持程度直接在https://taotoken.net/models里切换测试不用改任何 Server 代码。如果你打算把这套东西用在长期编码或 Agent 任务上Coding Plan 页面https://taotoken.net/coding-plan里有针对编码场景的额度方案比按量调用更适合高频使用。接入文档https://taotoken.net/doc里有不同语言的调用示例Python 之外的场景也能参考。最后留一个实用技巧调试工具调用时在 Server 的工具函数里加一行print(..., filesys.stderr)把入参打到 stderr。Cursor 会把 Server 的 stderr 收集到日志里你能看到模型实际传了什么参数。这比在模型侧猜要直接得多。stdio 通信下stdout 被协议占用调试输出必须走 stderr这一点新手很容易踩。