MCP工具实战(二)-Python封装MCP工具与调试

上一篇我们先统一了认知:MCP 不是 CLI 的替代,而是让模型稳定调用工具的标准协议层。
这一篇开始进入实战,目标很明确:

  1. 用 Python 从 0 封装一个可调用的 MCP Tool
  2. 讲清楚需要安装哪些库,以及每个库的作用
  3. 演示本地调试流程(含 mcp dev
  4. 给出一份能直接排错的 Debug 清单

目录

  1. 实战目标与最终效果
  2. Python 需要哪些库
  3. 项目初始化与安装依赖
  4. 封装第一个 MCP 工具(完整代码)
  5. 如何运行 MCP 服务
  6. 如何用 mcp dev 调试
  7. Inspector 与常见调试策略
  8. 常见问题与排错清单
  9. 总结

1. 实战目标与最终效果

我们做一个最小可运行服务器,暴露 2 个工具:

  • search_blog_posts:根据关键词搜索文章(示例数据)
  • get_post_detail:根据文章 id 获取详情

为什么用这个例子?

  • 参数简单,便于看懂 schema 生成逻辑
  • 返回结构清晰,便于你后续替换成真实数据库/搜索服务
  • 覆盖了 MCP 工具最关键的输入、输出和错误处理

2. Python 需要哪些库

官方 Python SDK 的核心包是 mcp
如果你要用命令行工具(比如 mcp devmcp install),需要安装带 cli 扩展的版本。

2.1 最小依赖

  • mcp[cli]
    • 提供 Python MCP SDK
    • 提供 mcp 命令行能力(mcp devmcp runmcp install

2.2 常见业务依赖(按需)

  • httpx:如果你的工具要请求外部 HTTP API
  • pydantic:复杂参数建模与校验时很好用(可选)
  • python-dotenv:本地读取 .env 配置(可选)

如果你只是做第一个 demo,mcp[cli] 就够了。


3. 项目初始化与安装依赖

这里用 uv 演示(官方文档也大量使用 uv)。你也可以换成 pip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1) 新建项目目录
mkdir mcp-blog-demo
cd mcp-blog-demo

# 2) 初始化项目
uv init

# 3) 创建虚拟环境(可选,但推荐)
uv venv

# Windows
.venv\Scripts\activate

# macOS/Linux
source .venv/bin/activate

# 4) 安装 MCP SDK + CLI 能力
uv add "mcp[cli]"

如果你习惯 pip:

1
pip install "mcp[cli]"

4. 封装第一个 MCP 工具(完整代码)

创建 server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from mcp.server.fastmcp import FastMCP

# 1) 初始化服务
mcp = FastMCP("blog-tools")

# 2) 示例数据(实际项目可替换为数据库/API)
POSTS = [
{"id": 1, "title": "MCP 入门", "tags": ["MCP", "AI"], "summary": "介绍 MCP 核心概念"},
{"id": 2, "title": "Python 封装 MCP Tool", "tags": ["MCP", "Python"], "summary": "从函数到工具"},
{"id": 3, "title": "MCP 调试实战", "tags": ["MCP", "Debug"], "summary": "mcp dev + Inspector"},
]


@mcp.tool()
def search_blog_posts(keyword: str, limit: int = 5) -> list[dict]:
"""根据关键词搜索文章。

Args:
keyword: 搜索关键词
limit: 返回数量上限,默认 5
"""
kw = keyword.strip().lower()
if not kw:
raise ValueError("keyword 不能为空")
if limit <= 0:
raise ValueError("limit 必须大于 0")

matched = []
for post in POSTS:
haystack = f"{post['title']} {' '.join(post['tags'])} {post['summary']}".lower()
if kw in haystack:
matched.append(post)
if len(matched) >= limit:
break
return matched


@mcp.tool()
def get_post_detail(post_id: int) -> dict:
"""根据文章 ID 获取详情。

Args:
post_id: 文章 ID
"""
for post in POSTS:
if post["id"] == post_id:
return post
raise ValueError(f"未找到 id={post_id} 的文章")


if __name__ == "__main__":
# 默认使用 stdio 传输,适合本地开发与客户端集成
mcp.run()

这段代码的关键点

  1. FastMCP("blog-tools") 定义了 MCP 服务名称。
  2. @mcp.tool() 把 Python 函数注册成 MCP 工具。
  3. 函数参数类型提示 + docstring,会被用于生成工具描述和参数约束。
  4. 业务错误要明确抛出,让客户端能看到清晰失败原因。

5. 如何运行 MCP 服务

有两种常见方式。

5.1 直接运行 Python 文件

1
python server.py

5.2 使用 MCP CLI 运行

1
uv run mcp run server.py

实战建议:本地调试优先 mcp dev,正式跑通后再接入你的 MCP 客户端(如 Claude Desktop、IDE Agent)。


6. 如何用 mcp dev 调试

这是 Python MCP 开发里最好用的命令之一。

6.1 基本用法

1
uv run mcp dev server.py

这条命令会帮助你快速进入调试流程(通常配合 Inspector 使用),你可以直观看到:

  • 服务是否正常启动
  • 工具是否被正确发现
  • 参数 schema 是否符合预期
  • 调用结果与错误输出是否清晰

6.2 常见进阶参数

1
2
3
4
5
# 运行时临时附加依赖
uv run mcp dev server.py --with pandas --with numpy

# 以 editable 方式挂载本地代码
uv run mcp dev server.py --with-editable .

6.3 一个容易忽略的限制

uv run mcp run / uv run mcp dev 的便捷能力,主要针对 FastMCP 方式。
如果你使用的是更底层的 server 写法,这两个命令未必适用,建议直接按底层服务方式运行。


7. Inspector 与常见调试策略

除了 mcp dev,建议你熟悉 MCP Inspector。

7.1 Inspector 快速启动

1
npx @modelcontextprotocol/inspector <command>

本地 Python 服务常见形式(示意):

1
2
3
4
5
npx @modelcontextprotocol/inspector \
uv \
--directory path/to/your/project \
run \
server.py

7.2 在 Inspector 里重点看什么

  1. Tools 列表
    • 工具名是否正确
    • 描述是否清晰
  2. 输入 schema
    • 必填字段是否正确
    • 类型是否符合预期(int/str/bool)
  3. 执行结果
    • 正常返回结构是否稳定
    • 异常时是否给出可理解的错误信息
  4. 通知与日志
    • 是否有初始化失败、参数错误等信号

8. 常见问题与排错清单

这部分是实战里最省时间的。

8.1 服务启动了,但客户端看不到工具

优先检查:

  1. 你是否真的加了 @mcp.tool()
  2. 是否启动了正确的 server.py
  3. 客户端配置里的 commandargs 是否对应当前环境
  4. 改完配置后,客户端是否“完全重启”

8.2 工具调用失败但没有明显报错

排查顺序:

  1. 看客户端日志(如 Claude Desktop 日志)
  2. 看 Inspector 的通知流和调用结果
  3. 检查参数类型(例如本该 int 却传了字符串)
  4. 检查函数内部是否吞掉了异常

8.3 stdio 模式下最容易犯的错误

不要往 stdout 打调试日志。

stdio 传输依赖标准输出承载协议消息;你在 stdout 里随手 print,会污染协议流。
调试日志应写到 stderr,或使用日志能力发送结构化日志。

8.4 环境变量问题(非常高频)

如果你用 API Key / DB URL:

  • 不要假设客户端会继承你终端全部环境变量
  • 在客户端配置中显式传 env
  • 路径尽量用绝对路径,避免工作目录差异导致读取失败

9. 总结

到这里,你已经完成了 MCP 工具封装的第一步:

  1. FastMCP 把 Python 函数变成了可调用工具
  2. 明确了 Python 依赖:mcp[cli] 是核心
  3. 掌握了本地调试主线:mcp dev + Inspector + 日志排查

下一篇我会专门写配置与工程化:
MCP 服务配置参数逐项解释(command / args / env / 超时 / 重试 / 安全)+ 一套可复用模板。


参考资料