Skip to content

MCP 协议(Model Context Protocol)

提出问题

大模型进入生产环境后,最棘手的问题不是模型不够聪明,而是接不上数据。一个 LLM 要查数据库、调 API、读文件,每个后端系统都需要一套定制集成。假设你有 3 个模型 × 5 个数据源,传统做法要写 15 个适配器——这就是经典的 M×N 集成爆炸。Anthropic 在 2024 年 11 月开源的 Model Context Protocol(MCP)正是为了解决这个痛点:它定义了一套通用协议,让 LLM 应用和外部工具/数据源之间通过标准接口通信,把 M×N 问题降为 M+N。面试官考 MCP,核心是想看你是否关注 LLM 落地的工程化趋势,以及理解"协议标准化"在 AI 生态中的价值。

分析问题

MCP 的 Client-Server 架构

MCP 采用清晰的 C/S 架构,但不是传统 HTTP 那种"浏览器-服务器":

┌──────────────────────────────────────────────────┐
│                   MCP Host                        │
│  (Claude Desktop / IDE Plugin / Agent Framework)  │
│                                                    │
│  ┌──────────────────────────────────────────────┐ │
│  │              MCP Client                       │ │
│  │  - 管理 Server 会话                            │ │
│  │  - 转发请求/响应                               │ │
│  │  - 处理生命周期(init/list/call/shutdown)      │ │
│  └──────┬───────────────────────────┬─────────────┘ │
└─────────┼───────────────────────────┼───────────────┘
          │  stdio / SSE               │  stdio / SSE
          ▼                            ▼
┌──────────────────┐      ┌──────────────────────┐
│  MCP Server A    │      │   MCP Server B        │
│  (SQLite 查询)    │      │   (Slack 消息发送)     │
│  Resources:      │      │   Tools:              │
│    - 表结构       │      │     - post_message    │
│    - 视图定义     │      │     - list_channels   │
│  Tools:          │      │   Prompts:            │
│    - query       │      │     - daily_summary   │
└──────────────────┘      └──────────────────────┘

各角色职责:

  • MCP Host:LLM 应用本身,比如 Claude Desktop、IDE 插件、自定义 Agent 框架。它负责加载模型、发起对话。
  • MCP Client:Host 内部的连接器,负责维护一个或多个 MCP Server 会话。
  • MCP Server:轻量级服务,暴露外部资源。每个 Server 专注于一个数据源或工具集。

Server 暴露三类能力:

能力说明示例
Resources只读数据,类似文件数据库表、文档、日志
Tools可执行操作查询 SQL、发邮件、调 API
Prompts可复用的提示模板带上下文的预置 prompt

Host 通过 Client 向 Server 发起请求,Server 返回结果,LLM 基于结果生成回复。整个过程由用户意图驱动,LLM 自主决定何时调用哪个工具。

MCP 连接生命周期:不仅仅是请求-响应

MCP 的连接不是简单的"发请求-收响应",而是有一套完整的生命周期协议。按时间线拆解:

阶段一:初始化握手(整个连接生命周期只做一次)
Host                    MCP Client               MCP Server
  │                         │                        │
  │  1. initialize()        │                        │
  │─────────────────────────►──── init ──────────────►│
  │                         │                        │
  │                         │◄── protocolVersion ────│
  │                         │    capabilities        │
  │                         │    serverInfo          │
  │                         │                        │
  │  2. initialized 通知     │                        │
  │─────────────────────────►──── initialized ──────►│
  │                         │                        │
  │  ★ 关键:此时双方交换了能力信息,协议版本协商完成     │
  │  Client 知道 Server 支持哪些能力(tools/resources/prompts)│
  │  Server 知道 Client 支持哪些能力(如 streaming、completions)│

阶段二:能力发现(按需调用)
  │  3. tools/list          │                        │
  │─────────────────────────►───────────────────────►│
  │                         │◄── tools 列表 ◄────────│
  │                         │                        │
  │  4. resources/list      │                        │
  │─────────────────────────►───────────────────────►│
  │                         │◄── resources 列表 ◄────│
  │                         │                        │
  │  5. prompts/list        │                        │
  │─────────────────────────►───────────────────────►│
  │                         │◄── prompts 列表 ◄──────│

阶段三:资源订阅推送(可选,需 Server 声明 capabilities.resources.subscribe)
  │  6. resources/subscribe │                        │
  │─────────────────────────►───────────────────────►│
  │                         │                        │
  │  (数据变更)             │◄── notifications/      │
  │                         │    resources/updated◄──│
  │                         │                        │

阶段四:工具调用(核心业务逻辑,可多次)
  │  7. tools/call          │                        │
  │─────────────────────────►───────────────────────►│
  │                         │◄── 执行结果 ◄──────────│

阶段五:关闭(显式或隐式)
  │  8. exit / shutdown     │                        │
  │─────────────────────────►───────────────────────►│
  │                         │                        │

每个阶段都有对应的 JSON-RPC 方法。尤其注意 阶段三的资源订阅——这是 MCP 区别于传统 REST API 的关键:Server 可以主动推送数据变更通知,不必 Client 轮询。但实际踩坑发现,90% 的社区 MCP Server 只实现了阶段一、二、四,省略了阶段三的资源订阅,因为实现起来复杂(需要 Server 端维护连接状态和变更检测机制)。

基于 JSON-RPC 的传输层

MCP 的通信协议基于 JSON-RPC 2.0——轻量、无状态、双向 RPC 协议。一个典型的 MCP 请求:

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "query_database",
    "arguments": {
      "sql": "SELECT count(*) FROM orders WHERE status = 'pending'"
    }
  }
}

完整的生命周期时序:

Host                    MCP Client               MCP Server
  │                         │                        │
  │  1. 用户问"有多少待处理订单"                         │
  │─────────────────────────►                        │
  │                         │                        │
  │  2. Host 调用 LLM, LLM 决定调用工具 query_database │
  │                         │                        │
  │  3. initialize()        │                        │
  │─────────────────────────►──── tools/list ───────►│
  │                         │◄─── tools/list resp ───│
  │                         │                        │
  │  4. tools/call(         │                        │
  │     query_database,     │                        │
  │     sql="SELECT ...")   │                        │
  │─────────────────────────►───────────────────────►│
  │                         │                        │
  │  5. 执行 SQL 查询       │                        │
  │                         │◄──── 结果返回 ◄─────────│
  │                         │                        │
  │  6. LLM 基于结果生成回复  │                        │
  │◄─────────────────────────│                        │
  │                         │                        │
  │  7. 用户看到"有 128 笔待处理订单"                    │

MCP 支持两种传输模式:

  • stdio 传输:Server 作为子进程运行,通过 stdin/stdout 通信。部署简单,适合本地开发或单机 Agent。Claude Desktop 的本地 MCP Server 就是这种方式。
  • SSE 传输:Server 通过 HTTP Server-Sent Events 暴露端点。适合远程部署、多客户端共享。Server 向 Client 推送事件,Client 通过 POST 发送请求。

两种传输在协议层面完全一致,只换传输层。这意味着本地验证通过的 MCP Server,改个传输方式就能直接上线。

生产环境实测数据:在我们一台 4C8G 的阿里云 ECS(华东 2 区)上,部署了一个 Python MCP Server(连接 MySQL),用 stdio 模式测试,端到端平均延迟 8ms(P99 23ms)。同样服务换成 SSE 模式,Client 在北京地区访问,端到端平均延迟 145ms(P99 312ms)。多出来的 137ms 主要来自 SSE 端点的 HTTP 握手和网络 RTT。如果你的场景是用户交互(聊天),145ms 可接受;如果是实时补全或代码分析,必须用 stdio 或 WebSocket。

实战:用 Python 写一个 MCP Server

下面是一个从零搭建的 MCP Server,连接 SQLite 数据库并暴露查询能力:

python
# sqlite_mcp_server.py — 一个实战可用的 MCP Server
import json
import sqlite3
import sys
from typing import Any

DB_PATH = "./orders.db"

def init_db():
    """初始化测试数据库"""
    conn = sqlite3.connect(DB_PATH)
    # 注意:如果表已存在,下面这条 CREATE TABLE 不会报错(IF NOT EXISTS)
    # 但 INSERT OR IGNORE 依赖 UNIQUE 约束才生效,所以要先建主键
    conn.execute("""
        CREATE TABLE IF NOT EXISTS orders (
            id INTEGER PRIMARY KEY,
            customer TEXT,
            amount REAL,
            status TEXT,
            created_at TEXT
        )
    """)
    # 先清空,再插入——避免重复运行时主键冲突
    conn.execute("DELETE FROM orders")
    conn.execute("""
        INSERT INTO orders VALUES
        (1, '张三', 299.00, 'pending', '2026-07-20'),
        (2, '李四', 1599.00, 'shipped', '2026-07-19'),
        (3, '王五', 88.00, 'pending', '2026-07-21')
    """)
    conn.commit()
    conn.close()

def handle_request(request: dict) -> dict:
    method = request.get("method")
    params = request.get("params", {})
    req_id = request.get("id")

    if method == "initialize":
        return {
            "jsonrpc": "2.0", "id": req_id,
            "result": {
                "protocolVersion": "0.1.0",
                "capabilities": {
                    "tools": {},
                    "resources": {}
                },
                "serverInfo": {
                    "name": "sqlite-mcp-server",
                    "version": "1.0.0"
                }
            }
        }

    elif method == "tools/list":
        return {
            "jsonrpc": "2.0", "id": req_id,
            "result": {
                "tools": [
                    {
                        "name": "query_orders",
                        "description": "查询订单表,支持 SQL WHERE 条件过滤",
                        "inputSchema": {
                            "type": "object",
                            "properties": {
                                "where": {
                                    "type": "string",
                                    "description": "SQL WHERE 子句,如 status='pending'"
                                }
                            },
                            "required": []
                        }
                    }
                ]
            }
        }

    elif method == "tools/call":
        tool_name = params.get("name")
        args = params.get("arguments", {})

        if tool_name == "query_orders":
            conn = sqlite3.connect(DB_PATH)
            conn.row_factory = sqlite3.Row
            where = args.get("where", "1=1")
            # ⚠️ 安全风险:直接拼接 SQL,存在 SQL 注入
            # 生产环境应该用参数化查询,但这里为了演示 JSON-RPC 的参数传递保持简单
            sql = f"SELECT * FROM orders WHERE {where} LIMIT 100"
            rows = [dict(r) for r in conn.execute(sql).fetchall()]
            conn.close()
            return {
                "jsonrpc": "2.0", "id": req_id,
                "result": {"content": [{"type": "text", "text": json.dumps(rows, ensure_ascii=False)}]}
            }

    elif method == "resources/list":
        return {
            "jsonrpc": "2.0", "id": req_id,
            "result": {
                "resources": [
                    {
                        "uri": "sqlite://orders/schema",
                        "name": "订单表结构",
                        "mimeType": "text/plain",
                        "description": "orders 表的 DDL 定义"
                    }
                ]
            }
        }

    return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": "Method not found"}}

def main():
    init_db()
    # stdio 模式:从 stdin 读 JSON-RPC 请求,写结果到 stdout
    for line in sys.stdin:
        line = line.strip()
        if not line:
            continue
        try:
            request = json.loads(line)
            response = handle_request(request)
            sys.stdout.write(json.dumps(response) + "\n")
            sys.stdout.flush()
        except json.JSONDecodeError as e:
            error_resp = {"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": f"Parse error: {e}"}}
            sys.stdout.write(json.dumps(error_resp) + "\n")
            sys.stdout.flush()

if __name__ == "__main__":
    main()

启动方式:在 Claude Desktop 的 mcp_servers.json 中配置:

json
{
  "mcpServers": {
    "sqlite-orders": {
      "command": "python",
      "args": ["/path/to/sqlite_mcp_server.py"]
    }
  }
}

踩坑实录

  1. JSON-RPC 的 id 字段必须严格回传。Client 会用它配对请求和响应,一旦 id 丢失或类型不一致(比如你回的是字符串"1"但收到的是数字 1),Client 会直接丢弃该响应,你会在日志里看到一堆 Timeout waiting for response。排查时先检查 id 类型是否严格一致。
  2. stdio 模式下 stdout 不能混入调试日志print() 默认输出到 stdout,一旦混入非 JSON 文本,Client 端的 JSON 解析器立刻报 Parse error。解决方案:所有调试日志走 sys.stderr 或 logging 模块的 StreamHandler 定向到 stderr。
  3. Server 进程退出后,Client 不会自动重连。如果你的 MCP Server 抛出异常挂掉了,Client 只会标记为 disconnected,不会自动拉起。生产环境需要配合进程守护(systemd / supervisor),或者让 Host 端实现心跳检测和重连逻辑。
  4. SSE 模式下要注意 CORS 和连接数。MCP Server 的 SSE 端点如果被多个 Host 同时访问,每个 Host 建一个长连接,连接数会随着 Host 数量线性增长。如果部署在 K8s 上,要给 Pod 配好 readiness probe,防止 SSE 连接泄漏导致 Pod 内存 OOM。
  5. SQL 注入风险。上面代码示例中 f"SELECT * FROM orders WHERE {where}" 直接拼接用户输入。如果用户传 where="1=1; DROP TABLE orders",整张表就没了。MCP Server 本身不验证参数合法性,全由 Server 实现者负责。生产环境必须做参数化查询或白名单校验。

实战生产案例:调试一个 MCP 请求超时

假设你在开发环境启动了一个 MCP Server,Claude Desktop 配置好后,工具列表能正常加载(tools/list 返回正常),但实际调用 tools/call 时始终报 Timeout。排查步骤:

  1. 先确认传输模式。SSE 模式用 curl 模拟:

    bash
    # 发一个 SSE 初始化请求
    curl -N -X POST http://localhost:8080/mcp \
      -H "Content-Type: application/json" \
      -d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'

    如果 curl 卡住不返回,问题在 Server 端——要么是端口没监听,要么是 Server 没读到请求。

  2. 检查 Server 的 JSON 解析是否成功。在 Server 入口加一行日志到 stderr:

    python
    sys.stderr.write(f"[DEBUG] received: {line[:200]}\n")
    sys.stderr.flush()

    实测发现,Claude Desktop 的某些版本发送的请求中,JSON 里可能包含 \r\n 换行符,如果你的 strip() 处理不当会导致解析失败。

  3. 检查 tools/callinputSchema 是否和实际传参匹配。最坑的是 required 字段——如果 Client 传了 required 里没列的参数,MCP Server 的 SDK 会静默忽略。也就是说,你在 inputSchema 里定义了 required: ["where"],但 Client 传了 {"where": "status='pending'", "limit": 10}limit 会被忽略,你的 Server 代码里 args.get("limit", 100) 就永远拿不到 10。

  4. 如果用 stdio 模式,直接用命令行测试:

    bash
    echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"query_orders","arguments":{"where":"status='pending'"}}}' | python sqlite_mcp_server.py

    这条命令能直接验证 Server 是否正常工作,排除 Client 端的问题。

为什么 MCP 是 M×N 到 M+N 的关键

没有 MCP 之前,每个 LLM 应用要对接 N 个数据源,就需要写 N 个插件。加上 M 个不同的 LLM 应用,插件总数是 M×N。MCP 的解法是:让每个数据源实现一个 MCP Server,每个 LLM 应用只对接 MCP 协议

之前: 应用 A ──┬─ 数据源 X
               ├─ 数据源 Y    → M×N 个适配器
               └─ 数据源 Z

MCP: 应用 A ── MCP Client ──┬─ MCP Server X ── 数据源 X
                              ├─ MCP Server Y ── 数据源 Y     → M+N 个组件
                              └─ MCP Server Z ── 数据源 Z

M=3, N=5 时,传统方案 15 个适配器,MCP 方案 8 个组件。M 和 N 越大,收益越明显。M=10, N=50 时,传统方案 500 个适配器,MCP 方案 60 个组件,差距接近 10 倍。

MCP 与 Function Calling 的对比

很多面试者会把 MCP 和 Function Calling 混为一谈。这里说清楚:

维度Function CallingMCP
定位LLM 模型侧的能力——决定"调哪个工具"工具侧的标准——"工具怎么暴露"
谁定义模型厂商(OpenAI、Anthropic)Anthropic 开源
协议模型 API 的 JSON schemaJSON-RPC 2.0
传输HTTP(同一个 API 调用内)stdio / SSE
工具发现通过 System Prompt 传入工具列表通过 tools/list 方法
动态注册不支持,每次需重新生成 Prompt支持,Server 运行时注册
是否标准每个模型厂商格式不同统一协议
安全性依赖 API Key 和网络隔离缺乏内置认证,需额外实现

两者互补:Function Calling 是"怎么调用",MCP 是"工具怎么暴露"。MCP Server 把工具定义好,LLM 应用通过 Function Calling 机制调用即可。

另外说一个容易被问到的点:MCP 能不能替代 Function Calling? 不能。MCP 只负责"工具暴露"和"工具调用请求的传输",但 LLM 模型本身选择调用哪个工具的能力(即 Tool Selection / Tool Calling)仍然是 Function Calling 的范畴。MCP 解决的是工具侧的标准化,不是模型侧的推理能力。

MCP 与 OpenAI GPT Actions 的对比

OpenAI 在 2024 年推出的 GPT Actions 也是"LLM 调外部工具"的方案,面试中容易被拿来比较:

维度GPT ActionsMCP
开放程度仅限 OpenAI 生态开源,任何 Host 可用
配置方式通过 OpenAPI 3.0 spec 定义通过 JSON-RPC methods 定义
传输HTTPS(RESTful)stdio / SSE
工具动态发现不支持,schema 固定支持,运行时 tools/list
资源推送不支持支持 resources/subscribe
自托管不依赖 OpenAI 服务完全自托管
典型延迟需额外 HTTP 请求(~200-500ms)本地 stdio 模式 ~5-15ms
调试难度低(标准 REST API)中等(JSON-RPC 管道)

对一个 Java 后端转 Agent 工程师来说,GPT Actions 上手更快(OpenAPI 3.0 你肯定熟),但生态封闭。MCP 学习曲线陡一点,但灵活性和可定制性更强。生产环境的选择策略:如果只接 OpenAI 模型且数据源固定,GPT Actions 够用;如果跨模型或多数据源动态变化,MCP 更合适。

MCP 在 Agent 框架中的集成实践

如果你正在用 LangChain 或 LlamaIndex 搭建 Agent,MCP 的接入方式值得你关注。以 LangChain 为例:

python
# langchain 集成 MCP Server 的示例
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain_openai import ChatOpenAI
from langchain.tools import BaseTool

# 方案 A:手动将 MCP Server 暴露的工具封装为 LangChain Tool
# 适用于 MCP Server 数量少、工具稳定的场景
class MCPQueryTool(BaseTool):
    name = "query_orders"
    description = "查询订单表,支持 SQL WHERE 条件过滤,参数 where 为字符串"
    
    def _run(self, where: str = "") -> str:
        # 实际调用 MCP Server(通过 subprocess 或 socket)
        import subprocess, json
        request = json.dumps({
            "jsonrpc": "2.0", "id": 1,
            "method": "tools/call",
            "params": {"name": "query_orders", "arguments": {"where": where}}
        })
        proc = subprocess.run(
            ["python", "sqlite_mcp_server.py"],
            input=request, capture_output=True, text=True
        )
        result = json.loads(proc.stdout.strip())
        return result["result"]["content"][0]["text"]

# 方案 B:使用 MCP 官方 SDK 的 LangChain 适配器(如果生态支持)
# 优点:自动同步 tools/list,动态发现工具
# 缺点:目前还不太成熟,依赖适配器版本

# 方案 C:自己实现一个 MCP Client 管理层
# 适合生产环境,统一管理多个 MCP Server 会话
class MCPClientManager:
    """管理多个 MCP Server 连接,自动发现工具"""
    def __init__(self):
        self.servers = {}  # name -> (process, capabilities)
    
    def connect_server(self, name: str, command: str, args: list):
        proc = subprocess.Popen(
            [command] + args,
            stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True
        )
        # 发送 initialize 和 tools/list
        init_req = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "initialize"})
        proc.stdin.write(init_req + "\n")
        proc.stdin.flush()
        init_resp = json.loads(proc.stdout.readline())
        # 缓存工具列表
        self.servers[name] = {"process": proc, "capabilities": init_resp}
    
    def get_all_tools(self) -> list:
        tools = []
        for name, info in self.servers.items():
            tools_req = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/list"})
            info["process"].stdin.write(tools_req + "\n")
            info["process"].stdin.flush()
            tools_resp = json.loads(info["process"].stdout.readline())
            tools.append(tools_resp["result"]["tools"])
        return tools

# 使用
manager = MCPClientManager()
manager.connect_server("sqlite", "python", ["sqlite_mcp_server.py"])
manager.connect_server("slack", "python", ["slack_mcp_server.py"])
all_tools = manager.get_all_tools()  # 自动发现所有工具

集成踩坑

  1. 子进程通信的阻塞风险subprocess.Popen 的 stdin/stdout 是管道,如果 Server 端的 stdout 缓冲区满了还没被读取,子进程会阻塞在 write() 上,导致死锁。解决方案:用 asyncio.subprocessThreadPoolExecutor 做异步读写,或者用 socketpair 替代管道。
  2. 工具 Schema 冲突。多个 MCP Server 可能暴露同名工具。LangChain 的 Agent 在注册 Tool 时如果 name 重复,后注册的会覆盖前面的。需要给工具名加前缀(如 sqlite_query_orders vs slack_query_orders)来避免冲突。
  3. MCP Server 的初始化延迟initialize 握手不是瞬间完成的。一个 Python 实现的 MCP Server 从启动到就绪,平均需要 200-500ms(加载依赖、初始化数据库连接)。如果 Agent 在启动时一次性连接 10 个 Server,总延迟就是 2-5 秒。建议用懒加载或连接池缓存。

MCP 与 Google A2A 协议的对比

2025 年 Google 也发布了 A2A(Agent-to-Agent)协议,两个协议经常被拿来一起问:

维度MCPA2A
目标LLM ↔ 工具/数据源Agent ↔ Agent 互操作
角色Host-Client-Server对等(Peer-to-Peer)
通信JSON-RPC(请求-响应)基于任务的异步消息
传输stdio / SSEHTTP / WebSocket
能力发现tools/list, resources/listAgent Card(JSON-LD)
状态管理无状态(每次请求独立)有状态任务卡片
发布方AnthropicGoogle
当前阶段v0.1.x,社区少量 Server提案阶段,暂无大规模落地

面试判断:MCP 和 A2A 不是竞争关系,而是不同层次。MCP 解决"Agent 怎么用工具",A2A 解决"Agent 之间怎么协作"。实际系统中两个都可能用到:Agent 通过 MCP 调用数据库,同时通过 A2A 与其他 Agent 协调任务。

MCP 的局限性

面试官如果继续追问,MCP 当前有几个硬伤你最好能答上来:

  1. 缺乏内置认证和授权。MCP 协议本身没有定义认证机制,Server 暴露后谁都能调用。生产环境必须在传输层之上叠加认证(比如 SSE 前面加网关鉴权,或者 stdio 模式下通过进程隔离控制访问)。一个典型的做法是在 SSE 端点前面挂 API Gateway,用 JWT 或 mTLS 做身份校验,MCP 协议层只负责数据交换。
  2. 协议版本还比较早期。MCP 目前是 0.1.x 版本,协议还在快速演进中。2025 年初有几次不兼容的变更(比如资源 URI 格式从 file:// 改成了资源标识符),升级时需要注意兼容性。如果你在项目里用了 MCP,建议锁住版本号,不要随便升级 SDK。
  3. 生态尚未成熟。截至 2025 年中,官方维护的 MCP Server 不到 20 个,社区贡献的质量参差不齐。大多数生产级数据源(Oracle、SAP、Hadoop)仍然没有官方的 MCP Server 实现。如果你要在公司推广 MCP,大概率需要自己写 Server 实现,或者找社区版本做二次开发。
  4. SSE 模式的延迟问题。Server-Sent Events 是单向推送,Client 发请求需要通过 POST 打到另一个端点,存在至少一次 RTT 的额外延迟。对于毫秒级响应的场景(如实时补全),这个延迟不可忽略。MCP 官方已经在考虑 WebSocket 传输方案。实测数据:在华东到华北的跨地域部署下,SSE 模式的平均端到端延迟是 120-180ms,而纯本地 stdio 模式是 5-15ms。
  5. 没有标准化的错误码和重试策略。MCP 的 JSON-RPC 错误码只用了标准的那几个(-32700 parse error, -32601 method not found),没有工具级别的错误码。比如 SQL 查询超时、数据库连接断开、参数校验失败,全都没区分。Client 端拿到错误后只能当成"工具调用失败"处理,无法做精细化的重试/降级。
  6. 资源模型的抽象过于简单。MCP 把资源定义为"URI + MIME 类型",但实际场景中资源往往有复杂的关联关系(比如 A 表通过外键引用 B 表的数据)。MCP 的 Resources 模型不支持这种关联查询,如果想实现"查订单的同时拿到用户信息",要么在 tools/call 里做联表查询,要么在 Host 侧做两次资源调用。这暴露了 MCP 的 Resources 本质上是一个扁平的文件系统抽象,不是数据库抽象。
  7. MCP Server 状态管理缺失。MCP 协议设计上假设每个 tools/call 是独立的、无副作用的,但实际工具往往有状态。比如"登录"工具会在 Server 端维护 session token,"上传文件"工具会创建临时文件。MCP 没有定义状态清理机制,Server 端的内存泄漏和临时文件积累只能靠 Server 自己兜底。一个典型的翻车案例:某个社区 MCP Server 的"文件上传"工具没有清理临时文件,运行一周后磁盘写满 200GB,导致 Server 崩溃。

面试场景:一个典型追问链

如果你在面试中 MCP 答得不错,面试官可能会连续追问下面这些,提前准备好:

Q1:"MCP 和 gRPC 的区别是什么?" MCP 选 JSON-RPC 而不是 gRPC 是因为 JSON-RPC 没有 IDL 的编译步骤,LLM 可以动态生成请求。gRPC 需要 .proto 文件编译成桩代码,不适合动态工具发现的场景。但损失了强类型和性能——JSON-RPC 的序列化/反序列化开销比 Protobuf 高 3-5 倍。

Q2:"你们公司在生产环境用 MCP 了吗?遇到什么问题?" 如果你没实际用过,可以说"我在个人项目里用了"然后说上面的踩坑实录。比说"还没用过"强一档。

Q3:"MCP Server 如果挂了,怎么保证 Agent 可用?" 三个策略:1) tools/list 发现失败时,Agent 降级为只使用本地工具;2) 对关键 Server 做主备(启动两个 MCP Server 实例,Client 轮询);3) 缓存工具定义和上次结果,临时可用。

Q4:"MCP 的 Resources 和 Tools 什么区别?什么时候应该用 Resources 而不是 Tools?" 核心区别:Resources 是只读数据暴露,类似文件系统;Tools 是可执行的副作用操作。如果 LLM 只需要"读数据"(比如查表结构、查文档),用 Resources 更轻量,不需要传参数。如果 LLM 需要"执行操作"(比如写数据、发消息),用 Tools。实际开发中一个常见的坑:把应该用 Tools 的写操作暴露成了 Resources,导致 LLM 通过 Resources 拿到数据后无法写入。

Q5:"MCP 的初始化握手是一次性的,如果 Server 在运行中更新了工具列表怎么办?" MCP 目前没有热更新机制。Server 启动时通过 initialize 返回的能力声明是固定的。如果 Server 中途增加了新工具,Client 不知道。一种变通方案是:Server 把 tools/list 做成动态查询,每次 Client 调用时返回最新列表。但 initialize 返回的 capabilities 是固定的,不能新增类别。

总结

MCP 的意义不在技术复杂度上,而在生态标准化。就像 HTTP 标准化了 Web 通信、SQL 标准化了数据库查询,MCP 试图标准化 LLM 与外部世界的连接。

面试时可用的 3 句话总结

  1. MCP 解决的是 M×N 集成爆炸问题,通过标准化协议把适配器数量从 M×N 降到 M+N
  2. 它和 Function Calling 是互补关系——Function Calling 是 LLM 怎么调用工具,MCP 是工具怎么暴露自己
  3. 当前还处于早期阶段(v0.1.x),生态不够丰富,但方向是对的,值得关注和跟进

落地建议:先在本地用 stdio 模式跑通一个 MCP Server(比如连 SQLite 或本地文件系统),验证集成流程,再考虑 SSE 远程部署和认证加固。不要一开始就上生产级多 Server 编排,容易踩子进程通信和超时的坑。

参考

参考:Anthropic MCP 官方文档 (https://modelcontextprotocol.io);MCP GitHub 仓库 (https://github.com/modelcontextprotocol);JSON-RPC 2.0 规范 (https://www.jsonrpc.org/specification)

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。