MCP 协议实战:用 Python 开发你的第一个 AI 工具服务

MCP(Model Context Protocol)是 Anthropic 推出的开放协议,让 AI 模型能够安全地连接外部数据源和工具。本文将带你从零理解 MCP 协议,并用 Python 开发一个完整的 MCP Server,让 Claude 等 AI 助手直接调用你编写的工具。


一、MCP 协议是什么?

1.1 核心概念

MCP(Model Context Protocol)定义了一套标准协议,让 AI 应用(Host)能够通过统一的接口访问外部工具和数据。

复制代码
┌──────────────────────────────────────────────────────────────┐
│                     MCP 架构全景                              │
│                                                              │
│  ┌──────────┐    ┌──────────┐    ┌──────────────────────┐    │
│  │  AI 应用  │    │ MCP 客户端 │    │    MCP 服务端 (Server) │    │
│  │  (Host)  │◄──►│ (Client)  │◄──►│                      │    │
│  │          │    │           │    │  ┌────────┐          │    │
│  │ Claude   │    │ 内置于    │    │  │ Tools  │ 工具调用  │    │
│  │ Cursor   │    │ Host 应用 │    │  │        │          │    │
│  │ VS Code  │    │ 之中      │    │  ├────────┤          │    │
│  │          │    │           │    │  │Resources│ 数据读取  │    │
│  └──────────┘    └──────────┘    │  │        │          │    │
│                                  │  ├────────┤          │    │
│                                  │  │Prompts │ 提示模板  │    │
│                                  │  └────────┘          │    │
│                                  └──────────────────────┘    │
└──────────────────────────────────────────────────────────────┘

1.2 MCP 三大核心能力

能力 说明 示例
Tools AI 可调用的函数 查询数据库、调用 API、执行计算
Resources AI 可读取的数据源 文件内容、数据库记录、日志
Prompts 可复用的提示模板 代码审查模板、文档生成模板

1.3 通信方式

MCP 支持两种传输方式:

复制代码
方式一: stdio(标准输入输出)
┌──────────┐   stdin/stdout   ┌──────────┐
│ AI 应用   │◄───────────────►│MCP Server│
│ (Host)   │                 │(子进程)    │
└──────────┘                 └──────────┘

方式二: SSE(Server-Sent Events)
┌──────────┐   HTTP + SSE    ┌──────────┐
│ AI 应用   │◄───────────────►│MCP Server│
│ (Host)   │   (网络通信)     │(远程服务) │
└──────────┘                 └──────────┘

二、开发环境搭建

2.1 安装依赖

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

2.2 验证安装

bash 复制代码
mcp --version

2.3 项目结构

复制代码
my-mcp-server/
├── server.py            # MCP Server 主文件
├── tools/
│   ├── weather.py       # 天气查询工具
│   ├── database.py      # 数据库查询工具
│   └── calculator.py    # 计算器工具
├── resources/
│   └── system_info.py   # 系统信息资源
├── prompts/
│   └── code_review.py   # 代码审查提示模板
├── pyproject.toml       # 项目配置
└── README.md

三、第一个 MCP Server(快速上手)

3.1 最简实现

python 复制代码
# server.py
from mcp.server.fastmcp import FastMCP

# 创建 MCP Server 实例
mcp = FastMCP(
    name="my-first-mcp-server",
    version="1.0.0",
)


@mcp.tool()
def add(a: int, b: int) -> int:
    """两个数字相加"""
    return a + b


@mcp.tool()
def get_current_time(timezone: str = "Asia/Shanghai") -> str:
    """获取当前时间

    Args:
        timezone: 时区名称,默认为 Asia/Shanghai
    """
    from datetime import datetime
    import pytz

    tz = pytz.timezone(timezone)
    now = datetime.now(tz)
    return now.strftime("%Y-%m-%d %H:%M:%S %Z")


# 启动 Server
if __name__ == "__main__":
    mcp.run()

3.2 运行测试

bash 复制代码
# 使用 MCP Inspector 测试(可视化调试工具)
mcp dev server.py

# 或直接运行
python server.py

四、实战:开发完整的企业工具服务

下面我们构建一个实用的 MCP Server,包含天气查询、数据库操作、文件管理等功能。

4.1 天气查询工具

python 复制代码
# tools/weather.py
import httpx
from mcp.server.fastmcp import tool

BASE_URL = "https://api.openweathermap.org/data/2.5"

@tool()
async def get_weather(city: str, api_key: str = "") -> str:
    """查询指定城市的当前天气信息

    Args:
        city: 城市名称(中文或英文),如 "北京" 或 "Beijing"
        api_key: OpenWeatherMap API Key(可选,默认使用内置测试 key)
    """
    params = {
        "q": city,
        "appid": api_key,
        "units": "metric",
        "lang": "zh_cn",
    }

    async with httpx.AsyncClient() as client:
        resp = await client.get(f"{BASE_URL}/weather", params=params)
        data = resp.json()

    if resp.status_code != 200:
        return f"查询失败: {data.get('message', '未知错误')}"

    return (
        f"【{data['name']} 天气】\n"
        f"天气: {data['weather'][0]['description']}\n"
        f"温度: {data['main']['temp']}°C (体感 {data['main']['feels_like']}°C)\n"
        f"湿度: {data['main']['humidity']}%\n"
        f"风速: {data['wind']['speed']} m/s"
    )

@tool()
async def get_forecast(city: str, days: int = 3, api_key: str = "") -> str:
    """查询未来几天的天气预报

    Args:
        city: 城市名称
        days: 预报天数(最多 5 天)
        api_key: OpenWeatherMap API Key
    """
    params = {
        "q": city,
        "appid": api_key,
        "units": "metric",
        "lang": "zh_cn",
        "cnt": min(days, 5) * 8,  # 每天约 8 个数据点
    }

    async with httpx.AsyncClient() as client:
        resp = await client.get(f"{BASE_URL}/forecast", params=params)
        data = resp.json()

    forecasts = []
    for item in data["list"][::8]:  # 每天取一个数据点
        forecasts.append(
            f"{item['dt_txt'][:10]}: "
            f"{item['weather'][0]['description']}, "
            f"{item['main']['temp']}°C"
        )

    return f"【{data['city']['name']} 未来天气预报】\n" + "\n".join(forecasts)

4.2 SQLite 数据库查询工具

python 复制代码
# tools/database.py
import sqlite3
from pathlib import Path
from mcp.server.fastmcp import tool

DB_PATH = Path("./data/app.db")

def _get_connection():
    """获取数据库连接"""
    DB_PATH.parent.mkdir(exist_ok=True)
    return sqlite3.connect(str(DB_PATH))

@tool()
def query_database(sql: str) -> str:
    """执行 SQL 查询语句(仅支持 SELECT)

    Args:
        sql: SQL 查询语句,仅允许 SELECT 操作
    """
    # 安全检查:只允许 SELECT
    normalized = sql.strip().upper()
    if not normalized.startswith("SELECT"):
        return "错误:仅支持 SELECT 查询,不允许修改数据"

    try:
        conn = _get_connection()
        cursor = conn.execute(sql)
        columns = [desc[0] for desc in cursor.description]
        rows = cursor.fetchall()
        conn.close()

        if not rows:
            return "查询结果为空"

        # 格式化为 Markdown 表格
        header = "| " + " | ".join(columns) + " |"
        separator = "| " + " | ".join(["---"] * len(columns)) + " |"
        data_rows = []
        for row in rows[:50]:  # 最多返回 50 行
            data_rows.append("| " + " | ".join(str(v) for v in row) + " |")

        table = "\n".join([header, separator] + data_rows)
        return f"查询返回 {len(rows)} 行:\n\n{table}"

    except Exception as e:
        return f"查询出错: {str(e)}"

@tool()
def list_tables() -> str:
    """列出数据库中的所有表及其结构"""
    conn = _get_connection()
    cursor = conn.execute(
        "SELECT name FROM sqlite_master WHERE type='table'"
    )
    tables = cursor.fetchall()
    conn.close()

    if not tables:
        return "数据库为空,暂无表"

    result = []
    for (table_name,) in tables:
        result.append(f"- **{table_name}**")

    return "数据库中的表:\n" + "\n".join(result)

4.3 文件管理工具

python 复制代码
# tools/file_manager.py
import os
from pathlib import Path
from mcp.server.fastmcp import tool

SAFE_DIR = Path("./data/workspace")  # 安全沙箱目录

def _safe_path(filepath: str) -> Path:
    """确保文件路径在安全目录内"""
    full_path = (SAFE_DIR / filepath).resolve()
    if not str(full_path).startswith(str(SAFE_DIR.resolve())):
        raise ValueError("路径超出安全范围")
    return full_path

@tool()
def read_file(filepath: str) -> str:
    """读取文件内容

    Args:
        filepath: 相对于工作目录的文件路径
    """
    path = _safe_path(filepath)
    if not path.exists():
        return f"文件不存在: {filepath}"
    return path.read_text(encoding="utf-8")

@tool()
def write_file(filepath: str, content: str) -> str:
    """写入文件内容

    Args:
        filepath: 相对于工作目录的文件路径
        content: 要写入的内容
    """
    path = _safe_path(filepath)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")
    return f"文件写入成功: {filepath} ({len(content)} 字符)"

@tool()
def list_files(directory: str = ".") -> str:
    """列出目录下的文件

    Args:
        directory: 相对于工作目录的目录路径
    """
    path = _safe_path(directory)
    if not path.exists():
        return f"目录不存在: {directory}"

    items = []
    for item in sorted(path.iterdir()):
        if item.is_dir():
            items.append(f"📁 {item.name}/")
        else:
            size = item.stat().st_size
            items.append(f"📄 {item.name} ({size} bytes)")

    return "\n".join(items) if items else "目录为空"

4.4 定义 Resources(数据资源)

python 复制代码
# resources/system_info.py
import platform
import psutil
from mcp.server.fastmcp import resource

@resource("system://info")
def get_system_info() -> str:
    """获取系统信息"""
    return f"""系统信息:
- 操作系统: {platform.system()} {platform.release()}
- Python: {platform.python_version()}
- CPU 核心: {psutil.cpu_count()}
- 内存总量: {psutil.virtual_memory().total // (1024**3)} GB
- 磁盘使用: {psutil.disk_usage('/').percent}%
"""

@resource("system://processes")
def get_top_processes() -> str:
    """获取占用资源最多的前 10 个进程"""
    procs = []
    for p in psutil.process_iter(["pid", "name", "cpu_percent", "memory_percent"]):
        try:
            info = p.info
            procs.append(info)
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            pass

    # 按 CPU 使用率排序
    procs.sort(key=lambda x: x["cpu_percent"] or 0, reverse=True)

    lines = ["PID     名称                CPU%    内存%"]
    lines.append("-" * 45)
    for p in procs[:10]:
        lines.append(
            f"{p['pid']:<8}{p['name']:<20}{p['cpu_percent']:.1f}%   {p['memory_percent']:.1f}%"
        )
    return "\n".join(lines)

4.5 定义 Prompts(提示模板)

python 复制代码
# prompts/code_review.py
from mcp.server.fastmcp import prompt

@prompt()
def code_review(code: str, language: str = "python") -> str:
    """代码审查提示模板

    Args:
        code: 需要审查的代码
        language: 编程语言
    """
    return f"""请对以下 {language} 代码进行专业审查:

```{language}
{code}

请从以下维度进行审查:

  1. 代码质量 --- 可读性、命名规范、代码风格
  2. 潜在 Bug --- 逻辑错误、边界条件、空指针风险
  3. 安全漏洞 --- 注入攻击、数据泄露、权限问题
  4. 性能优化 --- 时间复杂度、内存使用、I/O 效率
  5. 最佳实践 --- 设计模式、SOLID 原则、错误处理

请为每个维度给出 1-5 分评分和具体改进建议。"""

@prompt()

def explain_code(code: str) -> str:

"""代码解释提示模板

复制代码
Args:
    code: 需要解释的代码
"""
return f"""请用通俗易懂的语言解释以下代码的功能:

{code}

要求:

  1. 先用一句话概括整体功能

  2. 逐段解释关键逻辑

  3. 说明输入和输出

  4. 指出可能的使用场景"""

    4.6 组装完整 Server

    python 复制代码
    # server.py
    from mcp.server.fastmcp import FastMCP
    
    mcp = FastMCP(
        name="enterprise-toolkit",
        version="1.0.0",
        description="企业级 MCP 工具服务:天气查询、数据库操作、文件管理",
    )
    
    # ── 注册 Tools ──────────────────────────────────────
    from tools.weather import get_weather, get_forecast
    from tools.database import query_database, list_tables
    from tools.file_manager import read_file, write_file, list_files
    
    mcp.tool(get_weather)
    mcp.tool(get_forecast)
    mcp.tool(query_database)
    mcp.tool(list_tables)
    mcp.tool(read_file)
    mcp.tool(write_file)
    mcp.tool(list_files)
    
    
    # ── 注册 Resources ──────────────────────────────────
    from resources.system_info import get_system_info, get_top_processes
    
    mcp.resource("system://info")(get_system_info)
    mcp.resource("system://processes")(get_top_processes)
    
    
    # ── 注册 Prompts ────────────────────────────────────
    from prompts.code_review import code_review, explain_code
    
    mcp.prompt(code_review)
    mcp.prompt(explain_code)
    
    
    # ── 启动 ────────────────────────────────────────────
    if __name__ == "__main__":
        mcp.run()

五、配置与使用

5.1 在 Claude Desktop 中配置

编辑 Claude Desktop 配置文件:

macOS : ~/Library/Application Support/Claude/claude_desktop_config.json
Windows : %APPDATA%\Claude\claude_desktop_config.json

json 复制代码
{
  "mcpServers": {
    "enterprise-toolkit": {
      "command": "python",
      "args": ["C:/path/to/my-mcp-server/server.py"],
      "env": {
        "OPENWEATHER_API_KEY": "your-api-key"
      }
    }
  }
}

5.2 在 Cursor / VS Code 中配置

在项目根目录创建 .cursor/mcp.json

json 复制代码
{
  "mcpServers": {
    "enterprise-toolkit": {
      "command": "python",
      "args": ["./my-mcp-server/server.py"]
    }
  }
}

5.3 调试流程

复制代码
┌─────────────────────────────────────────────────────────┐
│                    MCP 调试流程                          │
│                                                         │
│  1. 启动 Inspector                                     │
│     $ mcp dev server.py                                │
│                     │                                   │
│                     ▼                                   │
│  2. 浏览器打开 http://localhost:5173                     │
│                     │                                   │
│                     ▼                                   │
│  3. 查看已注册的 Tools / Resources / Prompts            │
│     ┌─────────────────────────────────────┐            │
│     │ Tools:                              │            │
│     │   ✓ get_weather                     │            │
│     │   ✓ get_forecast                    │            │
│     │   ✓ query_database                  │            │
│     │   ✓ read_file                       │            │
│     │   ✓ write_file                      │            │
│     │   ✓ list_files                      │            │
│     │                                     │            │
│     │ Resources:                          │            │
│     │   ✓ system://info                   │            │
│     │   ✓ system://processes              │            │
│     │                                     │            │
│     │ Prompts:                            │            │
│     │   ✓ code_review                     │            │
│     │   ✓ explain_code                    │            │
│     └─────────────────────────────────────┘            │
│                     │                                   │
│                     ▼                                   │
│  4. 点击工具 → 填入参数 → 点击 Run → 查看返回结果       │
│                     │                                   │
│                     ▼                                   │
│  5. 确认无误后,配置到 AI 客户端正式使用                  │
└─────────────────────────────────────────────────────────┘

六、进阶:SSE 模式部署为远程服务

6.1 使用 SSE 传输

python 复制代码
# server_sse.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP(
    name="enterprise-toolkit-remote",
    host="0.0.0.0",
    port=8080,
)

# ... 注册 tools/resources/prompts (同上) ...

if __name__ == "__main__":
    # SSE 模式:通过 HTTP 暴露服务
    mcp.run(transport="sse")

6.2 Docker 部署

dockerfile 复制代码
# Dockerfile
FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
EXPOSE 8080

CMD ["python", "server_sse.py"]
yaml 复制代码
# docker-compose.yml
version: "3.8"
services:
  mcp-server:
    build: .
    ports:
      - "8080:8080"
    environment:
      - OPENWEATHER_API_KEY=${OPENWEATHER_API_KEY}
    volumes:
      - ./data:/app/data
    restart: unless-stopped

6.3 客户端配置(远程 SSE)

json 复制代码
{
  "mcpServers": {
    "enterprise-toolkit": {
      "url": "http://your-server:8080/sse"
    }
  }
}

七、MCP 协议消息流详解

当用户在 Claude 中说 "帮我查一下北京天气" 时,完整的消息流如下:

复制代码
用户: "帮我查一下北京天气"
  │
  ▼
┌──────────┐
│   Claude  │ 1. 分析意图,决定调用 tool: get_weather
│  (Host)  │
└────┬─────┘
     │ MCP Request (JSON-RPC)
     │ {
     │   "jsonrpc": "2.0",
     │   "method": "tools/call",
     │   "params": {
     │     "name": "get_weather",
     │     "arguments": {
     │       "city": "北京"
     │     }
     │   }
     │ }
     ▼
┌──────────┐
│MCP Server│ 2. 执行 get_weather("北京")
│ (Python) │
└────┬─────┘
     │ MCP Response
     │ {
     │   "jsonrpc": "2.0",
     │   "result": {
     │     "content": [{
     │       "type": "text",
     │       "text": "【北京 天气】\n天气: 晴\n温度: 22°C..."
     │     }]
     │   }
     │ }
     ▼
┌──────────┐
│   Claude  │ 3. 结合工具返回结果,生成自然语言回答
│  (Host)  │
└────┬─────┘
     │
     ▼
用户看到: "北京今天天气晴朗,气温 22°C,湿度 35%,风速 3 m/s。"

八、最佳实践与安全注意事项

8.1 工具设计原则

复制代码
┌───────────────────────────────────────────────────┐
│              MCP 工具设计原则                       │
├───────────────────────────────────────────────────┤
│                                                   │
│  1. 单一职责 ─── 每个工具只做一件事                 │
│                                                   │
│  2. 清晰描述 ─── docstring 是 AI 理解工具的关键     │
│                                                   │
│  3. 类型标注 ─── 参数必须有明确的类型和默认值        │
│                                                   │
│  4. 安全沙箱 ─── 限制文件/数据库访问范围             │
│                                                   │
│  5. 错误友好 ─── 返回清晰的错误信息,而非抛异常      │
│                                                   │
│  6. 幂等设计 ─── 相同输入应产生相同输出              │
│                                                   │
└───────────────────────────────────────────────────┘

8.2 安全清单

python 复制代码
# 安全示例:参数验证与路径限制
import re
from pathlib import Path

def validate_sql(sql: str) -> bool:
    """只允许 SELECT 查询"""
    forbidden = ["DROP", "DELETE", "INSERT", "UPDATE", "ALTER", "CREATE"]
    upper_sql = sql.upper()
    return all(kw not in upper_sql for kw in forbidden)

def safe_filepath(base_dir: Path, user_path: str) -> Path:
    """防止路径穿越攻击"""
    full_path = (base_dir / user_path).resolve()
    if not str(full_path).startswith(str(base_dir.resolve())):
        raise ValueError("非法路径访问")
    return full_path

def sanitize_input(text: str) -> str:
    """清理用户输入"""
    # 移除潜在的危险字符
    return re.sub(r'[<>&\']', '', text)

九、总结

本文从零到一完成了一个完整的 MCP Server 开发,关键知识点回顾:

知识点 说明
FastMCP Python MCP Server 开发的核心框架
@tool() 装饰器注册 AI 可调用的工具函数
@resource() 装饰器注册 AI 可读取的数据资源
@prompt() 装饰器注册可复用的提示模板
stdio 传输 本地开发首选,AI 应用直接启动子进程
SSE 传输 远程部署方案,通过 HTTP 暴露服务
MCP Inspector 官方可视化调试工具,mcp dev 启动

下一步探索

  • 将 MCP Server 接入企业内部 API(Jira、Confluence、飞书)
  • 开发多用户权限管理的 MCP 中间件
  • 结合 RAG 构建 MCP 知识库服务
  • 参与开源 MCP 生态,发布你的工具到 MCP Hub

MCP 协议正在快速成为 AI 工具调用的行业标准,掌握它意味着你能让 AI 真正融入你的工作流。

相关推荐
宸津-代码粉碎机2 小时前
Spring Boot 4.0 进阶实战+源码解析系列(持续更新)—— 从落地到源码,搞定面试与工作
java·人工智能·spring boot·后端·python·面试
Z.风止2 小时前
Large Model-learning(4)
人工智能·pytorch·笔记·python·深度学习·机器学习
IpdataCloud2 小时前
风控策略误杀正常用户?如何用IP离线库多维特征优化规则阈值
网络·tcp/ip·安全·ip
Fleshy数模2 小时前
openCV实现实时颜色识别:从基础检测到指定颜色区域提取
人工智能·opencv·计算机视觉
海兰2 小时前
【第3篇】使用LangGraph构建工作流
人工智能·windows
Jial-(^V^)2 小时前
使用强化学习微调大模型
人工智能·llm
不知名XL2 小时前
day02 mcp开发以及skill开发规范
python
风雨中的小七2 小时前
和AI一起搞事情#3:Claude Teammate 游戏开发翻车实录
人工智能
一个帅气昵称啊2 小时前
.NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块
人工智能·ai·.net