AI TOOLS

EP09. "MCP 服务端开发基础:为什么不用自己手写每个工具"

首页 › AI 工具 › Claude · Developer and Platform · Claude API · EP09
约 17 分钟· #EP09#Claude#Claude API
🔒 登录后可标记已读
  • 每接一个外部服务(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 / ListToolsResultClient 向 server 查询有哪些可用工具
CallToolRequest / CallToolResultClient 执行某个具体工具,拿到执行结果

完整流程示范:用户问"我有哪些 repository",走完整套流程要八步:

  1. 用户把问题提交给你的 server
  2. Server 透过 MCP client 向 MCP server 请求工具列表
  3. Server 把用户问题和工具列表一起发给 Claude
  4. Claude 判断要调用某个工具
  5. Server 透过 MCP client 实际执行这个工具
  6. 外部服务(这里是 GitHub)把数据返回
  7. Server 把结果传回给 Claude
  8. Claude 组织出最终答案,返回给用户

💡 "MCP client 把跟 server 通讯的复杂细节都抽象掉了,让你能专心写自己的应用逻辑。"


实战项目:CLI 文档管理聊天机器人

课程用一个 CLI 聊天机器人示范 MCP client 和 server 怎么互动,目标是搞清楚每个组件各自的角色,也能分辨出「这个回答是工具给的结果」还是「模型自己直接编的」。

项目要搭的东西:

  • 一个负责处理用户交互的 MCP client
  • 一个自己写的 MCP server,负责文档相关操作
  • 两个 server 端工具:读文档内容、更新文档内容
  • 文档存储用内存里的字典,不接数据库

📌 重要架构认知:"实际项目里,你通常只会实现 MCP client 或 MCP server 其中一个,很少两个都自己写。"——这个课程项目两个都写,纯粹是为了教学完整展示两端各自在做什么。

环境搭建:

  1. 解压 cli_project.zip
  2. .env 里加上 Anthropic API key
  3. 用 UV(推荐)或 pip 装依赖
  4. 跑起始版本的应用

启动方式:

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 还在持续开发中,实际界面可能跟截图不完全一样。"——这是官方课程自己提的免责声明。

测试工具的操作流程:

  1. 点 Connect,启动 MCP server 连接
  2. 找到 Tools 分区
  3. 点 List Tools,看有哪些可用工具
  4. 给要测的工具填好必填参数
  5. 点 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

官方文档:

  1. Building with the Claude API(Claude Academy 课程)— https://academy.claude.com/courses/building-with-the-claude-api