主题
第7章 MCP-大模型的USB
现代大模型应用面临的N×M胶水代码困境,催生了 MCP 协议——它就像大模型版的 USB-C,一套标准协议,让所有模型和所有工具实现即插即用。本章从 MCP 的理念和今世前生讲起,带读者快速体验 MCP 的实际效果,再手把手教读者开发自己的第一个 MCP Server。读完本章,你将拥有让大模型替你干活的全新工作方式。
MCP 的全称是 Model Context Protocol(模型上下文协议)。它由 Anthropic 在 2024 年底发起,基于 JSON-RPC 2.0 协议,允许大模型应用(Host)动态发现并调用远程或本地服务器(Server)提供的资源和工具。
如果说 RAG 是大模型的"外部记忆体",那么 MCP 就是大模型连接现实世界的"USB 接口"。它解决了一个工程上棘手的"N×M 胶水代码"问题:过去,每接入一个模型就要为每个数据源单独写一套适配代码,10 个模型 × 5 个数据源 = 50 套代码;而有了 MCP,只需一套标准协议,模型和数据源之间就能完成"即插即用"的全量复用。
更重要的是,MCP 不仅是一种协议,更是一种开放标准。它让 AI 模型能够安全、标准化地访问外部数据和工具,真正完成从"只会聊天"到"能干实事"的跨越。
打个比方:没有 MCP 之前,大模型就像被关在一个没有窗户的房间里。它读过很多书、知道很多事,但它看不见你的文件、连不上你的数据库、也无法感知外部世界。你想让它帮忙查个数据,得先把资料复印一份从门缝里塞进去——这就是我们每次往提示词里贴代码、贴表结构时在做的事。
而 MCP 相当于给这个房间装上了标准化的插座和传感器。只要插上一个符合 MCP 协议的"插头"(Server),模型就能直接"看到"外部数据,并能"操作"外部设备——读取文件、查询数据库、调用 API,全都在协议层完成,不需要你手动搬运了。
7.1 MCP的今世前生
MCP 不是凭空冒出来的。要理解它为什么诞生,我们需要回到一个更根本的问题:大模型天生就是"聋哑"的。
7.1.1 大模型的天生缺陷
大模型的运行依赖专用的推理框架和特定的硬件环境(GPU/NPU)。这种封闭的运行模式决定了它的一个根本局限:模型无法直接与外界进行信息交互,它只能根据你喂给它的文本内容做出回应。
比如,你想让模型帮你查一下今天的股价。模型的知识截止于训练日期,它不知道"今天"的数据。你没法对它说"你去看看雅虎财经",因为它根本就没有"看网页"这个能力——它的世界里只有文本输入和文本输出。
这个问题在 ChatGPT 爆火后迅速暴露了出来。用户很快发现,模型虽然"聪明",但它像个博学的瞎子——什么都知道,但什么都看不见。
7.1.2 Function Calling:第一次"开窗"
OpenAI 最早系统性解决了这个问题。他们在 2023 年推出了一个叫做 Function Calling(函数调用)的交互规范。
Function Calling 的核心思路很简单:告诉模型"有哪些外部工具可用",让模型在需要时主动提出调用请求。
具体来说,当用户向模型提问时,开发者可以同时传入一份 JSON 格式的"工具清单":
json
{
"name": "get_stock_price",
"description": "获取指定股票的实时价格",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "股票代码,如 AAPL、600519"
}
},
"required": ["symbol"]
}
}模型收到这份清单后,如果判断"用户的提问需要调用这个工具",它不会自己真的去调用——它只是输出一个结构化的 JSON 响应,告诉调用者:
json
{
"name": "get_stock_price",
"arguments": {
"symbol": "AAPL"
}
}然后,由调用者(你的程序)去真正执行这个函数,拿到结果后再喂回给模型,模型据此生成最终回答。
这里有一个容易搞混的关键点:外部工具的描述信息并不是混在 system prompt 或 user prompt 里发给模型的,而是放在一个独立的 tools 参数中。模型收到 tools 参数后会进入一种特殊的工作模式——它知道自己可以请求调用外部工具,但实际执行始终由调用者来承担。
7.1.3 模型是怎么"学会"函数调用的
你可能好奇:模型怎么"看懂" JSON 格式的工具声明,并且准确地返回调用参数?
答案在于训练。现代模型(如 DeepSeek-V4、GPT-4o、Claude 3.5 等)在训练阶段不只是学"背书",还专门进行了 Function Calling 的专项强化训练。具体来看两种机制:
1. 特殊 Token 标记
模型在训练时被喂入了大量带特殊标记的语料(这一训练方法可参考 TULU 3 的技术报告 [19])。当它看到类似 <tools>...</tools> 这样的报文结构时,会切换为工具调用模式——此时模型优先解析工具声明并按 Schema 约束输出结构化调用指令,而非自由发挥生成闲聊式文本。
以开源模型 Qwen3-30B-A3B-Instruct 为例(模型发布自 Qwen 团队 [20]),下载其模型权重文件中的 tokenizer_config.json,可以找到这样一段模板定义:
json
{%- elif message.role == "assistant" %}
{{- '<|im_start|>' + message.role + '\\n' + content }}
{%- if message.tool_calls %}
{%- for tool_call in message.tool_calls %}
...
{{- '<tool_call>{"name": "' }}
...
{{- '}</tool_call>' }}
{%- endfor %}
{%- endif %}
{{- '<|im_end|>' }}这段 Jinja 模板定义了模型在收到工具调用请求时如何格式化它的输出:用 <tool_call> 标签包裹 JSON 格式的调用信息。这不是运行时"理解",而是在训练阶段就刻在模型的参数里的行为模式。换句话说,训练时模型反复看到这种结构,学会了一读到 <tool_call> 就知道要输出 JSON 格式的调用参数,而不是闲聊。
2. 采样约束
在微调阶段,模型被专门训练为:当输入中包含工具声明时,优先输出严格的 JSON,而不是自由文本。这就像训练一个同声传译员——当他戴上耳机时,他知道现在不能聊天,只能逐句翻译。模型在这种训练下学会了"工具模式"和"对话模式"的切换。
7.1.4 一个 Function Calling 的完整示例
理解了 Function Calling 的原理之后,下面用一个完整的代码示例来演示它的实际工作流程。为了兼容不同的模型协议,我们使用 LiteLLM 这个库(关于 LiteLLM 的详细介绍参见本书第五章第 5 节)。依赖:pip install litellm(若直接用 DeepSeek/OpenAI 官方 SDK 亦可,见 3.1 节):
python
import json
import os
import litellm
# DeepSeek-V4-Pro 是 DeepSeek 截止 2026 年中推荐的主力模型
# 具体模型名和 API 地址以官方文档为准:https://api-docs.deepseek.com
model_name = "deepseek/deepseek-v4-pro"
base_url = "https://api.deepseek.com"
# 从环境变量中读取 API Key(实践中不要在代码中硬编码)
api_key = os.getenv("DEEPSEEK_API_KEY")
if not api_key:
print("请设置环境变量 DEEPSEEK_API_KEY")
print(" export DEEPSEEK_API_KEY='sk-xxx'")
exit(1)
# 1. 定义工具:告诉模型有哪些外部能力可用
tools = [
{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "获取指定股票的实时价格(模拟数据)",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "股票代码,如 AAPL、600519"
}
},
"required": ["symbol"]
}
}
}
]
# 2. System Prompt:设定模型的角色和行为
system_prompt = (
"你是一个金融数据助手。"
"当用户询问股票价格时,你必须调用 get_stock_price 工具。"
)
print(f"\n{'='*20} Function Calling 演示 {'='*20}")
try:
# 3. 发起请求:tools 参数独立传入,不混在 messages 中
response = litellm.completion(
model=model_name,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": "帮我查一下苹果(AAPL)的股价"}
],
tools=tools,
api_key=api_key,
base_url=base_url,
# tool_choice="required" # 可以强制模型必须调用工具
)
msg = response.choices[0].message
print("\n📨 模型原始响应:")
content_text = msg.content if msg.content else ""
print(f" - 文本内容: {json.dumps(content_text, ensure_ascii=False)}")
print(f" - 工具调用: {msg.tool_calls if hasattr(msg, 'tool_calls') else 'None'}")
# 4. 解析工具调用请求
if hasattr(msg, "tool_calls") and msg.tool_calls:
call = msg.tool_calls[0].function
print(f"\n🎯 模型请求调用: {call.name}")
print(f" 参数: {call.arguments}")
# 5. 模拟执行"真实"的函数调用
args = json.loads(call.arguments)
def get_stock_price(symbol: str) -> str:
"""模拟股价查询(实际应接入行情 API)"""
# 此处应替换为真正的数据源查询
mock_data = {"AAPL": 198.75, "GOOGL": 152.30}
price = mock_data.get(symbol)
if price is None:
return json.dumps({"error": f"未找到股票代码 {symbol}"})
return json.dumps({"symbol": symbol, "price": price})
tool_result = get_stock_price(args["symbol"])
print(f" 查询结果: {tool_result}")
except Exception as e:
print(f"❌ 调用失败: {str(e)}")这段代码展示了一个完整的 Function Calling 链路:定义工具 → 模型判断是否需要调用 → 输出 JSON 调用请求 → 程序执行真实调用 → 结果反馈给模型。注意,在这整个过程中,模型始终没有"真正执行"任何代码——它只是一个聪明的"翻译官",把自然语言请求翻译成结构化的函数调用提案,具体执行始终由你的代码来完成。
7.1.5 Function Calling 的四个痛点
Function Calling 确实解决了"让模型调用外部工具"的从 0 到 1 的问题。但它在实际工程中也暴露了四个明显的痛点:
1. 协议碎片化,跨模型兼容噩梦
上面的代码使用的是 OpenAI 风格的 Function Calling 格式。但 Anthropic 的 Claude 用的是另一套规范,Google 的 Gemini 又是另一套,OpenCode 等多模态 API 也各有各的写法。每接入一个新模型,你就要学一套新的工具定义格式。这不是"写代码",这是在"写适配器"。
2. 手写工具定义,效率极低
每个外部接口都要手工写成 JSON Schema 格式的工具声明。参数类型要对齐,描述要写得让模型"看懂",缺了就不调用。实际项目中可能有几十上百个工具——手工维护这些定义本身就是一场噩梦。
3. 上下文爆炸(Context Bloat)
在 Function Calling 模式下,所有的工具定义必须在每次请求时全部塞进上下文。你有 20 个工具?那每轮对话都要传 20 个工具的完整 JSON Schema。这不但浪费 Token,更严重的问题是:上下文长了,模型的注意力就会稀释,它可能"读漏"关键的工具声明,导致该调的时候不调。
4. 安全性差,权限边界模糊
Function Calling 的执行逻辑通常直接写在主程序中——模型请求查数据库,主程序就查;模型请求发邮件,主程序就发。权限边界在哪里?如果模型产生了幻觉,请求了一个不存在的操作,谁来兜底?模型直接拿到了"执行权",这在一个安全敏感的系统里是不可接受的。
7.1.6 MCP 怎么解决这四个问题
MCP 协议正是针对上述四个痛点而设计的。它不是"Function Calling 的替代品",而是在 Function Calling 之上建立的一层标准化抽象。
MCP 把复杂的调用抽象为三个核心概念:
Resources(资源):模型可以读取的只读数据。比如读取 .py 源文件、查看 SQL 表结构、查阅日志。相当于给模型一本参考手册,它只能查阅,不能涂改。
Tools(工具):模型可以执行的动作。比如发送邮件、查询股价、调整模拟器参数。相当于模型的手脚,由它在需要时"伸手"操作。
Prompts(提示模板):MCP Server 可以预设背景知识和交互模板。相当于给模型一本"专家工作手册",告诉它"在这种业务场景下你应该这样想问题"。
这三个概念组合在一起,MCP 从架构层面解决了 Function Calling 的四个痛点:
解决"协议碎片化":松耦合架构
在 Function Calling 模式下,模型和工具是紧耦合的——为 GPT 写的插件只能在 GPT 上用。MCP 把外部接口封装成独立的 MCP Server 进程,模型通过 JSON-RPC 2.0 这个标准协议与 Server 通信。一个 MCP Server 封装好后,不管是 DeepSeek、Claude 还是本地部署的 Llama,只要支持 MCP 协议,就能直接使用。"一次编写,到处运行"。
这就好比在 Function Calling 时代,你是在给每一台设备单独焊死一个专属接口("给 GPT 焊一个、给 Claude 焊一个");而在 MCP 时代,你只需要做一个 USB 设备——哪个电脑上有 USB 口,你就可以插上去用。
解决"上下爆炸":按需加载
Function Calling 需要把所有的工具定义一次性塞给模型。MCP 支持动态发现:模型启动时通过 MCP 协议询问 Server"你能提供哪些资源和工具",Server 返回清单;但模型只在需要的时候才真正请求某项资源或工具的详细信息。这大大节省了上下文的 Token 用量,也避免了工具太多时的"注意力漂移"。
解决"安全性差":进程隔离
MCP 的关键设计是:MCP Server 是独立进程。它像一个"外交官"——大模型提出请求,Server 在自己的环境里安全地执行(查数据库、读文件、调硬件),只把处理后的结果返回给模型。模型自始至终拿不到真实的数据库句柄,也碰不到文件系统的直接访问权。这种"沙箱化"的架构极大地缩小了安全风险面。
解决"效率低":开箱即用的生态
MCP 不仅是协议规范,更是带动了一个蓬勃的开发生态。Python 社区有 FastMCP,Node.js 社区有官方 SDK,你可以用几十行代码就把一个普通函数封装成标准的 MCP Tool。而且市面上已经出现了大量预制的 MCP Server,覆盖了文件系统、数据库、云服务、办公套件等各种场景——多数情况下你甚至不需要自己写代码,直接从 MCP 市场找一个现成的就能用。
7.1.7 一张表看清差异
| 维度 | Function Calling(传统模式) | MCP(现代协议) |
|---|---|---|
| 架构 | 紧耦合:模型 ↔ 代码 | 松耦合:模型 ↔ 协议 ↔ 服务 |
| 可移植性 | 差:每个模型有自己的格式 | 极强:跨模型、跨语言、跨平台 |
| 扩展性 | 工具多了复杂度线性增长 | 模块化、插拔式,生态驱动 |
| 安全性 | 模型直接请求,权限边界模糊 | Server 进程隔离,模型碰不到真实句柄 |
| 上下文管理 | 所有工具定义一次性传入 | 动态发现,按需加载 |
| 典型比喻 | 给每台设备焊死专属接口 | 通用 USB 接口,即插即用 |
这一节我们追溯了 MCP 的"今世前生":
- 大模型的天生缺陷:封闭的运行环境使它无法直接与外部交互。
- Function Calling 的出现:OpenAI 率先定义了模型与外部工具的交互规范,解决了"能不能做"的问题。这件事 OpenAI 做得漂亮。
- Function Calling 的局限:协议碎片化、手工维护成本高、上下文爆炸、安全边界模糊。
- MCP 的解决方案:通过标准化的 JSON-RPC 2.0 协议 + 进程隔离的 Server 架构,把"能不能做"升级为"好不好用、规不规范"。
在接下来的章节中,我们将先快速上手体验 MCP 的实际使用(7.2 节),然后深入学习如何开发自己的 MCP 服务(7.3 节)。
7.2 MCP快速体验
在上一节我们理清了 MCP 的设计动机和核心概念——现在你知道了"它是什么、为什么需要它",接下来自然的问题是:它到底怎么用?
体验 MCP 的门槛其实比你想象的低。你不需要先学会开发 MCP Server,也不需要买什么付费服务。这一节,我们走两条路:先用现成的公共平台连一个 MCP 服务感受一下,再亲手写一个最简单的 MCP Server,几分钟内看到一个能用的效果。
7.2.1 从公共服务平台快速连接一个 MCP
MCP 生态在 2024 年底 Anthropic 开源协议后就迅速膨胀。到 2026 年中期,几个主流的 MCP 服务平台已经聚合了成千上万个可用的 MCP Server。这些平台的角色类似于手机上的"应用商店"——你不需要自己开发,搜索、安装、配置就能用。
主要的 MCP 服务平台:
| 平台 | 地址 | 特点 |
|---|---|---|
| PulseMCP | https://pulsemcp.com | 社区 MCP Server 索引,按分类热度排序,配置片段一键复制 |
| Smithery | https://smithery.ai | MCP Server 市场和部署平台,生态丰富 |
| ModelScope MCP | https://www.modelscope.cn/mcp | 国产平台,中文生态更友好 |
这些平台上能找到的典型 MCP Server 包括:
- Google Workspace:让大模型直接读写你的 Gmail、Google Calendar、Google Docs
- Slack / Jira:查询团队消息和任务状态,相当于给大模型接了"企业协作总线"
- Alpha Vantage:获取实时股票数据,大模型可以直接帮你查行情、做分析
- GitHub / GitLab:操作 Issues、PR、仓库文件,开发场景的标配
安装和运行的通用流程:
大多数 MCP Server 通过 npx(Node.js 生态)或 uvx(Python 生态)分发。这意味着你本地需要有 Node.js 或 Python 环境。以配置一个 MCP 客户端(比如 Claude Desktop 或 Cursor)为例,典型的配置流程是:
1. 找到你要用的 MCP Server(比如在 PulseMCP 搜索 "Brave Search")
2. 复制平台提供的配置片段
3. 粘贴到你的 MCP 客户端的配置文件
4. 客户端自动拉取依赖并启动 MCP Server
5. 开始使用——大模型现在可以调用这个工具了不同客户端的配置文件位置不同。以 Claude Desktop 为例,macOS 下配置文件路径为
~/Library/Application Support/Claude/claude_desktop_config.json,Windows 下为%APPDATA%\Claude\claude_desktop_config.json。其他客户端请参考对应文档。
下面是一个在 Claude Desktop 中配置 Brave Search MCP Server 的实际配置示例:
json
{
"mcpServers": {
"brave-search": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-brave-search"],
"env": {
"BRAVE_API_KEY": "your-api-key-here"
}
}
}
}配置完成后重启客户端,你就会看到大模型"突然"可以联网搜索了——这就是 MCP 的体验:不改变模型本身,只是给它接了一个标准接口的工具。
几个需要注意的点:
- MCP Server vs MCP 插件:MCP Server 是一个独立进程,通过标准协议与客户端通信;MCP 插件通常是某个特定客户端(如 VSCode 插件)对 Server 的封装。两者不是同一个概念,配置方式也不同(具体原理详见 7.3 节)。
- 环境依赖:
npx分发依赖 Node.js,uvx依赖 Python +uv。安装前先node -v和uv --version确认环境。 - 账号和 API Key:很多 MCP Server 需要注册对应服务的账号并获取 API Key。这是上游服务的限制,不是 MCP 协议本身的要求。
- 安全考量:MCP Server 运行在本地,拥有你授予的权限。从公共平台安装 Server 时,注意查看其权限声明和社区反馈,避免安装来源不明的 Server。
7.2.2 自己手搓一个 MCP Server
用现成的 Server 是体验 MCP 最快的方式,但写一个 Server 是理解 MCP 最好的方式。接下来我们写一个极简的 MCP Server——它只提供一个工具:根据城市名返回天气预报。虽然功能简单,但它完整展示了 MCP Server 的骨架。
本节代码完整可运行,环境要求:Python 3.10+,安装
mcp包(pip install "mcp>=2")。本节用的是 2.x 的低层 API,1.x 的@server.list_tools()装饰器写法在 2.x 已改为构造参数回调。
Step 1:创建项目文件
weather_server/
├── weather.pyStep 2:编写 MCP Server
python
# weather.py
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import CallToolResult, ListToolsResult, TextContent, Tool
# 注册工具列表:告诉客户端"我能做什么"
async def list_tools(ctx, params) -> ListToolsResult:
return ListToolsResult(tools=[
Tool(
name="get_weather",
description="获取指定城市的当前天气信息",
inputSchema={
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如 'Beijing'、'Shanghai'"
}
},
"required": ["city"]
}
)
])
# 实现工具逻辑:当大模型调用这个工具时,真正运行什么
async def call_tool(ctx, params) -> CallToolResult:
name, arguments = params.name, params.arguments or {}
if name == "get_weather":
city = arguments.get("city", "")
# 模拟天气数据(实际项目中这里会调真实 API)
weather_data = {
"Beijing": {"temp": 28, "condition": "晴", "humidity": "45%"},
"Shanghai": {"temp": 32, "condition": "多云", "humidity": "70%"},
"Shenzhen": {"temp": 30, "condition": "阵雨", "humidity": "85%"}
}
info = weather_data.get(city, {"temp": "--", "condition": "数据未覆盖", "humidity": "--"})
result = f"{city}天气:{info['condition']},温度 {info['temp']}°C,湿度 {info['humidity']}"
return CallToolResult(content=[TextContent(type="text", text=result)])
return CallToolResult(content=[TextContent(type="text", text=f"未知工具: {name}")], is_error=True)
# 创建 Server 实例:把上面两个处理函数挂上去
server = Server("weather-server", on_list_tools=list_tools, on_call_tool=call_tool)
# 启动 Server:通过标准输入/输出与客户端通信
async def main():
async with stdio_server() as (reader, writer):
await server.run(reader, writer, server.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())几十行代码,一个能用的 MCP Server 就完成了。拆开来看,核心就两件事:
list_tools()——告诉客户端"我能提供什么工具",包括工具名、描述和参数 schema。客户端拿到这个列表后,会把这些工具的元信息注入给大模型,让模型知道可以调用什么。call_tool()——当大模型决定调用某个工具时,真正执行的函数。接收工具名和参数,返回文本结果。
Step 3:配置客户端使用这个 Server
假设你在用 Claude Desktop,配置文件路径与 7.2.1 节一致(macOS 下为 ~/Library/Application Support/Claude/claude_desktop_config.json),在配置文件中添加:
json
{
"mcpServers": {
"weather-server": {
"command": "python",
"args": ["/path/to/weather_server/weather.py"]
}
}
}重启后,你可以对大模型说:"北京今天天气怎么样?"如果一切正常,大模型会自动识别到 get_weather 工具,并调用它获得结果后回复你。
Step 4:用 MCP Inspector 调试
如果你还没配置客户端,或者想单独调试 Server,MCP 官方提供了一个调试工具——MCP Inspector。安装方式:
bash
npx @modelcontextprotocol/inspector python /path/to/weather_server/weather.py这会启动一个 Web 界面(默认 http://localhost:5173),你可以在其中直接查看 Server 暴露了哪些工具、手动调用工具并查看返回结果。对于开发和排错非常实用。
7.2.3 两种体验路径的总结
把两种体验方式放在一起对比,能更清楚地看到它们的定位:
| 公共服务平台 | 自己手搓 | |
|---|---|---|
| 门槛 | 低(搜一下、复制配置) | 中(需要基础 Python) |
| 时间 | 5 分钟 | 30 分钟 |
| 收获 | 感受"MCP 能干什么" | 理解"MCP 怎么运转" |
| 适合人群 | 想快速评估 MCP 价值的人 | 想深入使用/开发 MCP 的人 |
两条路互补而非互斥。建议先用公共平台连一个现成 Server,跑通后对这种机制有个直观感受,再动手写一个简单的 Server——当你看到自己写的 weather.py 被大模型调用的那一刻,你对 MCP 的理解会从"读到的"变成"体验到的"。
而一旦你亲手写过一个 Server,你自然就想知道更深入的问题了:MCP 协议到底定义了哪些交互?Server 和 Client 之间是怎样握手和通信的?下一节我们会进入 MCP 的开发细节——从协议规范到工具实现,从资源管理到错误处理,带你完整撸一个生产可用的 MCP Server。
7.3 如何开发一个MCP
在上一节中,我们通过 MCP 公共服务平台体验了"开箱即用"的便利——复制一段配置,重启客户端,大模型就有了读邮件、查股票、管文档的能力。这种体验很像手机安装 App:找到、安装、使用,三步搞定。
但体验只是起点。当你真正想把 MCP 用到实际工作中时,一个问题会自然浮现:"MCP 服务市场里没有我需要的工具怎么办?"
这正是本节要回答的问题。我们从一个无法绕过的现实出发:公共 MCP 服务虽然琳琅满目,但它们解决的是通用场景——比如连接 GitHub、查询天气、检索 Slack 消息。而真实业务中的需求往往是定制化的:
- 你需要大模型读取公司内部特有的数据库表结构,而不仅是一个通用的 SQL 查询工具;
- 你需要大模型按照你设计的审批流程来操作内部系统,而不是简单的 CRUD;
- 你的业务逻辑涉及多个内部服务的编排,这远不是"给一个 API key 就能用"的程度。
这些场景下,你必须自己开发 MCP 服务。值得庆幸的是:MCP 协议的设计让这个任务比你想象中简单得多。一个完整的 MCP 服务,从零到可运行,可能只需要几十行 Python 代码。你不需要理解底层协议的所有细节,只需要知道"我该暴露什么资源、什么工具,以及怎么暴露出去"。
本节就是你的MCP 开发实战指南。我们将用六个循序渐进的子节,带你走完"理解架构 → 学会工具 → 写服务端 → 遵循设计规范 → 部署上线 → 接入生产环境"的完整链路:
- 7.3.1 MCP 的架构设计:先理清 MCP 的三层模型——Host(宿主)、Client(客户端)、Server(服务器),理解它们各自的职责和协作方式。这是后续一切开发工作的基础。
- 7.3.2 MCP 开发神器:FastMCP:认识 Python 生态中最高效的 MCP 开发框架。一个装饰器就能把普通函数变成 MCP 工具,代码量锐减 90% 以上。
- 7.3.3 开发 MCP 服务器端:动手写第一个 MCP Server。从 STDIO 到 HTTP 传输,三个完整示例带你走通服务端开发的全路径。
- 7.3.4 MCP 设计指南:工具命名怎么定?描述写到什么粒度?日志往哪打?参数怎么校验?——工业级 MCP 开发必须遵守的规范全在这里。
- 7.3.5 MCP 部署与客户端调用:开发完了怎么上线?Python 和 Node.js 的部署方式各有门道。换个视角,学会从客户端发起请求,验证你的 Server 是否工作正常。
- 7.3.6 将 MCP 服务接入 AI 编程工具:打通最后一公里——把你手搓的 MCP Server 接入 Cline、Claude Code 等主流 AI 编程工具,让大模型在你的开发环境里真正"看懂"和"操作"你的项目。
读完本节,你将具备独立开发、部署和集成一个生产级 MCP 服务的能力。这不是纸上谈兵——我们希望你读完就能动手,把自己业务场景中最迫切需要的那把"瑞士军刀"做出来。
好,现在我们从架构开始。毕竟,只有先看清全貌,才知道砖该往哪里搬。
7.3.1 MCP的架构设计
开发 MCP 之前,你得先理清它的骨架。MCP 采用三层解耦架构,每层职责分明:
| 角色 | 定义 | 典型示例 |
|---|---|---|
| Host(宿主) | 运行大模型并管理 MCP 连接的上层应用。它是用户的入口,负责启动模型推理、渲染对话界面,并决定使用哪些 MCP Server。 | Claude Desktop、Cursor、VS Code(Cline 插件) |
| Client(客户端) | 嵌入在 Host 内部的协议层组件。它负责与 Server 建立连接、发送请求、管理会话生命周期。一个 Host 可以同时驱动多个 Client,每个 Client 连接一个 Server。 | MCP Python SDK 的 ClientSession、TypeScript SDK 的 Client |
| Server(服务器) | 暴露工具和资源的独立进程。它定义了"我能做什么"——查询数据库、读取文件、调用 API——然后等待 Host 中的大模型来调用。 | GitHub MCP Server、Postgres MCP Server、你自己写的天气查询 Server |
三层之间通过 JSON-RPC 2.0 协议通信。这是关键:协议层的标准化意味着用 Python 写的 Server 可以被 TypeScript 写的 Host 调用,只要两边都遵循 MCP 规范。正如 HTTP 让不同技术栈的 Web 服务可以互操作,MCP 让不同语言的大模型应用可以共享工具生态。
实际项目中,你主要开发的是 Server。 Host 通常是现成的 AI 应用(Claude Desktop、Cursor 等),Client 由官方 SDK 提供。你只需要专注于把业务能力封装进 Server:定义工具、暴露资源、编写逻辑。其余两层,SDK 已经帮你搞定了。
MCP 的核心三要素
MCP Server 可以向大模型暴露三种能力:
Resources(资源)——模型可读取的只读数据。可以理解为"Server 拥有的知识库"。例如,一个 Postgres MCP Server 可以将每张表的 Schema 暴露为资源:
postgres://mydb/table/users → 用户表的列定义和索引信息
postgres://mydb/table/orders → 订单表的列定义和索引信息资源是被动的——模型只能读取,不能修改。适合场景:数据库结构、配置文件、项目文档目录等。
Tools(工具)——模型可以调用的动态操作。可以理解为"Server 提供的能力"。例如:
query_database(sql="SELECT * FROM users WHERE active=1")
create_issue(title="修复登录Bug", assignee="zhangsan")工具是主动的——模型决定何时调用、传什么参数,Server 执行并返回结果。适合场景:执行 SQL、调用 API、发送消息、创建文件等。
Prompts(预设指令)——Server 推荐给模型的对话模板。例如,一个 GitLab MCP Server 可以提供"帮我写一份标准的 merge request 描述"的提示词模板。Prompt 不是必须的,但能降低用户的使用门槛——尤其是 Server 提供的工具较多、新手不知道从何下手时。
避坑指南
开发 MCP 之前,有两个常见的认知误区需要提前澄清。
误区一:描述越长越好。 很多人给 Tool 写描述时恨不得把整个业务文档塞进去。结果是模型产生"注意力漂移"——描述太长,关键词被稀释,模型反而不知道什么时候该调用这个工具。正确的做法是简洁明了:说清楚输入什么、输出什么、什么时候用。一句话能搞定就别写三句。
误区二:帮我想一个工具名就行。 工具名是模型判断"该不该调用"的核心依据之一。如果你把天气查询工具命名为 tool1,模型永远猜不到它能做什么。正确的做法是使用动宾结构:get_weather、create_issue、search_documents。让名称本身就是最好的自解释。
截至 2026 年,MCP 已成为大模型工具调用的事实标准。Anthropic 在 2024 年 11 月发布 MCP 规范后,OpenAI 于 2025 年 3 月在其 Agents SDK 中加入原生 MCP 支持,Google 随后也在 Gemini 智能体栈中集成 MCP。你用同一个 GitHub MCP Server,可以让 Claude、GPT-4o、Gemini 三家的模型都能使——这就是标准化的力量。
7.3.2 MCP开发神器:FastMCP
如果你用原生 Python SDK 写过一个完整的 MCP Server,你就会理解 FastMCP 是多么救命的存在。原生 SDK 需要手动定义 JSON Schema、手动处理参数校验、手动管理服务器生命周期——50 行代码可能只暴露了一个工具。FastMCP 把这个过程压缩成了一个装饰器。
是什么
FastMCP 是社区维护的第三方包(pip install fastmcp),在 MCP Python SDK 之上做了高层封装(官方 SDK 2.x 内置了同类的 MCPServer)。它的设计哲学是:函数就是工具,类型注解就是 Schema。你只需要写正常的 Python 函数,加上 @mcp.tool() 装饰器,FastMCP 自动完成其余的一切——参数 Schema 生成、类型校验、协议序列化、错误处理。
资源来源:GitHub - ModelContextProtocol/python-sdk,截至 2026 年 6 月已获得 23,000+ star。FastMCP 是此仓库的核心模块。
安装
bash
pip install fastmcp "mcp[cli]"这条命令装两样东西:第三方包 fastmcp(本节用的高层封装),以及官方 SDK 的 CLI 调试工具(mcp dev 等,即 MCP Inspector)。
第一个工具:一个装饰器的事
来看一个完整的例子——查询股票价格的 MCP Server:
python
from fastmcp import FastMCP # 第三方包,需 pip install fastmcp
mcp = FastMCP("MyStockServer")
@mcp.tool()
def get_stock_price(ticker: str) -> str:
"""获取实时股价,支持 A 股和美股代码。"""
# 此处对接你的数据库或API逻辑
prices = {
"AAPL": 198.50,
"TSLA": 245.30,
"600519": 1650.00, # 贵州茅台
}
price = prices.get(ticker.upper())
if price is None:
return f"未找到股票代码 {ticker} 的行情数据"
return f"{ticker.upper()} 最新价:{price}"
if __name__ == "__main__":
mcp.run()把这段代码保存为 stock_server.py,然后运行:
bash
python stock_server.py此刻,一个完整的 MCP Server 已经启动了。它通过 STDIO(标准输入输出)等待 Host 连接,大模型现在可以通过 get_stock_price 这个工具查询任何它知道的股票价格。
这段代码值得逐行品味,因为 FastMCP 在背后帮你做了太多事:
ticker: str—— 类型注解被自动转化为 JSON Schema,告诉大模型"这个参数是一个字符串"。"""获取实时股价..."""—— 函数的 Docstring 被自动提取为工具描述,大模型据此判断"什么时候该调用这个工具"。mcp.run()—— 自动启动 STDIO 模式的 JSON-RPC 服务器,无需手动处理stdin/stdout的序列化。- 参数校验 —— 如果大模型传错了参数(比如传了数字而不是字符串),FastMCP 会自动返回格式化的错误信息,不会让 Server 崩溃。
不止工具:资源和提示词
FastMCP 不仅支持 Tool,也支持 Resource 和 Prompt:
python
# 暴露静态资源
@mcp.resource("config://app")
def get_config() -> str:
"""返回应用配置文件内容。"""
return open("config.yaml").read()
# 暴露动态资源(带参数)
@mcp.resource("user://{user_id}")
def get_user_profile(user_id: str) -> dict:
"""返回指定用户的资料。"""
return {"id": user_id, "name": "张三", "role": "engineer"}
# 暴露提示词模板
@mcp.prompt()
def code_review_template() -> str:
"""生成代码审查的标准提示词。"""
return "请对以下代码进行全面审查,重点关注安全漏洞和性能问题:\n\n{code}"类型注解是铁律。 务必给每个参数加上类型提示。FastMCP 依赖这些注解生成 JSON Schema,没有类型提示的工具模型无法正确调用。也就是说,如果你写
def my_tool(x, y),FastMCP 会报错——它不知道x和y是什么类型,无法生成有效的 Schema。
7.3.3 开发MCP服务器端
掌握了 FastMCP 的基本用法,现在我们来写一个真实可用的 MCP Server。这个 Server 将以任务管理为场景——创建任务、列出任务、完成任务。这个场景足够贴近日常开发,又能在几个工具中展示完整的 MCP 开发模式。
完整示例:任务管理 MCP Server
python
"""
task_server.py — 一个完整的任务管理 MCP Server
使用 FastMCP,支持 STDIO 和 HTTP 两种传输模式
"""
from fastmcp import FastMCP
from datetime import datetime
from typing import Optional
import uuid
mcp = FastMCP("TaskManager")
# --- 内存存储(生产环境请替换为数据库) ---
tasks_store = {}
@mcp.tool()
def create_task(title: str, priority: Optional[str] = "normal") -> dict:
"""
创建一个新任务。
参数:
title: 任务标题
priority: 优先级,可选 high/normal/low,默认 normal
"""
valid_priorities = {"high", "normal", "low"}
if priority not in valid_priorities:
return {"error": f"无效的优先级 '{priority}',可选值:{valid_priorities}"}
task_id = str(uuid.uuid4())[:8]
task = {
"id": task_id,
"title": title,
"priority": priority,
"status": "pending",
"created_at": datetime.now().isoformat(),
}
tasks_store[task_id] = task
return {"message": "任务创建成功", "task": task}
@mcp.tool()
def list_tasks(status: Optional[str] = None) -> dict:
"""
列出所有任务,可按状态过滤。
参数:
status: 可选的状态过滤。留空则返回全部任务。
可选值:pending, in_progress, done
"""
if status and status not in {"pending", "in_progress", "done"}:
return {"error": f"无效的状态 '{status}',可选值:pending, in_progress, done"}
filtered = tasks_store.values()
if status:
filtered = [t for t in filtered if t["status"] == status]
return {"count": len(list(filtered)), "tasks": list(filtered)}
@mcp.tool()
def complete_task(task_id: str) -> dict:
"""
将指定任务标记为已完成。
参数:
task_id: 任务的唯一 ID
"""
task = tasks_store.get(task_id)
if task is None:
return {"error": f"任务 {task_id} 不存在"}
task["status"] = "done"
task["completed_at"] = datetime.now().isoformat()
return {"message": f"任务 {task_id} 已完成", "task": task}
if __name__ == "__main__":
mcp.run()把这段代码保存为 task_server.py,然后在终端执行 python task_server.py,一个功能完备的任务管理 MCP Server 就启动了。
三种启动模式
MCP 服务端有三种典型的启动方式。根据部署场景选择最合适的一种:
模式一:STDIO(本地子进程)
这是最简单、最安全的模式。Host 应用以子进程方式启动 Server,通过标准输入输出通信:
bash
# 直接运行
python task_server.py
# 或用 uv 临时装依赖后运行脚本(无需预先 pip install)
uv run --with fastmcp task_server.py
# 或走 MCP 官方 CLI(uvx --from 指定的是「提供命令的包」,不是要跑的脚本)
uvx --from mcp mcp run task_server.pySTDIO 模式下,Server 运行在本地,网络不可达。所有通信走进程间管道,天然避免了远程攻击面。缺点也很明显:只能被本机的 Host 使用,无法共享给团队其他成员。
模式二:SSE / Streamable HTTP(远程服务)
如果想把 MCP Server 部署为团队共享的远程服务,用 HTTP 模式。FastMCP 支持通过 transport 参数切换:
python
# 在 task_server.py 的最后,把 mcp.run() 改为:
if __name__ == "__main__":
mcp.run(transport="sse", host="0.0.0.0", port=8000)启动后,Server 会在 http://localhost:8000/sse 监听 HTTP 请求。团队其他成员的 Claude Desktop 或 Cursor 可以通过 HTTP URL 连接这个 Server。
安全提醒:HTTP 模式下,务必绑定到受信的内网地址或配置认证。MCP 协议规范明确要求 Server 验证
Origin请求头以防止 DNS 重绑定攻击。生产环境建议配合 API Key 或 OAuth 认证。
模式三:MCP Inspector(开发调试)
开发阶段,MCP 官方提供了一个网页版调试工具——MCP Inspector。它直接连接到你的 Server 进程,提供可视化的工具列表、参数填写和调用结果查看:
bash
mcp dev task_server.py这条命令会启动一个本地 Web 界面(默认 http://localhost:5173),你可以在浏览器里看到 create_task、list_tasks、complete_task 这三个工具,手动填写参数并查看返回结果。在接入 AI Host 之前,先用 Inspector 验证每个工具是否按预期工作——这能省掉你大量排查时间。
7.3.4 MCP设计指南
工具写出来了,但"能跑"和"好用"之间还有很大距离。以下六条设计原则,来自 MCP 社区和实际工程经验的总结。
原则一:Server 是"胶水层",不是业务层
不要在 MCP Server 内部写复杂的业务逻辑。 Server 的职责是协议翻译——接收 JSON-RPC 请求,调用后端 API,把结果格式化返回。复杂的业务规则、数据校验、事务管理应该放在 Server 背后的 API 层。
举个例子:如果你的 MCP Server 需要审批报销单,不要在里面写审批流的几十条业务规则。Server 应该只做两件事——把大模型发来的参数转成 HTTP 请求发给报销系统 API,再把 API 响应转成大模型能理解的格式返回。
这样做还有一个额外好处:同一套后端 API 既可以被 MCP Server 调用,也可以被前端页面调用——你不会把业务逻辑锁死在 MCP 这一条通路里。
原则二:工具命名 = 动词 + 名词
模型是根据工具名称和描述来判断是否调用的。一个叫 do_something 的工具和大模型说"猜猜我能干什么"没区别。
好的命名遵循动宾结构,一看就懂:
| ❌ 差 | ✅ 好 | 说明 |
|---|---|---|
tool1 | get_weather | 名称为空,模型无从判断 |
data | search_customers | 只说"是什么",不说"能做什么" |
query | query_database | 动词太泛,不知道查什么 |
run | deploy_service | 动词太泛,没有操作对象 |
原则三:描述精简到"输入→输出→何时用"
对每个工具,描述只需要回答三个问题:
- 输入什么参数?
- 输出什么结果?
- 什么场景下用这个工具?
三句话覆盖,绝不多写。以下是一个好例子和坏例子的对比:
❌ 冗长的描述:
"本工具用于在企业内部的任务管理系统中根据各种条件查询和筛选任务信息。它支持按优先级、状态、创建时间、负责人等多个维度进行精确或模糊查询……" (后面还有 5 行)
✅ 精炼的描述:
"列出所有任务,可按状态过滤。参数 status 可选 pending/in_progress/done,留空返回全部。需要在查看待办事项或检查进度时使用。"
简洁的描述让模型能快速匹配"用户说的话"和"工具能做的事"。冗长的描述中,关键信息被稀释,模型反而可能错过调用时机。
原则四:永远校验输入参数
大模型传给你的参数,不一定合法。它可能把 priority 写成 "urgent"(实际只接受 high/normal/low),也可能给 task_id 传一个空字符串。永远不要信任模型的输入,永远在 Server 端做校验。
在 7.3.3 的示例代码中,我们已经演示了参数校验的标准写法:
python
@mcp.tool()
def create_task(title: str, priority: Optional[str] = "normal") -> dict:
valid_priorities = {"high", "normal", "low"}
if priority not in valid_priorities:
return {"error": f"无效的优先级 '{priority}',可选值:{valid_priorities}"}
# ... 正常逻辑这不仅是安全需要,也是用户体验需要——如果 Server 因为非法参数 500 报错,用户只知道"出错了",不知道错在哪。返回明确的错误信息,让大模型可以自我纠正并重试。
原则五:STDIO 模式下严禁用 print()
这条规则在 STDIO 模式下是硬性规定,不遵守的直接后果是协议解析失败。
原因很简单:STDIO 传输模式下,stdout(标准输出)是 MCP 协议的通信信道——Client 和 Server 之间所有的 JSON-RPC 消息都通过它传递。如果你在代码里写了 print("任务创建成功"),这行字符串会被注入到 JSON-RPC 消息流中,Client 收到的东西就变成了非法 JSON。
正确的做法是使用 Python 的 logging 模块,并将日志输出到 stderr(标准错误):
python
import logging
import sys
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
stream=sys.stderr, # 关键:写到 stderr,不污染 stdout
)
logger = logging.getLogger(__name__)
@mcp.tool()
def create_task(title: str, priority: str = "normal") -> dict:
logger.info(f"创建任务: {title}, 优先级: {priority}")
# ...这样日志会打印到终端,方便你调试,但不会干扰 MCP 协议通信。
原则六:权限最小化
给 MCP Server 授予权限时,遵循最小权限原则:只给它完成本职工作所必需的权限,多一点都不给。
如果你的 MCP Server 只负责查询某个特定数据库的某几张表,就不要给它整个数据库的读写权限,更不要给它访问文件系统的权限。这样即使大模型被诱导做了不当操作,破坏范围也是可控的。
7.3.5 MCP部署与客户端调用
Server 写好了,接下来要让它跑起来——不是本地 python task_server.py 就完事了。部署方式决定了 Server 的可用范围,而一条客户端验证能帮你确认 Server 是否真的跑起来了。
Python 部署:两种运行模式
本地 STDIO 部署
本地开发或单机使用时,STDIO 是最简单可靠的选择。无需部署服务、无需配置网络,直接通过进程间管道通信。
在 Claude Desktop 的配置文件(macOS 路径:~/Library/Application Support/Claude/claude_desktop_config.json)中添加:
json
{
"mcpServers": {
"task-manager": {
"command": "python",
"args": ["/path/to/task_server.py"]
}
}
}如果你使用 uv 管理 Python 包,可以用 uvx 启动(自动处理依赖):
json
{
"mcpServers": {
"task-manager": {
"command": "uvx",
"args": ["--from", "mcp", "python", "/path/to/task_server.py"]
}
}
}保存配置后重启 Claude Desktop。此时打开对话,大模型就已经能看到 create_task、list_tasks、complete_task 这三个工具了。
远程 HTTP 部署
当 Server 需要被团队共享,或者部署在服务器上时,用 HTTP 模式。启动方式参考 7.3.3 节中的"模式二"。配置文件中将 command 替换为 url:
json
{
"mcpServers": {
"task-manager": {
"url": "http://your-server:8000/"
}
}
}Node.js 用户:MCP 不绑定语言。如果你用 TypeScript/Node.js 开发 Server,部署方式和 Python 完全一致:本地 STDIO 用
npx启动,远程 HTTP 用url配置。目前 MCP TypeScript SDK 与 Python SDK 保持功能对等。
客户端调用:换个视角看 MCP
写完 Server 之后,建议你再用 MCP Client 调用一次自己的 Server。这有两个好处:第一,验证 Server 确实能正常工作,不依赖 AI Host 的主观判断;第二,理解 Client 端的协议交互,今后调试时会更有底气。
以下是一个完整的 Python 客户端示例,连接刚才写的 task_server.py,列出工具并调用:
python
"""
client_demo.py — MCP 客户端调用示例
验证 task_server.py 是否正常工作
"""
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
# 1. 启动 Server 并建立连接
server_params = StdioServerParameters(
command="python",
args=["task_server.py"],
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 2. 初始化会话
await session.initialize()
print("✅ MCP 会话初始化成功\n")
# 3. 列出所有可用工具
tools = await session.list_tools()
print("📋 可用工具列表:")
for tool in tools.tools:
print(f" • {tool.name}: {tool.description}")
print()
# 4. 调用工具:创建任务
result = await session.call_tool(
"create_task",
arguments={"title": "编写MCP使用文档", "priority": "high"},
)
print(f"📝 创建任务结果:\n{result.content[0].text}\n")
# 5. 调用工具:列出任务
result = await session.call_tool("list_tasks", arguments={})
print(f"📋 任务列表:\n{result.content[0].text}\n")
if __name__ == "__main__":
asyncio.run(main())环境要求:运行客户端需要安装 MCP SDK:
pip install "mcp[cli]"。将client_demo.py放在task_server.py同一目录下,然后运行python client_demo.py。
执行结果应该类似于:
✅ MCP 会话初始化成功
📋 可用工具列表:
• create_task: 创建一个新任务。
• list_tasks: 列出所有任务,可按状态过滤。
• complete_task: 将指定任务标记为已完成。
📝 创建任务结果:
{"message": "任务创建成功", "task": {"id": "a3f2c1d8", ...}}
📋 任务列表:
{"count": 1, "tasks": [{"id": "a3f2c1d8", "title": "编写MCP使用文档", ...}]}这段客户端代码做了四件事:启动 Server 子进程 → 建立 STDIO 会话 → 发现工具列表 → 调用并获取结果。这四个步骤是所有 MCP Host 与 Server 交互的标准流程,理解它就理解了整个 MCP 的运行时行为。
7.3.6 将MCP服务接入AI编程工具
MCP Server 已经开发完、调试通,最后一步:把它接入日常使用的 AI 编程工具,让大模型在实际场景中调用它。
接入 Cline(VS Code 插件)
Cline 是 VS Code 中最流行的 AI 编程助手插件之一。自 2025 年起,Cline 完整支持 MCP 协议,可以直连本地或远程的 MCP Server。
配置步骤:
打开 VS Code,在 Cline 插件设置中找到
MCP Servers配置项。点击
Add MCP Server,填写配置:json{ "task-manager": { "command": "python", "args": ["/path/to/task_server.py"] } }点击
Connect。如果连接成功,Cline 的状态栏会显示 🟢 绿色指示。在 Cline 对话中输入"帮我创建一个优先级为 high 的任务:修复登录页面报错",模型会自动识别并使用
create_task工具。
接入 Claude Code(终端 AI 编程工具)
Claude Code 的配置思路与 Cline 完全一致,只是注册方式略有不同:
方式一:通过命令行注册
bash
claude mcp add task-manager -- python /path/to/task_server.py方式二:通过配置文件
在项目根目录创建 .mcp.json:
json
{
"mcpServers": {
"task-manager": {
"command": "python",
"args": ["task_server.py"]
}
}
}一个小技巧:CC-SWITCH
社区中有一个非官方的配置管理工具 cc-switch(非 MCP 官方维护),可以批量管理多个 AI 编程工具的 MCP 配置——定义一次 Server,一键同步到 Cline、Claude Code、Cursor 等多个平台。
bash
# 安装 cc-switch(注意:这是管理 MCP 配置的命令行工具,
# 与第 2 章那个同名 GUI 应用不是同一个发行物)
npm install -g @songhe/cc-switch # 包名与第 3 章 3.4 节保持一致
# 定义 MCP Server(一次定义)
cc-switch add task-manager --command python --args task_server.py
# 同步到所有支持的平台(一键同步)
cc-switch sync --all这一节,我们走完了 MCP 开发的完整闭环。从 MCP 的三层架构(Host / Client / Server)开始,明确了开发者的主战场在 Server 层。接着认识了 FastMCP——Python 生态中最高效的 MCP 开发框架,一个装饰器就能把普通函数变成工具,代码量锐减 90% 以上。然后动手写了一个任务管理 MCP Server,完整覆盖了 STDIO、HTTP 和 Inspector 三种启动模式。在设计规范部分,我们梳理了六条来自工程实践的原则:Server 是胶水层而非业务层、命名遵循动宾结构、描述精简到"输入→输出→何时用"、永远校验输入参数、STDIO 模式下禁用 print、权限最小化。部署阶段,Python 本地 STDIO 部署和远程 HTTP 部署各有关键配置,而客户端代码验证是上线前最有信心的测试手段。最后,我们把亲手写的 Server 接入了 Cline 和 Claude Code,完成了从"理解 MCP"到"让 MCP 为自己工作"的跨越。
从下一章开始,我们将进入一个更大的主题:大模型应用的技术选型——面对眼花缭乱的开源模型、推理引擎、向量数据库和 Agent 框架,该如何做出有依据的选择。这是一个比"怎么用模型"高一层的问题:在真实的工程约束下,你该把筹码压在哪些技术栈上。我们下一章见。