EP09. "MCP 服务端开发基础:为什么不用自己手写每个工具"
🔒 登录后可标记已读- 每接一个外部服务(GitHub、Slack、数据库……)就要自己写一套工具 schema + 实现,服务功能一多,这活儿做不完——MCP(Model Context Protocol)把「写工具定义」这件事从你的应用里搬到专门的 MCP server 上,让你不用重复造轮子
- MCP client 是你的 server 跟 MCP server 之间的沟通桥梁,负责列出有哪些工具(
ListToolsRequest)、执行具体工具(CallToolRequest),把复杂的通讯细节都藏起来 - 用官方 Python SDK 的
FastMCP+@mcp.tool装饰器写 MCP server,函数的 type hint 会自动变成 JSON schema,不用像 [EP04](./EP04_工具调用基础.md) 那样手写一份 schema 字典 - MCP Inspector(
mcp dev)是专门用来单独测试 MCP server 上每个工具的开发工具,不用先把整个应用接好才能验证一个工具能不能用 - 前置知识:读过 [EP04](./EP04_工具调用基础.md)(工具调用基本概念:tool schema、tool function、tool result),MCP 是在这套基础上多加一层「谁来写、谁来维护这些工具定义」的分工
重点内容
MCP 解决的问题:不是"要不要用工具",是"这些工具谁来写"
MCP 是一层通讯协议,给 Claude 提供上下文和工具,不用你自己写一堆繁琐的集成代码。
没有 MCP 时的痛点:要接入一个服务(比如 GitHub),开发者得给每个想要的功能,各自写一份 schema、各自写一份实现——服务功能越丰富,这活儿越累人。
MCP 怎么解决:把「创建和维护工具定义」这个负担,从你的应用转移到专门的 MCP server 上——这些 server 相当于外部服务的一层包装(wrapper)。
📌 常见问题澄清:
- MCP server 谁来写:通常服务提供方自己会出官方实现(比如 AWS 自己发布针对自家服务的 MCP server)
- 跟直接调用 API 有什么不同:MCP 提供现成写好的 tool schema 和 function;直接调 API 的话,这些都要你自己写
- MCP 跟 Tool Use 是什么关系:两者是互补关系,不是二选一——MCP 关心的是"这些工具定义由谁创建、谁维护",Tool Use(见 [EP04](./EP04_工具调用基础.md))关心的是"Claude 怎么调用工具"这个执行层的机制
📌 一句话总结:"MCP server 已经帮你把 tool schema 和对应函数都定义好了,不用自己再去搭建和维护复杂的集成代码。"
MCP Client:跟 MCP Server 对话的桥梁
MCP client 是你的 server 跟外部 MCP server 之间的通讯桥梁,相当于访问外部工具和服务的入口。
通讯方式灵活:MCP client 和 server 通常是在同一台机器上,用标准输入/输出(stdio)通讯;但也能换成 HTTP、WebSocket,或其他网络协议——不绑定单一传输方式。
两组核心消息类型:
| 消息 | 作用 |
|---|---|
ListToolsRequest / ListToolsResult | Client 向 server 查询有哪些可用工具 |
CallToolRequest / CallToolResult | Client 执行某个具体工具,拿到执行结果 |
完整流程示范:用户问"我有哪些 repository",走完整套流程要八步:
- 用户把问题提交给你的 server
- Server 透过 MCP client 向 MCP server 请求工具列表
- Server 把用户问题和工具列表一起发给 Claude
- Claude 判断要调用某个工具
- Server 透过 MCP client 实际执行这个工具
- 外部服务(这里是 GitHub)把数据返回
- Server 把结果传回给 Claude
- Claude 组织出最终答案,返回给用户
💡 "MCP client 把跟 server 通讯的复杂细节都抽象掉了,让你能专心写自己的应用逻辑。"
实战项目:CLI 文档管理聊天机器人
课程用一个 CLI 聊天机器人示范 MCP client 和 server 怎么互动,目标是搞清楚每个组件各自的角色,也能分辨出「这个回答是工具给的结果」还是「模型自己直接编的」。
项目要搭的东西:
- 一个负责处理用户交互的 MCP client
- 一个自己写的 MCP server,负责文档相关操作
- 两个 server 端工具:读文档内容、更新文档内容
- 文档存储用内存里的字典,不接数据库
📌 重要架构认知:"实际项目里,你通常只会实现 MCP client 或 MCP server 其中一个,很少两个都自己写。"——这个课程项目两个都写,纯粹是为了教学完整展示两端各自在做什么。
环境搭建:
- 解压
cli_project.zip .env里加上 Anthropic API key- 用 UV(推荐)或 pip 装依赖
- 跑起始版本的应用
启动方式:
UV:
uv run main.py
标准 Python:
python main.py
💡 验证基线的建议做法:先问一个有明确答案、跟文档无关的问题(比如"1+1 等于多少"),确认模型本身能正常工作;再问一个关于文档的问题,看看"没接工具之前"模型会怎么回答(大概率是编的或者说不知道)——这样后面接上工具之后,才好对比出工具到底有没有真的起作用。
用 FastMCP 定义工具
官方 Python SDK 提供 FastMCP,写 MCP server 上的工具比手写 tool schema([EP04](./EP04_工具调用基础.md) 那种写法)省事很多。
初始化 server:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("DocumentMCP", log_level="ERROR")
示范用的文档存储(纯内存字典):
docs = {
"deposition.md": "This deposition covers the testimony of Angela Smith, P.E.",
"report.pdf": "The report details the state of a 20m condenser tower.",
"financials.docx": "These financials outline the project's budget and expenditure",
"outlook.pdf": "This document presents the projected future performance of the",
"plan.md": "The plan outlines the steps for the project's implementation.",
"spec.txt": "These specifications define the technical requirements for the equipment"
}
工具一:读文档,用 @mcp.tool 装饰器,doc_id 找不到时抛 ValueError:
@mcp.tool(
name="read_doc_contents",
description="Read the contents of a document and return it as a string."
)
def read_document(
doc_id: str = Field(description="Id of the document to read")
):
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
return docs[doc_id]
工具二:编辑文档,接受 doc_id、old_str、new_str 三个参数,用 Python 内建的 replace() 做查找替换:
@mcp.tool(
name="edit_document",
description="Edit a document by replacing a string in the documents content with a new string."
)
def edit_document(
doc_id: str = Field(description="Id of the document that will be edited"),
old_str: str = Field(description="The text to replace. Must match exactly, including whitespace."),
new_str: str = Field(description="The new text to insert in place of the old text.")
):
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
docs[doc_id] = docs[doc_id].replace(old_str, new_str)
💡 用 FastMCP 的好处:
- 从 Python 的 type hint 自动生成 JSON schema,不用手写
- 有类型安全跟 IDE 提示的辅助,比起手写 schema(EP04 那种做法),样板代码少很多
📌 两个工具都在函数里检查文档存不存在,出错时抛出描述清楚的错误信息——让 Claude 看得懂错误内容,有机会自己调整下一步动作。
用 MCP Inspector 单独测试工具
MCP Inspector 是官方 SDK 内建的开发工具,专门用来在不接整个应用的情况下,单独测试 server 上的每个工具。
启动:
mcp dev mcp_server.py
会在 6277 端口起一个开发服务器,附带一个浏览器可打开的 URL,打开就是 MCP Inspector 的仪表板。
📌 "MCP Inspector 还在持续开发中,实际界面可能跟截图不完全一样。"——这是官方课程自己提的免责声明。
测试工具的操作流程:
- 点 Connect,启动 MCP server 连接
- 找到 Tools 分区
- 点 List Tools,看有哪些可用工具
- 给要测的工具填好必填参数
- 点 Run Tool 执行,看结果
示范:测文档读取功能——输入一个文档 ID(比如 "deposition.md"),跑一下,面板会显示结果和成功信息。操作还能串联起来:先编辑一份文档,紧接着再跑一次读取工具,验证改动是不是真的生效了。
💡 "Inspector 创造出一个高效的开发循环"——能孤立测试单个工具,不用先把整个应用都接好才能验证,加快开发速度,也方便针对性排查问题。
常见错误
- ❌ 每接一个新外部服务都从零手写工具 schema——先查一下这个服务有没有官方或社区维护的 MCP server,很可能已经有人做好了
- ❌ 以为一个项目要同时实现 MCP client 跟 MCP server 才算「正确做法」——实际项目通常只需要做其中一边,两个都做只是这个课程项目为了教学完整性
- ❌ 工具函数出错直接让程序崩溃,不抛有意义的错误信息——像
ValueError(f"Doc with id {doc_id} not found")这种清楚的报错,Claude 能读懂,才有机会自己纠正下一步 - ❌ 没接上完整应用前,直接在正式对话流程里一个个工具试错——应该先用 MCP Inspector(
mcp dev)单独把每个工具跑通,再整合进应用,省下大量排查时间
Sources
官方文档:
- Building with the Claude API(Claude Academy 课程)— https://academy.claude.com/courses/building-with-the-claude-api