文章目录


前言

本文并非单纯的理论讲解,而是一份“实战+原理”双驱动的完整指南,专为希望快速掌握 MCP 协议、搭建可落地 MCP 服务的开发者打造。我们将从最基础的环境准备入手,手把手教你搭建一个可运行的 Weather MCP Server,再通过“中间人日志”拆解 MCP 协议的底层通信细节,带你理解 Client 与 Server 之间的交互逻辑,甚至教会你绕过第三方客户端,直接与 MCP Server 进行通信。


第一部分:创建你的第一个 MCP Server

1.1 环境准备

必需工具
工具 用途 安装说明
Python MCP Server 运行环境 macOS 自带;Windows 需从官网 File 下载
uv Python 包管理器 用于项目和依赖管理
VS Code 代码编辑器 开发 MCP Server
Claude Client VS Code 插件 MCP Host,用于测试 MCP Server

版本要求:Python >= 3.10

1.2 创建 Weather MCP Server

步骤整体流程图

开始创建项目目录初始化虚拟环境安装依赖包编写 weather.py 配置 MCP Client 测试运行完成

开始

创建项目目录

初始化虚拟环境

安装依赖包

编写 weather.py

配置 MCP Client

测试运行

完成

1.2.1 项目初始化
# 创建项目
uv init weather

# 进入项目目录
cd weather

# 创建虚拟环境
uv venv

# 激活虚拟环境
source .venv/bin/activate  # macOS/Linux
# 或
.venv\Scripts\activate     # Windows

# 安装依赖
uv add mcp httpx

依赖说明

  • mcp - MCP 协议核心库
  • httpx - HTTP 客户端,用于调用天气 API
1.2.2 编写 weather.py

创建 weather.py 文件,完整代码如下:

# 1. 导入必要的库
from mcp import FastMCP
import httpx
from typing import Any

# 2. 创建 MCP Server 实例
# ⚠️ 重要:添加 log_level="error" 防止日志干扰通信
mcp = FastMCP("weather", log_level="error")

# 3. 定义常量
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"

# 4. 工具函数:HTTP 请求封装
async def make_nws_request(url: str) -> dict[str, Any] | None:
    """向美国国家气象局 API 发起请求"""
    headers = {"User-Agent": USER_AGENT}
    async with httpx.AsyncClient() as client:
        response = await client.get(url, headers=headers, timeout=30.0)
        response.raise_for_status()
        return response.json()

# 5. 工具函数:格式化预警信息
def format_alert(alert: dict[str, Any]) -> str:
    """格式化单条预警信息"""
    properties = alert.get("properties", {})
    return f"""
事件: {properties.get('event', 'Unknown')}
区域: {properties.get('areaDesc', 'Unknown')}
严重性: {properties.get('severity', 'Unknown')}
描述: {properties.get('description', 'No description available')}
指示: {properties.get('instruction', 'No instructions available')}
""".strip()

# 6. Tool 1: 获取天气预警
@mcp.tool()
async def get_alerts(state: str) -> str:
    """
    获取美国某个州的天气预警信息

    Args:
        state: 美国州代码(两个字母,如 CA, NY, TX)

    Returns:
        该州的天气预警信息
    """
    url = f"{NWS_API_BASE}/alerts?area={state}"
    data = await make_nws_request(url)

    if not data:
        return f"无法获取 {state} 的预警信息"

    features = data.get("features", [])
    if not features:
        return f"{state} 当前没有天气预警"

    # 格式化所有预警
    alerts = [format_alert(alert) for alert in features[:5]]  # 最多5条
    return "\n\n---\n\n".join(alerts)

# 7. Tool 2: 获取天气预报
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """
    获取指定坐标的天气预报

    Args:
        latitude: 纬度
        longitude: 经度

    Returns:
        未来几天的天气预报
    """
    # 第一步:获取预报办公室信息
    points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
    points_data = await make_nws_request(points_url)

    if not points_data:
        return f"无法获取坐标 ({latitude}, {longitude}) 的信息"

    # 第二步:从返回数据中提取预报 URL
    forecast_url = points_data.get("properties", {}).get("forecast")
    if not forecast_url:
        return "无法获取预报 URL"

    # 第三步:请求天气预报
    forecast_data = await make_nws_request(forecast_url)
    if not forecast_data:
        return "无法获取天气预报"

    # 格式化预报信息
    periods = forecast_data.get("properties", {}).get("periods", [])
    if not periods:
        return "没有可用的预报数据"

    # 格式化输出
    forecasts = []
    for period in periods[:5]:  # 最多5个时段
        forecasts.append(f"""
{period.get('name', 'Unknown')}:
温度: {period.get('temperature', 'N/A')}°{period.get('temperatureUnit', 'F')}
天气: {period.get('shortForecast', 'N/A')}
详情: {period.get('detailedForecast', 'N/A')}
""".strip())

    return "\n\n".join(forecasts)

# 8. 启动 MCP Server
if __name__ == "__main__":
    # stdio 是默认传输方式,可省略 transport 参数
    mcp.run()
1.2.3 代码关键点解析
📌 装饰器 @mcp.tool()

这个装饰器做了三件事:

  1. 注册函数为 Tool - 让 MCP Server 知道这个函数可以被调用
  2. 提取函数注释 - 将 docstring 转换为 Tool 描述
  3. 生成参数规范 - 根据类型注解生成 JSON Schema

示例

@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """获取指定坐标的天气预报"""
    pass

会被转换为:

{
  "name": "get_forecast",
  "description": "获取指定坐标的天气预报",
  "inputSchema": {
    "type": "object",
    "properties": {
      "latitude": { "type": "number" },
      "longitude": { "type": "number" }
    },
    "required": ["latitude", "longitude"]
  }
}
📌 Transport: stdio
mcp.run()
  • stdio = Standard Input/Output(标准输入输出),是默认传输
  • MCP Server 通过 stdin 接收请求,通过 stdout 发送响应
  • 这是目前最常用、最稳定的 MCP 通信方式

1.3 配置 Claude Client

配置步骤
MCP Server Claude Client MCP 配置文件 VS Code 开发者 MCP Server Claude Client MCP 配置文件 VS Code 开发者 1. 打开 Claude Client 2. 点击配置图标 3. 编辑 MCP 配置 4. 保存配置 5. 自动启动 Server 6. 返回可用 Tools 7. 显示已注册工具
配置文件内容

打开 Claude Client 配置(通常在 ~/.config/claude/config.json),添加:

{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": ["run", "weather.py"],
      "cwd": "/your/path/to/weather",
      "env": {}
    }
  }
}

配置说明

  • command: 启动命令(使用 uv 运行)
  • args: 命令参数
  • cwd: 工作目录(必须是绝对路径
  • env: 环境变量(可选)

1.4 测试运行

  1. 保存配置 - Claude Client 会自动重启并加载 MCP Server
  2. 新建对话 - 在 Claude Client 中创建新对话
  3. 测试查询
纽约明天的天气怎么样?
  1. 观察结果
  • Claude 会识别到 get_forecast 工具
  • 提示是否批准调用
  • 点击 “Approve” 后执行
  • 返回天气预报数据
成功标志

✅ 看到 Tool 调用提示

✅ 参数自动提取(纬度: 40.7128, 经度: -74.006)

✅ 返回天气预报数据

✅ Claude 总结并展示结果


第二部分:MCP 协议底层分析

2.1 问题:如何看到协议通信?

虽然我们成功创建了 MCP Server,但有个问题:

我们并不清楚 Client 和 Server 之间到底交换了什么数据?

  • MCP Server 是由 Claude Client 启动的
  • 它们的通信通过 stdin/stdout 进行
  • 我们无法直接看到这些数据流

2.2 解决方案:MCP Logger 中间人

架构设计

JSON 请求

转发请求

JSON 响应

转发响应

记录日志

Claude Client

mcp_logger.py

MCP Server

mcp.log 文件

核心思路

  1. 在 Client 和 Server 之间插入一个 中间人脚本
  2. 这个脚本透明转发所有数据
  3. 同时将数据记录到日志文件
mcp_logger.py 实现
#!/usr/bin/env python3
"""
MCP Logger - 截获并记录 MCP 协议通信
"""
import sys
import subprocess
import threading
import json

LOG_FILE = "mcp.log"

def log_message(direction: str, data: str):
    """记录消息到日志文件"""
    with open(LOG_FILE, "a", encoding="utf-8") as f:
        f.write(f"{direction}: {data}\n")

def forward_stream(input_stream, output_stream, direction: str):
    """转发数据流并记录"""
    try:
        for line in input_stream:
            log_message(direction, line.strip())
            output_stream.write(line)
            output_stream.flush()
    except Exception as e:
        log_message("ERROR", str(e))

def main():
    server_command = sys.argv[1:]
    process = subprocess.Popen(
        server_command,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
        universal_newlines=True,
        bufsize=1
    )

    input_thread = threading.Thread(
        target=forward_stream,
        args=(sys.stdin, process.stdin, "输入")
    )
    output_thread = threading.Thread(
        target=forward_stream,
        args=(process.stdout, sys.stdout, "输出")
    )

    input_thread.start()
    output_thread.start()

    process.wait()
    input_thread.join()
    output_thread.join()

if __name__ == "__main__":
    main()
使用方法

修改 Claude Client 配置:

{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["/path/to/mcp_logger.py", "uv", "run", "weather.py"],
      "cwd": "/path/to/weather"
    }
  }
}

2.3 协议通信完整解析

启动 Server 后,查看 mcp.log 文件,我们会看到以下交互过程:

阶段 1:握手(Handshake)
Server Client Server Client initialize (协议版本、客户端信息) initialized (服务端能力、版本) notifications/initialized (确认)

① Client 发送初始化请求

输入: {
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-07",
    "clientInfo": {
      "name": "claude-client",
      "version": "3.12.3"
    },
    "capabilities": {}
  }
}

② Server 响应初始化

输出: {
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-07",
    "serverInfo": {
      "name": "weather",
      "version": "1.0.0"
    },
    "capabilities": {
      "tools": {}
    }
  }
}

③ Client 确认

输入: {
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}
阶段 2:工具发现(Tool Discovery)
Server Client Server Client tools/list (请求工具列表) tools 数组 (返回所有工具定义) resources/list (请求资源列表) [] (空数组,无资源)

④ Client 请求工具列表

输入: {
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}

⑤ Server 返回工具定义

输出: {
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "get_alerts",
        "description": "获取美国某个州的天气预警信息\n\nArgs:\n    state: 美国州代码(两个字母,如 CA, NY, TX)",
        "inputSchema": {
          "type": "object",
          "properties": {
            "state": {
              "type": "string"
            }
          },
          "required": ["state"]
        }
      },
      {
        "name": "get_forecast",
        "description": "获取指定坐标的天气预报\n\nArgs:\n    latitude: 纬度\n    longitude: 经度",
        "inputSchema": {
          "type": "object",
          "properties": {
            "latitude": {
              "type": "number"},
            "longitude": {
              "type": "number"}
          },
          "required": ["latitude", "longitude"]
        }
      }
    ]
  }
}
🔍 深入理解:JSON Schema

inputSchema 遵循 JSON Schema 规范,用于描述参数结构:

{
  "type": "object",
  "properties": {
    "latitude": { "type": "number" },
    "longitude": { "type": "number" }
  },
  "required": ["latitude", "longitude"]
}

含义

  • 参数是一个对象
  • 必须包含 latitudelongitude 两个字段
  • 两个字段都是数字类型
  • 两个字段都是必需的
阶段 3:工具调用(Tool Call)
Server AI 模型 Client 用户 Server AI 模型 Client 用户 纽约明天的天气怎么样? 发送问题 + 工具列表 选择 get_forecast + 提取参数 请求批准调用 批准 tools/call (调用工具) 返回天气数据 发送结果 生成总结 显示答案

⑥ Client 调用工具

输入: {
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "get_forecast",
    "arguments": {
      "latitude": 40.7128,
      "longitude": -74.006
    }
  }
}

⑦ Server 返回结果

输出: {
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Tonight:\n温度: 45°F\n天气: Partly Cloudy\n详情: Partly cloudy, with a low around 45...\n\nFriday:\n温度: 58°F\n天气: Sunny\n详情: Sunny, with a high near 58..."
      }
    ]
  }
}

2.4 协议通信时序图

MCP Server Claude Client MCP Server Claude Client 阶段 1: 握手 阶段 2: 工具发现 阶段 3: 等待用户请求 用户: "纽约明天天气?" 阶段 4: 工具调用 将结果发给 AI 模型总结 initialize 1 initialized 2 notifications/initialized 3 tools/list 4 [get_alerts, get_forecast] 5 resources/list 6 [] 7 tools/call(get_forecast, {lat:40.7, lon:-74}) 8 "Tonight: 45°F..." 9

2.5 关键发现

通过分析日志,我们发现了 MCP 协议的几个核心特点:

特点 说明
基于 JSON-RPC 2.0 所有消息都遵循 JSON-RPC 规范
请求 - 响应模式 Client 主动请求,Server 响应
标准化格式 Tool 定义使用 JSON Schema
异步支持 支持异步函数调用
传输无关 可以使用 stdio、HTTP 等多种传输方式

第三部分:直接与 MCP Server 通信

3.1 不需要 MCP Host 也能通信

通过前面的协议分析,我们已经完全理解了 MCP 的通信格式。现在一个有趣的问题是:

我们能不能绕过 Claude Client,直接与 MCP Server 对话?

答案是:完全可以!

MCP Server 只是一个遵循协议规范的程序,它不关心对面是谁。只要你发送的消息符合 MCP 协议格式,它就会正常响应。

3.2 手动通信演示

启动 MCP Server

在终端中直接运行 MCP Server:

cd /path/to/weather
uv run weather.py

现在 Server 正在等待 stdin 输入。

重要提醒

手动输入必须使用单行压缩 JSON,不能换行,否则 Server 无法解析。

步骤 1:发送初始化请求
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-07",
    "clientInfo": { "name": "手动测试", "version": "1.0.0" },
    "capabilities": {}
  }
}

Server 响应

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-07",
    "serverInfo": { "name": "weather", "version": "1.0.0" },
    "capabilities": { "tools": {} }
  }
}
步骤 2:发送初始化完成通知
{ "jsonrpc": "2.0", "method": "notifications/initialized" }

(Server 不会响应此消息)

步骤 3:请求工具列表
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
步骤 4:调用工具
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "get_forecast",
    "arguments": { "latitude": 40.7128, "longitude": -74.006 }
  }
}

3.3 使用脚本自动化测试

创建 test_mcp.py 进行自动化测试:

import subprocess
import json

def send_message(process, message):
    process.stdin.write(json.dumps(message) + '\n')
    process.stdin.flush()

def read_response(process):
    line = process.stdout.readline()
    return json.loads(line)

# 启动 Server
process = subprocess.Popen(
    ['uv', 'run', 'weather.py'],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True,
    universal_newlines=True,
    bufsize=1
)

# 1. 初始化
send_message(process, {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
        "protocolVersion": "2025-03-07",
        "clientInfo": {"name": "test", "version": "1.0"},
        "capabilities": {}
    }
})
print("初始化响应:", read_response(process))

# 2. 初始化完成
send_message(process, {
    "jsonrpc": "2.0",
    "method": "notifications/initialized"
})

# 3. 获取工具列表
send_message(process, {
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/list"
})
print("工具列表:", read_response(process))

# 4. 调用工具
send_message(process, {
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
        "name": "get_forecast",
        "arguments": {"latitude": 40.7128, "longitude": -74.006}
    }
})
print("预报结果:", read_response(process))

process.terminate()

3.4 关键发现

通过手动通信实验,我们证明了:

发现 说明
协议独立性 MCP 协议与具体的 Host 实现无关
纯文本协议 所有通信都是 JSON 文本,易于调试
会话式设计 必须按顺序完成握手才能正常调用
可编程性 可以用任何语言实现 MCP Server

第四部分:MCP 协议本质解析

4.1 重新审视 MCP 协议的范围

在深入研究了 MCP 的底层通信后,我们需要回答一个核心问题:

MCP 协议到底规定了什么?它与 AI 模型的关系是什么?

外部能力层

MCP协议层

大模型层

AI应用层 MCP Host

用户侧

用户

Claude Client

AI 大模型

MCP Server

天气 / 文件 / API 等工具

4.2 MCP 协议的真实作用域

🎯 MCP 协议规定的内容

MCP 协议只规定了 Client 和 Server 之间的通信:

规定内容 说明
工具注册 Server 如何告诉 Client 有哪些工具可用
工具定义 使用 JSON Schema 描述参数格式
工具调用 Client 如何请求执行工具
结果返回 Server 如何返回执行结果
传输协议 基于 JSON-RPC 2.0
传输方式 stdio、HTTP、WebSocket 等
❌ MCP 协议规定的内容

MCP 协议完全没有规定以下内容:

不规定的内容 说明
与模型的交互 Host 如何把工具信息传给模型
工具选择逻辑 模型如何决定使用哪个工具
参数提取方式 模型如何从用户问题中提取参数
结果处理 模型如何总结工具返回的结果

4.3 “模型上下文协议” 名称解析

为什么叫 “模型上下文协议”?

模型(Model)

指 AI 大语言模型。

上下文(Context)

模型可访问的外部环境与能力集合,包括工具、资源、提示模板,而非对话历史上下文。

协议(Protocol)

一套标准化的通信规范。

真正的含义

MCP 是一个让模型标准化感知与使用外部环境资源的协议

它统一了 AI 应用调用外部工具的方式,实现一次编写、随处调用。

4.4 MCP 在 AI 应用架构中的定位

工具层

MCP 协议层

AI 层 (非 MCP 协议)

应用层 (非 MCP 协议)

用户层

MCP 协议

用户

用户界面

MCP Host

大语言模型

MCP Client

MCP Server

实际工具实现

4.5 核心要点总结

要点 说明
MCP ≠ 模型交互协议 MCP 只负责工具的注册、发现、调用
MCP = 工具标准化 让任何工具都能以统一方式暴露给 AI 应用
与语言无关 可用 Python、Node.js、Go 等任何语言实现
与模型无关 适用于任何支持工具调用的 AI 模型
单 Client 架构 一个 Host 只有一个 Client,可连接多 Server

总结与最佳实践

5.1 核心知识回顾

通过本文,我们从实战到原理全面掌握了 MCP 协议:

  • 从零搭建可运行的 MCP Server
  • 抓包分析完整协议通信流程
  • 理解 MCP 在 AI 架构中的真实定位
  • 掌握调试、手动调用、自动化测试方法

5.2 开发最佳实践

  1. 完整类型注解 + 清晰文档

    让 AI 准确理解工具用途与参数含义。

  2. 控制日志输出

    使用 log_level="error" 避免干扰 stdio 通信。

  3. 绝对路径配置

    Host 配置中 cwd 必须使用绝对路径。

  4. 输入校验与安全

    防止路径穿越、非法调用等风险。

  5. 异步优先

    提升并发与 IO 密集型工具性能。

5.3 常见问题与解决方案

  • Server 启动失败:检查路径、Python 版本、依赖安装
  • 工具不显示:检查 @mcp.tool()、docstring、类型注解
  • 参数错误:优化描述与 JSON Schema
  • 通信异常:检查是否有多余日志污染 stdio

5.4 进阶方向

  • 多工具组合与复杂业务封装
  • 资源(Resource)与提示模板(Prompt)使用
  • 自定义 MCP Server开发
  • HTTP / WebSocket 传输模式
  • 安全、权限、多租户扩展

5.5 参考资源

官方 Python SDK(https://pypi.org/project/mcp/)是「便捷工具」,不是「强制要求」:它封装了 MCP 协议的核心逻辑(如 FastMCP 类、@mcp.tool() 装饰器),能帮开发者快速搭建符合规范的 MCP Server,减少重复编码,而非必须依赖的「外壳」。
本质逻辑:大模型只认「MCP 协议格式」,不认「开发工具 / 语言」—— 哪怕你不使用任何 SDK,手动编写代码实现 MCP 协议的握手、工具调用、结果返回逻辑,开发的 Server 也能正常与大模型通信。


结语

MCP 协议的本质很简单: 一个让 AI 应用标准化访问外部工具的统一协议。

MCP 本质就是 AI 应用(大模型客户端)与外部工具之间的标准化通信中间层,目的是让不同开发的工具、不同大模型,能通过同一套规则互相通信。

它不依赖特定模型、不绑定特定客户端,真正实现了工具生态的跨平台、可移植、可扩展。

理解它,你就掌握了下一代 AI Agent 工具化的核心基础设施。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐