第 10 章:模型上下文协议 (MCP) 实战 —— 打造专属工具,连接无限世界

🎯 学习目标

完成本章后,你将能够:

  • ✅ 深刻理解 MCP 架构(Host-Client-Server)及其在 Claude Code 中的运作机制。
  • ✅ 使用 Python SDK 快速搭建第一个功能完整的 MCP Server。
  • ✅ 实现三大核心场景:SQLite 数据库交互RESTful API 调用本地文件安全读写
  • ✅ 掌握高级技巧:为工具添加缓存机制身份验证动态参数校验
  • ✅ 建立严格的安全防线:实施权限控制、输入清洗与标准化错误处理。

10.1 MCP 架构解析:理解 Claude Code 如何与外部世界对话

🏗️ 核心三角关系

MCP 的设计哲学是解耦。它定义了三个角色:

  1. Host (宿主) : Claude Code CLI
    • 职责:发起请求,展示结果,管理用户交互。
    • 它不知道具体工具怎么实现,只知道通过标准协议调用。
  2. Client (客户端) : 嵌入在 Host 中的适配器。
    • 职责:将 Host 的意图转换为 MCP 协议消息,发送给 Server。
  3. Server (服务端) : 你编写的程序 (Python/Node.js)。
    • 职责:暴露具体的Tools (工具)Resources (资源)Prompts (提示词)
    • 例如:一个连接 PostgreSQL 的 Server,暴露 query_db 工具。

🔄 通信流程

渲染错误: Mermaid 渲染失败: Parse error on line 2: ... | Host[Claude Code (Host)] Host --> -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

💡 为什么需要 MCP?

  • 标准化:不再为每个新工具写特定的 Plugin 代码,只需遵循 MCP 协议。
  • 安全性:Server 运行在独立进程,权限隔离。Host 只能调用显式暴露的工具。
  • 灵活性:可以用任何语言写 Server(Python 做数据分析,Go 做高性能服务),Host 统一调用。

10.2 MCP Server 快速入门:使用 Python SDK 创建你的第一个 MCP Server

我们将使用官方推荐的 Python SDK (mcp) 来构建服务器。

📦 环境准备

bash 复制代码
# 创建虚拟环境
python -m venv mcp-env
source mcp-env/bin/activate  # Windows: mcp-env\Scripts\activate

# 安装 SDK
pip install mcp

🚀 Hello World: 创建一个计算器工具

创建文件 calculator_server.py:

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

# 1. 初始化服务器
mcp = FastMCP("Demo Calculator")

# 2. 定义工具 (Tool)
@mcp.tool()
def add(a: int, b: int) -> int:
    """将两个整数相加。"""
    return a + b

@mcp.tool()
def multiply(a: int, b: int) -> int:
    """将两个整数相乘。"""
    return a * b

# 3. 启动服务器 (使用 STDIO 传输)
if __name__ == "__main__":
    mcp.run()

⚙️ 配置 Claude Code 连接

修改项目根目录的 .claude-code.json (或全局配置),注册这个 Server:

json 复制代码
{
  "mcpServers": {
    "calculator": {
      "command": "python",
      "args": ["/absolute/path/to/calculator_server.py"],
      "env": {} 
    }
  }
}

🧪 测试运行

  1. 重启 Claude Code 会话。
  2. 输入:"请帮我计算 123 乘以 456,然后加上 789。"
  3. 观察 :Claude Code 会自动发现 calculator Server 提供的 addmultiply 工具,并按顺序调用它们,最后给出结果。

10.3 MCP 实战案例:连接真实世界

🗄️ 案例一:SQLite 数据库连接器

目标:让 AI 能查询本地开发数据库。

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

mcp = FastMCP("SQLite Connector")

@mcp.tool()
def query_database(sql: str) -> str:
    """
    执行只读 SQL 查询 (SELECT)。
    警告:严禁执行 INSERT/UPDATE/DELETE/DROP 等操作。
    """
    # 安全校验:简单黑名单过滤 (生产环境需更严格解析)
    forbidden_keywords = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER"]
    if any(keyword in sql.upper() for keyword in forbidden_keywords):
        return "Error: 禁止执行写操作。"

    try:
        conn = sqlite3.connect("dev.db")
        cursor = conn.cursor()
        cursor.execute(sql)
        rows = cursor.fetchall()
        # 返回 Markdown 表格格式
        headers = [desc[0] for desc in cursor.description]
        result = "| " + " | ".join(headers) + " |\n"
        result += "| " + " | ".join(["---"] * len(headers)) + " |\n"
        for row in rows:
            result += "| " + " | ".join(str(x) for x in row) + " |\n"
        conn.close()
        return result
    except Exception as e:
        return f"Error: {str(e)}"

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

配置后,AI 可以说:"查询 users 表中年龄大于 25 的用户",自动转化为 SQL 并返回表格。

🌐 案例二:调用外部天气 API

目标:获取实时数据。

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

mcp = FastMCP("Weather Service")

@mcp.tool()
def get_weather(city: str) -> str:
    """获取指定城市的当前天气信息。"""
    api_key = os.getenv("WEATHER_API_KEY")
    if not api_key:
        return "Error: 未配置 API 密钥。"
    
    url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric"
    try:
        resp = requests.get(url, timeout=5)
        resp.raise_for_status()
        data = resp.json()
        return f"城市:{city}\n温度:{data['main']['temp']}°C\n天气:{data['weather'][0]['description']}\n湿度:{data['main']['humidity']}%"
    except Exception as e:
        return f"Error: {str(e)}"

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

📂 案例三:安全的本地文件读取器

目标:允许 AI 读取特定目录下的配置文件,但禁止访问其他路径。

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

mcp = FastMCP("Safe File Reader")
ALLOWED_DIR = Path("/Users/dev/project/configs").resolve()

@mcp.tool()
def read_config(filename: str) -> str:
    """读取 configs 目录下的配置文件内容。"""
    # 路径解析与安全校验
    full_path = (ALLOWED_DIR / filename).resolve()
    
    # 防止目录遍历攻击 (../)
    if not str(full_path).startswith(str(ALLOWED_DIR)):
        return "Error: 访问被拒绝,路径超出允许范围。"
    
    if not full_path.exists():
        return f"Error: 文件 {filename} 不存在。"
        
    try:
        return full_path.read_text(encoding="utf-8")
    except Exception as e:
        return f"Error: {str(e)}"

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

10.4 高级 MCP 开发:构建更智能的工具

⚡ 引入缓存机制

对于耗时或昂贵的操作(如 API 调用),添加内存缓存以避免重复请求。

python 复制代码
from functools import lru_cache
import time

@mcp.tool()
@lru_cache(maxsize=100)
def get_expensive_data(query_id: str) -> str:
    """获取昂贵数据,结果缓存 10 分钟 (简化版示例)"""
    # 实际生产中需结合时间戳失效策略
    time.sleep(2) # 模拟延迟
    return f"Data for {query_id}"

🔐 集成身份验证

工具可以检查环境变量或令牌,确保只有授权操作才能执行。

python 复制代码
@mcp.tool()
def deploy_to_production(service_name: str, token: str) -> str:
    """部署服务到生产环境 (需要 Admin Token)"""
    admin_token = os.getenv("PROD_DEPLOY_TOKEN")
    if token != admin_token:
        return "Error: 认证失败,无权执行生产部署。"
    
    # 执行部署逻辑...
    return f"✅ {service_name} 部署成功。"

AI 在调用时会被要求提供 token,或者从安全上下文中自动获取。

📝 动态资源 (Resources)

除了 Tools (动作),MCP 还支持 Resources (数据源)。

python 复制代码
@mcp.resource("config://app/settings")
def get_app_settings() -> str:
    """提供应用设置的实时视图"""
    return '{"theme": "dark", "lang": "zh-CN"}'

AI 可以直接 /read config://app/settings 来获取数据,无需调用函数。


10.5 MCP 安全与最佳实践:权限控制、输入验证与错误处理

MCP 赋予了 AI 强大的能力,但也带来了巨大的风险。必须严守安全底线。

🛡️ 核心安全原则

  1. 最小权限原则 (Least Privilege)

    • Server 进程应以最低必要权限运行。
    • 数据库连接使用只读账号,除非明确需要写入。
    • 文件访问限制在特定白名单目录。
  2. 严格的输入验证 (Input Validation)

    • 永远不要信任 AI 生成的参数
    • 对所有字符串参数进行长度限制、字符集检查。
    • 对 SQL 参数使用参数化查询(虽然我们在 Server 端做了黑名单,但最好用 ORM 或预编译语句)。
    • 对文件路径进行 resolve() 和前缀检查,防止 ../../etc/passwd 攻击。
  3. 标准化错误处理

    • 不要让 Python 堆栈跟踪直接暴露给 AI(可能泄露路径结构)。
    • 捕获所有异常,返回清晰的、人类可读的错误消息。
    • 区分"用户错误"(参数不对)和"系统错误"(服务挂了)。
python 复制代码
try:
    # 危险操作
    pass
except ValueError as e:
    return f"Invalid input: {str(e)}" # 用户错误
except Exception as e:
    # 记录详细日志到文件,但对 AI 只返回通用消息
    log_error(e) 
    return "System error occurred. Please check logs." 
  1. 审计与日志
    • 记录每一次工具调用的时间、工具名、参数摘要和执行结果。
    • 便于事后追溯 AI 的行为。

🚫 常见陷阱

  • 硬编码密钥 :绝对禁止!全部使用 os.getenv()
  • 无限递归:避免 Tool A 调用 Tool B,Tool B 又反过来调用 Tool A。
  • 阻塞主线程:所有 I/O 操作必须异步或放在子线程,否则会导致整个 MCP 连接超时。

🧪 动手练习:构建你的全能助手

练习 1:Git 状态查询工具

  1. 创建一个 MCP Server,暴露 get_git_status 工具。
  2. 内部执行 git status --porcelain
  3. 解析输出,用自然语言告诉 AI 哪些文件被修改、新增或删除。
  4. 测试:问 AI"我现在有哪些未提交的更改?"

练习 2:带缓存的汇率转换器

  1. 创建一个 Server,调用免费汇率 API。
  2. 实现缓存:同一货币对在 1 小时内重复查询直接返回缓存值。
  3. 测试连续两次查询 USD 到 CNY 的汇率,观察第二次是否瞬间返回。

练习 3:安全文件沙箱

  1. 编写一个文件读取 Server,仅允许访问 ./docs 目录。
  2. 尝试让 AI 读取 ../.env 文件。
  3. 验证 Server 是否正确拦截了该请求并返回错误。

❓ 常见陷阱与解答 (FAQ)

问题 原因分析 解决方案
Claude Code 找不到工具 .claude-code.json 配置路径错误,或 Server 启动失败。 检查 commandargs 路径;手动运行 Server 脚本看是否有报错。
工具调用超时 Server 执行了阻塞操作(如同步网络请求)且耗时过长。 优化代码速度,或在 Server 端使用异步 IO。
AI 胡乱传参 工具描述 (Description) 不清晰。 优化 @mcp.tool() 的 docstring,明确参数类型和格式要求。
权限报错 Server 进程没有权限访问某些文件或端口。 检查文件权限 (chmod),或以正确用户身份运行。

📝 本章小结

  • MCP 是桥梁:它标准化了 AI 与外部世界的连接方式,让扩展变得简单且统一。
  • Python SDK 强大易用:几行代码即可将任意 Python 脚本转化为 AI 可调用的工具。
  • 场景无限:从数据库查询到 API 调用,再到本地文件操作,MCP 让 AI 真正落地业务。
  • 安全是生命线:必须实施严格的输入验证、权限控制和错误处理,防止 AI 造成破坏。

🚀 下一步预告

拥有了自定义工具后,我们将进入 第 11 章:多代理协作与编排,学习如何让多个具备不同技能的 Agent 相互协作,共同完成复杂的系统工程任务!

相关推荐
菜鸟小九3 小时前
JVM垃圾回收
java·jvm·算法
爱丽_3 小时前
SQL 事务主线:ACID、隔离级别、MVCC 与一致性读
jvm·矩阵
敖正炀4 小时前
Java 线程状态变化与ObjectMonitor之间的关系
jvm·后端
江不清丶4 小时前
垃圾收集算法深度解析:从标记-清除到分代收集的演进之路
java·jvm·算法
庞轩px5 小时前
【无标题】
java·开发语言·jvm
小鱼不会骑车5 小时前
JVM 内存管理与垃圾回收(GC)深度解析
jvm
敖正炀5 小时前
重量级锁ObjectMonitor 详解
jvm
gelald5 小时前
JVM - 类加载机制
java·jvm·后端
96775 小时前
C++ 内存管理的核心——RAII 机制。两种锁 lock_guard, unique_lock
java·jvm·c++