MCP协议深度解析:构建统一的AI工具集成标准指南 🔌🤖

← 返回博客首页

MCP协议深度解析:构建统一的AI工具集成标准指南 🔌🤖

发布日期: 2026-04-30

技术领域: 协议设计、工具集成、AI基础设施、Agent框架

目标读者: AI工程师、Agent开发者、系统架构师、工具开发者

技术难度: ⭐⭐⭐⭐ (高级)

第一章:MCP协议设计哲学

1.1 为什么需要MCP?

在MCP出现之前,AI Agent的工具集成面临以下"巴别塔困境":

问题表现后果
协议碎片化OpenAI Function Calling、Anthropic Tool Use、LlamaIndex ToolSpec 各不兼容工具需为每个框架单独适配
类型信息丢失JSON Schema描述能力有限,无法表达复杂约束参数校验靠运气,运行时错误频发
缺少双向通信工具调用是"请求-响应"模式,无法推送事件无法实现流式输出、进度通知
无统一发现机制每个框架自行加载工具列表无法动态注册/注销工具
传输层绑定大多数实现与HTTP/WS强绑定无法在本地进程内高效通信

MCP正是为了解决这些问题而设计的:一个传输无关的、类型安全的、双向的、可发现的工具调用协议

1.2 MCP的核心设计原则


┌─────────────────────────────────────────────┐

│              MCP Architecture                │

├─────────────────────────────────────────────┤

│  Host (LLM Application / Agent Framework)    │

│  ┌──────────────┐    ┌──────────────────┐   │

│  │ MCP Client    │◄──►│  MCP Transport   │   │

│  └──────┬───────┘    └────────┬─────────┘   │

│         │                     │              │

│    ┌────▼────┐          ┌────▼────┐         │

│    │  stdio  │          │   SSE   │         │

│    │(子进程) │          │ (HTTP)  │         │

│    └─────────┘          └─────────┘         │

├─────────────────────────────────────────────┤

│           MCP Server (工具提供方)            │

│  ┌────────────────────────────────────┐     │

│  │  Tool Registry (工具注册表)        │     │

│  │  ├── tool_a: get_weather           │     │

│  │  ├── tool_b: read_file             │     │

│  │  └── tool_c: execute_sql          │     │

│  └────────────────────────────────────┘     │

└─────────────────────────────────────────────┘

设计原则:

1. 传输无关性:MCP定义的是消息格式与交互流程,不绑定任何传输层。当前支持stdio(子进程通信)和SSE(Server-Sent Events,HTTP流式),未来可扩展至WebSocket、Unix Domain Socket等。

2. 类型安全:MCP使用JSON Schema的扩展版本描述工具参数和返回值,支持复杂嵌套类型、枚举约束、min/max校验等。

3. 双向通信:不仅支持"Agent调用工具"的正向流程,还支持"工具向Agent推送事件"的反向流程——进度通知、中间结果、状态变更均可实时推送。

4. 运行时发现:工具列表不是静态配置的,客户端可以在运行时通过 tools/list 请求获取服务端所有可用工具及其Schema定义。

5. 会话管理:MCP支持带状态的长期会话,服务端可以维护上下文状态(如数据库连接池、已打开的浏览器页面)。

第三章:构建MCP Server

3.1 最简单的MCP Server


"""

mcp_weather_server.py — 一个完整的MCP Server示例

提供天气查询和城市信息查询两个工具

"""



import json

import sys

import httpx

from typing import Any





class McpServer:

    """MCP Server实现"""

    

    def __init__(self):

        self.tools: dict[str, dict] = {}

        self.session_id: str | None = None

        self._register_default_tools()

    

    def _register_default_tools(self):

        """注册内置工具"""

        self._register_tool(

            name="get_weather",

            description="查询指定城市的当前天气",

            input_schema={

                "type": "object",

                "properties": {

                    "city": {

                        "type": "string",

                        "description": "城市名称(中文),如:北京、上海、深圳"

                    },

                    "units": {

                        "type": "string",

                        "enum": ["celsius", "fahrenheit"],

                        "description": "温度单位",

                        "default": "celsius"

                    }

                },

                "required": ["city"]

            }

        )

        self._register_tool(

            name="get_city_info",

            description="获取指定城市的基本信息",

            input_schema={

                "type": "object",

                "properties": {

                    "city": {

                        "type": "string",

                        "description": "城市名称"

                    }

                },

                "required": ["city"]

            }

        )

    

    def _register_tool(self, name: str, description: str, input_schema: dict):

        """注册一个工具"""

        self.tools[name] = {

            "name": name,

            "description": description,

            "inputSchema": input_schema

        }

    

    async def _handle_tool_call(self, name: str, arguments: dict) -> dict:

        """处理工具调用"""

        if name == "get_weather":

            city = arguments["city"]

            units = arguments.get("units", "celsius")

            return await self._get_weather(city, units)

        elif name == "get_city_info":

            return self._get_city_info(arguments["city"])

        else:

            return {"isError": True, "content": [

                {"type": "text", "text": f"未知工具: {name}"}

            ]}

    

    async def _get_weather(self, city: str, units: str) -> dict:

        """模拟天气查询(实际项目中可替换为真实API调用)"""

        # 这里使用模拟数据

        weather_data = {

            "北京": {"temp": 25, "condition": "晴", "humidity": 45},

            "上海": {"temp": 28, "condition": "多云", "humidity": 60},

            "深圳": {"temp": 32, "condition": "阵雨", "humidity": 75},

        }

        data = weather_data.get(city, {"temp": 20, "condition": "未知", "humidity": 50})

        

        unit_symbol = "°C" if units == "celsius" else "°F"

        temp = data["temp"] if units == "celsius" else round(data["temp"] * 9/5 + 32)

        

        return {

            "content": [{

                "type": "text",

                "text": f"{city}当前天气:{temp}{unit_symbol},{data['condition']},"

                       f"湿度:{data['humidity']}%"

            }]

        }

    

    def _get_city_info(self, city: str) -> dict:

        """返回城市信息"""

        info = {

            "北京": {"population": "2154万", "area": "16410 km²", "timezone": "UTC+8"},

            "上海": {"population": "2487万", "area": "6340 km²", "timezone": "UTC+8"},

            "深圳": {"population": "1768万", "area": "1997 km²", "timezone": "UTC+8"},

        }

        data = info.get(city, {"population": "未知", "area": "未知", "timezone": "未知"})

        return {

            "content": [{

                "type": "text",

                "text": f"📍 {city}:人口 {data['population']},"

                       f"面积 {data['area']},时区 {data['timezone']}"

            }]

        }

    

    async def handle_message(self, raw: str) -> str | None:

        """处理一条MCP消息"""

        try:

            msg = json.loads(raw)

        except json.JSONDecodeError:

            return None

        

        msg_id = msg.get("id")

        method = msg.get("method")

        params = msg.get("params", {})

        

        # 处理请求类消息

        if "id" in msg:

            if method == "initialize":

                return json.dumps({

                    "jsonrpc": "2.0",

                    "id": msg_id,

                    "result": {

                        "protocolVersion": "2025-03-26",

                        "capabilities": {"tools": {}},

                        "serverInfo": {

                            "name": "weather-mcp-server",

                            "version": "0.1.0"

                        }

                    }

                })

            elif method == "tools/list":

                return json.dumps({

                    "jsonrpc": "2.0",

                    "id": msg_id,

                    "result": {

                        "tools": list(self.tools.values())

                    }

                })

            elif method == "tools/call":

                result = await self._handle_tool_call(

                    params["name"],

                    params.get("arguments", {})

                )

                return json.dumps({

                    "jsonrpc": "2.0",

                    "id": msg_id,

                    "result": result

                })

            elif method == "shutdown":

                return json.dumps({

                    "jsonrpc": "2.0",

                    "id": msg_id,

                    "result": None

                })

        

        return None





async def main():

    """stdio传输模式入口"""

    server = McpServer()

    

    # 从stdin读取MCP消息(JSON-RPC)

    while True:

        line = sys.stdin.readline()

        if not line:

            break

        

        # 解析Content-Length header

        if line.startswith("Content-Length: "):

            length = int(line.split(": ")[1])

            # 跳过空行

            sys.stdin.readline()

            # 读取消息体

            raw = sys.stdin.read(length)

            response = await server.handle_message(raw)

            if response:

                content_length = len(response.encode("utf-8"))

                sys.stdout.write(f"Content-Length: {content_length}\r\n\r\n")

                sys.stdout.write(response)

                sys.stdout.flush()





if __name__ == "__main__":

    import asyncio

    asyncio.run(main())

3.2 使用Python MCP SDK

实际生产中,建议直接使用MCP Python SDK


"""使用MCP SDK构建Server"""



from mcp.server import Server

from mcp.server.stdio import stdio_server

from mcp.types import Tool, TextContent

import httpx





# 创建Server实例

server = Server("weather-server")





@server.list_tools()

async def list_tools() -> list[Tool]:

    """注册工具列表"""

    return [

        Tool(

            name="get_weather",

            description="查询指定城市的当前天气",

            inputSchema={

                "type": "object",

                "properties": {

                    "city": {"type": "string", "description": "城市名称"},

                    "units": {"type": "string", "enum": ["celsius", "fahrenheit"]}

                },

                "required": ["city"]

            }

        )

    ]





@server.call_tool()

async def call_tool(name: str, arguments: dict) -> list[TextContent]:

    """处理工具调用"""

    if name == "get_weather":

        city = arguments["city"]

        units = arguments.get("units", "celsius")

        # 实际使用 httpx 调用天气API

        # async with httpx.AsyncClient() as client:

        #     resp = await client.get(f"https://api.weather.com/v1/{city}")

        #     data = resp.json()

        

        return [TextContent(

            type="text",

            text=f"{city} 天气:25°C,晴 🌤️"

        )]

    

    raise ValueError(f"Unknown tool: {name}")





async def main():

    """使用stdio传输模式运行"""

    async with stdio_server() as (read, write):

        await server.run(read, write, server.create_initialization_options())

3.3 MCP Client实现


"""Hermes Agent风格的MCP Client"""



import asyncio

import json

import subprocess

from typing import AsyncIterator





class McpClient:

    """MCP客户端——管理与MCP Server的stdio连接"""

    

    def __init__(self, server_command: list[str]):

        self.server_command = server_command

        self.process: subprocess.Popen | None = None

        self.tools: list[dict] = []

        self._msg_counter = 0

    

    async def connect(self):

        """启动MCP Server进程并完成初始化握手"""

        self.process = await asyncio.create_subprocess_exec(

            *self.server_command,

            stdin=subprocess.PIPE,

            stdout=subprocess.PIPE,

            stderr=subprocess.PIPE

        )

        

        # 1. 初始化

        result = await self._send("initialize", {

            "protocolVersion": "2025-03-26",

            "capabilities": {"tools": {}},

            "clientInfo": {"name": "hermes-mcp-client", "version": "1.0.0"}

        })

        self.server_info = result.get("serverInfo", {})

        

        # 2. 发送initialized通知

        await self._notify("notifications/initialized")

        

        # 3. 获取工具列表

        tools_result = await self._send("tools/list")

        self.tools = tools_result.get("tools", [])

        

        print(f"✅ 已连接 MCP Server: {self.server_info.get('name')}")

        print(f"🔧 已发现 {len(self.tools)} 个工具:")

        for t in self.tools:

            print(f"   • {t['name']}: {t.get('description', '无描述')}")

    

    async def call_tool(self, name: str, arguments: dict = None) -> dict:

        """调用指定工具"""

        return await self._send("tools/call", {

            "name": name,

            "arguments": arguments or {}

        })

    

    async def list_tools(self) -> list[dict]:

        """刷新工具列表"""

        result = await self._send("tools/list")

        self.tools = result.get("tools", [])

        return self.tools

    

    async def _send(self, method: str, params: dict = None) -> dict:

        """发送JSON-RPC请求并等待响应"""

        self._msg_counter += 1

        request = {

            "jsonrpc": "2.0",

            "id": f"msg-{self._msg_counter}",

            "method": method,

            "params": params or {}

        }

        

        # 序列化 + 写入stdin

        payload = json.dumps(request)

        header = f"Content-Length: {len(payload.encode())}\r\n\r\n"

        self.process.stdin.write((header + payload).encode())

        await self.process.stdin.drain()

        

        # 读取响应

        response_line = await self.process.stdout.readline()

        if not response_line.startswith(b"Content-Length: "):

            raise RuntimeError(f"Unexpected response header: {response_line}")

        

        length = int(response_line.decode().split(": ")[1])

        await self.process.stdout.readline()  # 空行

        

        raw = await self.process.stdout.readexactly(length)

        response = json.loads(raw)

        

        if "error" in response:

            raise RuntimeError(f"Tool error: {response['error']}")

        

        return response.get("result", {})

    

    async def _notify(self, method: str, params: dict = None):

        """发送通知(不需要响应)"""

        request = {

            "jsonrpc": "2.0",

            "method": method,

            "params": params or {}

        }

        payload = json.dumps(request)

        header = f"Content-Length: {len(payload.encode())}\r\n\r\n"

        self.process.stdin.write((header + payload).encode())

        await self.process.stdin.drain()

    

    async def close(self):

        """优雅关闭连接"""

        if self.process:

            await self._send("shutdown")

            self.process.terminate()

            await self.process.wait()

            self.process = None





# ===== 使用示例 =====

async def main():

    # 连接到MCP Server

    client = McpClient(["python3", "mcp_weather_server.py"])

    await client.connect()

    

    # 调用天气查询工具

    result = await client.call_tool("get_weather", {

        "city": "北京",

        "units": "celsius"

    })

    print(f"\n📡 工具调用结果: {result}")

    

    await client.close()





if __name__ == "__main__":

    asyncio.run(main())

第五章:MCP vs 其他工具集成方案

5.1 方案对比

维度MCPOpenAI Function CallingLangChain ToolsAnthropic Tool Use
开放标准✅ 开源协议❌ 厂商锁定❌ 框架锁定❌ 厂商锁定
类型安全✅ JSON Schema扩展⚠️ 基础Schema⚠️ Pydantic⚠️ 基础Schema
传输层✅ 传输无关❌ HTTP Only❌ 框架内❌ HTTP Only
双向通信✅ 通知推送❌ 请求-响应❌ 框架内❌ 请求-响应
运行时发现✅ 原生支持⚠️ 需额外实现⚠️ 需框架⚠️ 需额外
会话管理✅ 原生支持❌ 无状态❌ 无状态❌ 无状态
生态规模🚀 快速增长🌟 最大🌟 大🌟 大

5.2 MCP Server生态速查

以下是当前已支持MCP协议的Server(2026年4月):

类别Server功能
🗄️ 数据库mcp-server-sqliteSQLite数据库查询
mcp-server-postgresPostgreSQL schema探索
mcp-server-mysqlMySQL数据库管理
📁 文件系统mcp-server-filesystem安全的文件读写
mcp-server-githubGitHub API集成
🌐 Webmcp-server-fetchHTTP请求/网页抓取
mcp-server-puppeteer浏览器自动化
📊 数据分析mcp-server-pandasPandas交互
mcp-server-plotly数据可视化
🔧 开发工具mcp-server-gitGit操作
mcp-server-dockerDocker容器管理
mcp-server-sentrySentinel/KV存储

第七章:MCP的未来

7.1 即将到来的增强

MCP协议正处于快速演进阶段,以下能力已在路线图中:

- Streaming Tool Calls:工具输出可以流式传输,支持大文件读取和长时间计算

- Nested Tool Calls:工具可以调用其他工具(工具编排)

- Capability Negotiation:更细粒度的能力协商(Server可以选择性暴露能力)

- Auth & Authorization:OAuth2集成,安全的工具授权

7.2 MCP与Web生态的类比

WebMCP类比
HTTPMCP Transport传输协议
HTML + CSSTool Schema内容描述
REST APItools/call操作接口
WebSocket未来: Stream双向传输
DNS未来: MCP Registry服务发现
BrowserMCP Client消费者
Web ServerMCP Server提供者

参考资源:

- MCP Protocol Specification

- MCP Python SDK

- Awesome MCP Servers

- Hermes Agent MCP Integration