python AI工程(一)python实现mcp(2)sdk:用 FastMCP 把本地工具接进 Cline MCP 的配置与验证

发布时间:2026/10/11 8:07:09
python AI工程(一)python实现mcp(2)sdk:用 FastMCP 把本地工具接进 Cline MCP 的配置与验证 1. 从零跑通 FastMCP本地工具服务接入 Cline MCP 的完整链路FastMCP 是 Python 生态里把普通函数变成 MCP 工具最省事的 SDK它把 stdio、SSE、Streamable HTTP 三种传输方式都封装好了你只要写几个带类型注解的函数加个mcp.tool()装饰器就能被 Cline、Cursor 这类支持 MCP 的客户端直接调用。这篇面向的是已经写过一点 Python、想把本地业务逻辑比如内存里的用户表、学校表暴露给 AI 编程助手的开发者尤其是那些在 Cline 里配 MCP 时卡在command路径、cwd工作目录、ModuleNotFoundError这几个坑上的人。我会用一个tools_demo项目做例子从service/store.py的内存数据开始到mcp/server.py注册工具再到 Cline 的cline_mcp_settings.json配置最后用tools-agent命令行验证整条链路。整个过程不需要联网调远程服务纯本地 stdio 就能跑通适合当作 python AI 工程里 MCP SDK 的第一块垫脚石。我试过把 MCP 服务直接塞进 Cline 的全局配置里结果因为 Python 解释器指向了系统全局而不是虚拟环境报了一晚上的No module named mcp。后来把command改成.venv/Scripts/python.exe的绝对路径问题才消失。所以下面每个路径我都会写清楚你照着改成本机目录就行。先明确一下这个 demo 的定位它不是一个生产级的用户管理系统而是一个“最小可验证”的 MCP 服务。service/store.py里用线程锁保护了一个内存字典预置了两个学校和一条用户记录mcp/server.py通过 FastMCP 把user_list、user_create、school_list三个函数注册成工具。Cline 作为 MCP 客户端通过 stdio 启动这个 Python 进程读取工具列表然后在对话里按需调用。整条链路的关键在于Cline 启动的 Python 进程必须能 import 到service.store而service目录在项目根目录下所以cwd必须指向项目根且mcp/目录下不能有__init__.py否则会和 PyPI 的mcp包冲突。2. TaoToken 前置给 MCP 工具调用准备一个稳定的模型入口MCP 本身只负责“工具怎么被调用”但真正决定 AI 助手能不能理解你的自然语言、决定调哪个工具、传什么参数的是背后的模型。Cline 默认会让你填一个 OpenAI 兼容的 API 地址和 Key如果你直接用官方地址在国内网络环境下经常遇到超时或者 401。TaoToken 在这里的角色就是一个 OpenAI 兼容的模型网关它提供/v1/chat/completions接口Cline 里填上 Base URL 和 API Key 就能用模型 ID 可以选gpt-5.1这类支持 function calling 的模型。你需要先拿到两样东西API Key 和 Base URL。API Key 在控制台的 API Keys 页面创建Base URL 固定是https://taotoken.net/api注意这个地址不带/v1后缀Cline 的 OpenAI 配置里通常会自动补/v1如果它没补你就在 Base URL 后面手动加上/v1。模型 ID 填gpt-5.1或者你在模型对话页面看到的其他可用模型名。这里有个容易混淆的点TaoToken 的 API 地址和官网地址是两个不同的域名。官网是https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content用来注册和看文档API 是https://taotoken.net/api用来给 Cline 发请求。不要把官网地址填进 Cline 的 Base URL否则会返回 HTML 而不是 JSON。如果你只是想让 Cline 能调用本地 MCP 工具其实模型用哪个都行只要支持 function calling。但如果你后面要跑tools-agent那个命令行客户端它内部也是用 OpenAI 兼容接口发请求的所以同样需要配好LLM_BASE_URL和LLM_API_KEY。我建议你先把 TaoToken 的 Key 拿到手后面 Cline 和命令行客户端共用同一个 Key省得来回切换。另外Cline 的 MCP 配置和模型配置是分开的。MCP 配置在cline_mcp_settings.json里模型配置在 Cline 的设置面板里。很多人第一次配的时候只配了 MCP结果 Cline 能列出工具但对话时模型不响应就是因为模型那栏没填。所以这一节先把模型入口准备好下一节再写 MCP 的服务端和客户端配置。3. 可复制配置FastMCP 服务端 Cline MCP 客户端这一节是全文的核心我会把service/store.py、mcp/server.py、requirements.txt和 Cline 的cline_mcp_settings.json四份配置都写出来你直接复制改路径就能用。先看项目结构这是所有路径的基准tools_demo/ ├── README.md ├── pyproject.toml ├── requirements.txt ├── .gitignore ├── service/ │ ├── __init__.py │ └── store.py └── mcp/ └── server.py注意mcp/目录下没有__init__.py这是故意的。因为 PyPI 上有个同名的mcp包如果你在mcp/里放了__init__.pyPython 会把这个目录当成一个包import 的时候可能优先加载你的目录而不是 PyPI 的包导致from mcp.server.fastmcp import FastMCP失败。requirements.txt内容如下mcp[cli]1.2.0 pydantic2.0.0 email-validator2.0.0service/store.py是内存数据层用 Pydantic 模型定义 User 和 School用线程锁保护字典操作。完整代码如下Shared in-memory store for API and MCP (single process each; same module one store per process). from __future__ import annotations import uuid from threading import Lock from typing import Any from pydantic import BaseModel, EmailStr, Field class User(BaseModel): id: str name: str email: EmailStr | None None school_id: str | None Field(defaultNone) class School(BaseModel): id: str name: str region: str | None None class InMemoryStore: def __init__(self) - None: self._lock Lock() self._users: dict[str, dict[str, Any]] {} self._schools: dict[str, dict[str, Any]] {} self._seed() def _seed(self) - None: s1 str(uuid.uuid4()) s2 str(uuid.uuid4()) self._schools[s1] {id: s1, name: North Institute, region: US-West} self._schools[s2] {id: s2, name: East Academy, region: EU-Central} u1 str(uuid.uuid4()) self._users[u1] { id: u1, name: Ada Lovelace, email: adaexample.com, school_id: s1, } def list_users(self) - list[User]: with self._lock: return [User.model_validate(u) for u in self._users.values()] def get_user(self, user_id: str) - User | None: with self._lock: raw self._users.get(user_id) return User.model_validate(raw) if raw else None def create_user(self, name: str, email: str | None, school_id: str | None) - User: if school_id is not None and school_id not in self._schools: raise ValueError(school_id does not exist) uid str(uuid.uuid4()) row {id: uid, name: name, email: email, school_id: school_id} with self._lock: self._users[uid] row return User.model_validate(row) def update_user( self, user_id: str, name: str | None, email: str | None, school_id: str | None, ) - User | None: with self._lock: row self._users.get(user_id) if not row: return None if school_id is not None and school_id not in self._schools: raise ValueError(school_id does not exist) if name is not None: row[name] name if email is not None: row[email] email if school_id is not None: row[school_id] school_id return User.model_validate(dict(row)) def delete_user(self, user_id: str) - bool: with self._lock: return self._users.pop(user_id, None) is not None def list_schools(self) - list[School]: with self._lock: return [School.model_validate(s) for s in self._schools.values()] def get_school(self, school_id: str) - School | None: with self._lock: raw self._schools.get(school_id) return School.model_validate(raw) if raw else None def create_school(self, name: str, region: str | None) - School: sid str(uuid.uuid4()) row {id: sid, name: name, region: region} with self._lock: self._schools[sid] row return School.model_validate(row) def update_school( self, school_id: str, name: str | None, region: str | None, ) - School | None: with self._lock: row self._schools.get(school_id) if not row: return None if name is not None: row[name] name if region is not None: row[region] region return School.model_validate(dict(row)) def delete_school(self, school_id: str) - bool: with self._lock: if school_id not in self._schools: return False for u in self._users.values(): if u.get(school_id) school_id: u[school_id] None del self._schools[school_id] return True store InMemoryStore()mcp/server.py是 FastMCP 服务端注册三个工具默认 stdio 传输 MCP server: tools call service/store.py in-process. Run from project root: python mcp/server.py Do not add mcp/__init__.py so the PyPI mcp package imports correctly. from __future__ import annotations import argparse import os import sys from pathlib import Path from typing import Any from mcp.server.fastmcp import FastMCP _root Path(__file__).resolve().parent.parent if str(_root) not in sys.path: sys.path.insert(0, str(_root)) from service.store import store mcp FastMCP( tools-demo-bridge, json_responseTrue, ) mcp.tool() def user_list() - list[dict[str, Any]]: List all users. return [u.model_dump(modejson) for u in store.list_users()] mcp.tool() def user_create(name: str, email: str | None None, school_id: str | None None) - dict[str, Any]: Create a user. Optional email and school_id (must reference an existing school). try: u store.create_user(name, email, school_id) except ValueError as e: return {ok: False, detail: str(e)} return u.model_dump(modejson) mcp.tool() def school_list() - list[dict[str, Any]]: List all schools. return [s.model_dump(modejson) for s in store.list_schools()] def main() - None: parser argparse.ArgumentParser(descriptionMCP server for tools_demo) parser.add_argument( --transport, defaultos.environ.get(MCP_TRANSPORT, stdio), choices(stdio, streamable-http, sse), helpMCP transport (default: stdio), ) args parser.parse_args() mcp.run(transportargs.transport) if __name__ __main__: main()Cline 的 MCP 配置文件是cline_mcp_settings.json在 Cline 面板里点 MCP Servers 再点 Configure 就能打开。Windows 路径用双反斜杠或者正斜杠都行我习惯用正斜杠避免转义问题{ mcpServers: { tools-demo: { command: C:/python-project/tools_demo/.venv/Scripts/python.exe, args: [ C:/python-project/tools_demo/mcp/server.py ], cwd: C:/python-project/tools_demo, env: {} } } }Linux 或 macOS 下command改成/path/to/tools_demo/.venv/bin/pythonargs和cwd同理。三个字段缺一不可command指向虚拟环境里的 Pythonargs指向 server.py 的绝对路径cwd指向项目根目录。cwd决定了 Python 进程的工作目录server.py里用Path(__file__).resolve().parent.parent把项目根插进sys.path所以即使cwd不对只要args路径对import 也能成功。但为了保险cwd还是填项目根。如果你用的是 Cline 的 MCP 市场安装方式它可能会把配置写到全局的cline_mcp_settings.json里路径在 VS Code 的settings.json同目录。不管哪种方式核心就是这三个字段。4. 验证请求从手动启动到 Cline 对话调用配置写完之后先别急着在 Cline 里点重载先用命令行手动启动一次确认服务端本身没问题。这一步能帮你把“服务端错误”和“客户端配置错误”分开。进入项目目录激活虚拟环境安装依赖cd /path/to/tools_demo python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txtWindows 下激活命令是.venv\Scripts\activate。安装完成后直接运行服务端python mcp/server.py如果 stdio 传输正常你会看到进程挂起没有报错输出光标停在下一行等待输入。这说明 FastMCP 已经启动正在通过标准输入输出等待 MCP 协议消息。按 CtrlC 退出。如果你想验证工具注册是否成功可以加--transport streamable-http启动一个 HTTP 服务python mcp/server.py --transport streamable-http然后用 curl 请求工具列表curl -X POST http://127.0.0.1:8000/mcp \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -d {jsonrpc:2.0,id:1,method:tools/list,params:{}}返回的 JSON 里应该能看到user_list、user_create、school_list三个工具每个工具的inputSchema里包含参数类型和必填项。这一步验证的是 FastMCP 服务端本身和 Cline 无关。接下来回到 Cline打开 MCP Servers 面板点 Restart 或者 Reload。如果配置正确tools-demo旁边会显示一个绿色的圆点展开后能看到三个工具。如果显示红色或者一直转圈看 Cline 的输出面板里面会有 stderr 日志。在 Cline 的对话里输入列出 tools-demo 里的所有用户然后创建一个新用户名字叫 Grace Hopper邮箱 graceexample.com。Cline 会先调用user_list返回 Ada Lovelace 那条记录然后调用user_create传入 name 和 email。如果school_id不传创建出来的用户school_id是 null。你可以在对话里继续问“现在有哪些学校”它会调用school_list返回两个预置学校。这里有个细节Cline 调用工具时模型需要支持 function calling。如果你在 Cline 里配的模型不支持它会直接把工具描述当普通文本处理不会真正发起调用。所以第 2 节里让你准备 TaoToken 的 Key就是为了确保模型这一侧没问题。如果你还想验证更复杂的链路可以用tools-agent那个命令行客户端。它同时连接本地的tools_demoMCP 和远程的 Playwright MCP然后让模型自己决定调哪个工具。配置在.env里LLM_BASE_URLhttps://taotoken.net/api/v1 LLM_API_KEY你的Key LLM_MODELgpt-5.1 TOOLS_DEMO_ROOTC:/python-project/tools_demo PLAYWRIGHT_MCP_ENABLEDtrue然后运行tools-agent List users from the demo using tools, then summarize available tools.它会先连接tools_demo的 stdio 服务再尝试连接 Playwright 的远程 MCP最后把工具列表转成 OpenAI function schema 发给模型。模型返回 tool_calls 后客户端通过ClientSessionGroup.call_tool执行把结果塞回 messages 继续下一轮。这个循环最多跑max_tool_rounds次默认 8 次。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节列的都是真实会遇到的报错每个都给出定位方法和修复动作。报错一ModuleNotFoundError: No module named mcp这是最高频的。原因通常是 Cline 启动的 Python 不是虚拟环境里的那个或者虚拟环境里没装mcp[cli]。先确认command字段指向的是.venv/Scripts/python.exeWindows或.venv/bin/pythonLinux/macOS而不是系统全局的python。然后在项目根目录下用同一个解释器执行.venv/Scripts/python.exe -c import mcp; print(mcp.__file__)如果打印出.venv/Lib/site-packages/mcp/__init__.py说明解释器和包都对。如果报错重新pip install -r requirements.txt。还有一个隐蔽原因mcp/目录下不小心放了__init__.py导致 Python 把项目里的mcp目录当成包而不是 PyPI 的mcp。删掉那个文件即可。报错二local proxy failed或Connection refused这个通常出现在 Cline 尝试连接远程 MCP 的时候。如果你只配了本地 stdio 的tools-demo不应该出现这个错。检查cline_mcp_settings.json里有没有多余的url字段本地 stdio 服务不需要url只需要command、args、cwd。如果你确实配了远程 MCP确认那个远程地址在浏览器里能访问且返回的是 MCP 协议响应而不是 HTML 登录页。报错三reading choices或Cannot read properties of undefined (reading choices)这是模型接口返回的 JSON 结构不对。Cline 期望的是 OpenAI 格式的{choices: [{message: {...}}]}但如果 Base URL 填错比如填成了官网地址而不是 API 地址返回的可能是 HTML 或者{error: ...}。检查 Cline 的模型配置里 Base URL 是不是https://taotoken.net/api或者https://taotoken.net/api/v1Key 是不是以sk-开头。如果用的是tools-agent检查.env里的LLM_BASE_URL有没有带/v1。报错四OAuth相关错误有些 MCP 客户端在连接远程服务时会尝试 OAuth 流程如果你的服务端没有实现 OAuth就会卡住。本地 stdio 服务不涉及 OAuth所以如果你看到 OAuth 报错说明 Cline 在尝试连接一个远程 MCP。检查cline_mcp_settings.json里是不是混入了远程服务的配置或者 Cline 的 MCP 市场自动添加了某个需要 OAuth 的服务。把不需要的条目删掉只保留tools-demo。报错五工具调用返回{ok: false, detail: school_id does not exist}这是业务逻辑错误不是配置错误。user_create里传了一个不存在的school_idstore.create_user会抛ValueError被捕获后返回这个 JSON。先调school_list拿到真实的学校 ID再传进去。这个设计是故意的让模型能根据错误信息自我纠正。报错六Cline 里工具列表为空如果tools-demo显示绿色但展开没有工具检查server.py里mcp.tool()装饰器有没有漏掉或者函数有没有被if __name__ __main__之外的代码覆盖。另外FastMCP 的工具注册是在模块加载时完成的如果server.py在 import 阶段就抛异常工具列表也会空。手动运行python mcp/server.py看有没有 traceback。6. 语义一致 CTA把 MCP 工具接进你的日常编码流跑通这个 demo 之后你手里就有了一套可复制的模板service/store.py换成你自己的业务逻辑mcp/server.py里加几个mcp.tool()函数Cline 配置里改一下路径就能让 AI 助手直接操作你的本地数据。下一步可以试试把user_create换成调用真实数据库或者把school_list换成查询内部 API。如果你在配 Cline 的模型时还没填 Key可以去 TaoToken 控制台创建一个https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 然后在 API Keys 页面生成 Keyhttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。Base URL 用https://taotoken.net/api模型 ID 填gpt-5.1。如果你更习惯用 Claude Code 那套工具链可以看接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有针对不同客户端的配置示例。想先验证模型对话是否正常可以直接在模型对话页面发一条消息https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果你打算长期用 MCP 做 Agent 开发Coding Plan 里包含了更稳定的调用额度https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后提醒一句mcp/目录下千万别放__init__.py这个坑我踩过两次每次都要花十分钟才想起来。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询