Files
zhuangang/topics/ai/mcp/01-protocol-server.md

5.4 KiB
Raw Permalink Blame History

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
mcp
MCP
Model Context Protocol
tools
resources
prompts
stdio
Streamable HTTP
intermediate
ai.function-calling.tool-loop
3
ai.mcp.scenario.internal-tools-gateway
ai.ai-coding.index
ai.mcp.interview
10
11
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 怎么选?