一文拆解MCP协议

文章目录

什么是MCP协议?

MCP(Model Context Protocol,模型上下文协议)是一个用于连接大语言模型(如 GPT、Claude 等)与外部数据源和工具的开放协议 。可以把它理解为 AI 的 USB 标准

通过这个协议,大模型可以发起对外部世界的连接。操作和感知模型以外的世界。

为什么要有MCP协议?

如果没有外部工具,大模型就是一个只会说不会干的,并且无法实时获取外部信息,思想上的巨人,行动上的矮子。除了聊天什么都做不了。

MCP协议定义了外部工具接入大模型的统一标准,任何实现了这个标准的工具,就可以被大模型调用,实现 "一次开发,多处使用"

大模型这个时候才能变成Agent,一个真正的万能助手,可以根据自己的思考,付诸行动。

MCP协议在Agent中如何工作?

初始化阶段:MCP Server 会通过MCP协议 注册 当前MCP服务器提供的功能,以及调用函数的方式 到Agent。

LLM调用阶段:Agent会把 当前可用的的MCP工具,用户问题,发送到大模型。

工具执行:大模型根据用户问题,以及可用的MCP工具,决定执行执行某个mcp工具。

以使用Agent找咖啡店为例:
External MCP Server 大模型(API) Agent 用户 External MCP Server 大模型(API) Agent 用户 初始化阶段 用户提问 第一次LLM调用 工具执行 第二次LLM调用(可选) 启动服务器并获取工具列表 返回工具元数据 构建系统提示(包含工具描述) "帮我找一下附近的咖啡店" 发送完整提示 [系统提示 + 用户问题] 返回结构化响应 {"action": "search_places", "params": {...}} 调用工具 search_places(params) 调用百度地图API 返回POI数据 返回工具结果 [历史对话 + 工具结果 + 用户问题] 生成最终回答 "附近有3家咖啡店:星巴克、瑞幸、Tims"

MCP协议详解

MCP 支持多种传输协议。

  • stdio (标准输入/输出): 本地进程间通信
  • http/sse: HTTP + Server-Sent Events
  • http/websocket: WebSocket 双向通信
  • tcp/sse: TCP + Server-Sent Events
  • tcp/websocket: TCP + WebSocket

Client 与 Server 完整通信流程

MCP Server Agent/MCP Client 用户 MCP Server Agent/MCP Client 用户 1. 初始化阶段 2. 用户请求阶段 3. 工具调用阶段 4. 结果处理阶段 5. 资源查询(可选) 发送initialize请求 返回initialize响应 发送notify_initialized通知 发送tools/list请求 返回工具列表 发送notify_tools/list通知 "搜索北京的咖啡店" 解析用户意图,选择工具 发送tools/call请求 执行工具逻辑 返回工具结果 "找到10家咖啡店..." 发送resources/list请求 返回资源列表

通信内容:

json 复制代码
// 1. 初始化阶段
Client -> Server:
{
  "jsonrpc": "2.0",
  "id": "req_1",
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": {},
      "resources": {}
    },
    "clientInfo": {
      "name": "MCP测试客户端",
      "version": "1.0.0"
    }
  }
}

Server -> Client:
{
  "jsonrpc": "2.0",
  "id": "req_1",
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": {},
      "resources": {}
    },
    "serverInfo": {
      "name": "百度地图MCP服务器",
      "version": "1.0.0"
    }
  }
}

Server -> Client (通知):
{
  "jsonrpc": "2.0",
  "method": "notify_initialized",
  "params": {}
}

// 2. 工具列表查询
Client -> Server:
{
  "jsonrpc": "2.0",
  "id": "req_2",
  "method": "tools/list",
  "params": {}
}

Server -> Client:
{
  "jsonrpc": "2.0",
  "id": "req_2",
  "result": {
    "tools": [
      {
        "name": "search_places",
        "description": "搜索地点信息",
        "inputSchema": {
          "type": "object",
          "properties": {
            "query": { "type": "string" },
            "location": { "type": "string", "default": "北京" },
            "radius": { "type": "number", "default": 5000 }
          },
          "required": ["query"]
        }
      },
      {
        "name": "get_directions",
        "description": "获取导航路线",
        "inputSchema": {
          "type": "object",
          "properties": {
            "origin": { "type": "string" },
            "destination": { "type": "string" },
            "mode": { 
              "type": "string", 
              "enum": ["driving", "walking", "transit"] 
            }
          },
          "required": ["origin", "destination"]
        }
      }
    ]
  }
}

Server -> Client (通知):
{
  "jsonrpc": "2.0",
  "method": "notify_tools/list",
  "params": {
    "tools": [...]
  }
}

// 3. 工具调用
Client -> Server:
{
  "jsonrpc": "2.0",
  "id": "req_3",
  "method": "tools/call",
  "params": {
    "name": "search_places",
    "arguments": {
      "query": "咖啡店",
      "location": "北京",
      "radius": 3000
    }
  }
}

Server -> Client:
{
  "jsonrpc": "2.0",
  "id": "req_3",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "[\n  {\n    \"name\": \"星巴克\",\n    \"address\": \"朝阳区xxx\",\n    \"distance\": \"500米\"\n  },\n  {\n    \"name\": \"瑞幸咖啡\",\n    \"address\": \"海淀区xxx\",\n    \"distance\": \"800米\"\n  }\n]"
      }
    ]
  }
}

// 4. 资源查询
Client -> Server:
{
  "jsonrpc": "2.0",
  "id": "req_4",
  "method": "resources/list",
  "params": {}
}

Server -> Client:
{
  "jsonrpc": "2.0",
  "id": "req_4",
  "result": {
    "resources": [
      {
        "uri": "file:///config/map.json",
        "mimeType": "application/json",
        "name": "地图配置"
      }
    ]
  }
}

stdio 实现mcp伪代码

mcp client 实现

管理mcp server进程,获取工具列表,发起mcp调用

js 复制代码
// mcp_client.js
const { spawn } = require('child_process');

class MCPClient {
  constructor(serverConfig) {
    this.serverProcess = null;
    this.requestCallbacks = new Map();
    this.tools = new Map();
    this.requestId = 0;
  }

  async connect(command, args = [], env = {}) {
    return new Promise((resolve, reject) => {
      // 启动子进程
      this.serverProcess = spawn(command, args, {
        env: { ...process.env, ...env },
        stdio: ['pipe', 'pipe', 'pipe']
      });

      // 设置消息处理器
      this.serverProcess.stdout.on('data', (data) => {
        this.handleServerOutput(data);
      });

      this.serverProcess.stderr.on('data', (data) => {
        console.error('MCP Server stderr:', data.toString());
      });

      this.serverProcess.on('error', (error) => {
        reject(new Error(`启动MCP服务器失败: ${error.message}`));
      });

      this.serverProcess.on('close', (code) => {
        console.log(`MCP服务器已关闭,退出码: ${code}`);
      });

      // 执行初始化握手
      setTimeout(async () => {
        try {
          await this.initialize();
          await this.listTools();
          resolve();
        } catch (error) {
          reject(error);
        }
      }, 100);
    });
  }

  handleServerOutput(data) {
    const lines = data.toString().split('\n');
    
    lines.forEach(line => {
      if (!line.trim()) return;
      
      try {
        const message = JSON.parse(line);
        this.handleMessage(message);
      } catch (error) {
        console.error('解析JSON失败:', line, error);
      }
    });
  }

  handleMessage(message) {
    // 处理响应
    if (message.id !== undefined && this.requestCallbacks.has(message.id)) {
      const { resolve, reject } = this.requestCallbacks.get(message.id);
      
      if (message.error) {
        reject(new Error(`RPC错误 [${message.error.code}]: ${message.error.message}`));
      } else {
        resolve(message.result);
      }
      
      this.requestCallbacks.delete(message.id);
    }
    // 处理通知
    else if (message.method) {
      this.handleNotification(message.method, message.params);
    }
  }

  handleNotification(method, params) {
    switch (method) {
      case "notify_initialized":
        console.log('MCP服务器初始化完成');
        break;
      case "notify_tools/list":
        console.log('工具列表已更新:', params.tools);
        params.tools.forEach(tool => {
          this.tools.set(tool.name, tool);
        });
        break;
      case "notify_shutdown":
        console.log('服务器关闭通知:', params.reason);
        break;
      default:
        console.log('收到未知通知:', method, params);
    }
  }

  async sendRequest(method, params) {
    const id = `req_${++this.requestId}`;
    const request = {
      jsonrpc: "2.0",
      id,
      method,
      params
    };

    return new Promise((resolve, reject) => {
      this.requestCallbacks.set(id, { resolve, reject });
      this.serverProcess.stdin.write(JSON.stringify(request) + '\n');
      
      // 设置超时
      setTimeout(() => {
        if (this.requestCallbacks.has(id)) {
          this.requestCallbacks.delete(id);
          reject(new Error(`请求超时: ${method}`));
        }
      }, 30000);
    });
  }

  async initialize() {
    const result = await this.sendRequest('initialize', {
      protocolVersion: "2024-11-05",
      capabilities: {
        tools: {},
        resources: {}
      },
      clientInfo: {
        name: "MCP测试客户端",
        version: "1.0.0"
      }
    });
    
    console.log('初始化成功:', result);
    return result;
  }

  async listTools() {
    const result = await this.sendRequest('tools/list', {});
    
    result.tools.forEach(tool => {
      this.tools.set(tool.name, tool);
    });
    
    console.log('可用工具:', Array.from(this.tools.keys()));
    return result;
  }

  async callTool(toolName, args) {
    if (!this.tools.has(toolName)) {
      throw new Error(`工具未找到: ${toolName}`);
    }

    const result = await this.sendRequest('tools/call', {
      name: toolName,
      arguments: args
    });
    
    return result;
  }

  async listResources() {
    return await this.sendRequest('resources/list', {});
  }

  disconnect() {
    if (this.serverProcess) {
      this.serverProcess.kill();
      this.serverProcess = null;
    }
  }
}

// 客户端使用示例
async function main() {
  const client = new MCPClient();
  
  try {
    // 连接MCP服务器
    console.log('正在连接MCP服务器...');
    await client.connect('node', ['mcp_server.js']);
    
    // 用户模拟请求:搜索咖啡店,真实情况为模型返回结果中通知需要调用某个工具
    console.log('\n模拟用户请求:搜索咖啡店');
    const result = await client.callTool('search_places', {
      query: "咖啡店",
      location: "北京",
      radius: 3000
    });
    
    console.log('工具调用结果:', JSON.stringify(result, null, 2));
    
    // 查询可用资源
    const resources = await client.listResources();
    console.log('\n可用资源:', JSON.stringify(resources, null, 2));
    
  } catch (error) {
    console.error('错误:', error);
  } finally {
    // 断开连接
    setTimeout(() => {
      client.disconnect();
      console.log('客户端已断开连接');
    }, 1000);
  }
}

// 运行客户端
if (require.main === module) {
  main();
}

mcp server 实现

提供mcp 服务,注册工具

js 复制代码
// mcp_server.js
const readline = require('readline');

class MCPServer {
  constructor() {
    this.tools = {
      search_places: {
        description: "搜索地点信息",
        inputSchema: {
          type: "object",
          properties: {
            query: { type: "string" },
            location: { type: "string", default: "北京" },
            radius: { type: "number", default: 5000 }
          },
          required: ["query"]
        },
        execute: async (args) => {
          // 模拟调用百度地图API
          return [
            { name: "星巴克", address: "朝阳区xxx", distance: "500米" },
            { name: "瑞幸咖啡", address: "海淀区xxx", distance: "800米" }
          ];
        }
      },
      get_directions: {
        description: "获取导航路线",
        inputSchema: {
          type: "object",
          properties: {
            origin: { type: "string" },
            destination: { type: "string" },
            mode: { type: "string", enum: ["driving", "walking", "transit"] }
          },
          required: ["origin", "destination"]
        },
        execute: async (args) => {
          return {
            distance: "10公里",
            duration: "30分钟",
            steps: ["从起点出发", "直行2公里", "左转到达"]
          };
        }
      }
    };

    this.resources = {
      "map-config": {
        uri: "file:///config/map.json",
        mimeType: "application/json",
        name: "地图配置"
      }
    };
  }

  start() {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
      terminal: false
    });

    rl.on('line', async (line) => {
      try {
        const message = JSON.parse(line);
        await this.handleMessage(message);
      } catch (error) {
        this.sendError(null, -32700, "解析错误", error.message);
      }
    });

    // 处理进程退出
    process.on('SIGINT', () => this.shutdown());
    process.on('SIGTERM', () => this.shutdown());
  }

  async handleMessage(message) {
    const { jsonrpc, id, method, params } = message;

    if (jsonrpc !== "2.0") {
      this.sendError(id, -32600, "无效请求", "必须是JSON-RPC 2.0");
      return;
    }

    switch (method) {
      case "initialize":
        await this.handleInitialize(id, params);
        break;
      case "tools/list":
        await this.handleListTools(id);
        break;
      case "tools/call":
        await this.handleToolCall(id, params);
        break;
      case "resources/list":
        await this.handleListResources(id);
        break;
      default:
        this.sendError(id, -32601, "方法未找到", `未知方法: ${method}`);
    }
  }

  async handleInitialize(id, params) {
    // 1. 发送初始化响应
    this.sendResponse(id, {
      protocolVersion: "2024-11-05",
      capabilities: {
        tools: {},
        resources: {}
      },
      serverInfo: {
        name: "百度地图MCP服务器",
        version: "1.0.0"
      }
    });

    // 2. 发送初始化完成通知(稍后发送)
    setTimeout(() => {
      this.sendNotification("notify_initialized", {});
    }, 10);
  }

  async handleListTools(id) {
    const tools = Object.entries(this.tools).map(([name, tool]) => ({
      name,
      description: tool.description,
      inputSchema: tool.inputSchema
    }));

    this.sendResponse(id, { tools });
  }

  async handleToolCall(id, params) {
    const { name, arguments: args } = params;
    const tool = this.tools[name];

    if (!tool) {
      this.sendError(id, -32602, "无效参数", `工具未找到: ${name}`);
      return;
    }

    try {
      const result = await tool.execute(args);
      this.sendResponse(id, {
        content: [{
          type: "text",
          text: JSON.stringify(result, null, 2)
        }]
      });
    } catch (error) {
      this.sendError(id, -32000, "执行错误", error.message);
    }
  }

  async handleListResources(id) {
    const resources = Object.entries(this.resources).map(([uri, resource]) => ({
      uri: resource.uri,
      mimeType: resource.mimeType,
      name: resource.name
    }));

    this.sendResponse(id, { resources });
  }

  sendResponse(id, result) {
    const response = {
      jsonrpc: "2.0",
      id,
      result
    };
    this.writeLine(response);
  }

  sendNotification(method, params) {
    const notification = {
      jsonrpc: "2.0",
      method,
      params
    };
    this.writeLine(notification);
  }

  sendError(id, code, message, data = null) {
    const error = {
      jsonrpc: "2.0",
      id,
      error: {
        code,
        message,
        data
      }
    };
    this.writeLine(error);
  }

  writeLine(obj) {
    console.log(JSON.stringify(obj));
  }

  shutdown() {
    this.sendNotification("notify_shutdown", { reason: "服务器关闭" });
    process.exit(0);
  }
}

// 启动服务器
const server = new MCPServer();
server.start();

大模型如何知道调用那个工具?

部分大模型原生支持,可以直接作为参数传递。

原生支持:专用"信道"

以OpenAI的API为例,当你调用chat.completions.create时,传递一个独立的tools参数(在早期版本中是functions),其值就是一个JSON Schema的数组。

python 复制代码
# 伪代码示例:OpenAI API 调用结构
response = openai.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "北京今天天气怎么样?"}],
    tools=[{  # 这是一个独立的参数!
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "...",
            "parameters": {...}  # 完整的 JSON Schema
        }
    }]
)

模型在训练阶段就对这种结构化输入进行了特殊优化。收到请求后,模型内部的专用路径会被激活,它只负责解析用户意图、匹配工具、并生成一个严格符合参数Schema的JSON对象 。这个过程独立于文本生成逻辑,就像为"工具调用"这个任务开通了一条VIP专用通道,不与普通的"对话"通道混杂

这种方式工具的调用识别比较准确。

提示工程:混入"对话流"

大模型的本质就是一个词语接龙,聊天工具,在提示词规定好交付方式就行,举个最简单的例子如下:

复制代码
系统指令:你是一个助手,可以调用工具。可用工具如下:
1. 工具名: get_weather
   描述: 获取指定城市的当前天气。
   参数: 
       - location: (字符串) 城市名,如"北京"。
       - unit: (字符串) 温度单位,可选"celsius"或"fahrenheit"。
你必须严格按照以下JSON格式输出调用决定:
{"action": "工具名", "action_input": {"参数": "值"}}
用户问题:北京今天天气怎么样?

此种方式可能 稳定性与精度比较低,在复杂场景下可能存在工具识别出错,参数不对等问题。

相关推荐
乾元2 小时前
拒绝服务的进化:AI 调度下的分布式协同攻击策略
人工智能·分布式
困死,根本不会2 小时前
OpenCV摄像头实时处理:从单特征到联合识别(形状识别 + 颜色识别 + 形状颜色联合识别)
人工智能·opencv·计算机视觉
工具人呵呵2 小时前
[嵌入式AI从0开始到入土]22_基于昇腾310P RC模式的ACT模型部署实践
人工智能
yj_sharing2 小时前
PyTorch深度学习实战:从模型构建到训练技巧
人工智能·pytorch·深度学习
安全二次方security²2 小时前
CUDA C++编程指南(7.31&32&33&34)——C++语言扩展之性能分析计数器函数和断言、陷阱、断点函数
c++·人工智能·nvidia·cuda·断点·断言·性能分析计数器函数
bksheng2 小时前
【Dify】安装与部署
人工智能
狸奴算君2 小时前
告别数据泄露:三步构建企业级AI的隐私保护盾
人工智能
Christo32 小时前
TKDE-2026《Efficient Co-Clustering via Bipartite Graph Factorization》
人工智能·算法·机器学习·数据挖掘
jackylzh2 小时前
PyTorch 2.x 中 `torch.load` 的 `FutureWarning` 与 `weights_only=False` 参数分析
人工智能·pytorch·python