🎯 学习目标
完成本章后,你将能够:
- ✅ 深刻理解 MCP 架构(Host-Client-Server)及其在 Claude Code 中的运作机制。
- ✅ 使用 Python SDK 快速搭建第一个功能完整的 MCP Server。
- ✅ 实现三大核心场景:SQLite 数据库交互 、RESTful API 调用 、本地文件安全读写。
- ✅ 掌握高级技巧:为工具添加缓存机制 、身份验证 和动态参数校验。
- ✅ 建立严格的安全防线:实施权限控制、输入清洗与标准化错误处理。
10.1 MCP 架构解析:理解 Claude Code 如何与外部世界对话
🏗️ 核心三角关系
MCP 的设计哲学是解耦。它定义了三个角色:
- Host (宿主) : Claude Code CLI 。
- 职责:发起请求,展示结果,管理用户交互。
- 它不知道具体工具怎么实现,只知道通过标准协议调用。
- Client (客户端) : 嵌入在 Host 中的适配器。
- 职责:将 Host 的意图转换为 MCP 协议消息,发送给 Server。
- 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": {}
}
}
}
🧪 测试运行
- 重启 Claude Code 会话。
- 输入:"请帮我计算 123 乘以 456,然后加上 789。"
- 观察 :Claude Code 会自动发现
calculatorServer 提供的add和multiply工具,并按顺序调用它们,最后给出结果。
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 强大的能力,但也带来了巨大的风险。必须严守安全底线。
🛡️ 核心安全原则
-
最小权限原则 (Least Privilege)
- Server 进程应以最低必要权限运行。
- 数据库连接使用只读账号,除非明确需要写入。
- 文件访问限制在特定白名单目录。
-
严格的输入验证 (Input Validation)
- 永远不要信任 AI 生成的参数。
- 对所有字符串参数进行长度限制、字符集检查。
- 对 SQL 参数使用参数化查询(虽然我们在 Server 端做了黑名单,但最好用 ORM 或预编译语句)。
- 对文件路径进行
resolve()和前缀检查,防止../../etc/passwd攻击。
-
标准化错误处理
- 不要让 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."
- 审计与日志
- 记录每一次工具调用的时间、工具名、参数摘要和执行结果。
- 便于事后追溯 AI 的行为。
🚫 常见陷阱
- 硬编码密钥 :绝对禁止!全部使用
os.getenv()。 - 无限递归:避免 Tool A 调用 Tool B,Tool B 又反过来调用 Tool A。
- 阻塞主线程:所有 I/O 操作必须异步或放在子线程,否则会导致整个 MCP 连接超时。
🧪 动手练习:构建你的全能助手
练习 1:Git 状态查询工具
- 创建一个 MCP Server,暴露
get_git_status工具。 - 内部执行
git status --porcelain。 - 解析输出,用自然语言告诉 AI 哪些文件被修改、新增或删除。
- 测试:问 AI"我现在有哪些未提交的更改?"
练习 2:带缓存的汇率转换器
- 创建一个 Server,调用免费汇率 API。
- 实现缓存:同一货币对在 1 小时内重复查询直接返回缓存值。
- 测试连续两次查询 USD 到 CNY 的汇率,观察第二次是否瞬间返回。
练习 3:安全文件沙箱
- 编写一个文件读取 Server,仅允许访问
./docs目录。 - 尝试让 AI 读取
../.env文件。 - 验证 Server 是否正确拦截了该请求并返回错误。
❓ 常见陷阱与解答 (FAQ)
| 问题 | 原因分析 | 解决方案 |
|---|---|---|
| Claude Code 找不到工具 | .claude-code.json 配置路径错误,或 Server 启动失败。 |
检查 command 和 args 路径;手动运行 Server 脚本看是否有报错。 |
| 工具调用超时 | Server 执行了阻塞操作(如同步网络请求)且耗时过长。 | 优化代码速度,或在 Server 端使用异步 IO。 |
| AI 胡乱传参 | 工具描述 (Description) 不清晰。 | 优化 @mcp.tool() 的 docstring,明确参数类型和格式要求。 |
| 权限报错 | Server 进程没有权限访问某些文件或端口。 | 检查文件权限 (chmod),或以正确用户身份运行。 |
📝 本章小结
- MCP 是桥梁:它标准化了 AI 与外部世界的连接方式,让扩展变得简单且统一。
- Python SDK 强大易用:几行代码即可将任意 Python 脚本转化为 AI 可调用的工具。
- 场景无限:从数据库查询到 API 调用,再到本地文件操作,MCP 让 AI 真正落地业务。
- 安全是生命线:必须实施严格的输入验证、权限控制和错误处理,防止 AI 造成破坏。
🚀 下一步预告 :
拥有了自定义工具后,我们将进入 第 11 章:多代理协作与编排,学习如何让多个具备不同技能的 Agent 相互协作,共同完成复杂的系统工程任务!