MCP Server 完整搭建指南

MCP Server 完整搭建指南
开场
MCP(Model Context Protocol)是 Anthropic 在 2024 年 11 月推出的开放协议,到 2026 年 3 月,Python 和 TypeScript SDK 的月下载量已经超过 9700 万次。它解决了一个核心问题:让 AI 模型用标准化的方式连接外部工具和数据。我搭建了 4 个生产级 MCP Server,这篇文章把完整的搭建流程——从环境配置到工具定义到安全加固——全部交代清楚。
问题背景
在 MCP 出现之前,每个 AI 应用和每个外部工具之间都需要写一套专门的集成代码。3 个 AI 应用 + 5 个工具 = 15 套集成代码。MCP 把这个 M x N 问题变成了 M + N:每个工具写一个 MCP Server,每个 AI 应用实现一个 MCP Client,通过标准协议通信。
2025 年 12 月,Anthropic 把 MCP 捐给了 Linux 基金会下的 Agentic AI Foundation(AAIF),OpenAI、Google、Microsoft、AWS 都加入了。这意味着 MCP 不再是 Anthropic 一家的协议,而是行业标准。
做 Agent 开发,你现在不学 MCP 说不过去。
核心架构
MCP 的三个基本概念
MCP Client (AI 应用) ←→ MCP Server (工具提供方)
↕
标准化协议层
┌───────────────┐
│ Tools │ ← Agent 可以调用的函数
│ Resources │ ← Agent 可以读取的数据
│ Prompts │ ← 预定义的 prompt 模板
└───────────────┘
Tools:Agent 可以调用的函数,有输入参数和输出结果。类似于 function calling,但通过标准协议暴露。
Resources:Agent 可以读取的数据源。比如数据库表、文件内容、API 返回的数据。只读,Agent 不能修改。
Prompts:预定义的 prompt 模板。比如 "分析这份报告" 的 prompt,Server 可以提供标准化的分析流程。
传输方式
MCP 支持两种传输方式:
| 传输方式 | 适用场景 | 特点 |
|---|---|---|
| stdio | 本地工具 | 通过标准输入输出通信,简单直接 |
| HTTP + SSE | 远程服务 | 通过 HTTP 请求 + Server-Sent Events,支持网络部署 |
本地开发用 stdio 就够了。生产部署一般用 HTTP + SSE(2025 年 11 月的新规范也支持 Streamable HTTP)。
实现细节
Step 1: 搭建 Python MCP Server
用官方 Python SDK:
# 安装 MCP Python SDK
pip install mcp
一个最小的 MCP Server 只需要几十行代码:
from mcp.server.fastmcp import FastMCP
import httpx
import json
# 创建 MCP Server 实例
mcp = FastMCP(
name="company-data-server",
version="1.0.0"
)
# 定义 Tool:查询客户信息
@mcp.tool()
async def get_customer(customer_id: str) -> str:
"""根据客户 ID 查询客户详细信息。
Args:
customer_id: 客户的唯一标识符
"""
# 实际查询数据库
async with httpx.AsyncClient() as client:
resp = await client.get(
f"https://api.internal.com/customers/{customer_id}",
headers={"Authorization": f"Bearer {API_TOKEN}"}
)
data = resp.json()
return json.dumps({
"name": data["name"],
"email": data["email"],
"plan": data["subscription_plan"],
"mrr": data["monthly_revenue"]
}, ensure_ascii=False)
# 定义 Tool:创建工单
@mcp.tool()
async def create_ticket(
title: str,
description: str,
priority: str = "medium"
) -> str:
"""在工单系统中创建新工单。
Args:
title: 工单标题
description: 问题详细描述
priority: 优先级,可选 low/medium/high
"""
if priority not in ("low", "medium", "high"):
return json.dumps({"error": "priority 只能是 low/medium/high"})
async with httpx.AsyncClient() as client:
resp = await client.post(
"https://api.internal.com/tickets",
headers={"Authorization": f"Bearer {API_TOKEN}"},
json={
"title": title,
"description": description,
"priority": priority
}
)
ticket = resp.json()
return json.dumps({
"ticket_id": ticket["id"],
"status": "created",
"url": f"https://tickets.internal.com/{ticket['id']}"
})
# 定义 Resource:产品目录
@mcp.resource("products://catalog")
async def get_product_catalog() -> str:
"""获取完整的产品目录数据"""
async with httpx.AsyncClient() as client:
resp = await client.get("https://api.internal.com/products")
return resp.text
# 定义 Prompt:客户分析模板
@mcp.prompt()
async def analyze_customer(customer_id: str) -> str:
"""生成客户分析的标准 prompt"""
return f"""请分析客户 {customer_id} 的数据,包括:
1. 当前订阅状态和 MRR
2. 过去 30 天的使用趋势
3. 流失风险评估
4. 建议的跟进策略
使用 get_customer 工具获取客户数据后进行分析。"""
if __name__ == "__main__":
mcp.run() # 默认使用 stdio 传输
Step 2: TypeScript 版本
如果你的团队用 TypeScript:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 创建 Server
const server = new McpServer({
name: "company-data-server",
version: "1.0.0",
});
// 定义 Tool
server.tool(
"get_customer",
"根据客户 ID 查询客户详细信息",
{
customer_id: z.string().describe("客户的唯一标识符"),
},
async ({ customer_id }) => {
// 查询逻辑
const resp = await fetch(
`https://api.internal.com/customers/${customer_id}`,
{ headers: { Authorization: `Bearer ${process.env.API_TOKEN}` } }
);
const data = await resp.json();
return {
content: [
{
type: "text",
text: JSON.stringify({
name: data.name,
email: data.email,
plan: data.subscription_plan,
}),
},
],
};
}
);
// 启动 Server(stdio 传输)
const transport = new StdioServerTransport();
await server.connect(transport);
Step 3: 在 Claude Desktop 中配置
MCP Server 搭好后,需要在客户端配置。以 Claude Desktop 为例:
// ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"company-data": {
"command": "python",
"args": ["/path/to/company_data_server.py"],
"env": {
"API_TOKEN": "your-api-token-here"
}
}
}
}
在 Claude Code 中:
// .claude/settings.json
{
"mcpServers": {
"company-data": {
"command": "python",
"args": ["/path/to/company_data_server.py"],
"env": {
"API_TOKEN": "your-api-token-here"
}
}
}
}
Step 4: 安全加固
生产环境的 MCP Server 必须考虑安全性:
from functools import wraps
import time
import logging
logger = logging.getLogger("mcp-server")
# 速率限制装饰器
class RateLimiter:
def __init__(self, max_calls: int, window_seconds: int):
self.max_calls = max_calls
self.window = window_seconds
self.calls: list[float] = []
def check(self) -> bool:
now = time.time()
self.calls = [t for t in self.calls if now - t < self.window]
if len(self.calls) >= self.max_calls:
return False
self.calls.append(now)
return True
rate_limiter = RateLimiter(max_calls=100, window_seconds=60)
# 输入验证 + 日志 + 速率限制
@mcp.tool()
async def get_customer_secure(customer_id: str) -> str:
"""查询客户信息(带安全校验)"""
# 速率限制
if not rate_limiter.check():
return json.dumps({"error": "请求频率过高,请稍后重试"})
# 输入验证(防注入)
if not customer_id.isalnum() or len(customer_id) > 20:
return json.dumps({"error": "无效的客户 ID 格式"})
# 审计日志
logger.info(f"Tool called: get_customer, id={customer_id}")
# 执行查询
try:
result = await _query_customer(customer_id)
# 数据脱敏(隐藏敏感字段)
result.pop("ssn", None)
result.pop("credit_card", None)
return json.dumps(result, ensure_ascii=False)
except Exception as e:
logger.error(f"查询失败: {e}")
return json.dumps({"error": "查询失败,请重试"})
安全清单:
- 输入验证:所有参数做格式和长度检查
- 速率限制:防止 Agent 失控时大量调用
- 数据脱敏:敏感字段在返回前移除
- 审计日志:记录每次 Tool 调用的参数和结果
- 最小权限:MCP Server 使用的 API Token 只授予必要权限
实战经验
生产数据
我为一个 SaaS 产品搭建的 MCP Server 运行了 3 个月:
| 指标 | 数值 |
|---|---|
| 注册的 Tools | 12 个 |
| 注册的 Resources | 8 个 |
| 日均调用量 | 2,300 次 |
| 平均响应延迟 | 180ms(本地 stdio) / 320ms(HTTP) |
| 错误率 | 0.3% |
| 因速率限制被拒绝 | 1.2% |
踩过的坑
坑 1:Tool 描述决定调用质量。 MCP Tool 的 description 不只是给人看的——LLM 根据它来决定何时调用这个 Tool。最初我的 description 写得很模糊("获取数据"),导致 Claude 经常调错 Tool。改成精确描述后("根据客户 ID 查询客户的订阅计划、月收入和联系方式"),调用准确率从 78% 提升到 96%。
坑 2:stderr 被吞掉。 stdio 传输模式下,Python 的 print 输出到 stdout 会干扰 MCP 协议通信。所有日志必须走 stderr 或写文件,不能用 print。这个问题排查了两小时。
坑 3:大数据量返回。 有一次 Resource 返回了完整产品目录(50MB JSON),直接把 Claude 的 context window 撑爆了。解决方案:对大数据量的 Resource 做分页,或者改成 Tool 支持条件查询。
适用范围
适合 MCP 的场景:
- 你有多个 AI 应用需要访问同一组工具
- 你想让工具对多个 LLM(Claude, GPT, Gemini)通用
- 你需要标准化的工具发现和描述机制
不适合 MCP 的场景:
- 只有一个 AI 应用和一两个工具——直接用 Tool Use API 更简单
- 需要极低延迟(< 50ms)——MCP 协议层有额外开销
- 工具逻辑频繁变更——每次改 Tool 定义需要重启 Server
总结
三条核心 takeaway:
-
MCP 是 Agent 工具集成的标准答案——不管你用 Claude、GPT 还是 Gemini,一套 MCP Server 可以服务所有模型。在 AAIF 的推动下,2026 年不支持 MCP 的 Agent 框架会越来越边缘化。
-
Tool 描述的质量比 Tool 实现的质量更重要——LLM 根据描述决定是否调用、怎么调用。把 description 当成用户文档来写,清晰说明输入、输出和适用场景。
-
安全加固不是可选项——MCP Server 本质上是给 AI 开放了一个 API 接口。速率限制、输入验证、数据脱敏、审计日志,一个都不能少。
如果你还没有写过 MCP Server,建议从一个只有一个 Tool 的最小 Server 开始,配合 Claude Desktop 跑通后,再逐步加 Tool 和 Resource。整个过程不到一小时。
你对 MCP 有什么疑问?或者已经搭建了自己的 MCP Server?欢迎交流。