Skip to content

实操 04 MCP 实战:协议机制与 Server 开发 ​

所属:AI 知识图谱 → 实操系列(practice/)。正文只回答了"MCP 该不该用"(03 篇约 20 行),本篇回答"它到底是什么、怎么自己写一个"。

速览卡 ​

项内容
一句话定位把 MCP 从"听说过的协议"变成"我能自己开发并接入一个 Server",解决工具集成的 N×M 问题
核心内容协议要解决什么、Host/Client/Server 架构、三原语(Tools/Resources/Prompts)、传输方式、开发一个 Server 并接入、调试与安全
前置知识实操 01(Function Calling 的原理与代码)
读完能做到自己写一个 MCP Server(如封装内部数据库查询),并把它接入支持 MCP 的编码工具
配套正文协议生态定位与"保持可替换抽象层"的建议见 03 篇

MCP(Model Context Protocol)最初由 Anthropic 提出并开源,现已被多家厂商与工具采纳 [易变] 生态采纳情况以官方站点为准。本篇讲机制,不绑定具体 SDK 版本。


一、先搞懂 MCP 解决什么问题 ​

没有 MCP 之前,把 N 个工具接进 M 个 AI 应用,要写 N×M 个适配:

改造前(N×M)                改造后(N+M)
  应用1 ─┬─ 工具A             应用1 ─┐        ┌─ 工具A
        ├─ 工具B                    ├─ MCP ─┤
  应用2 ─┼─ 工具A              应用2 ─┘  协议   ├─ 工具B
        └─ 工具B                              └─ 工具C

MCP 是一层标准协议:工具侧实现一次 Server,应用侧实现一次 Client,之后任意组合。这正是 03 篇把 MCP 归为"工具集成层"的原因——它解决的是连接成本,不是智能能力。

它不是什么(避免误解):

  • ❌ 不是 Agent 框架(不负责规划、循环、记忆)
  • ❌ 不是模型(不生成内容)
  • ✅ 是工具与数据的标准化接入协议,约等于"AI 应用的 USB-C"

二、架构与三原语 ​

2.1 三个角色 ​

┌────────────────────────────────────────────┐
│ Host(宿主应用:IDE / 桌面端 / 你的 Agent) │
│   └── MCP Client(每个 Server 一个连接)    │
│         ├────────────▶ MCP Server A(数据库)│
│         ├────────────▶ MCP Server B(文件系统)│
│         └────────────▶ MCP Server C(内部 API)│
└────────────────────────────────────────────┘
角色谁实现职责
Host应用方(IDE、桌面 App、你的服务)承载模型交互,管理多个 Client
Client应用方(通常 SDK 内置)与一个 Server 维持一条连接
Server你要写的部分暴露工具/资源/提示词,处理调用

2.2 三原语:Tools / Resources / Prompts ​

原语谁控制作用类比
Tools模型调用让模型"动手":查库、写文件、调 APIFunction Calling 的工具
Resources应用控制可读取的数据(文件内容、DB 记录)只读数据源
Prompts用户控制预置的提示词模板(如 /review)斜杠命令

最关键的是 Tools——它就是把实操 01 第五节的 Function Calling 标准化了:你写一次工具定义,任何 MCP Client 都能发现并调用。


三、自己写一个 MCP Server(Python) ​

场景:把"查询内部员工信息数据库"封装成工具,让 AI 助手能查。

bash
pip install mcp          # 官方 Python SDK [易变]
python
# server.py —— 一个最小可用的 MCP Server
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("hr-tools")

# ① 定义工具:类型标注 + docstring 就是给模型看的"使用说明书"
@mcp.tool()
def get_employee(department: str, limit: int = 10) -> list[dict]:
    """查询指定部门的员工列表。

    Args:
        department: 部门名称,如 "技术部"、"市场部"
        limit: 返回条数上限,默认 10
    """
    # 真实实现:查数据库 / 调内部 API
    rows = query_hr_db(department, limit)
    return [{"name": r.name, "role": r.role, "email": r.email} for r in rows]

@mcp.tool()
def get_oncall(week: str) -> dict:
    """查询某一周的值班安排。

    Args:
        week: ISO 周格式,如 "2026-W36"
    """
    return query_oncall(week)

if __name__ == "__main__":
    mcp.run(transport="stdio")      # 本地场景走 stdio

三个决定成败的细节:

  1. 工具描述(docstring)决定调用质量。模型只看名字和描述来决定"该不该调用我、参数怎么填"。写得含糊 → 该调不调、参数乱填。这条与实操 01 Function Calling 完全一致。
  2. 参数用类型标注 + 默认值。limit: int = 10 会被自动转成 JSON Schema,不用手写。
  3. 返回值要简洁。别把整张表的原始字段全丢回去,只返回模型需要的字段——省 Token 也降低误解。

四、传输方式:stdio 与 HTTP ​

方式场景特点
stdio本地 Server(默认)Host 以子进程启动 Server,通过标准输入/输出通信。零网络配置、天然隔离,本地工具首选
SSE / Streamable HTTP远程 ServerServer 独立部署,多客户端可共享;需处理鉴权、并发、网络异常

选型判断:

工具只在本机跑 / 需访问本地文件 → stdio
工具要多人共享 / 部署在服务器   → HTTP(必须做鉴权)

生产环境用 HTTP 时,与 09 篇权限最小化原则对齐:每个客户端独立凭证、按身份过滤可见工具、记录调用审计日志。


五、接入一个 Host(以编码工具为例) ​

主流支持 MCP 的编码工具配置形态类似(具体文件名 [易变],查所用工具的文档):

json
{
  "mcpServers": {
    "hr-tools": {
      "command": "python",
      "args": ["/abs/path/to/server.py"],
      "env": { "HR_DB_URL": "..." }
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
    }
  }
}

配置后重启 Host,工具列表会出现 get_employee / get_oncall,模型就能在对话中自行判断调用。注意:凭据走 env 传入,不要写死在代码里(对照 09 篇数据泄露风险点)。


六、调试:出问题先查这三处 ​

症状排查方向
Host 里看不到我的工具① ​Server 进程是否启动成功(看 Host 日志);② 命令路径是否绝对;③ stdio 模式下 stdout 被你的 print 污染会破坏协议
模型看到工具但不调用工具/参数描述不够明确;改 docstring,写清"什么时候用我"
调用报参数错类型标注缺失或写得含糊;检查必填/选填

stdio 模式最常见的一个坑:

python
print("调试信息")        # ❌ 污染 stdout,协议解析直接崩
# 正确:
import sys; print("调试信息", file=sys.stderr)     # ✅ 走 stderr
# 或用 SDK 提供的日志接口

官方 SDK 一般自带调试器(mcp dev server.py 之类,命令以当期文档为准 [易变]),可交互式列出工具并手动调用——先手动调通,再让模型调。


七、安全:Server 是一段有权限的代码 ​

MCP Server 在你的机器上执行,工具能做什么,模型就能让它做什么。对照 03 篇提示注入与 09 篇:

风险防护
间接提示注入(外部文档里藏"调用删除工具")破坏性操作必须人工确认;工具权限按操作类型分级
Server 权限过大最小权限:只给必要的文件目录/数据库只读账号
第三方 Server 来源不明只用官方/可信来源,审代码后再接;优先 stdio 隔离
敏感数据外泄工具返回前脱敏;记录调用审计
无调用上限加频率限制与熔断(对照 05 篇稳定性)

判断原则:把每个工具都当成"一个可能被诱导的实习生"——它有权做的事,就是最坏情况下会做的事。


八、什么时候别用 MCP ​

对照 03 篇"保持可替换抽象层"的建议:

情况建议
工具只给你自己的一个 Agent 用,且不打算换 Host直接写 Function Calling 更简单,无协议开销
工具要被多个 Host/多个应用复用MCP 价值最大
需要跨组织分发工具MCP(生态标准,别人不用改代码就能接)
工具调用要求极低延迟/高吞吐的核心链路协议有序列化开销,评估后再定

协议是手段不是目的:如果你的工具只有一个消费者,先别上 MCP。


九、动手任务 ​

  • [ ] 任务 1:跑通第三节的 server.py(先把 query_hr_db 换成返回固定假数据的函数),用 Host 配置接入,在对话里问"技术部有哪些人",观察模型是否自动调用工具
  • [ ] 任务 2(刻意制造 bug):在 Server 里加一行 print("debug") 到 stdout,看协议如何崩溃;再用 stderr 改写,验证恢复正常——亲手踩一次这个坑印象最深
  • [ ] 任务 3(描述质量实验):把 get_employee 的 docstring 改写成含糊版本(如"查询员工"),对比模型调用的准确率差异,体会"描述即接口契约"
  • [ ] 任务 4(进阶):给工具加一个破坏性操作(如 delete_employee),实现"调用前必须返回确认请求",思考如何防间接提示注入
  • [ ] 自检:能不看资料说出——三原语各自由谁控制?stdio 与 HTTP 怎么选?为什么 Server 的 stdout 不能被污染?什么情况下不该用 MCP?

延伸方向 ​

  • Function Calling 基础(MCP Tools 就是它的标准化)→ 实操 01
  • 有了工具,如何组织成能自主执行的 Agent → 实操 06 Agent 实战
  • 协议生态定位、A2A/ACP/AG-UI 分工与采用建议 → 03 篇
  • 工具调用的权限设计与注入防护 → 09 篇