返回文章列表

一文读懂 MCP(Model Context Protocol)

May 28, 2026

面向工程师与技术决策者的入门指南。从概念到实战,配套 Python SDK 示例。


一、为什么会有 MCP?

在 MCP 出现之前,把一个 AI 应用接入外部系统大致是这样一种状态:

  • 想让模型查数据库?写一套 function calling 的 schema、写 handler、写权限校验。
  • 想让模型读文件?再写一遍。
  • 同一个"查 GitHub Issue"的能力,Claude 桌面端、Cursor、VS Code、自研 Agent,各写一遍。
  • 工具多了之后,prompt 维护成本急剧上升,跨应用复用几乎为零。

这就是典型的 M × N 集成问题:M 个 AI 应用 × N 个数据源/工具 = M × N 套对接代码。

MCP(Model Context Protocol,模型上下文协议) 是 Anthropic 牵头、目前已成为事实标准的开放协议,目标是把这个 M × N 收敛为 M + N

  • 数据源/工具方只需要实现一次 MCP 服务端
  • AI 应用方只需要实现一次 MCP 客户端
  • 任意客户端都能即插即用地连接任意服务端。

官方一句话比喻:MCP 之于 AI 应用,相当于 USB-C 之于电子设备——一个统一的接口,连接万物。


二、一图总览

                          ┌─ Tools       (模型调用,有副作用)
              Server ─────┼─ Resources   (应用读取,只读上下文)
             /            └─ Prompts     (用户触发,工作流模板)
            /
        Client
       /
  Host
       \                  ┌─ Sampling    (Server 反向请求 LLM)
        Client ───────────┼─ Elicitation (Server 反向问用户)
             \            ├─ Roots       (Client 告知工作目录)
              \           └─ Logging     (Server 输出日志)
               Server

         数据层:JSON-RPC 2.0
         传输层:stdio(本地) / Streamable HTTP(远程,支持 OAuth)
  • 传输层:Client 与 Server 之间用 JSON-RPC 2.0 通信,承载在 stdio 或 Streamable HTTP 之上。
  • Server 原语:Server 主动暴露给 Host 使用的能力,分别面向模型(Tools)、应用(Resources)、用户(Prompts)三类不同的"决策者"。
  • Client 原语:Client 暴露给 Server 反向调用的能力,让 Server 在不直接依赖 LLM/UI 的前提下也能完成 agentic 工作流。

三、三个最容易混淆的角色:Host / Client / Server

这是入门 MCP 时最容易绕晕的地方。它们三者的关系是这样的:

MCP Host (AI Application)

Dedicated

connection

Dedicated

connection

Dedicated

connection

Dedicated

connection

MCP Client 1

MCP Client 2

MCP Client 3

MCP Client 4

MCP Server A - Local

(e.g. Filesystem)

MCP Server B - Local

(e.g. Database)

MCP Server C - Remote

(e.g. Sentry)

注意右下角 Server C 同时连接了两个 Client——远程 Server 通常服务多个 Client,而本地 stdio Server 一般是 1:1。

角色 角色定位 举例
Host(宿主) 用户直接交互的 AI 应用,负责协调多个 Client Claude Desktop、Cursor、VS Code、ChatGPT、你自研的 Agent
Client(客户端) Host 内部的一个组件,每个 Client 与一个 Server 维持 1:1 的专用连接 Host 启动时为每个配置的 Server 各创建一个 Client
Server(服务端) 提供工具/数据/模板的程序,本地或远程均可 filesystem、postgres、github、sentry、Slack 等

一个常见误解:以为 "Server" 一定是远程服务。错。Server 指的是协议角色,不是部署形态。 通过 stdio 启动的本地子进程同样是 Server。


四、协议的两层:数据层 + 传输层

MCP 的协议设计干净地分成了两层,便于在不同场景复用。

1. 数据层(Data Layer)

基于 JSON-RPC 2.0,定义消息的结构与语义。包含:

  • 生命周期管理:连接建立、能力协商、连接关闭
  • 服务端能力:tools、resources、prompts
  • 客户端能力:sampling、elicitation、logging、roots
  • 通知机制:列表变更、进度更新等

2. 传输层(Transport Layer)

负责把数据层的 JSON 消息从一端送到另一端。MCP 目前定义了两种官方传输:

传输方式 适用场景 特点
stdio 本地 Server Host 把 Server 作为子进程启动,通过标准输入/输出通信。零网络开销,最适合本地工具(文件、Git、本地 DB)。
Streamable HTTP 远程 Server 客户端用 HTTP POST 发请求,服务端可选用 Server-Sent Events (SSE) 推送流式响应。支持标准 HTTP 鉴权(Bearer Token、OAuth),适合 SaaS 化的 MCP Server。

旧版协议中还有一个独立的 SSE 传输,新规范已合并到 Streamable HTTP,新写代码请直接选 Streamable HTTP。


五、服务端三大原语:Tools / Resources / Prompts

这是日常开发中接触最频繁的部分。三者非常容易混淆,但它们的设计意图完全不同——关键差异在于"谁来决定调用"

原语 控制权 类比 典型用途
Tools(工具) 模型控制 类似 REST 的 POST 模型自主决定调用,会产生副作用:写数据库、发消息、调 API
Resources(资源) 应用控制 类似 REST 的 GET 被动数据源,只读。由宿主应用选择什么时机把哪些上下文塞给模型
Prompts(提示) 用户控制 类似 Slash Command 预制的工作流模板,用户主动选择调用(如 /plan-vacation

我喜欢这样记忆:

Tools 是动词,Resources 是名词,Prompts 是工作流。

Tools:模型主动调用

# 模型看到用户说"查下旧金山天气",自己决定调用这个工具
@mcp.tool()
def get_weather(city: str) -> str:
    """获取某个城市的当前天气"""
    return fetch_weather_api(city)

相关方法:tools/list(发现)、tools/call(执行)。

Resources:应用按需读取

# 应用决定是否要把这段 README 塞给模型作为上下文
@mcp.resource("file:///{path}")
def read_file(path: str) -> str:
    return open(path).read()

每个资源有一个 URI(如 file:///README.mdpostgres://mydb/schema),支持模板化(weather://forecast/{city})。相关方法:resources/listresources/templates/listresources/readresources/subscribe

Tools vs Resources:完整链路与选型

光看"控制权"不够直观,对比一下两者的完整数据流:

Tools 的链路(模型回合内触发):

1. Host 启动 → 调 tools/list 拿 schema
2. Host 把 schema 注入 LLM 调用的 tools 字段
3. 用户发消息 → LLM 推理 → 输出 tool_use 块
4. Host 截获 → 路由到对应 Server → 发 tools/call
5. Server 执行(跑 SQL、调 API)→ 返回 content
6. Host 把 content 作为 tool_result 拼回对话
7. LLM 继续生成(可能再触发下一轮,循环 3-6)

Resources 的链路(对话回合之外注入):

1. Host 启动 → 调 resources/list 和 resources/templates/list
2. Host 把资源在 UI 上呈现(文件树 / @ 补全 / 自动建议)
3. 资源进入对话上下文的三种触发方式:
   a. 用户手动 @ 引用
   b. 应用启发式自动塞入
   c. 桥接为 Tool 让模型自主调用(下面详述)
4. Host 调 resources/read(uri) → Server 返回 contents
5. Host 把 contents 作为 user 消息的一部分发给 LLM
6. LLM 收到时是"已经在上下文里"的事实

一句话区分

Tools 在 LLM 回合内被模型主动拉取;Resources 在 LLM 回合外被应用主动推入。

"数据但需要计算"选哪个? 判断顺序:

  1. 有副作用? 有 → 必须 Tool(哪怕只是埋点)。
  2. 要让模型自主决定调用? → Tool。
  3. 要让用户/应用预先选好作为上下文塞入? → Resource Template。
  4. 客户端生态考量:现状是 Tools 在所有 Host 上都支持,Resources 的 UI 支持差异较大。很多 Server 会把同一能力同时暴露为 Tool 和 Resource——Tool 兜底兼容,Resource 给支持的 Host 用更优雅的呈现。

Resources 也能由 LLM 驱动吗? 协议层并未禁止——"应用控制"是设计意图而非技术约束。常见的做法是 Resource-as-Tool 桥接:Host 在 LLM 的工具列表里额外注入两个合成工具:

{ "name": "list_resources", "description": "列出所有可用 MCP 资源", ... }
{ "name": "read_resource",  "description": "按 URI 读取资源",        ... }

LLM 决定调用 read_resource(uri="postgres://schema/users") → Host 转发为 resources/read形式是 Tool,本质是 LLM 在驱动 Resource。Claude Code 默认就这么做。

"应用启发式塞入"具体是什么? 几个真实场景:

  • IDE 类 Host 的上下文注入:每次发消息时自动塞入当前光标所在文件、最近编辑过的几个文件、.cursorrules / CLAUDE.md 等约定配置。
  • 项目初始化自动加载:打开项目时自动读 README.mdpackage.json.env.example 作为 system context。
  • 语义检索自动召回:对 user message 做 embedding,对所有 resources 做向量检索,Top-K 命中自动拼入。
  • 订阅式实时同步:用户改了文件 → Server 推 notifications/resources/updated → Host 把新内容刷进下一轮上下文。

这些都不需要用户和模型显式发起,由 Host 用一套规则决定。

Prompts:用户显式触发

@mcp.prompt()
def plan_vacation(destination: str, days: int) -> str:
    """生成一次度假规划"""
    return f"请帮我规划一次为期 {days} 天的 {destination} 之旅..."

在 Claude Desktop、Claude Code、Cursor 这类应用中,Prompt 通常表现为 / 命令或命令面板里的快捷入口。

关于 Prompts 有三个常被混淆的点需要澄清:

1. "只能由用户触发"是 UX 约定,不是协议铁律。 协议层没限制谁调 prompts/get,任何持有 session 的一方都能触发。"user-controlled" 是规范给 Host 的 UX 建议,意在让用户感知到自己在主动发起,避免模型偷偷调用模板。自研 Agent 完全可以在某个 workflow 节点上自动 prompts/get,技术上没人会拦。

2. 返回值不只是字符串。 prompts/get 真正返回的是一个 messages 数组:

{
  "messages": [
    { "role": "user",      "content": { "type": "text",  "text": "..." } },
    { "role": "assistant", "content": { "type": "text",  "text": "..." } },
    { "role": "user",      "content": { "type": "image", "data": "base64...", "mimeType": "image/png" } },
    { "role": "user",      "content": { "type": "resource", "resource": { "uri": "...", "text": "..." } } }
  ]
}

可以构造多轮 few-shot 示例包含图片/音频的多模态 prompt嵌入资源引用。FastMCP 里 @mcp.prompt() 返回字符串只是便利封装——它会被自动包装成单条 user 消息。要更复杂的结构,显式返回 list[Message]

from mcp.server.fastmcp.prompts import base

@mcp.prompt()
def debug_session(error: str) -> list[base.Message]:
    return [
        base.UserMessage("我遇到了这个错误:"),
        base.UserMessage(error),
        base.AssistantMessage("我来帮你分析。先确认几个问题..."),
    ]

3. Prompts ≠ 简化版 Skills。 这两者经常被混为一谈,但解决的是完全不同维度的问题:

维度 MCP Prompts Claude Skills
核心问题 让用户把已知任务用模板化方式发起 让模型在合适的时候自主获得一项能力
触发者 用户(UI 主动选) 模型(基于 SKILL.md frontmatter 自主判断)
形态 协议消息,通过 JSON-RPC 取 文件系统上的 markdown bundle(含可选脚本)
内容 返回 messages 数组喂给 LLM markdown 指令 + 可执行代码 + 资源文件
执行 纯模板替换,不执行代码 Agent 可执行其中的脚本
控制平面 服务端原语 模型侧能力

更准确的类比:

Prompts 像 IDE 的 Code Snippet 或 Notion 的 Template:用户选 → 自动填模板。 Skills 像 Unix 的 man page + 可执行脚本:模型自己读说明书 → 决定要不要用 → 可能还执行其中代码。

一句话:Prompts 是"用户能用的模板",Skills 是"模型能学的技能"。


六、客户端原语:让 Server 反过来"调用"Client

服务端不只是被动响应,它也可以主动向客户端请求某些能力。客户端原语有四个:

客户端原语 用途 经典场景
Sampling Server 请求 Host 的 LLM 帮自己做一次推理 Server 想分析 47 个航班选项但不想内置 LLM SDK,于是让 Client 代为完成
Elicitation Server 请求用户提供信息或确认操作 "请确认这笔 $3000 的订单",配 JSON Schema 描述需要哪些字段
Roots Client 告诉 Server "你只能在这些目录下工作" IDE 把当前打开的项目目录暴露给 Server
Logging Server 向 Client 发送日志,便于调试与可观测 tool 执行中打印 debug 信息

Sampling 的精妙之处:Server 想用 LLM 但不想自己花钱、不想绑定模型供应商,于是把"调用 LLM"的工作反向托管给 Client。Client 已经有 LLM 访问能力(用户已经付费/授权了),所以让 Client 出钱出力,并且用户可在中途审核 prompt 和返回内容——这天然就是 human-in-the-loop。


七、生命周期:一次完整的握手

理解了角色和原语,我们用 JSON-RPC 看一次最小完整流程,便于将来抓包调试时不抓瞎。

Step 1. 初始化握手

// Client → Server
{
  "jsonrpc": "2.0", "id": 1, "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "elicitation": {} },
    "clientInfo": { "name": "example-client", "version": "1.0.0" }
  }
}
// Server → Client
{
  "jsonrpc": "2.0", "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "tools": { "listChanged": true },
      "resources": {}
    },
    "serverInfo": { "name": "example-server", "version": "1.0.0" }
  }
}
// Client → Server(通知,无 id 无返回值)
{ "jsonrpc": "2.0", "method": "notifications/initialized" }

这里有几个关键点:

  1. 协议版本协商:双方必须使用兼容的版本,否则连接终止。
  2. 能力声明:Server 在 capabilities显式列出自己支持的原语——本例中只声明了 toolsresources,意味着这个 Server 不支持 prompts,Client 也就不会调用 prompts/* 相关方法。声明了 tools.listChanged: true 的服务端,将来工具列表变更时会主动发 notifications/tools/list_changed
  3. {} 不是空对象:它表示"我支持这个能力,但没有可配置选项"——是声明的最小形式。

Step 2. 发现工具

// Client → Server
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }

Step 3. 调用工具

// Client → Server
{
  "jsonrpc": "2.0", "id": 3, "method": "tools/call",
  "params": {
    "name": "weather_current",
    "arguments": { "location": "San Francisco", "units": "imperial" }
  }
}

Step 4. 服务端主动通知

// Server → Client(工具列表变更,无需响应)
{ "jsonrpc": "2.0", "method": "notifications/tools/list_changed" }

八、Python SDK 实战

接下来全部用 Python 官方 SDK(mcp)演示。

1. 安装

推荐使用 uv

uv init mcp-demo
cd mcp-demo
uv add "mcp[cli]"

也可以用 pip:

pip install "mcp[cli]"

2. 最简服务端:Tools + Resources + Prompts 全家桶

server.py

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Demo")

# ---- Tool:模型主动调用,会产生副作用 ----
@mcp.tool()
def add(a: int, b: int) -> int:
    """两数相加"""
    return a + b

# ---- Resource:应用按需读取 ----
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
    """根据名字生成个性化问候"""
    return f"Hello, {name}!"

# ---- Prompt:用户显式触发 ----
@mcp.prompt()
def greet_user(name: str, style: str = "friendly") -> str:
    """生成不同风格的问候 prompt 模板"""
    styles = {
        "friendly": "请写一段温暖、友好的问候",
        "formal":   "请写一段正式的问候",
        "casual":   "请写一段轻松随意的问候",
    }
    return f"{styles[style]},对象是名叫 {name} 的人。"

if __name__ == "__main__":
    mcp.run()  # 默认 stdio 传输

启动方式三选一:

# 调试:MCP Inspector 提供 Web UI
uv run mcp dev server.py

# 安装进 Claude Desktop
uv run mcp install server.py

# 切换为 HTTP 传输(生产推荐)
# 在代码里改成 mcp.run(transport="streamable-http")

3. 结构化输出:用 Pydantic 自动生成 Schema

FastMCP 会从类型注解自动生成 JSON Schema,模型侧会更容易理解返回数据:

from pydantic import BaseModel, Field
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Weather")

class WeatherData(BaseModel):
    temperature: float = Field(description="摄氏度")
    humidity: float = Field(description="百分比")
    condition: str = Field(description="天气状况,如 sunny / cloudy")

@mcp.tool()
def get_weather(city: str) -> WeatherData:
    """获取指定城市的天气信息(结构化返回)"""
    return WeatherData(temperature=22.5, humidity=45.0, condition="sunny")

4. Context 注入:日志、进度、Sampling、Elicitation

向 tool 加一个 Context 参数,FastMCP 会自动注入。借助它可以做日志、进度、反向调用 LLM、向用户索取信息:

from mcp.server.fastmcp import Context, FastMCP
from mcp.types import SamplingMessage, TextContent

mcp = FastMCP("Advanced")

# 进度上报 + 日志
@mcp.tool()
async def long_task(name: str, ctx: Context, steps: int = 5) -> str:
    await ctx.info(f"开始执行任务:{name}")
    for i in range(steps):
        await ctx.report_progress(
            progress=(i + 1) / steps,
            total=1.0,
            message=f"步骤 {i+1}/{steps}",
        )
    return f"任务 {name} 完成"

# Sampling:反向请求 Client 的 LLM
@mcp.tool()
async def summarize(topic: str, ctx: Context) -> str:
    """让 Host 的 LLM 帮我总结一下"""
    result = await ctx.session.create_message(
        messages=[
            SamplingMessage(
                role="user",
                content=TextContent(type="text", text=f"请用一句话总结 {topic}"),
            )
        ],
        max_tokens=100,
    )
    return result.content.text if result.content.type == "text" else str(result.content)

5. 生命周期管理:启动时建连接,关闭时清理

需要在启动时初始化数据库连接池等长生命周期资源时,使用 lifespan

from dataclasses import dataclass
from contextlib import asynccontextmanager
from mcp.server.fastmcp import Context, FastMCP

@dataclass
class AppContext:
    db: "Database"

@asynccontextmanager
async def app_lifespan(server: FastMCP):
    db = await Database.connect()
    try:
        yield AppContext(db=db)
    finally:
        await db.close()

mcp = FastMCP("MyApp", lifespan=app_lifespan)

@mcp.tool()
def query(sql: str, ctx: Context) -> str:
    app_ctx: AppContext = ctx.request_context.lifespan_context
    return app_ctx.db.execute(sql)

6. 客户端:stdio 连接本地 Server

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    params = StdioServerParameters(
        command="uv",
        args=["run", "server.py"],
    )
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            tools = await session.list_tools()
            print("Tools:", [t.name for t in tools.tools])

            result = await session.call_tool("add", arguments={"a": 5, "b": 3})
            print("Result:", result.content[0].text)

asyncio.run(main())

7. 客户端:Streamable HTTP 连接远程 Server

import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async def main():
    async with streamablehttp_client("http://localhost:8000/mcp") as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            print("Tools:", [t.name for t in tools.tools])

asyncio.run(main())

九、在 Claude Code 中的具体映射

理论讲完了,看一下三大原语在真实 Host 里到底长什么样。以 Claude Code 为例:

Tools → mcp__<server>__<tool> 工具

服务端的每个 tool 都被改名后注入模型的 tool 列表。例如装了 github MCP server,模型看到的工具叫 mcp__github__create_issue

特殊机制 Tool Search(默认开启):tool 定义不是一次性塞进 context window,模型先看到一个 ToolSearch 工具,按需查找。所以装 20 个 Server 也不会撑爆上下文。可以用 alwaysLoad: true 把高频 Server 强制常驻。

Resources → @ 引用 + 桥接 Tool 双路并存

Claude Code 对 Resources 的支持正好印证上一节的讨论——应用驱动和模型驱动同时存在

路径 A:用户主动 @ 引用

Can you analyze @github:issue://123 and suggest a fix?
Compare @postgres:schema://users with @docs:file://database/user-model

输入 @ 触发自动补全,从所有连接的 Server 列出可用资源,支持模糊搜索。被引用的资源自动作为 attachment 拉进上下文。

路径 B:模型主动调用(桥接为 Tool)

官方文档原话:

"Claude Code automatically provides tools to list and read MCP resources when servers support them"

Claude Code 自动把 resources/listresources/read 包装成内置工具暴露给模型——模型可以自主决定要不要去翻资源。

Prompts → /mcp__<server>__<prompt> Slash 命令

Server 定义的每个 prompt 都映射成一个 slash 命令:

/mcp__github__list_prs
/mcp__github__pr_review 456
/mcp__jira__create_issue "Bug in login flow" high
  • 参数按 prompt 的 schema 解析,空格分隔;
  • 服务端和 prompt 名里的空格被规范化为下划线;
  • Prompt 返回的 messages 数组直接拼进对话。

客户端原语支持情况

  • Elicitation:自动弹交互对话框(form 模式或 URL 模式),可用 hook 自动应答。
  • Roots:Server 启动时设置 CLAUDE_PROJECT_DIR 环境变量,Server 也可以调 roots/list 查询当前工作目录。

对 Server 作者的启示

不同客户端对 MCP 原语的支持程度差异很大。如果你的 Server 想兼容性最大化:

  • 必须支持 Tools——这是目前所有 MCP 客户端都支持的最大公约数。
  • 重要能力建议双重暴露:既写成 Tool 兜底,也写成 Resource Template 给支持的 Host 用更优雅的方式呈现。比如查询类的接口,既提供 query_user(id) Tool,又暴露 user://{id} Resource Template。
  • Prompts 锦上添花:对于支持 Prompts 的 Host,它能显著提升用户在常见工作流上的体验。

十、MCP vs Function Calling vs 自研插件协议

这是公司内部分享时最常被问的问题之一。

维度 Function Calling 自研插件协议 MCP
标准化程度 各家 LLM 厂商各有定义 公司内部一套 跨厂商开放标准
生态复用 每个模型一套 schema 几乎为零 一次实现,跨 Host 通用
上下文资源 通常只有"函数调用" 自定义 Tools / Resources / Prompts 三类原语
反向能力 不支持 一般不支持 Sampling / Elicitation / Roots
传输与鉴权 内嵌 API 自定 stdio / Streamable HTTP + OAuth
适合场景 单一应用内部小范围 私有强定制 工具/数据要被多端复用、要标准化

一个粗略的判断准则:

  • 能力只服务于一个 AI 应用、几乎不会被复用 → Function Calling 足够。
  • 能力会被多个 AI 应用消费(团队多个产品 + 第三方工具如 Claude Desktop / Cursor)→ 优先选 MCP
  • 需要给用户/模型暴露资源和工作流模板,而不只是函数MCP 的 Resources / Prompts 是 Function Calling 没有的

十一、什么时候用 MCP

凡是建议,必有反例。下面几种情况,强行套 MCP 反而增加复杂度:

  1. 只是 Prompt 工程:没有外部系统交互,纯粹是 prompt 调优,不需要 MCP。
  2. 极致低延迟的同进程调用:MCP 即使是 stdio,也有 JSON-RPC 序列化开销,对延迟敏感的场景直接函数调用更划算。
  3. 能力跟应用强耦合、永远不会复用:自己造一套 function calling 更轻量。
  4. 跨语言、跨进程的 RPC 通用诉求:那不是 MCP 的目标,请用 gRPC / OpenAPI。

十二、生产实践中的几点建议

实际使用 MCP 时,下面几条是踩过坑后总结的:

1. 工具名要带命名空间

不要叫 search,叫 github_search_issuesjira_search_tickets。多个 Server 同时连接时,模型才能准确路由。

2. description 写给模型看,不是写给人看

工具描述会直接进入模型上下文,措辞会显著影响调用准确率。要写清楚:做什么、何时该用、何时不该用、输入约束

3. 危险操作必须 Elicitation

写库、发消息、扣款……这类有副作用的操作,主动调用 elicitation/create 让用户确认,而不是相信"模型不会乱来"。

4. Streamable HTTP 配合 OAuth

远程 Server 不要再用 Bearer Token 写死在配置里。OAuth 是规范推荐的鉴权方式,配合 roots 限定 Server 的工作范围。

5. 善用 listChanged 通知

工具集随业务动态变化(如新接入一个数据源)时,发 notifications/tools/list_changed,Client 会立即刷新,无需重启。

6. 开发期用 MCP Inspector

uv run mcp dev server.py

会启一个 Web UI,可视化地查看 tools/resources/prompts、手动调用、看 JSON-RPC 报文,比靠 print 调试快十倍。


十三、参考文档


结语

MCP 在 2024 年底问世,到今天已经被 Claude、ChatGPT、VS Code、Cursor 等主流 AI 应用普遍支持,成为 AI 工具生态的事实标准。

它的设计哲学很简单:

协议归协议,模型归模型。 MCP 不规定你怎么用 LLM、怎么管理上下文,它只规定"AI 应用如何与外部系统通信"这一件事,并把它做到了足够通用与足够干净。

对工程师而言,理解 MCP 的回报很高:写一次 Server,便能被生态里的所有 Host 复用;写一次 Client,便能即插即用任意社区 Server。在 AI 应用碎片化加剧的今天,MCP 是少数能跨厂商、跨产品沉淀工程价值的协议层


© 2026