5.4 KiB
id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week, order
| id | title | type | domain | stack | tags | level | prerequisites | est_hours | related | week | order | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ai.mcp.protocol-server | MCP 协议和服务开发:tools / resources / prompts、传输方式、接入客户端 | lesson | ai |
|
|
intermediate |
|
3 |
|
|
1 |
MCP 协议和服务开发:tools / resources / prompts、传输方式、接入客户端
基于 MCP Python SDK 2.x 编写。注意 1.x → 2.x 有不兼容变化(例如
FastMCP改名为MCPServer,导入路径变为mcp.server.mcpserver),网上很多教程还是 1.x 写法。以官方文档为准:https://modelcontextprotocol.io/ 、https://py.sdk.modelcontextprotocol.io/
解释说明
MCP(Model Context Protocol,模型上下文协议) 是把“工具和数据”接入 AI 应用的开放协议。类比:USB 接口——一个 MCP 服务写好之后,Cursor、Claude Desktop、Claude Code、你自己的 Agent 都能接入,不用为每个应用写一遍集成。
| 角色 | 说明 |
|---|---|
| Host(宿主) | 用户使用的 AI 应用,例如 Cursor、你的客服系统 |
| Client(客户端) | 宿主内部和某个服务保持连接的组件,一个服务一个客户端 |
| Server(服务) | 提供能力的程序,例如“内部知识库服务”“数据库查询服务” |
服务端提供三类能力:
| 能力 | 谁决定使用 | 例子 |
|---|---|---|
| tools(工具) | 模型决定调用 | 搜索文档、创建工单 |
| resources(资源) | 应用 / 用户选择后作为上下文 | 某篇文档、某张表的结构 |
| prompts(提示词模板) | 用户选择 | “基于知识库回答”模板 |
客户端也可以向服务端提供能力:sampling(服务端请求客户端的模型生成内容)、roots(告诉服务端可访问的目录)、elicitation(服务端向用户索要补充信息)。
传输方式:stdio(宿主启动服务子进程,通过标准输入输出通信,适合本地工具)和 Streamable HTTP(远程服务,支持多客户端,需要鉴权)。早期的 HTTP + SSE 传输已被 Streamable HTTP 取代。底层消息格式是 JSON-RPC 2.0。
MCP 和 Function Calling 的关系
Function Calling 是模型和应用之间的接口(模型说要调什么);MCP 是应用和工具提供方之间的接口(应用从哪里发现工具、怎么调用)。Agent 从 MCP 服务拿到工具列表 → 转成 Function Calling 的工具定义交给模型 → 模型决定调用 → Agent 通过 MCP 调用服务。
案例
公司内部有知识库和 IT 工单系统。写一个 MCP 服务:search_docs(只读)、create_ticket(有副作用,带幂等 ID)、文档资源、回答模板。开发人员在 Cursor 里接入后,可以直接问“VPN 连不上怎么办”;同一个服务也接入了团队的 LangGraph 客服 Agent。
代码示例
服务端(完整版本 examples/kb-server/kb_server.py):
from pydantic import BaseModel, Field
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("internal-kb", instructions="公司内部知识库和 IT 工单工具。")
@mcp.tool()
def search_docs(query: str, top_k: int = 3) -> list[SearchHit]:
"""在内部知识库中按关键词搜索文档,返回文档 ID 和摘要。""" # 文档字符串就是工具描述
...
class TicketIn(BaseModel):
title: str = Field(min_length=4, max_length=80)
category: str = Field(pattern="^(it|hr|finance)$")
request_id: str = Field(description="客户端生成的唯一 ID,用于幂等")
@mcp.tool()
def create_ticket(ticket: TicketIn) -> dict:
"""创建一张工单。会产生副作用:同一个 request_id 只创建一次。"""
...
@mcp.resource("kb://docs/{doc_id}")
def read_doc(doc_id: str) -> str:
return DOCS.get(doc_id, "文档不存在")
if __name__ == "__main__":
mcp.run() # stdio;远程部署用 mcp.run("streamable-http")
客户端测试(进程内连接,不需要启动服务,完整版本 test_kb_server.py):
from mcp import Client
async with Client(mcp) as c:
tools = await c.list_tools()
r = await c.call_tool("search_docs", {"query": "VPN 失败"})
print(r.structured_content) # {'result': [{'doc_id': 'vpn', 'snippet': ...}]}
接入 Cursor:在项目的 .cursor/mcp.json 中配置(格式以 Cursor 文档为准):
{
"mcpServers": {
"internal-kb": { "command": "python", "args": ["topics/ai/mcp/examples/kb-server/kb_server.py"] }
}
}
易错点
- 按 1.x 教程写
from mcp.server.fastmcp import FastMCP,在 2.x 中直接报错;先确认 SDK 版本。 - stdio 服务里用
print输出调试信息:会污染标准输出(协议通道),日志要写到标准错误。 - 工具描述写得太简单,模型不知道什么时候该用。
- 远程(HTTP)服务没有鉴权就暴露在网络上。
- 有副作用的工具没有幂等,客户端重试时重复执行。
- 一个服务暴露了几十个工具,占用大量上下文;按领域拆分服务,或在客户端按需启用。
面试常问
- MCP 是什么?解决什么问题?和 Function Calling 的关系?
- tools、resources、prompts 的区别?
- stdio 和 Streamable HTTP 怎么选?