一篇文章教你学会MCP

MCP 是 Anthropic 推出的开源协议,旨在标准化 AI 模型与外部工具、数据源之间的连接方式。它就像 AI 世界的 "USB-C 接口"------一个统一的标准,让任何模型都能无缝接入任何工具。

tip 读完本文你会收获什么

  • 彻底理解 MCP 的架构设计思想
  • 掌握 Python / TypeScript 双语言 Server 开发
  • 学会 Client 与 Server 的完整连接流程
  • 了解资源订阅、动态工具、反向采样等高级特性
  • 收获可直接落地的生产级代码模板 :::

📖 目录

  • [1. 什么是 MCP?](#1. 什么是 MCP? "#1-%E4%BB%80%E4%B9%88%E6%98%AF-mcp")
  • [2. 为什么需要 MCP?](#2. 为什么需要 MCP? "#2-%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81-mcp")
  • [3. 核心架构](#3. 核心架构 "#3-%E6%A0%B8%E5%BF%83%E6%9E%B6%E6%9E%84")
  • [4. 核心概念详解](#4. 核心概念详解 "#4-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E8%AF%A6%E8%A7%A3")
  • [5. 传输机制](#5. 传输机制 "#5-%E4%BC%A0%E8%BE%93%E6%9C%BA%E5%88%B6")
  • [6. 动手实战:构建 MCP 服务端](#6. 动手实战:构建 MCP 服务端 "#6-%E5%8A%A8%E6%89%8B%E5%AE%9E%E6%88%98%E6%9E%84%E5%BB%BA-mcp-%E6%9C%8D%E5%8A%A1%E7%AB%AF")
  • [7. 动手实战:构建 MCP 客户端](#7. 动手实战:构建 MCP 客户端 "#7-%E5%8A%A8%E6%89%8B%E5%AE%9E%E6%88%98%E6%9E%84%E5%BB%BA-mcp-%E5%AE%A2%E6%88%B7%E7%AB%AF")
  • [8. 服务端与客户端如何连接](#8. 服务端与客户端如何连接 "#8-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E4%B8%8E%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%A6%82%E4%BD%95%E8%BF%9E%E6%8E%A5")
  • [9. Claude Code 中使用 MCP](#9. Claude Code 中使用 MCP "#9-claude-code-%E4%B8%AD%E4%BD%BF%E7%94%A8-mcp")
  • [10. 高级主题](#10. 高级主题 "#10-%E9%AB%98%E7%BA%A7%E4%B8%BB%E9%A2%98")
  • [11. 常见问题与调试](#11. 常见问题与调试 "#11-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E4%B8%8E%E8%B0%83%E8%AF%95")
  • [12. 最佳实践](#12. 最佳实践 "#12-%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5")

1. 什么是 MCP?

MCP(Model Context Protocol,模型上下文协议) 是一个开放的、标准化的通信协议,用于在 AI 大语言模型(LLM)与外部世界之间建立双向连接。

简单来说,MCP 解决了 LLM 的 "信息孤岛" 问题------模型本身只知道训练数据中的内容,无法访问实时数据、文件系统、数据库或 API。MCP 提供了一种标准方式,让模型能够:

  • 调用外部工具(Tool Calling)------ 让 AI 执行操作
  • 读取外部资源(Resource Reading)------ 让 AI 获取实时数据
  • 使用预置提示模板(Prompt Templates)------ 标准化交互流程
  • 沙箱化安全执行------ 内置安全隔离机制

传统方式 vs MCP 方式

scss 复制代码
┌─────────────────────────────────────────────────────┐
│                    传统方式                           │
│                                                      │
│   AI 模型  ──── 定制胶水代码 ──── 工具 A             │
│            ──── 定制胶水代码 ──── 工具 B             │
│            ──── 定制胶水代码 ──── 工具 C             │
│                                                      │
│   每个工具都需要单独集成,维护成本高                    │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│                    MCP 方式                           │
│                                                      │
│                     ┌─────────────────┐              │
│   AI 模型 ◄────────│   MCP 客户端    │              │
│                     └──────┬──────────┘              │
│                            │ MCP 协议                 │
│              ┌─────────────┼─────────────┐           │
│              │             │             │           │
│         ┌────▼────┐  ┌────▼────┐  ┌────▼────┐      │
│         │MCP 服务端│  │MCP 服务端│  │MCP 服务端│      │
│         │ (文件系统)│  │ (数据库) │  │ (API)   │      │
│         └─────────┘  └─────────┘  └─────────┘      │
│                                                      │
│   统一标准,一次集成,处处可用                           │
└─────────────────────────────────────────────────────┘

:::tip MCP 的本质 MCP 是一个 标准化的中间层协议。它在 AI 模型和具体工具之间建立了一个通用接口,让模型不需要关心工具的内部实现,也让工具开发者不需要针对每个模型做适配。这就是 "USB-C 接口" 比喻的含义------所有设备用同一个接口通信。 :::


2. 为什么需要 MCP?

2.1 痛点分析

痛点 传统方案 MCP 方案
集成成本 每个工具都要写定制代码 统一协议,即插即用
安全隔离 各方案自行实现 协议内置沙箱机制
跨平台 绑定特定平台/框架 语言无关,平台无关
生态碎片 工具无法复用 社区共享 MCP Server
维护成本 N×M 的集成矩阵 1 个标准协议

2.2 两种方式的代码对比

python 复制代码
# 🔴 传统方式:每个工具都要单独适配
def call_github_api(prompt):
    # 100行 GitHub 特定代码
    pass

def call_database(prompt):
    # 100行数据库特定代码
    pass

def call_file_system(prompt):
    # 100行文件系统特定代码
    pass

# 🟢 MCP 方式:统一接口
# 1. 启动 MCP 服务端(谁都可以写,任何人可以用)
# 2. MCP 客户端自动发现并连接
# 3. AI 模型通过统一接口调用所有工具

:::warning 传统方案的致命缺陷 在没有 MCP 之前,每个 AI 应用如果要接入 10 个工具,就需要写 10 套适配代码。10 个 AI 应用各自接入 10 个工具,就是 100 套适配代码。这是典型的 M×N 集成灾难------而 MCP 用一个协议把这个复杂度降到了 O(N+M)。 :::


3. 核心架构

3.1 架构总览

arduino 复制代码
┌────────────────────────────────────────────────────────────────┐
│                        Host(宿主应用)                           │
│                                                                  │
│  ┌─────────────┐     ┌──────────────────────┐                   │
│  │             │     │                      │                   │
│  │   AI 模型   │◄───►│   MCP Client         │                   │
│  │  (Claude/   │     │   (MCP 客户端)        │                   │
│  │   GPT 等)   │     │                      │                   │
│  └─────────────┘     └──────────┬───────────┘                   │
│                                 │                                │
└─────────────────────────────────┼────────────────────────────────┘
                                  │
                 JSON-RPC 2.0 over stdio / SSE / Streamable HTTP
                                  │
         ┌────────────────────────┼────────────────────────┐
         │                        │                        │
┌────────▼────────┐    ┌──────────▼──────────┐    ┌───────▼───────┐
│  MCP Server A   │    │   MCP Server B      │    │ MCP Server C  │
│  ┌───────────┐  │    │  ┌───────────────┐  │    │ ┌───────────┐ │
│  │ 📦 Tools  │  │    │  │   📦 Tools    │  │    │ │ 📦 Tools  │ │
│  │ 📁 Resources│ │    │  │  📁 Resources │  │    │ │📁 Resources│ │
│  │ 💬 Prompts│  │    │  │  💬 Prompts   │  │    │ │💬 Prompts │ │
│  └───────────┘  │    │  └───────────────┘  │    │ └───────────┘ │
│  文件系统操作    │    │   GitHub API 操作   │    │ 数据库操作    │
└─────────────────┘    └─────────────────────┘    └──────────────┘

3.2 三层角色

角色 职责 实例
🏠 Host(宿主) 运行 AI 模型、管理 MCP 客户端、协调交互 Claude Desktop、VS Code、自定义 App
🔌 Client(客户端) 与 Server 建立连接、协议路由、工具发现 MCP SDK 中的 Client
⚙️ Server(服务端) 暴露 Tools / Resources / Prompts,执行实际操作 文件系统 Server、GitHub Server、数据库 Server

4. 核心概念详解

MCP 定义了 三大原语(Primitives),构成了 AI 与外部世界交互的完整体系。理解这三个概念是掌握 MCP 的关键。

4.1 🛠️ Tools(工具)

Tools 是模型可调用的"函数",类比 OpenAI 的 Function Calling。模型通过自然语言理解用户意图,自主决定调用哪个工具。

typescript 复制代码
// 定义一个 "查询天气" 的工具
{
  name: "get_weather",
  description: "获取指定城市的实时天气信息",
  inputSchema: {
    type: "object",
    properties: {
      city: {
        type: "string",
        description: "城市名称,如 'Beijing', 'Shanghai'"
      }
    },
    required: ["city"]
  }
}

Tools 的执行流程:

arduino 复制代码
用户提问
    │
    ▼
AI 分析意图 ────────────── 直接回答
    │                          ▲
    │ 需要工具                  │
    ▼                          │
Client 发送 tool_call ─────────┘
    │                    AI 解读结果回复用户
    ▼                          ▲
Server 执行工具 ───────────────┘
    │              Client 传回结果
    ▼
返回结构化结果

4.2 📁 Resources(资源)

Resources 是模型可读取的「结构化数据」,以 URI 标识,支持多种 MIME 类型。它就像模型的"文件系统"。

typescript 复制代码
// Resource 示例:数据库表结构
{
  uri: "db://schema/users",
  name: "Users Table Schema",
  description: "users 表的列定义及其说明",
  mimeType: "application/json"
}

// Resource 返回的内容
{
  "contents": [
    {
      "uri": "db://schema/users",
      "mimeType": "application/json",
      "text": {
        "table": "users",
        "columns": [
          {"name": "id",       "type": "INTEGER",      "primary": true},
          {"name": "email",    "type": "VARCHAR(255)",  "unique": true},
          {"name": "created_at","type": "TIMESTAMP"}
        ]
      }
    }
  ]
}

:::tip Tools vs Resources,如何区分?

  • Tools = 执行动作(有副作用),如"创建文件"、"发送邮件"、"更新数据库"
  • Resources = 读取数据(无副作用),如"查看文件内容"、"获取表结构"、"读取配置"

简单记忆:Tool 是"动词",Resource 是"名词"。 :::

4.3 💬 Prompts(提示模板)

Prompts 是预定义的提示词模板,包含参数化占位符,帮助用户快速发起标准化的交互。

typescript 复制代码
// 定义一个 Code Review 提示模板
{
  name: "code_review",
  description: "对代码片段进行专业的 Code Review",
  arguments: [
    {
      name: "language",
      description: "编程语言",
      required: true
    },
    {
      name: "code",
      description: "待审查的代码",
      required: true
    }
  ]
}

:::tip 三大原语的数据流向

  • Tools:Host → Client → Server → 执行 → 结果返回 → Host
  • Resources:Host → Client → Server → 数据返回 → Host
  • Prompts:Host → Client → Server → 模板返回 → Host → 填入 AI 上下文

三者都是 Client 发起、Server 响应,但用途完全不同。 :::


5. 传输机制

MCP 支持多种传输层,适应不同场景:

5.1 传输方式对比

传输方式 适用场景 优点 缺点
stdio 本地进程通信 简单、零配置、最安全 仅限本地
SSE 远程 HTTP 通信 支持远程、防火墙友好 单向推送
Streamable HTTP 现代远程通信 双向流、高效 需要 HTTP/2

5.2 stdio 传输------最常用的方式

arduino 复制代码
┌──────────────────┐          ┌──────────────────┐
│   MCP Client     │  stdin   │   MCP Server     │
│                  ├─────────►│                  │
│  (父进程)        │  stdout  │  (子进程)         │
│                  │◄─────────┤                  │
└──────────────────┘          └──────────────────┘

通信方式:JSON-RPC 2.0 消息流入/流出
启动方式:Client 作为父进程 spawn Server 进程

:::warning stdio 传输的局限 stdio 模式下,Server 的 stdout 全被 MCP 协议占用。所以 日志必须输出到 stderr,否则会破坏 JSON-RPC 消息格式,导致通信失败。这是新手最常见的坑! :::

5.3 JSON-RPC 2.0 消息格式

MCP 的所有通信都基于 JSON-RPC 2.0,一共四种消息类型:

json 复制代码
// 📤 请求(Request)------ 有 id,期待响应
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": { "city": "Beijing" }
  }
}

// 📥 成功响应(Response)------ 带 result
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "北京当前天气:晴,温度 28°C,湿度 45%"
      }
    ]
  }
}

// 📥 错误响应(Error)------ 带 error
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": "city 参数不能为空"
  }
}

// 📢 通知(Notification)------ 无 id,无需回复
{
  "jsonrpc": "2.0",
  "method": "notifications/cancelled",
  "params": { "requestId": 1 }
}

6. 动手实战:构建 MCP 服务端

下面分别使用 Python(FastMCP)TypeScript(官方 SDK) 构建一个完整的 MCP Server------包含天气查询、网络搜索、数学计算、文件读取等功能。

6.1 Python 版本 ------ 推荐入门选择

python 复制代码
# server.py - 天气查询 MCP Server
"""
一个完整的 MCP Server 示例
功能:查询天气 + 文件读取 + 数学计算
"""

from mcp.server.fastmcp import FastMCP
import httpx
import json
from pathlib import Path

# ═══════════════════════════════════
# 1️⃣ 创建 MCP Server 实例
# ═══════════════════════════════════

mcp = FastMCP("我的助手服务")

# ═══════════════════════════════════
# 🛠️ 工具定义
# ═══════════════════════════════════

@mcp.tool()
async def get_weather(city: str) -> str:
    """获取指定城市的实时天气信息。

    Args:
        city: 城市名称,如 'Beijing', 'Tokyo', 'New York'
    """
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"https://wttr.in/{city}?format=j1",
            timeout=10.0
        )
        data = response.json()
        current = data["current_condition"][0]

        return (
            f"🌍 {city} 的天气:\n"
            f"  🌡️  温度:{current['temp_C']}°C\n"
            f"  💧 湿度:{current['humidity']}%\n"
            f"  🌤️  天气:{current['weatherDesc'][0]['value']}\n"
            f"  💨 风速:{current['windspeedKmph']} km/h"
        )

@mcp.tool()
async def search_web(query: str, num_results: int = 5) -> str:
    """搜索网络信息。

    Args:
        query: 搜索关键词
        num_results: 返回结果数量,默认5条
    """
    return (
        f"🔍 关于 '{query}' 的搜索结果(模拟):\n" +
        "\n".join(
            [f"  {i+1}. 结果 {i+1} - {query}"
             for i in range(num_results)]
        )
    )

@mcp.tool()
async def calculate(expression: str) -> str:
    """执行数学计算。

    Args:
        expression: 数学表达式,如 '2 + 3 * 4', 'sqrt(16)'
    """
    import math
    try:
        # ⚠️ 安全注意:生产环境需更严格的表达式校验
        allowed_names = {
            k: v for k, v in math.__dict__.items()
            if not k.startswith("__")
        }
        result = eval(
            expression,
            {"__builtins__": {}},
            allowed_names
        )
        return f"📐 {expression} = {result}"
    except Exception as e:
        return f"❌ 计算错误: {str(e)}"

# ═══════════════════════════════════
# 📁 资源定义
# ═══════════════════════════════════

@mcp.resource("file://docs/{filename}")
async def get_document(filename: str) -> str:
    """读取文档文件内容。"""
    docs_dir = Path("./docs")
    file_path = docs_dir / filename

    if not file_path.exists():
        return f"❌ 文件不存在: {filename}"
    if not file_path.is_file():
        return f"❌ 不是有效文件: {filename}"

    content = file_path.read_text(encoding="utf-8")
    return f"📄 {filename}:\n\n{content}"

@mcp.resource("config://settings")
async def get_settings() -> str:
    """获取应用配置信息。"""
    return json.dumps({
        "version": "1.0.0",
        "language": "zh-CN",
        "timezone": "Asia/Shanghai",
        "features": ["weather", "search", "calculator"]
    }, ensure_ascii=False, indent=2)

# ═══════════════════════════════════
# 💬 提示模板定义
# ═══════════════════════════════════

@mcp.prompt()
async def code_review(language: str, code: str) -> str:
    """Code Review 提示模板"""
    return f"""请对以下 {language} 代码进行专业的 Code Review:

代码:

{code}

python 复制代码
请从以下维度评审:
1. 🔒 安全性:是否存在安全漏洞
2. ⚡ 性能:是否有性能瓶颈
3. 📖 可读性:代码是否清晰易懂
4. 🧩 设计:架构是否合理
5. 🧪 测试:是否易于测试
"""

@mcp.prompt()
async def summarize_article(content: str) -> str:
    """文章摘要提示模板"""
    return f"""请对以下文章进行总结:

{content}

请提供:
- 📌 一句话总结
- 🔑 3-5 个关键要点
- 💡 个人见解
"""

# ═══════════════════════════════════
# 🚀 启动 Server
# ═══════════════════════════════════

if __name__ == "__main__":
    mcp.run(transport="stdio")

:::tip FastMCP vs 原始 SDK FastMCP 是 Python SDK 的上层封装,用装饰器 @mcp.tool() / @mcp.resource() / @mcp.prompt() 即可注册,代码量减少 60%。如果是简单项目,优先使用 FastMCP ;需要更细粒度控制时再用底层 Server 类。 :::

6.2 TypeScript/Node.js 版本

typescript 复制代码
// server.ts - MCP Server TypeScript 实现
import { McpServer }
  from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport }
  from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// ═══════════════════════════════════
// 1️⃣ 创建 Server
// ═══════════════════════════════════

const server = new McpServer({
  name: "我的助手服务",
  version: "1.0.0",
});

// ═══════════════════════════════════
// 🛠️ 工具:获取天气
// ═══════════════════════════════════

server.tool(
  "get_weather",
  "获取指定城市的实时天气信息",
  {
    // 使用 Zod 定义参数 Schema
    city: z.string().describe("城市名称,如 'Beijing'"),
  },
  async ({ city }) => {
    try {
      const response = await fetch(
        `https://wttr.in/${encodeURIComponent(city)}?format=j1`
      );
      const data = await response.json();
      const current = data.current_condition[0];

      return {
        content: [{
          type: "text",
          text: [
            `🌍 ${city} 的天气:`,
            `  🌡️  温度:${current.temp_C}°C`,
            `  💧 湿度:${current.humidity}%`,
            `  🌤️  天气:${current.weatherDesc[0].value}`,
            `  💨 风速:${current.windspeedKmph} km/h`,
          ].join("\n"),
        }],
      };
    } catch (error: any) {
      return {
        content: [{
          type: "text",
          text: `❌ 获取天气失败:${error.message}`
        }],
        isError: true,
      };
    }
  }
);

// ═══════════════════════════════════
// 🛠️ 工具:搜索网络
// ═══════════════════════════════════

server.tool(
  "search_web",
  "搜索网络信息",
  {
    query: z.string().describe("搜索关键词"),
    num_results: z.number()
      .min(1).max(10).default(5)
      .describe("结果数量"),
  },
  async ({ query, num_results }) => {
    const results = Array.from(
      { length: num_results },
      (_, i) => `  ${i + 1}. 关于 "${query}" 的结果 ${i + 1}`
    );

    return {
      content: [{
        type: "text",
        text: `🔍 关于 '${query}' 的搜索结果:\n${results.join("\n")}`,
      }],
    };
  }
);

// ═══════════════════════════════════
// 📁 资源定义
// ═══════════════════════════════════

server.resource(
  "config",
  "config://settings",
  {
    name: "应用配置",
    description: "应用的运行时配置"
  },
  async (uri) => ({
    contents: [{
      uri: uri.href,
      text: JSON.stringify({
        version: "1.0.0",
        language: "zh-CN",
        timezone: "Asia/Shanghai",
        features: ["weather", "search", "calculator"],
      }, null, 2),
    }],
  })
);

// ═══════════════════════════════════
// 💬 提示模板
// ═══════════════════════════════════

server.prompt(
  "code_review",
  "对代码进行 Code Review",
  {
    language: z.string().describe("编程语言"),
    code: z.string().describe("待审查的代码"),
  },
  ({ language, code }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text:
          `请对以下 ${language} 代码进行专业的 Code Review:\n\n` +
          `代码:\n\`\`\`\n${code}\n\`\`\`\n\n` +
          `请从安全性、性能、可读性、设计、测试五个维度评审。`,
      },
    }],
  })
);

// ═══════════════════════════════════
// 🚀 启动 Server
// ═══════════════════════════════════

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("🚀 MCP Server 已启动 (stdio 模式)");
}

main().catch(console.error);

7. 动手实战:构建 MCP 客户端

7.1 Python 客户端

python 复制代码
# client.py - MCP Client 完整实现
"""
MCP 客户端示例
连接上面的天气查询 Server 并与之交互
"""

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

async def main():
    # ═══════════════════════════════════
    # 1️⃣ 配置 Server 启动参数
    # ═══════════════════════════════════

    server_params = StdioServerParameters(
        command="python",
        args=["server.py"],
        env=None,                   # 可注入环境变量
    )

    # ═══════════════════════════════════
    # 2️⃣ 建立连接
    # ═══════════════════════════════════

    async with AsyncExitStack() as stack:
        # 启动 Server 子进程并建立双向通信管道
        stdio_transport = await stack.enter_async_context(
            stdio_client(server_params)
        )
        read_stream, write_stream = stdio_transport

        # 创建客户端会话
        session = await stack.enter_async_context(
            ClientSession(read_stream, write_stream)
        )

        # 握手:交换协议版本和能力信息
        await session.initialize()
        print("✅ 已连接到 MCP Server")

        # ═══════════════════════════════════
        # 3️⃣ 发现可用工具
        # ═══════════════════════════════════

        tools_result = await session.list_tools()
        print("\n📋 可用工具:")
        for tool in tools_result.tools:
            print(f"  • {tool.name}: {tool.description}")

        # ═══════════════════════════════════
        # 4️⃣ 调用工具
        # ═══════════════════════════════════

        # 4.1 查询天气
        print("\n" + "=" * 50)
        print("🌤️  调用 get_weather...")
        result = await session.call_tool(
            "get_weather",
            arguments={"city": "Beijing"}
        )
        print(result.content[0].text)

        # 4.2 搜索网络
        print("\n" + "=" * 50)
        print("🔍 调用 search_web...")
        result = await session.call_tool(
            "search_web",
            arguments={
                "query": "Python MCP tutorial",
                "num_results": 3
            }
        )
        print(result.content[0].text)

        # 4.3 数学计算
        print("\n" + "=" * 50)
        print("📐 调用 calculate...")
        result = await session.call_tool(
            "calculate",
            arguments={"expression": "(100 + 200) * 0.15"}
        )
        print(result.content[0].text)

        # ═══════════════════════════════════
        # 5️⃣ 读取资源
        # ═══════════════════════════════════

        print("\n" + "=" * 50)
        print("📁 读取资源...")

        # 列出所有可用资源
        resources_result = await session.list_resources()
        print("\n📋 可用资源:")
        for resource in resources_result.resources:
            print(f"  • {resource.uri}: {resource.name}")

        # 读取配置资源
        config = await session.read_resource("config://settings")
        print(f"\n⚙️  配置内容:\n{config.contents[0].text}")

        # ═══════════════════════════════════
        # 6️⃣ 获取提示模板
        # ═══════════════════════════════════

        print("\n" + "=" * 50)
        print("💬 获取提示模板...")

        prompts_result = await session.list_prompts()
        print("\n📋 可用提示模板:")
        for prompt in prompts_result.prompts:
            print(f"  • {prompt.name}: {prompt.description}")

        # 使用 Code Review 提示模板
        review_prompt = await session.get_prompt(
            "code_review",
            arguments={
                "language": "Python",
                "code": "def add(a,b): return a+b"
            }
        )
        print(
            f"\n📝 Code Review 提示词:"
            f"{review_prompt.messages[0].content.text[:200]}..."
        )

# ═══════════════════════════════════
# 🚀 运行
# ═══════════════════════════════════

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

7.2 TypeScript 客户端

typescript 复制代码
// client.ts - MCP Client TypeScript 实现
import { Client }
  from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport }
  from "@modelcontextprotocol/sdk/client/stdio.js";

async function main() {
  // ═══════════════════════════════════
  // 1️⃣ 创建传输层(启动 Server 子进程)
  // ═══════════════════════════════════

  const transport = new StdioClientTransport({
    command: "node",
    args: ["dist/server.js"],   // 编译后的 Server
  });

  // ═══════════════════════════════════
  // 2️⃣ 创建 Client 并连接
  // ═══════════════════════════════════

  const client = new Client({
    name: "我的 MCP 客户端",
    version: "1.0.0",
  });

  await client.connect(transport);
  console.log("✅ 已连接到 MCP Server");

  // ═══════════════════════════════════
  // 3️⃣ 发现工具
  // ═══════════════════════════════════

  const { tools } = await client.listTools();
  console.log("\n📋 可用工具:");
  tools.forEach((tool) => {
    console.log(`  • ${tool.name}: ${tool.description}`);
  });

  // ═══════════════════════════════════
  // 4️⃣ 调用工具
  // ═══════════════════════════════════

  // 查询天气
  console.log("\n" + "=".repeat(50));
  console.log("🌤️  调用 get_weather...");
  const weatherResult = await client.callTool({
    name: "get_weather",
    arguments: { city: "Tokyo" },
  });
  console.log((weatherResult.content as any)[0].text);

  // 计算
  console.log("\n" + "=".repeat(50));
  console.log("📐 调用 calculate...");
  const calcResult = await client.callTool({
    name: "calculate",
    arguments: { expression: "2 ** 10" },
  });
  console.log((calcResult.content as any)[0].text);

  // ═══════════════════════════════════
  // 5️⃣ 读取资源
  // ═══════════════════════════════════

  const { resources } = await client.listResources();
  console.log("\n📋 可用资源:");
  resources.forEach((res) => {
    console.log(`  • ${res.uri}: ${res.name}`);
  });

  const config = await client.readResource({
    uri: "config://settings",
  });
  console.log(
    `\n⚙️  配置:\n${(config.contents[0] as any).text}`
  );

  // ═══════════════════════════════════
  // 6️⃣ 关闭连接
  // ═══════════════════════════════════

  await client.close();
  console.log("\n👋 连接已关闭");
}

main().catch(console.error);

8. 服务端与客户端如何连接

这一节是整个 MCP 通信的核心------理解连接生命周期,就理解了 MCP 的全貌。

8.1 连接生命周期(五个阶段)

arduino 复制代码
┌──────────────────────────────────────────────────────────┐
│                  MCP 连接生命周期                           │
│                                                          │
│  1. 启动阶段                                              │
│  ┌───────────────────────────────────────────────────┐  │
│  │  Client spawn Server 子进程                        │  │
│  │       │                                            │  │
│  │       ▼                                            │  │
│  │  Server 启动,通过 stdout 发送就绪信号               │  │
│  └───────────────────────────────────────────────────┘  │
│                        │                                  │
│  2. 握手阶段            ▼                                  │
│  ┌───────────────────────────────────────────────────┐  │
│  │  Client ──── initialize ────────► Server          │  │
│  │  Client ◄─── InitializeResult ─── Server          │  │
│  │                                                   │  │
│  │  交换:协议版本 + 能力声明                          │  │
│  │  Client:我的协议版本、客户端信息                    │  │
│  │  Server:我的能力(tools/resources/prompts)       │  │
│  └───────────────────────────────────────────────────┘  │
│                        │                                  │
│  3. 发现阶段            ▼                                  │
│  ┌───────────────────────────────────────────────────┐  │
│  │  Client ──── tools/list ──────────► Server        │  │
│  │  Client ◄─── ListToolsResult ───── Server         │  │
│  │                                                   │  │
│  │  Client ──── resources/list ──────► Server        │  │
│  │  Client ◄─── ListResourcesResult ── Server        │  │
│  │                                                   │  │
│  │  Client ──── prompts/list ────────► Server        │  │
│  │  Client ◄─── ListPromptsResult ─── Server         │  │
│  └───────────────────────────────────────────────────┘  │
│                        │                                  │
│  4. 工作阶段            ▼                                  │
│  ┌───────────────────────────────────────────────────┐  │
│  │  循环:                                            │  │
│  │  AI 决定调用工具 → tools/call → Server 执行 → 返回  │  │
│  │  AI 根据结果继续推理 → 再次调用 → ...              │  │
│  └───────────────────────────────────────────────────┘  │
│                        │                                  │
│  5. 关闭阶段            ▼                                  │
│  ┌───────────────────────────────────────────────────┐  │
│  │  Client 关闭 stdin → kill 子进程 → 连接断开        │  │
│  └───────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────┘

8.2 握手协议------能力协商

握手是 Client 和 Server 互相"认识"的过程,也是能力协商的关键步骤:

json 复制代码
// === ① 客户端发送 InitializeRequest ===
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": {
        "listChanged": true
      },
      "sampling": {}
    },
    "clientInfo": {
      "name": "MyApp",
      "version": "1.0.0"
    }
  }
}

// === ② 服务端返回 InitializeResult ===
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": {
        "listChanged": true
      },
      "resources": {
        "subscribe": true,
        "listChanged": true
      },
      "prompts": {
        "listChanged": true
      },
      "logging": {}
    },
    "serverInfo": {
      "name": "我的助手服务",
      "version": "1.0.0"
    }
  }
}

// === ③ 客户端确认(发送 initialized 通知)===
{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}

8.3 完整消息流时序图

arduino 复制代码
时间线          Client                          Server
──────          ──────                          ──────
  │
  │  ① 启动 Server 子进程 ──────────────────────► 启动
  │                                               │
  │  ② ◄──────── init (能力声明)───────────────── 就绪
  │     ──────── initialized ───────────────────► │
  │                                               │
  │  ③ ──────── tools/list ────────────────────► │
  │     ◄──────── [get_weather,                   │
  │               search_web,                     │
  │               calculate] ──────────────────   │
  │                                               │
  │  ④ ──────── tools/call ────────────────────► │
  │            {name:"get_weather",               │
  │             arguments:{city:"Beijing"}}       │
  │                                               │ 执行
  │                                               │ API 调用
  │                                               │
  │     ◄──────── 天气结果 ─────────────────────  │
  │                                               │
  │  ⑤ ──────── resources/read ────────────────► │
  │            {uri:"config://settings"}          │
  │     ◄──────── 配置 JSON ────────────────────  │
  │                                               │
  │  ⑥ ──────── 关闭 stdin ────────────────────► │
  │     ──────── kill 进程 ────────────────────►  退出
  ▼

:::tip 为什么用子进程而不是 HTTP? stdio 模式中 Client 是父进程、Server 是子进程的设计有深层原因:最小权限原则。Server 的生命周期完全受 Client 控制------Client 启动时创建、关闭时销毁。这种方式天然适合桌面应用和本地开发工具的场景,也避免了端口占用和网络攻击面。 :::


9. Claude Code 中使用 MCP

Claude Code 是目前对 MCP 支持最好的 AI 编程工具,通过简单配置即可接入任意 MCP Server。

9.1 四种 Server 接入方式

json 复制代码
// 配置文件路径:~/.claude/claude_desktop_config.json
// 或项目级配置:.claude/mcp.json
{
  "mcpServers": {

    // ─────────────────────────────────
    // 方式 1:本地 stdio Server(推荐)
    // ─────────────────────────────────
    "my-weather": {
      "command": "python",
      "args": ["/path/to/server.py"],
      "env": {
        "API_KEY": "your-api-key"
      }
    },

    // ─────────────────────────────────
    // 方式 2:远程 HTTP Server (SSE)
    // ─────────────────────────────────
    "remote-database": {
      "url": "https://my-mcp-server.example.com/sse",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN"
      }
    },

    // ─────────────────────────────────
    // 方式 3:Docker 容器
    // ─────────────────────────────────
    "docker-tools": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "mcp/server-image:latest"
      ]
    },

    // ─────────────────────────────────
    // 方式 4:Node.js 包(通过 npx)
    // ─────────────────────────────────
    "github": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-github"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxxx"
      }
    }
  }
}

9.2 管理 MCP Server

bash 复制代码
# 查看已注册的 MCP Server
claude mcp list
# 输出:
# ┌─────────────────┬──────────┬─────────┐
# │ Server          │ Status   │ Tools   │
# ├─────────────────┼──────────┼─────────┤
# │ my-weather      │ Connected│ 3       │
# │ remote-database │ Connected│ 5       │
# │ docker-tools    │ Stopped  │ -       │
# │ github          │ Connected│ 12      │
# └─────────────────┴──────────┴─────────┘

# 查看某个 Server 的详细信息
claude mcp get my-weather

# 手动控制 Server 启停
claude mcp start my-weather
claude mcp stop my-weather

9.3 社区热门 MCP Server

Server 名称 功能 安装命令
server-github GitHub 仓库/PR/Issue 管理 npx @modelcontextprotocol/server-github
server-postgres PostgreSQL 数据库查询 npx @modelcontextprotocol/server-postgres
server-filesystem 安全的文件系统操作 npx @modelcontextprotocol/server-filesystem
server-brave-search Brave 搜索引擎 npx @modelcontextprotocol/server-brave-search
server-puppeteer 浏览器自动化/截图 npx @modelcontextprotocol/server-puppeteer
server-memory 持久化记忆存储 npx @modelcontextprotocol/server-memory
server-slack Slack 消息管理 npx @modelcontextprotocol/server-slack
server-sentry Sentry 错误监控 npx @modelcontextprotocol/server-sentry

:::tip 选择建议

  • 个人开发 :从 filesystem + github 开始,覆盖日常 80% 场景
  • 后端开发 :加 postgres 或你使用的数据库对应 Server
  • 全栈/前端 :加 puppeteer 做浏览器交互
  • 安全红线 :filesystem Server 务必限制目录范围,比如只允许 /workspace :::

10. 高级主题

:::details 点击展开:资源订阅机制

10.1 资源订阅机制

MCP 支持 "订阅-推送" 模式,Server 可主动向 Client 推送资源变更通知:

python 复制代码
# Server 端:定义可订阅的资源
@mcp.resource("file://logs/app.log")
async def get_log() -> str:
    return Path("./app.log").read_text()

# 当文件变化时,Server 主动通知所有订阅的 Client
# Client 收到通知后决定是否重新读取

# Client 端:订阅资源
await session.subscribe_resource("file://logs/app.log")

# 设置资源变更回调
async def on_resource_updated(uri: str):
    """当订阅的资源更新时触发"""
    print(f"📢 资源已更新: {uri}")
    content = await session.read_resource(uri)
    print(f"   新内容: {content.contents[0].text[:100]}...")

session.on_resource_updated = on_resource_updated

:::

:::details 点击展开:服务端采样(反向调用 AI)

10.2 服务端采样------Server → Client 请求 AI 推理

MCP 支持 反向调用:Server 可以向 Client 请求 AI 推理能力。这意味着你的工具可以利用 AI 来处理中间结果。

python 复制代码
# Server 端:请求 Client 进行 AI 推理
@mcp.tool()
async def analyze_sentiment(text: str) -> str:
    """分析文本情感(通过反向调用 AI 实现)"""
    # 向 Client 请求 AI 推理
    response = await mcp.request_sampling(
        messages=[{
            "role": "user",
            "content": {
                "type": "text",
                "text": (
                    "分析以下文本的情感,"
                    "只回答 'positive', 'negative', 或 'neutral':"
                    f"\n\n{text}"
                )
            }
        }],
        max_tokens=10
    )
    return f"情感分析结果:{response.content.text}"

:::warning 使用限制 并非所有 Client 都支持采样功能。Claude Code 目前不支持服务端采样,自建 Client 需要在 initialize 时声明 sampling 能力。 ::: :::

:::details 点击展开:动态工具注册

10.3 动态工具列表

工具列表可以在运行时动态变化,无需重启 Server:

python 复制代码
# 动态注册工具
class DynamicTools:
    def __init__(self):
        self._tools = {}

    def register_tool(
        self,
        name: str,
        handler: callable,
        schema: dict
    ):
        """运行时动态添加工具"""
        self._tools[name] = {
            "handler": handler,
            "schema": schema
        }
        # 通知所有 Client 工具列表已更新
        mcp.notify_tool_list_changed()

    def unregister_tool(self, name: str):
        """运行时移除工具"""
        del self._tools[name]
        mcp.notify_tool_list_changed()

dynamic_tools = DynamicTools()

# 根据配置动态加载工具
for tool_config in load_tools_from_config():
    dynamic_tools.register_tool(
        name=tool_config["name"],
        handler=create_handler(tool_config),
        schema=tool_config["schema"]
    )

:::

:::details 点击展开:多 Server 编排

10.4 多 Server 编排

真实场景中往往需要同时连接多个 MCP Server:

python 复制代码
# 同时连接多个 MCP Server
async def connect_multiple_servers():
    servers = {
        "files": StdioServerParameters(
            command="npx",
            args=[
                "-y",
                "@modelcontextprotocol/server-filesystem",
                "/workspace"
            ]
        ),
        "database": StdioServerParameters(
            command="npx",
            args=[
                "-y",
                "@modelcontextprotocol/server-postgres"
            ],
            env={"DATABASE_URL": "postgresql://..."}
        ),
        "github": StdioServerParameters(
            command="npx",
            args=[
                "-y",
                "@modelcontextprotocol/server-github"
            ],
            env={"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxx"}
        ),
    }

    sessions = {}
    async with AsyncExitStack() as stack:
        for name, params in servers.items():
            transport = await stack.enter_async_context(
                stdio_client(params)
            )
            read, write = transport
            session = await stack.enter_async_context(
                ClientSession(read, write)
            )
            await session.initialize()
            sessions[name] = session
            print(f"✅ {name} 已连接")

        # 汇总所有 Server 的工具
        all_tools = {}
        for name, session in sessions.items():
            result = await session.list_tools()
            for tool in result.tools:
                all_tools[f"{name}:{tool.name}"] = (
                    session, tool
                )

        print(f"\n📋 总计 {len(all_tools)} 个工具可用")

        # 使用特定工具
        session, _ = all_tools["files:read_file"]
        result = await session.call_tool(
            "read_file",
            {"path": "/workspace/app.py"}
        )

:::


11. 常见问题与调试

11.1 开启调试日志

python 复制代码
# Server 端开启详细日志
import logging
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    handlers=[
        logging.FileHandler("mcp_server.log"),
        logging.StreamHandler()  # 注意:输出到 stderr!
    ]
)
typescript 复制代码
// Client 端监控所有通信消息
import { createLogger }
  from "@modelcontextprotocol/sdk/shared/logging.js";

const logger = createLogger("my-mcp-client");
logger.setLevel("debug");

client.onMessage = (message) => {
  logger.debug(
    `📩 收到: ${JSON.stringify(message, null, 2)}`
  );
};

11.2 常见错误速查表

错误症状 可能原因 解决方案
Connection refused Server 未启动或端口错误 检查 Server 进程是否运行
Protocol version mismatch 客户端/服务端协议版本不兼容 统一 MCP SDK 版本
Tool not found 工具名称拼写错误或未注册 先调用 list_tools() 确认
Invalid params 参数类型/格式不正确 对照 inputSchema 仔细检查
Timeout 工具执行超时 增加超时时间或优化工具性能
Broken pipe Server 进程异常崩溃 查看 Server 的 stderr 日志
stdin/stdout closed 进程通信管道被意外关闭 确保 Server 在工具执行期间不退出

11.3 调试技巧

bash 复制代码
# 1. 直接 stdin 测试 Server
$ python server.py
# 粘贴 JSON-RPC 消息看响应

# 2. 使用官方 Inspector(强烈推荐)
$ npx @modelcontextprotocol/inspector python server.py
# 会打开一个 Web UI,可交互式测试所有工具

# 3. 检查进程
$ ps aux | grep mcp

# 4. 查看 Server 日志(注意是 stderr!)
$ python server.py 2> server.log
$ tail -f server.log

# 5. 测试远程 Server 连通性
$ nc -v my-mcp-server.example.com 3000

:::tip 调试神器:MCP Inspector 官方提供的 @modelcontextprotocol/inspector 是调试 MCP Server 的最佳工具。它会启动一个本地 Web 页面,让你可以:

  • 查看 Server 所有 Tools / Resources / Prompts
  • 交互式调用工具并查看结果
  • 查看完整的 JSON-RPC 消息历史
  • 模拟 Client 的各种边界情况 :::

12. 最佳实践

12.1 安全指南

python 复制代码
# ❌ 危险示例:直接 eval 用户输入
@mcp.tool()
async def dangerous_exec(code: str) -> str:
    return str(eval(code))  # 这等于给 AI 一个远程 shell!

# ✅ 安全示例:白名单 + 沙箱
@mcp.tool()
async def safe_calculate(expression: str) -> str:
    import re
    # 1. 严格白名单校验
    if not re.match(r'^[\d\s+\-*/().%\s]+$', expression):
        return "❌ 只允许基本数学运算"

    # 2. 禁用所有内置函数
    safe_dict = {"__builtins__": {}}

    # 3. 限制计算资源
    try:
        result = eval(expression, safe_dict, {})
        return f"结果: {result}"
    except Exception as e:
        return f"❌ 错误: {str(e)}"

:::danger 安全红线

  • 永远不要 直接把用户输入传给 eval()exec()os.system()
  • 务必限制 filesystem Server 的根目录范围
  • 始终验证 工具参数的类型、长度、范围
  • 不要 在工具中硬编码 API Key,用环境变量
  • 警惕 工具间循环调用(Tool A → Tool B → Tool A) :::

12.2 错误处理最佳实践

python 复制代码
# ✅ 分层的错误处理
@mcp.tool()
async def robust_tool(param: str) -> str:
    try:
        # 1. 参数前置校验
        if not param or len(param) > 1000:
            return "❌ 参数无效:不能为空且不超过1000字符"

        # 2. 执行业务逻辑
        result = await do_something(param)

        # 3. 返回结构化结果
        return f"✅ 操作成功:{result}"

    except ConnectionError as e:
        return f"❌ 网络错误:{e},请检查网络连接"
    except PermissionError as e:
        return f"❌ 权限不足:{e}"
    except Exception as e:
        # 兜底:记录日志但不暴露内部细节
        logging.exception(f"工具执行异常: {e}")
        return "❌ 未知错误,请联系管理员"

12.3 性能优化

python 复制代码
# ✅ 使用 TTL 缓存减少重复计算
from functools import lru_cache
import time

@lru_cache(maxsize=100)
def cached_computation(key: str) -> str:
    """缓存计算结果"""
    return expensive_operation(key)

@mcp.tool()
async def smart_tool(query: str) -> str:
    cache_key = f"tool_result:{hash(query)}"

    # 检查缓存是否有效
    if cache_key in cache:
        cached = cache[cache_key]
        if time.time() < cached["expires"]:
            return cached["result"]

    # 执行并缓存
    result = await do_expensive_work(query)
    cache[cache_key] = {
        "result": result,
        "expires": time.time() + 60   # 60秒 TTL
    }
    return result

12.4 工具设计五原则

原则 说明 ✅ 好例子 ❌ 坏例子
单一职责 一个工具只做一件事 read_file(path) magic_do_everything()
清晰描述 description 准确说明功能 "读取指定路径的文件内容" "处理文件"
类型安全 Schema 严格定义 count: z.number().int() count: z.any()
幂等性 读操作可安全重复 get_weather(city) toggle_feature_flag()
明确错误 错误信息具体可操作 "文件不存在: /a/b.txt" "出错了"

📚 参考资源


💡 一句话总结:MCP 让 AI 模型从"孤岛"走向"互联",它为 AI Agent 时代提供了标准化的"神经系统"。掌握了 MCP,你就掌握了构建下一代 AI 应用的核心技能。


📝 本文写于 2026年6月 · 欢迎点赞收藏,如有错误或建议,评论区见!

相关推荐
血小溅1 小时前
Skill 脚本语言选型:Python、Node.js、Shell 到底怎么选?
人工智能·后端
范闲1 小时前
Charmbracelet TUI 生态系统指南
后端
颜进强1 小时前
AI性能参数-截断、延迟与流式输出
前端·后端·ai编程
浮游本尊1 小时前
Java学习第44天 - 本地二级缓存 Caffeine、Redis 分布式锁与热点 Key / 库存预扣
后端
浮游本尊2 小时前
Java学习第43天 - Redis 缓存基础、Cache-Aside 模式与缓存一致性
后端
云技纵横2 小时前
线程池 OOM 实战:无界队列配错,5 万个任务撑爆 JVM
后端
渣波2 小时前
拒绝 SQL 焦虑!手把手带你用 NestJS + Prisma + DTO 写出“防弹”级后端代码
javascript·数据库·后端
用户61541317281272 小时前
# 写接口自动化时,我在断言上栽过的两个跟头
后端
SamDeepThinking2 小时前
Java微服务练习方式
java·后端·微服务