MCP协议从零实现:手写一个完整的 Model Context Protocol 服务器与客户端

引言

2025-2026年是AI Agent从概念走向落地的爆发期。从Claude Code、Cursor到Visual Studio Code Copilot,几乎主流AI应用都在向"智能体"形态演进------它们不再只是问答机器人,而是能够自主调用工具、读取文件、操作数据库的真实生产力工具。

然而,这些能力背后有一个关键的标准化协议正在悄然颠覆整个生态:MCP(Model Context Protocol,模型上下文协议)

由Anthropic推出的MCP,被业界称为"AI界的USB-C接口"。它统一了AI应用与外部数据源、工具之间的通信标准,让一次开发、到处运行成为可能。截至2026年中,MCP生态已覆盖Claude、ChatGPT、VS Code、Cursor等主流平台,注册MCP服务器数量突破数千个。

本文将从零开始手写一个完整的MCP服务器和客户端。我们不依赖任何MCP SDK,直接基于JSON-RPC协议构建,让你从底层彻底理解MCP的工作机制。

实战指南 :本文提供完整可运行的代码示例,建议边看边实践。更多AI Agent实战教程参见:DeepSeek 实战指南系列


一、MCP 核心架构解析

1.1 什么是MCP?

MCP(Model Context Protocol)是一个开放的、标准化的协议,用于在AI应用(如Claude、VS Code等)和外部数据源/工具之间建立安全的通信通道。

类比理解:

  • HTTP 让浏览器与Web服务器通信 → 互联网时代的基础设施

  • MCP 让AI应用与外部工具通信 → AI Agent时代的基础设施

MCP协议的核心使命是回答三个问题:

  1. AI如何发现可用工具? → 通过 tools/list 方法

  2. AI如何调用工具? → 通过 tools/call 方法

  3. AI如何获取上下文数据? → 通过 resources/read 方法

1.2 三要素角色

MCP架构由三个层次构成:

MCP主机(Host):运行AI大模型的应用,例如Claude Desktop、VS Code、Cursor等。主机负责协调多个MCP客户端。

MCP客户端(Client):与具体MCP服务器建立一对一连接的信道组件。每个MCP客户端维护与一个MCP服务器的专用连接。

MCP服务器(Server):提供工具、资源或提示模板的外部程序。可以本地运行(如文件系统服务器),也可以远程部署(如Sentry MCP服务器)。

复制代码
┌─────────────────────────────────────┐
│          MCP 主机 (VS Code)         │
│  ┌──────────┐  ┌──────────┐        │
│  │ Client 1 │  │ Client 2 │        │
│  └────┬─────┘  └────┬─────┘        │
└───────┼──────────────┼──────────────┘
        │              │
┌───────▼──────┐ ┌─────▼────────┐
│ Server A     │ │ Server B     │
│ (Filesystem) │ │ (Database)   │
└──────────────┘ └──────────────┘

1.3 两层协议栈

MCP协议分为两个核心层次:

数据层(Data Layer):基于JSON-RPC 2.0协议,定义了客户端与服务器之间的消息格式、生命周期管理和核心原语(工具、资源、提示模板)。数据层是MCP协议最核心的部分,也是开发者最常直接接触的层次。它定义了三种服务器端原语和三种客户端原语,分别对应AI应用与外部世界的输入输出交互。

传输层(Transport Layer):定义了通信机制和信道。传输层负责建立连接、消息封帧、安全通信等底层工作。MCP支持两种传输方式:

  • Stdio传输:通过标准输入/输出流进行本地进程间通信。这是最常用的开发调试方式,性能最优,因为不需要网络开销。服务器进程由客户端直接启动,双方通过stdin/stdout交换JSON-RPC消息。注意Stdio模式下日志必须输出到stderr,否则会破坏协议消息的完整性。

  • Streamable HTTP传输:基于HTTP POST请求发送客户端到服务器的消息,配合Server-Sent Events(SSE)实现服务端推送能力。这种传输方式支持远程服务器通信,可以使用Bearer Token、API Key等标准HTTP认证方式。Anthropic推荐使用OAuth 2.0获取认证令牌,确保远程调用的安全性。

协议版本与兼容性 :MCP当前使用的协议版本标识为 2024-11-05(以日期命名)。在初始化阶段,客户端和服务器会协商协议版本,确保双方使用兼容的协议能力。如果服务器不支持客户端请求的版本,可以在初始化响应中声明自己支持的版本,由客户端决定是否继续连接。


二、协议底层:JSON-RPC 2.0 精要

在动手写MCP之前,我们必须先理解其底层的RPC协议。MCP完全基于JSON-RPC 2.0规范,每个消息都是一个JSON对象。

2.1 请求格式

复制代码
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}

关键字段:

  • jsonrpc:固定为 "2.0"

  • id:请求唯一标识,响应会携带相同的id

  • method:要调用的方法名

  • params:方法参数(可选)

2.2 响应格式

复制代码
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "calculator",
        "description": "执行数学计算",
        "inputSchema": {
          "type": "object",
          "properties": {
            "expression": {"type": "string"}
          },
          "required": ["expression"]
        }
      }
    ]
  }
}

2.3 通知格式

通知是无需响应的请求,id字段被省略:

复制代码
{
  "jsonrpc": "2.0",
  "method": "notifications/tools/list_changed",
  "params": {}
}

2.4 错误响应

当请求处理失败时,服务器返回错误响应:

复制代码
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32601,
    "message": "Method not found"
  }
}

错误响应与成功响应的区别在于包含error字段而非result字段。错误对象包含三个属性:code(整数错误码)、message(错误描述字符串)和可选的data(附加错误信息)。

标准JSON-RPC错误码:

错误码 含义 说明
-32700 解析错误 服务端收到无效的JSON,应检查消息格式
-32600 无效请求 发送的JSON不是一个合法的请求对象
-32601 方法未找到 服务端不存在请求的方法
-32602 无效参数 方法参数类型或数量不合法
-32603 内部错误 服务端执行方法时发生运行时错误

除了标准JSON-RPC错误码,MCP协议还定义了一些扩展错误码用于特定场景。理解这些错误码对于调试MCP应用程序至关重要------大部分连接问题都可以通过查看错误码来快速定位。

2.5 消息交互实例

以下是一个完整的MCP消息交互序列,展示从初始化到工具调用的全过程:

第1步:客户端发送初始化请求

复制代码
--> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"my-mcp-client","version":"1.0.0"}}}

<-- {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{},"resources":{}},"serverInfo":{"name":"my-mcp-server","version":"1.0.0"}}}

第2步:客户端发送初始化完成通知(无需响应)

复制代码
--> {"jsonrpc":"2.0","method":"notifications/initialized"}

第3步:客户端列出可用工具

复制代码
--> {"jsonrpc":"2.0","id":2,"method":"tools/list"}

<-- {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"calculator","description":"执行数学计算","inputSchema":{"type":"object","properties":{"expression":{"type":"string"}},"required":["expression"]}}]}}

第4步:客户端调用工具

复制代码
--> {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"calculator","arguments":{"expression":"2+2"}}}

<-- {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"计算结果: 4"}]}}

理解这三个字段(jsonrpc版本标识、id请求标识、method方法名)的交互模式,就掌握了MCP协议80%的消息交互逻辑。


三、从零构建MCP服务器(纯Python,无SDK)

本节我们将完全不依赖任何MCP SDK,只用Python标准库从底层实现一个完整的MCP服务器。这能让你真正理解MCP协议的本质。

3.1 基础架构设计

我们的MCP服务器需要处理以下核心方法:

方法 说明
initialize 初始化连接,协商能力
tools/list 列出所有可用工具
tools/call 调用指定工具
resources/list 列出可用资源
resources/read 读取指定资源
notifications/initialized 客户端初始化完成通知

3.2 实现JSON-RPC消息处理器

复制代码
import json
import sys
import logging
from typing import Any, Callable

logging.basicConfig(level=logging.INFO, stream=sys.stderr)

class JSONRPCMessage:
    """JSON-RPC 2.0 消息封装"""

    @staticmethod
    def create_request(id: int, method: str, params: dict = None) -> str:
        msg = {
            "jsonrpc": "2.0",
            "id": id,
            "method": method
        }
        if params is not None:
            msg["params"] = params
        return json.dumps(msg)

    @staticmethod
    def create_response(id: int, result: Any) -> str:
        return json.dumps({
            "jsonrpc": "2.0",
            "id": id,
            "result": result
        })

    @staticmethod
    def create_error(id: int, code: int, message: str, data: Any = None) -> str:
        error = {"code": code, "message": message}
        if data is not None:
            error["data"] = data
        return json.dumps({
            "jsonrpc": "2.0",
            "id": id,
            "error": error
        })

    @staticmethod
    def create_notification(method: str, params: dict = None) -> str:
        msg = {
            "jsonrpc": "2.0",
            "method": method
        }
        if params is not None:
            msg["params"] = params
        return json.dumps(msg)

3.3 实现MCP服务器核心

复制代码
class MCPServer:
    """从零实现的MCP服务器"""

    def __init__(self, server_name: str = "my-mcp-server", version: str = "1.0.0"):
        self.server_name = server_name
        self.version = version
        self.tools: dict[str, dict] = {}
        self.tool_handlers: dict[str, Callable] = {}
        self.resources: dict[str, dict] = {}
        self.request_id = 0

    def register_tool(self, name: str, description: str, 
                      input_schema: dict, handler: Callable):
        """注册一个MCP工具"""
        self.tools[name] = {
            "name": name,
            "description": description,
            "inputSchema": input_schema
        }
        self.tool_handlers[name] = handler

    def register_resource(self, uri: str, name: str, 
                          description: str, mime_type: str = "text/plain"):
        """注册一个MCP资源"""
        self.resources[uri] = {
            "uri": uri,
            "name": name,
            "description": description,
            "mimeType": mime_type
        }

    async def handle_message(self, raw_message: str) -> str | None:
        """处理一条JSON-RPC消息"""
        try:
            message = json.loads(raw_message)
        except json.JSONDecodeError:
            return JSONRPCMessage.create_error(0, -32700, "Parse error")

        method = message.get("method", "")
        msg_id = message.get("id")
        params = message.get("params", {})

        # 初始化阶段
        if method == "initialize":
            return self._handle_initialize(msg_id, params)

        # 初始化完成通知(无响应)
        if method == "notifications/initialized":
            logging.info("Client initialized")
            return None

        # 工具列出
        if method == "tools/list":
            return JSONRPCMessage.create_response(
                msg_id, {"tools": list(self.tools.values())}
            )

        # 工具调用
        if method == "tools/call":
            return await self._handle_tool_call(msg_id, params)

        # 资源列出
        if method == "resources/list":
            return JSONRPCMessage.create_response(
                msg_id, {"resources": list(self.resources.values())}
            )

        # 资源读取
        if method == "resources/read":
            return await self._handle_resource_read(msg_id, params)

        # 未知方法
        return JSONRPCMessage.create_error(
            msg_id, -32601, f"Method not found: {method}"
        )

    def _handle_initialize(self, msg_id: int, params: dict) -> str:
        """处理初始化请求"""
        protocol_version = params.get("protocolVersion", "2024-11-05")
        client_name = params.get("clientInfo", {}).get("name", "unknown")
        logging.info(f"Client connecting: {client_name} (v{protocol_version})")

        # 返回服务器能力声明
        return JSONRPCMessage.create_response(msg_id, {
            "protocolVersion": protocol_version,
            "capabilities": {
                "tools": {},
                "resources": {}
            },
            "serverInfo": {
                "name": self.server_name,
                "version": self.version
            }
        })

    async def _handle_tool_call(self, msg_id: int, params: dict) -> str:
        """处理工具调用请求"""
        tool_name = params.get("name", "")
        arguments = params.get("arguments", {})

        if tool_name not in self.tool_handlers:
            return JSONRPCMessage.create_error(
                msg_id, -32602, f"Unknown tool: {tool_name}"
            )

        try:
            handler = self.tool_handlers[tool_name]
            result = await handler(**arguments)
            return JSONRPCMessage.create_response(msg_id, {
                "content": [{"type": "text", "text": str(result)}]
            })
        except Exception as e:
            logging.error(f"Tool execution error: {e}")
            return JSONRPCMessage.create_error(
                msg_id, -32603, f"Internal error: {str(e)}"
            )

    async def _handle_resource_read(self, msg_id: int, params: dict) -> str:
        """处理资源读取请求"""
        uri = params.get("uri", "")
        if uri not in self.resources:
            return JSONRPCMessage.create_error(
                msg_id, -32602, f"Resource not found: {uri}"
            )
        # 返回资源内容(简化实现)
        return JSONRPCMessage.create_response(msg_id, {
            "contents": [{
                "uri": uri,
                "mimeType": self.resources[uri]["mimeType"],
                "text": f"内容来自资源: {uri}"
            }]
        })

3.4 Stdio传输层实现

MCP通过Stdio传输层实现进程间通信。服务器从stdin读取JSON-RPC消息,将响应写入stdout。

复制代码
import asyncio

class StdioTransport:
    """Stdio传输层"""

    def __init__(self, server: MCPServer):
        self.server = server

    async def read_line(self) -> str | None:
        """从stdin读取一行"""
        loop = asyncio.get_event_loop()
        line = await loop.run_in_executor(None, sys.stdin.readline)
        return line.strip() if line else None

    def write_message(self, message: str):
        """将消息写入stdout"""
        sys.stdout.write(message + "\n")
        sys.stdout.flush()

    async def run(self):
        """运行事件循环"""
        while True:
            line = await self.read_line()
            if not line:
                break

            response = await self.server.handle_message(line)
            if response:
                self.write_message(response)

3.5 注册具体工具

现在,让我们为服务器注册几个实用的工具:

复制代码
import subprocess
import os
from datetime import datetime

async def calculator(expression: str) -> str:
    """执行数学计算"""
    try:
        # 注意: 生产环境应使用更安全的方式
        result = eval(expression)
        return f"计算结果: {result}"
    except Exception as e:
        return f"计算错误: {str(e)}"

async def get_current_time(timezone: str = "Asia/Shanghai") -> str:
    """获取当前时间"""
    now = datetime.now()
    return f"当前时间: {now.strftime('%Y-%m-%d %H:%M:%S')} (时区: {timezone})"

async def list_directory(path: str = ".") -> str:
    """列出目录内容"""
    try:
        files = os.listdir(path)
        return "\n".join(files) if files else "(空目录)"
    except Exception as e:
        return f"读取目录失败: {str(e)}"

# 构建完整的服务器
def create_demo_server() -> MCPServer:
    server = MCPServer("demo-mcp-server", "1.0.0")

    server.register_tool(
        name="calculator",
        description="执行数学计算,支持四则运算和函数",
        input_schema={
            "type": "object",
            "properties": {
                "expression": {
                    "type": "string",
                    "description": "数学表达式,如 '2 + 2' 或 'sin(pi/2)'"
                }
            },
            "required": ["expression"]
        },
        handler=calculator
    )

    server.register_tool(
        name="get_current_time",
        description="获取指定时区的当前时间",
        input_schema={
            "type": "object",
            "properties": {
                "timezone": {
                    "type": "string",
                    "description": "时区名称,如 'Asia/Shanghai'",
                    "default": "Asia/Shanghai"
                }
            }
        },
        handler=get_current_time
    )

    server.register_tool(
        name="list_directory",
        description="列出指定目录的文件和文件夹",
        input_schema={
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "目录路径",
                    "default": "."
                }
            }
        },
        handler=list_directory
    )

    # 注册资源
    server.register_resource(
        uri="server://info",
        name="服务器信息",
        description="MCP服务器的基本信息"
    )

    return server

3.6 启动入口

复制代码
async def main():
    server = create_demo_server()
    transport = StdioTransport(server)
    logging.info(f"Starting MCP server: {server.server_name} v{server.version}")
    await transport.run()

if __name__ == "__main__":
    asyncio.run(main())

四、从零构建MCP客户端

有了服务器,我们还需要一个客户端来连接和交互。同样,我们不依赖任何SDK。

复制代码
import json
import asyncio
import subprocess
from typing import Any

class MCPClient:
    """从零实现的MCP客户端"""

    def __init__(self):
        self.process: subprocess.Popen | None = None
        self.request_id = 0
        self.pending_requests: dict[int, asyncio.Future] = {}

    async def connect_stdio(self, command: str, *args: str):
        """通过Stdio连接到MCP服务器"""
        self.process = await asyncio.create_subprocess_exec(
            command, *args,
            stdin=asyncio.subprocess.PIPE,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE
        )

        # 启动消息读取循环
        asyncio.create_task(self._read_loop())

    async def _read_loop(self):
        """持续读取服务器响应"""
        while self.process and self.process.stdout:
            line = await self.process.stdout.readline()
            if not line:
                break
            try:
                response = json.loads(line.decode().strip())
                msg_id = response.get("id")
                if msg_id in self.pending_requests:
                    future = self.pending_requests.pop(msg_id)
                    future.set_result(response)
            except json.JSONDecodeError:
                continue

    async def send_request(self, method: str, params: dict = None) -> dict:
        """发送JSON-RPC请求并等待响应"""
        self.request_id += 1
        request_id = self.request_id

        message = {
            "jsonrpc": "2.0",
            "id": request_id,
            "method": method
        }
        if params is not None:
            message["params"] = params

        future = asyncio.get_event_loop().create_future()
        self.pending_requests[request_id] = future

        # 发送消息
        self.process.stdin.write((json.dumps(message) + "\n").encode())
        await self.process.stdin.drain()

        # 等待响应
        response = await future

        if "error" in response:
            error = response["error"]
            raise Exception(f"MCP error [{error['code']}]: {error['message']}")

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

    async def initialize(self):
        """初始化MCP连接"""
        result = await self.send_request("initialize", {
            "protocolVersion": "2024-11-05",
            "capabilities": {},
            "clientInfo": {
                "name": "my-mcp-client",
                "version": "1.0.0"
            }
        })
        # 发送初始化完成通知
        self.process.stdin.write(
            (json.dumps({
                "jsonrpc": "2.0",
                "method": "notifications/initialized"
            }) + "\n").encode()
        )
        await self.process.stdin.drain()
        return result

    async def list_tools(self) -> list[dict]:
        """列出所有可用工具"""
        result = await self.send_request("tools/list")
        return result.get("tools", [])

    async def call_tool(self, name: str, arguments: dict = None) -> Any:
        """调用指定工具"""
        params = {"name": name}
        if arguments:
            params["arguments"] = arguments
        result = await self.send_request("tools/call", params)
        return result

    async def list_resources(self) -> list[dict]:
        """列出所有可用资源"""
        result = await self.send_request("resources/list")
        return result.get("resources", [])

    async def close(self):
        """关闭连接"""
        if self.process:
            self.process.terminate()
            await self.process.wait()

4.1 使用客户端交互

复制代码
async def demo_client_interaction():
    """演示客户端与服务器的交互"""

    # 1. 连接到服务器
    client = MCPClient()
    await client.connect_stdio("python", "mcp_server.py")

    # 2. 初始化连接
    init_result = await client.initialize()
    print(f"已连接服务器: {init_result['serverInfo']['name']}")
    print(f"协议版本: {init_result['protocolVersion']}")
    print(f"能力: {list(init_result['capabilities'].keys())}")
    print()

    # 3. 列出所有工具
    tools = await client.list_tools()
    print(f"可用工具 ({len(tools)}):")
    for tool in tools:
        print(f"  - {tool['name']}: {tool['description']}")
    print()

    # 4. 调用工具
    print("调用 calculator 工具: 2 + 3 * 4")
    result = await client.call_tool("calculator", {"expression": "2 + 3 * 4"})
    print(f"结果: {result['content'][0]['text']}")
    print()

    print("调用 get_current_time 工具")
    result = await client.call_tool("get_current_time")
    print(f"结果: {result['content'][0]['text']}")
    print()

    # 5. 列出资源
    resources = await client.list_resources()
    print(f"可用资源 ({len(resources)}):")
    for res in resources:
        print(f"  - {res['name']} ({res['uri']})")

    # 6. 关闭连接
    await client.close()

# 运行演示
# asyncio.run(demo_client_interaction())

五、使用官方SDK快速构建(进阶)

理解底层协议后,在实际开发中我们通常会使用MCP SDK来加速开发。MCP提供了Python、TypeScript、Java、Kotlin等多语言SDK。

5.1 安装Python SDK

复制代码
pip install "mcp[cli]"

5.2 使用FastMCP构建服务器(简化版)

MCP SDK从1.2.0版本开始提供了 FastMCP 类,使用Python类型提示和docstring自动生成工具定义:

复制代码
from mcp.server.fastmcp import FastMCP
import httpx
import json

# 创建MCP服务器实例
mcp = FastMCP("data-analyzer")

@mcp.tool()
async def fetch_json_data(url: str) -> str:
    """获取JSON格式的远程数据

    Args:
        url: 数据源的URL地址
    """
    async with httpx.AsyncClient() as client:
        response = await client.get(url, timeout=30.0)
        response.raise_for_status()
        data = response.json()
        return json.dumps(data, indent=2, ensure_ascii=False)

@mcp.tool()
async def analyze_text(text: str, operation: str = "word_count") -> str:
    """分析文本的基本统计信息

    Args:
        text: 待分析的文本内容
        operation: 分析操作: word_count(词数), char_count(字符数), 
                   line_count(行数), all(全部)
    """
    operations = {
        "word_count": f"词数: {len(text.split())}",
        "char_count": f"字符数: {len(text)}",
        "line_count": f"行数: {len(text.splitlines())}",
        "all": (
            f"词数: {len(text.split())}\n"
            f"字符数: {len(text)}\n"
            f"行数: {len(text.splitlines())}"
        )
    }
    return operations.get(operation, f"未知操作: {operation}")

@mcp.resource("config://app/settings")
def get_settings() -> str:
    """返回一个模拟的应用配置(资源示例)"""
    return json.dumps({
        "app_name": "Data Analyzer",
        "version": "2.0.0",
        "max_request_size": "10MB",
        "enable_logging": True
    }, indent=2)

if __name__ == "__main__":
    # 使用Stdio传输运行
    mcp.run(transport="stdio")

运行方式:

复制代码
python data_analyzer_server.py

5.3 使用异步客户端SDK

复制代码
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import asyncio

async def main():
    # 配置服务器参数
    server_params = StdioServerParameters(
        command="python",
        args=["data_analyzer_server.py"]
    )

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            # 初始化
            await session.initialize()

            # 列出工具
            tools = await session.list_tools()
            print("可用工具:")
            for tool in tools:
                print(f"  {tool.name}: {tool.description}")

            # 调用工具
            result = await session.call_tool(
                "analyze_text",
                arguments={
                    "text": "Hello MCP! This is a test.",
                    "operation": "all"
                }
            )
            print(f"\n分析结果:\n{result.content[0].text}")

            # 读取资源
            resource = await session.read_resource("config://app/settings")
            print(f"\n配置信息:\n{resource.contents[0].text}")

asyncio.run(main())

六、MCP与DeepSeek的深度结合

到目前为止,我们构建的MCP服务器和客户端都是通用的。接下来,让我们看看如何将MCP与DeepSeek大模型结合,真正打造一个可以自主调用工具的AI Agent。

6.1 架构设计

将DeepSeek与MCP结合,本质上是构建一个ReAct(推理+行动)循环。DeepSeek大模型作为推理核心,MCP作为行动接口。

复制代码
用户输入 → DeepSeek大模型 → MCP客户端 → MCP服务器 → 外部工具
                              ↑                    ↓
                          └─── 工具调用结果 ────────┘

核心流程分为五个步骤:

第一步:意图理解。DeepSeek接收用户的自然语言输入,通过其强大的语义理解能力解析出用户的真实意图。例如用户说"帮我看看今天的代码提交记录",DeepSeek能理解这需要调用Git工具。

第二步:工具选择 。结合MCP客户端提供的工具列表(通过tools/list获取),DeepSeek判断应该使用哪个或哪些工具来完成任务。这个过程与人类选择工具的逻辑相似------"我需要查看Git记录,所以选择'git_log'工具"。

第三步:参数生成 。DeepSeek根据用户需求生成工具所需的参数。例如git_log工具可能需要branch(分支名)、since(起始日期)等参数。DeepSeek的推理能力确保了参数生成的准确性。

第四步:工具执行。DeepSeek输出结构化的函数调用(function call),MCP客户端将其转换成JSON-RPC消息发送给MCP服务器执行。这一步完全是协议层面的通信,DeepSeek不直接参与。

第五步:结果综合。MCP服务器返回工具执行结果后,DeepSeek将结果与用户的问题上下文结合,生成自然语言的回复。例如拿到Git提交记录后,它会整理成易读的格式,甚至分析提交频率和贡献者统计。

如果一次工具调用不够,整个循环会重复进行,直到任务完成或达到预设的最大轮次。这就是ReAct循环的核心思想------推理、行动、观察、再推理。

6.2 集成代码示例

复制代码
from openai import OpenAI
import json

# 配置DeepSeek API(假设使用兼容OpenAI接口的DeepSeek API)
client = OpenAI(
    base_url="https://api.deepseek.com/v1",
    api_key="your-deepseek-api-key"
)

class DeepSeekMCPAgent:
    """集成DeepSeek与MCP的智能体"""

    def __init__(self, mcp_client: MCPClient, system_prompt: str = None):
        self.mcp_client = mcp_client
        self.system_prompt = system_prompt or (
            "你是一个智能助手,可以使用各种工具来帮助用户完成任务。"
            "当你需要调用工具时,请使用函数调用格式。"
        )
        self.tools = []

    async def load_tools(self):
        """从MCP服务器加载工具定义"""
        mcp_tools = await self.mcp_client.list_tools()
        self.tools = [
            {
                "type": "function",
                "function": {
                    "name": t["name"],
                    "description": t["description"],
                    "parameters": t["inputSchema"]
                }
            }
            for t in mcp_tools
        ]

    async def chat(self, user_message: str, max_turns: int = 5) -> str:
        """多轮对话,支持工具调用"""
        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": user_message}
        ]

        for turn in range(max_turns):
            # 调用DeepSeek
            response = client.chat.completions.create(
                model="deepseek-chat",
                messages=messages,
                tools=self.tools if self.tools else None,
                tool_choice="auto" if self.tools else None
            )

            msg = response.choices[0].message

            # 如果模型决定直接回复,返回结果
            if not msg.tool_calls:
                return msg.content

            # 添加模型响应到对话
            messages.append(msg)

            # 处理每个工具调用
            for tool_call in msg.tool_calls:
                function_name = tool_call.function.name
                function_args = json.loads(tool_call.function.arguments)

                # 通过MCP调用工具
                try:
                    result = await self.mcp_client.call_tool(
                        function_name, function_args
                    )
                    tool_result = result["content"][0]["text"]
                except Exception as e:
                    tool_result = f"工具调用失败: {str(e)}"

                # 添加工具调用结果到对话
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": tool_result
                })

        return "已达到最大对话轮次"

6.3 效果演示

当用户问"帮我计算一下当前时间加上8小时是什么时候,然后列出当前目录有哪些文件"时,DeepSeek会:

  1. 调用 get_current_time 工具获取当前时间
  2. 根据时间结果计算加8小时
  3. 调用 list_directory 工具列出文件
  4. 综合所有结果回复用户

这种自主调用工具的能力,正是MCP赋予AI Agent的核心价值。


七、MCP的高级特性

7.1 能力协商

MCP的初始化阶段包含能力协商。服务器声明自己支持哪些功能(工具、资源、提示模板),客户端也可以声明自己的支持(如采样能力):

复制代码
# 客户端声明支持采样
server_result = await client.send_request("initialize", {
    "protocolVersion": "2024-11-05",
    "capabilities": {
        "sampling": {}  # 支持采样请求
    },
    "clientInfo": {"name": "advanced-client", "version": "1.0.0"}
})

7.2 实时通知

当工具或资源发生变化时,服务器可以主动发送通知:

复制代码
# 服务器端:通知工具列表已变更
transport.write_message(
    JSONRPCMessage.create_notification(
        "notifications/tools/list_changed"
    )
)

7.3 进度追踪

对于长时间运行的操作,MCP支持进度追踪:

复制代码
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {
    "progressToken": "token-001",
    "progress": 50,
    "total": 100
  }
}

7.4 任务(Experimental)

MCP的实验性功能------任务,提供持久化的执行包装器,支持异步结果检索和状态跟踪:

复制代码
{
  "jsonrpc": "2.0",
  "method": "tasks/schedule",
  "params": {
    "name": "batch-process",
    "uri": "tasks://batch/process-001"
  }
}

八、最佳实践与生产化建议

8.1 安全性

  • Stdio服务器:绝不向stdout输出日志(会破坏JSON-RPC协议),始终使用stderr或文件日志
  • HTTP服务器:使用OAuth 2.0进行认证授权
  • 输入验证:严格校验工具参数,避免命令注入
  • 最小权限:MCP服务器只开放必要的工具和资源

8.2 日志规范

复制代码
# ✅ 正确的日志方式(Stdio传输)
import sys
import logging
logging.basicConfig(level=logging.INFO, stream=sys.stderr)

# ✅ 或者使用print重定向到stderr
print("Server started", file=sys.stderr)

# ❌ 错误方式 ------ 会破坏协议
print("Server started")  # 输出到stdout

8.3 错误处理

  • 始终捕获工具执行异常并返回友好错误信息
  • 使用标准JSON-RPC错误码
  • 实现超时机制,防止工具调用挂起
  • 在MCP客户端实现重试逻辑,处理瞬态网络故障

8.4 与Function Calling的对比

MCP与OpenAI提出的Function Calling模式既有相似之处,又有本质区别:

维度 MCP Function Calling
标准化程度 开放协议,跨平台 OpenAI专有格式
工具发现 tools/list动态发现 需预配置工具定义
传输层 Stdio/HTTP双模式 仅HTTP
资源访问 原生支持Resources原语 无标准资源机制
生态兼容 Claude、ChatGPT、VS Code等 仅OpenAI兼容API
安全控制 内置能力协商、OAuth 依赖应用层实现

简单来说,Function Calling是OpenAI API的一个特性,而MCP是一个独立的开放协议。MCP的设计目标更宏大------它要成为AI应用连接外部世界的事实标准。从生态兼容性来看,支持MCP的客户端越来越多,而Function Calling仍然局限于OpenAI生态。

8.5 与LangChain工具的对比

LangChain是另一个广泛使用的AI应用框架,它也提供了工具调用机制。与MCP相比:

  • LangChain的工具:框架内嵌的抽象,需要通过LangChain的组件链来调用,耦合度较高
  • MCP的工具:协议层的标准化接口,无关框架,任何支持MCP的客户端都可以调用

LangChain更像是一个全栈开发框架,而MCP专注于定义AI与工具之间的通信协议。实际项目中,两者也可以结合使用------LangChain作为协调层,MCP作为工具层的标准化接口。

8.6 测试与调试

MCP官方提供了Inspector工具用于调试MCP服务器:

复制代码
# 使用MCP Inspector调试服务器
npx @modelcontextprotocol/inspector python my_server.py

Inspector提供了一个Web界面,可以查看:

  • 工具列表和参数定义

  • 发送测试工具调用请求

  • 查看JSON-RPC消息日志

  • 监控服务器资源使用情况

对于开发者来说,这是一个非常实用的调试工具,可以快速验证MCP服务器的行为是否符合预期。

8.4 部署策略

  • 本地MCP服务器:适用于文件系统、本地数据库等敏感操作
  • 远程MCP服务器:适用于云API、SaaS服务集成
  • 建议:对每个数据源/工具使用独立的MCP服务器,便于隔离和扩展

九、MCP生态全景与未来展望

9.1 当前生态格局

截至2026年中,MCP生态已经初具规模:

主流AI客户端全面支持

  • Claude Desktop与Claude Code :Anthropic自家产品,对MCP的支持最原生和完善

  • ChatGPT :2025年晚些时候开始支持MCP协议连接

  • Visual Studio Code :通过内置的MCP支持,可以让Copilot直接调用外部工具

  • Cursor :作为AI原生IDE,对MCP有着深度的集成

  • JetBrains IDE:2026年初开始支持MCP

官方MCP服务器参考实现

Anthropic官方维护了多个参考MCP服务器实现,涵盖常见场景:

  • server-filesystem:安全的文件系统操作(读、写、遍历)

  • server-github:GitHub API集成(Issue、PR、代码搜索)

  • server-postgres:PostgreSQL数据库查询

  • server-sqlite:SQLite数据库交互

  • server-puppeteer:浏览器自动化

  • server-sentry:错误监控和告警查询

  • server-slack:Slack消息和工作空间管理

这些参考实现以MIT协议开源,是学习MCP服务器开发的最佳实践参考。

9.2 典型应用场景

场景一:智能代码审查

将MCP服务器连接到代码仓库(GitHub/GitLab),AI就可以直接读取PR差异、检查代码规范、自动生成审查意见。开发者只需说"帮我审查这个PR",AI自动完成全部工作。

场景二:数据分析助手

连接数据库MCP服务器,AI可以执行SQL查询、生成可视化图表、分析数据趋势。不再需要手动编写复杂的SQL语句,用自然语言就能完成数据探索。

场景三:自动化运维

连接服务器监控和告警MCP服务器,AI可以实时监听系统状态、分析日志文件、自动执行故障恢复脚本。这是一种从"被动告警"到"主动运维"的范式转变。

场景四:个人知识管理

连接本地文件系统和笔记应用的MCP服务器,AI可以像个人秘书一样管理文档、整理笔记、检索信息。

9.3 未来趋势

MCP的发展正遵循着与HTTP、USB等标准化协议相似的轨迹:从初期的碎片化集成,走向统一的标准化协议,最终成为AI Agent时代的基础设施。

几个值得关注的方向:

  1. MCP Registry:类似Docker Hub的MCP服务器注册中心,开发者可以发现、安装和分享MCP服务器
  2. MCP Gateway:企业级的MCP网关,统一管理多个MCP服务器的访问控制、限流和监控
  3. 多模态MCP:扩展MCP协议以支持图像、音频等非文本数据的传输
  4. 标准化安全认证:OAuth 2.0 + MCP的深度集成,形成企业级安全标准

结语

本文从零开始,完整地实现了一个MCP(Model Context Protocol)服务器和客户端。从JSON-RPC协议底层,到Stdio传输层,再到工具注册和调用,我们没有依赖任何SDK,完全理解了MCP的核心原理。

通过本文,你应该掌握了:

  1. MCP的基础架构 --- 主机、客户端、服务器三层架构
  2. JSON-RPC 2.0协议 --- MCP的底层通信协议
  3. 从零构建MCP服务器 --- 纯Python实现,理解协议本质
  4. 从零构建MCP客户端 --- 连接、初始化、工具调用全流程
  5. 官方SDK快速开发 --- FastMCP的简化用法
  6. 与DeepSeek集成 --- 打造真正的AI Agent

MCP正在成为AI Agent时代的基础设施。就像HTTP协议统一了Web通信、USB-C统一了设备连接一样,MCP正在统一AI应用与外部世界的交互标准。随着Claude、ChatGPT、VS Code等主流平台全面支持MCP,以及社区贡献的数千个MCP服务器生态的成熟,AI从"问答工具"到"数字助手"的进化正在加速。

未来已来,只是分布不均。掌握MCP,就是掌握AI Agent时代的"接口"能力。


实战指南 :本文配套完整代码示例。更多DeepSeek大模型与AI Agent实战教程参见:DeepSeek 实战指南系列

相关推荐
良木生香19 小时前
【C++初阶】STL—— Stack & Queue 从入门到精通:容器适配器、迭代器与经典面试题
java·开发语言·c++·算法·zookeeper
lbb 小魔仙19 小时前
Python 性能分析工具实战:cProfile、memory_profiler、line_profiler 使用指南
开发语言·python
小小晓.19 小时前
C++小白记:vector
开发语言·c++·算法
小刘学技术19 小时前
AI人工智能决策树分类器:原理、实现与应用
开发语言·人工智能·python·算法·决策树·机器学习·数据挖掘
脱胎换骨-军哥19 小时前
C++ 嵌入式编程实例:从寄存器操作到底层驱动开发
开发语言·c++·驱动开发
鱼子星_20 小时前
【C++】vector
开发语言·c++·笔记·stl
double_eggm20 小时前
uniapp.3
开发语言·前端·javascript
白玉cfc20 小时前
熟悉Objective-C
开发语言·ios·objective-c
abcy07121320 小时前
flink窗口类型
开发语言·python·算法