MCP Server开发实战——从零写一个自己的MCP工具,让AI调用你的API
title: MCP Server开发实战——从零写一个自己的MCP工具,让AI调用你的API
tags: MCP,MCP Server,Model Context Protocol,FastMCP,Agent开发,Python,Claude Desktop,Cursor,工具调用,AI Agent
category: 人工智能
MCP Server开发实战——从零写一个自己的MCP工具,让AI调用你的API
本文是《AI编程与Agent实战》系列第09篇。第08篇拆解了Agent的底层运行逻辑:ReAct循环、Tool Calling机制、从Function Calling到MCP的标准化进程。结尾留了一个问题:MCP把工具定义从「每个应用自己写」升级为「一个Server被所有客户端共享」,那这个Server到底怎么写。
系列前置阅读:第01篇:工具横评 | 第02篇:Cursor入门 | 第03篇:Claude Code实战 | 第04篇:本地模型编程 | 第05篇:Agentic Engineering | 第06篇:AI代码安全 | 第07篇:全栈项目实战 | 第08篇:Agent开发入门
MCP协议在2026年4月的纽约开发者峰会上交出了一份成绩单:110万月度SDK下载量、17,468个公开索引服务器、150+组织加入Agentic AI Foundation。Uber在主台上展示了他们的生产数据:5000+工程师使用MCP连接的Agent,10,000+内部服务被自动转换为MCP工具,每周60,000+次Agent执行。
数字背后是一个工程现实:MCP已经从「Anthropic的开源实验」变成了「企业Agent基础设施」。2025年12月Anthropic将MCP捐赠给Linux基金会旗下的Agentic AI Foundation后,OpenAI、Google、Microsoft、AWS全部跟进支持。截至2026年Q1,41%的软件组织已经在生产环境中使用MCP服务器。
但「支持」和「会写」是两回事。当前MCP Server开发中文实战内容严重稀缺,大部分文章停留在概念科普。这篇从零开始,用FastMCP写一个完整的、可运行的MCP Server,覆盖Resources/Tools/Prompts三大原语、调试方法、Claude Desktop接入和Docker部署。
来源:MCP Dev Summit NYC 2026, Anthropic AAIF捐赠公告 2025.12, Digital Applied MCP Adoption Statistics 2026, Stacklok State of MCP 2026
目录
- MCP协议回顾:为什么Agent世界需要一个USB-C
- 三大原语:Resources、Tools、Prompts的分工
- 环境准备:FastMCP与项目搭建
- 实战第一步:写一个Tool(可执行的工具)
- 实战第二步:写一个Resource(可读取的数据源)
- 实战第三步:写一个Prompt(可复用的提示模板)
- 完整Server:把它们组合起来
- 调试:用MCP Inspector可视化测试
- 接入Claude Desktop和Cursor
- 从本地到远程:Docker部署与HTTP传输
- 辩证看待:MCP的现实问题与陷阱
- 总结与下一篇预告
1. MCP协议回顾:为什么Agent世界需要一个USB-C
1.1 N×M集成困境
在MCP出现之前,让一个AI模型调用外部工具,需要为每一个「模型×工具」组合写一套定制集成。10个AI应用接入100个工具,就是最多1000套集成代码。每个组合都要单独维护,换一个模型或换一个工具,重写一遍。
OpenAI在2023年6月推出了Function Calling,解决了「让模型输出结构化函数调用」的问题,但没解决工具定义与实现的可复用性。你给Claude写的工具定义,GPT拿过来不能直接用。ChatGPT的Plugin框架也类似,局限于OpenAI自己的生态。
MCP的洞察是架构层面的:参考语言服务器协议(LSP)的设计思路,用JSON-RPC 2.0定义一个标准化的通信协议。工具提供方写一个MCP Server,任何支持MCP的客户端(Claude Desktop、Cursor、VS Code、ChatGPT、Gemini)都能直接连接使用。写一次,到处跑。
1.2 2026年的增长曲线
MCP的增长速度在开发者协议史上几乎找不到对标物。以下是关键节点:
| 时间 | 事件 | 数据 |
|---|---|---|
| 2024.11 | Anthropic发布MCP | ~200万月下载 |
| 2025.03 | OpenAI正式支持MCP | - |
| 2025.04 | Google DeepMind支持MCP | - |
| 2025.07 | Microsoft支持MCP | - |
| 2025.09 | MCP Registry公开预览 | - |
| 2025.12 | 捐赠给Linux基金会AAIF | 10,000+活跃服务器 |
| 2026.03 | 月下载量突破9700万 | 9700万+/月 |
| 2026.04 | MCP Dev Summit NYC | 110万/月,1200人参会 |
| 2026.05 | 官方Registry API快照 | 9,652个最新服务器记录 |
| 2026.07 | 2026-07-28 Spec候选版发布 | 无状态化,移除initialize握手 |
作为参照,React的npm包用了约3年达到1亿月下载,MCP用了16个月。Kubernetes用了近4年才在企业环境中达到类似的部署密度。MCP的增长速度是这两个项目的数倍。
来源:Anthropic AAIF捐赠公告 2025.12, ai2.work MCP分析 2026.03, agentmarketcap.ai MCP Dev Summit报道 2026.04, Digital Applied MCP Adoption Statistics 2026
1.3 三层架构
MCP的架构分三层,理解这三层是开发MCP Server的前提:
Host(宿主)。 AI应用本身。Claude Desktop、Cursor、VS Code,或者你自己写的Agent应用。Host管理会话状态,决定连接哪些Server,控制用户授权。
Client(客户端)。 Host内部的协议通信处理器。一个Host可以创建多个Client,每个Client连接一个不同的Server。Client负责JSON-RPC消息的收发和capability negotiation。
Server(服务器)。 轻量级进程,暴露能力(工具、数据、提示词)。一个GitHub MCP Server可能提供仓库搜索、文件读取、PR创建。一个Postgres MCP Server提供查询执行和schema检查。Server控制暴露什么能力,Client控制发什么请求,Host控制用户授权了什么。三层权限边界,不是单一集成点。
这个分离对企业安全至关重要。Server端控制暴露什么能力,Client端控制发什么请求,Host端控制用户授权了什么操作。一个写得好的MCP Server可以被Claude和ChatGPT同时使用,中间不需要任何代码修改。
来源:modelcontextprotocol.io官方文档, ai2.work
2. 三大原语:Resources、Tools、Prompts的分工
MCP Server暴露三种原语(primitives),每种有明确的职责边界。搞混它们是新手最常见的错误。
Tools(工具)。 模型可以主动调用的函数。执行一个动作,返回一个结果。查天气、发邮件、写数据库、调API。这是最常用的原语,也是Agent「做事」的核心途径。控制权在模型手里:模型决定什么时候调、传什么参数。
Resources(资源)。 模型可以读取的数据源。通过URI暴露,只读。配置文件、数据库记录、API文档、日志文件。控制权在应用手里:应用决定什么时候把哪个Resource塞进上下文,模型不主动调。
Prompts(提示模板)。 可复用的消息模板,用户或应用可以按名字调用。把一段复杂的指令封装成一个可调用的prompt,避免每次对话重复输入。控制权在用户手里:用户在客户端UI里选择调用哪个prompt。
一句话区分:Tools是「模型决定调用」,Resources是「应用决定提供」,Prompts是「用户决定使用」。
| 原语 | 控制方 | 用途 | 类比 |
|---|---|---|---|
| Tools | 模型 | 执行动作,产生副作用 | 函数调用 |
| Resources | 应用 | 提供只读上下文 | 文件系统/REST GET |
| Prompts | 用户 | 封装可复用指令 | 函数模板/快捷指令 |
一个设计良好的MCP Server会合理使用三种原语。把查询操作做成Tool(因为模型需要根据上下文决定何时查),把静态配置做成Resource(因为应用需要稳定的数据源),把常用的工作流指令做成Prompt(因为用户不想每次都打一遍长指令)。
来源:modelcontextprotocol.io/docs/concepts, tutorials.technology, danilchenko.dev
3. 环境准备:FastMCP与项目搭建
3.1 为什么用FastMCP
MCP官方提供了Python SDK(mcp包)和TypeScript SDK,但直接用裸SDK需要手写JSON Schema、手动处理JSON-RPC消息分发、自己管transport生命周期。100行代码里有80行是样板。
FastMCP是Jeremiah Lowin创建的Python框架(Lowin同时也是Prefect的创始人),用装饰器+类型注解把样板代码全部消掉。你写一个带类型注解的Python函数,加上@mcp.tool()装饰器,FastMCP自动生成JSON Schema、校验输入、路由响应。同样的模式适用于@mcp.resource()和@mcp.prompt()。
截至2026年4月,FastMCP每日下载量超过100万次,驱动着约70%的MCP Server(跨所有语言)。2026年1月发布的v3.0引入了组件版本控制、细粒度授权、OpenTelemetry仪表化和多种Provider类型。本篇使用FastMCP 3.x。
来源:danilchenko.dev FastMCP指南, firecrawl.org.cn FastMCP教程, PyPI fastmcp
3.2 创建项目
# 创建项目目录
mkdir devops-mcp-server && cd devops-mcp-server
# 用uv初始化(推荐,比pip快10-100倍)
uv init
uv add fastmcp
# 或者用pip
pip install fastmcp
验证安装:
# test_install.py
from fastmcp import FastMCP
print("FastMCP安装成功!")
python test_install.py
# 输出: FastMCP安装成功!
3.3 项目结构
devops-mcp-server/
├── server.py # 入口文件,创建FastMCP实例,注册处理器
├── tools/
│ └── __init__.py # Tool处理函数
├── requirements.txt # 依赖
└── .env # 密钥(不要提交到git)
当Server的工具超过3-4个时,把Handler拆到单独的tools/模块里。这也方便单元测试时不需要启动MCP Server进程。
4. 实战第一步:写一个Tool(可执行的工具)
这个MCP Server的场景设定为「DevOps助手」:让AI查询服务状态、查看日志、管理部署。这是企业内部最常见的MCP Server类型。
4.1 第一个Tool:查询服务状态
# server.py
from fastmcp import FastMCP
mcp = FastMCP("devops-server")
@mcp.tool()
def get_service_status(service_name: str) -> dict:
"""查询指定服务的运行状态。
当用户询问某个服务是否在运行、健康状态如何时调用。
不要在用户只是提到服务名称但没有查询意图时调用。
Args:
service_name: 服务名称,如 'api-gateway'、'user-service'、'payment-service'
Returns:
包含服务状态信息的字典:status(running/stopped/error),
uptime(运行时长), version(版本号)
"""
# 模拟服务注册表
services = {
"api-gateway": {"status": "running", "uptime": "15d 3h", "version": "2.4.1"},
"user-service": {"status": "running", "uptime": "8d 12h", "version": "1.8.0"},
"payment-service": {"status": "error", "uptime": "0d 0h", "version": "3.2.0", "error": "DB connection timeout"},
"notification-service": {"status": "stopped", "uptime": "0d 0h", "version": "1.1.5"},
}
if service_name not in services:
return {"error": f"服务 '{service_name}' 不存在", "available_services": list(services.keys())}
return services[service_name]
if __name__ == "__main__":
mcp.run() # 默认使用stdio传输
4.2 逐行解析
mcp = FastMCP("devops-server") 创建Server实例。名字是Server的唯一标识,客户端连接时会显示这个名字。
@mcp.tool() 装饰器把这个函数注册为一个MCP Tool。FastMCP从函数的类型注解自动生成JSON Schema,从docstring提取工具描述。模型看到的就是这段docstring。docstring写得好不好,直接决定模型什么时候调这个工具。
函数的docstring是模型理解工具的唯一渠道。 三个要点:第一,说明这个工具什么时候该调(“当用户询问某个服务是否在运行时调用”)。第二,说明什么时候不该调(“不要在用户只是提到服务名称但没有查询意图时调用”)。第三,参数描述要具体到可接受的值。
返回值用dict。 FastMCP自动序列化为JSON。模型能读懂结构化JSON,比纯文本更好解析。错误也返回dict(带error字段),不要raise异常。模型看到error字段会理解出了什么问题,然后调整策略。
4.3 写好Tool的六条规则
- 名字短、动宾结构。
get_service_status,不要perform_service_status_query_operation。 - docstring写给一个看不到代码的同事。 模型只看到name、description、parameters,看不到你的实现。
- 返回可序列化的值。 str、dict、list。FastMCP自动处理。
- 错误返回描述性消息,不要抛异常。 让模型能把问题转达给用户。
- description里写明调用时机。 "什么时候该调"和"什么时候不该调"都写上。这不是可选优化,是必须。
- 参数用类型注解。
service_name: str比service_name让FastMCP生成的Schema更精确,模型调用更准确。
来源:tutorials.technology Build MCP Server 2026, aitechconnect.in FastMCP Tutorial, danilchenko.dev
5. 实战第二步:写一个Resource(可读取的数据源)
Resource是只读的数据源,通过URI暴露。适合暴露配置、文档、服务清单这类稳定数据。
5.1 静态Resource:服务清单
@mcp.resource("config://services")
def get_services_config() -> str:
"""返回所有已注册服务的配置清单。
包含服务名称、端口、依赖关系和健康检查端点。
"""
config = {
"services": [
{"name": "api-gateway", "port": 8080, "health": "/health", "depends_on": ["user-service"]},
{"name": "user-service", "port": 8081, "health": "/health", "depends_on": ["postgres"]},
{"name": "payment-service", "port": 8082, "health": "/health", "depends_on": ["postgres", "redis"]},
{"name": "notification-service", "port": 8083, "health": "/health", "depends_on": ["redis"]},
],
"databases": [
{"name": "postgres", "host": "db.internal", "port": 5432},
{"name": "redis", "host": "cache.internal", "port": 6379},
]
}
import json
return json.dumps(config, ensure_ascii=False, indent=2)
5.2 动态Resource:带URI模板
Resource支持URI模板,可以捕获路径参数:
@mcp.resource("logs://services/{service_name}/latest")
def get_service_logs(service_name: str) -> str:
"""返回指定服务的最新日志(最后50行)。
URI示例: logs://services/api-gateway/latest
"""
# 模拟日志读取
mock_logs = {
"api-gateway": """[2026-07-21 10:32:01] INFO Request: GET /api/users -> 200 (12ms)
[2026-07-21 10:32:03] INFO Request: POST /api/auth -> 200 (45ms)
[2026-07-21 10:32:05] WARN Rate limit: 192.168.1.100 approaching threshold
[2026-07-21 10:32:08] INFO Request: GET /api/products -> 200 (8ms)
[2026-07-21 10:32:10] ERROR Health check: payment-service timeout (3000ms)""",
"user-service": """[2026-07-21 10:31:58] INFO User login: user_12345
[2026-07-21 10:32:00] INFO Token refreshed: user_67890
[2026-07-21 10:32:04] INFO DB query: SELECT * FROM users WHERE id=? (3ms)
[2026-07-21 10:32:06] WARN Slow query: users table scan (>100ms)
[2026-07-21 10:32:09] INFO User logout: user_12345""",
}
if service_name not in mock_logs:
return f"错误:服务 '{service_name}' 的日志不存在"
return mock_logs[service_name]
当客户端请求logs://services/api-gateway/latest时,FastMCP自动调用get_service_logs(service_name="api-gateway")并返回结果。URI模板里的{service_name}变成了函数参数。
5.3 Resource vs Tool的区别
一个常见疑问:查日志为什么不做成Tool?因为控制权不同。Tool是模型主动决定调用的,Resource是应用决定提供的。如果你的场景是「用户问某个服务的日志,模型去查」,用Tool。如果你的场景是「应用在对话开始时自动把服务清单塞进上下文」,用Resource。
实际开发中,查询类操作建议用Tool。Resource更适合那些稳定的、不需要条件判断的数据:全局配置、schema定义、服务注册表。让模型决定何时查日志(Tool),把服务清单作为背景信息提供(Resource)。
来源:tutorials.technology, modelcontextprotocol.io/docs/concepts
6. 实战第三步:写一个Prompt(可复用的提示模板)
Prompt是封装好的消息模板,用户可以在客户端UI中按名字调用。适合把复杂的工作流指令打包成一键可用的快捷方式。
6.1 故障排查Prompt
@mcp.prompt()
def incident_response(service_name: str, severity: str = "high") -> str:
"""生成服务故障排查的标准化指令。
当用户需要排查服务故障时调用此prompt,会生成包含排查步骤的指令。
Args:
service_name: 发生故障的服务名称
severity: 严重程度,'low'、'medium'、'high'、'critical',默认 'high'
"""
return f"""你是一个DevOps故障排查Agent。请按以下步骤排查 {service_name} 服务的故障(严重级别:{severity}):
1. 首先调用 get_service_status 查询 {service_name} 的当前状态
2. 读取 {service_name} 的最新日志(resource: logs://services/{service_name}/latest)
3. 如果状态是 error,分析日志中的错误信息
4. 如果是数据库连接问题,检查依赖的数据库服务状态
5. 给出诊断结论和修复建议
注意事项:
- 严重级别为 critical 时,优先给出紧急止血方案
- 严重级别为 low 时,给出长期优化建议
- 每一步的结论都要有日志或状态数据支撑,不要臆测"""
6.2 Prompt的价值
没有Prompt的话,用户每次排查故障都要手动打一遍上面的指令。有了Prompt,用户在Claude Desktop的对话框里选择incident_response,填入service_name=payment-service和severity=critical,一条完整的排查指令就自动生成了。
Prompt的本质是把「人知道怎么做但不想每次重复说」的工作流封装成可调用的模板。在团队场景下,Prompt还能标准化工作流程:所有人都用同一个排查prompt,输出格式和步骤一致。
来源:tutorials.technology, modelcontextprotocol.io/docs/concepts
7. 完整Server:把它们组合起来
把上面的Tool、Resource、Prompt组合成一个完整的Server文件:
# server.py
"""DevOps MCP Server - 让AI查询服务状态、读取日志、排查故障"""
import json
from fastmcp import FastMCP
mcp = FastMCP("devops-server")
# ============================================================
# 模拟数据(实际项目中替换为真实的服务注册表和日志系统)
# ============================================================
SERVICES = {
"api-gateway": {
"status": "running", "uptime": "15d 3h", "version": "2.4.1",
"port": 8080, "depends_on": ["user-service"]
},
"user-service": {
"status": "running", "uptime": "8d 12h", "version": "1.8.0",
"port": 8081, "depends_on": ["postgres"]
},
"payment-service": {
"status": "error", "uptime": "0d 0h", "version": "3.2.0",
"port": 8082, "depends_on": ["postgres", "redis"],
"error": "DB connection timeout"
},
"notification-service": {
"status": "stopped", "uptime": "0d 0h", "version": "1.1.5",
"port": 8083, "depends_on": ["redis"]
},
}
MOCK_LOGS = {
"api-gateway": """[2026-07-21 10:32:01] INFO Request: GET /api/users -> 200 (12ms)
[2026-07-21 10:32:03] INFO Request: POST /api/auth -> 200 (45ms)
[2026-07-21 10:32:05] WARN Rate limit: 192.168.1.100 approaching threshold
[2026-07-21 10:32:08] INFO Request: GET /api/products -> 200 (8ms)
[2026-07-21 10:32:10] ERROR Health check: payment-service timeout (3000ms)""",
"user-service": """[2026-07-21 10:31:58] INFO User login: user_12345
[2026-07-21 10:32:00] INFO Token refreshed: user_67890
[2026-07-21 10:32:04] INFO DB query: SELECT * FROM users WHERE id=? (3ms)
[2026-07-21 10:32:06] WARN Slow query: users table scan (>100ms)
[2026-07-21 10:32:09] INFO User logout: user_12345""",
"payment-service": """[2026-07-21 10:32:00] ERROR Failed to connect to postgres: timeout after 5000ms
[2026-07-21 10:32:02] ERROR Retry 1/3: connecting to postgres...
[2026-07-21 10:32:07] ERROR Retry 2/3: connecting to postgres...
[2026-07-21 10:32:12] ERROR Retry 3/3: connecting to postgres...
[2026-07-21 10:32:12] CRITICAL All retries exhausted. Service entering degraded mode.""",
"notification-service": """[2026-07-21 09:00:00] INFO Service shutting down (manual stop)
[2026-07-21 09:00:01] INFO Cleanup complete. Goodbye.""",
}
# ============================================================
# Tools:模型主动调用的可执行函数
# ============================================================
@mcp.tool()
def get_service_status(service_name: str) -> dict:
"""查询指定服务的运行状态。
当用户询问某个服务是否在运行、健康状态如何时调用。
不要在用户只是提到服务名称但没有查询意图时调用。
Args:
service_name: 服务名称,如 'api-gateway'、'user-service'、'payment-service'
"""
if service_name not in SERVICES:
return {
"error": f"服务 '{service_name}' 不存在",
"available_services": list(SERVICES.keys())
}
return SERVICES[service_name]
@mcp.tool()
def list_all_services() -> list:
"""列出所有已注册的服务及其基本状态。
当用户想知道系统中有哪些服务时调用。
返回服务名称、状态和版本的简表。
"""
return [
{"name": name, "status": info["status"], "version": info["version"]}
for name, info in SERVICES.items()
]
@mcp.tool()
def restart_service(service_name: str, force: bool = False) -> dict:
"""重启指定的服务。
当用户明确要求重启某个服务时调用。
如果服务状态是running且force=False,返回确认提示。
如果服务状态是error或stopped,直接重启。
Args:
service_name: 要重启的服务名称
force: 是否强制重启(跳过确认),默认False
"""
if service_name not in SERVICES:
return {"error": f"服务 '{service_name}' 不存在"}
service = SERVICES[service_name]
if service["status"] == "running" and not force:
return {
"action": "confirm_required",
"message": f"服务 '{service_name}' 正在运行。确认要重启吗?设置 force=True 强制重启。"
}
# 模拟重启
SERVICES[service_name]["status"] = "running"
SERVICES[service_name]["uptime"] = "0d 0h"
if "error" in SERVICES[service_name]:
del SERVICES[service_name]["error"]
return {
"action": "restarted",
"service": service_name,
"new_status": "running",
"message": f"服务 '{service_name}' 已成功重启"
}
# ============================================================
# Resources:应用可读取的只读数据源
# ============================================================
@mcp.resource("config://services")
def get_services_config() -> str:
"""所有已注册服务的完整配置清单。
包含服务名称、端口、依赖关系。
"""
config = {
"services": [
{"name": name, "port": info["port"], "depends_on": info["depends_on"]}
for name, info in SERVICES.items()
],
"databases": [
{"name": "postgres", "host": "db.internal", "port": 5432},
{"name": "redis", "host": "cache.internal", "port": 6379},
]
}
return json.dumps(config, ensure_ascii=False, indent=2)
@mcp.resource("logs://services/{service_name}/latest")
def get_service_logs(service_name: str) -> str:
"""指定服务的最新日志(最后50行)。
URI示例: logs://services/api-gateway/latest
"""
if service_name not in MOCK_LOGS:
return f"错误:服务 '{service_name}' 的日志不存在"
return MOCK_LOGS[service_name]
# ============================================================
# Prompts:用户可调用的提示模板
# ============================================================
@mcp.prompt()
def incident_response(service_name: str, severity: str = "high") -> str:
"""生成服务故障排查的标准化指令。
当用户需要排查服务故障时调用。
Args:
service_name: 发生故障的服务名称
severity: 严重程度,'low'、'medium'、'high'、'critical'
"""
return f"""你是一个DevOps故障排查Agent。请按以下步骤排查 {service_name} 服务的故障(严重级别:{severity}):
1. 首先调用 get_service_status 查询 {service_name} 的当前状态
2. 读取 {service_name} 的最新日志(resource: logs://services/{service_name}/latest)
3. 如果状态是 error,分析日志中的错误信息
4. 如果是数据库连接问题,检查依赖的数据库服务状态
5. 给出诊断结论和修复建议
严重级别为 critical 时,优先给出紧急止血方案。
严重级别为 low 时,给出长期优化建议。
每一步的结论都要有日志或状态数据支撑,不要臆测"""
# ============================================================
# 入口
# ============================================================
if __name__ == "__main__":
mcp.run() # 默认stdio传输,适合Claude Desktop和Cursor本地接入
这个Server有3个Tool(查状态、列服务、重启服务)、2个Resource(服务配置、服务日志)、1个Prompt(故障排查指令)。完整可运行,不到200行代码。运行python server.py后,Server会等待客户端通过stdio连接。
8. 调试:用MCP Inspector可视化测试
写完Server不接客户端直接跑,出了问题不知道是Server的锅还是客户端的锅。MCP Inspector是官方调试工具,提供一个Web UI让你在浏览器里直接测试Tool、Resource和Prompt。
8.1 启动Inspector
# 安装并启动Inspector(需要Node.js 22+)
npx @modelcontextprotocol/inspector python server.py
Inspector会同时启动两个服务:
- Inspector Client UI:
http://localhost:6274(浏览器打开这个) - Inspector Proxy:
http://localhost:6277(自动连接你的Server)
8.2 在Inspector中测试
打开http://localhost:6274后:
测试Tool。 左侧选择Tools标签,看到三个Tool:get_service_status、list_all_services、restart_service。点击get_service_status,在右侧表单中输入service_name=payment-service,点击Run。下方面板显示返回的JSON:{"status": "error", "error": "DB connection timeout", ...}。
测试Resource。 切到Resources标签,看到两个Resource URI。点击logs://services/payment-service/latest,右侧显示完整的日志文本。点击config://services,显示服务配置JSON。
测试Prompt。 切到Prompts标签,看到incident_response。输入service_name=payment-service、severity=critical,Run后显示生成的完整prompt文本。
Inspector还提供JSON-RPC消息追踪:每一次调用的完整请求和响应都能在消息日志里看到。调试时这是定位问题的第一工具:消息发出去了吗?Server收到了吗?返回了什么?
8.3 传递环境变量
如果你的Server需要API Key等环境变量:
# 通过 -e 标志传递环境变量
npx @modelcontextprotocol/inspector \
-e API_KEY=your_key \
-e DB_URL=postgresql://... \
python server.py
# 用 -- 分隔Inspector标志和Server参数
npx @modelcontextprotocol/inspector \
-e API_KEY=your_key \
-- python server.py --debug
来源:mcpsearch.com/packages/@modelcontextprotocol/inspector, modelcontextprotocol.io/docs/tools/inspector, cloud.tencent.com/developer/mcp/server/11587
9. 接入Claude Desktop和Cursor
9.1 Claude Desktop配置
Claude Desktop的MCP配置文件位置:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
编辑配置文件:
{
"mcpServers": {
"devops-server": {
"command": "python",
"args": ["/绝对路径/to/server.py"],
"env": {
"PYTHONUNBUFFERED": "1"
}
}
}
}
保存后重启Claude Desktop。在对话界面中,你应该能看到工具图标出现了新工具。试着问:「payment-service的状态怎么样?」Claude会自动调用get_service_status并返回结果。
PYTHONUNBUFFERED=1是关键。Python默认缓冲stdout,MCP通过stdio通信需要无缓冲。不加这个环境变量,Server可能看起来启动了但Claude一直等待响应。
9.2 Cursor配置
Cursor的MCP配置文件位置:
- 全局:
~/.cursor/mcp.json - 项目级:
.cursor/mcp.json(优先于全局)
{
"mcpServers": {
"devops-server": {
"command": "python",
"args": ["/绝对路径/to/server.py"],
"env": {
"PYTHONUNBUFFERED": "1"
}
}
}
}
保存后在Cursor的Settings → MCP中确认Server状态为绿色(已连接)。在Composer或Chat中使用@devops-server即可调用Server提供的工具。
9.3 VS Code配置
VS Code的MCP配置文件位置:.vscode/mcp.json(项目级)
{
"servers": {
"devops-server": {
"command": "python",
"args": ["/绝对路径/to/server.py"],
"env": {
"PYTHONUNBUFFERED": "1"
}
}
}
}
9.4 常见问题排查
Server连不上。 检查路径是否用绝对路径。Windows路径用双反斜杠或正斜杠。检查Python是否在PATH中。
工具不出现。 检查Inspector中Server是否正常启动。检查函数是否有类型注解和docstring。没有docstring的Tool不会出现在工具列表里。
调用超时。 检查PYTHONUNBUFFERED=1是否设置。检查Server的日志输出是否走了stderr而不是stdout。stdio传输下stdout是JSON-RPC通信通道,日志必须走sys.stderr。
import sys
print("调试信息", file=sys.stderr) # 正确:日志走stderr
# print("调试信息") # 错误:这会破坏JSON-RPC通信
来源:freemcplab.com Inspector文档, mcpsearch.com, chatforest.com MCP Docker指南
10. 从本地到远程:Docker部署与HTTP传输
stdio传输只适合本地单用户场景。当你的Server需要被多个Agent使用、部署到服务器、或被远程客户端访问时,需要切换到HTTP传输。
10.1 切换到Streamable HTTP
FastMCP支持一行代码切换传输方式:
# server_http.py
if __name__ == "__main__":
mcp.run(
transport="streamable-http",
host="0.0.0.0",
port=8000
)
streamable-http是MCP规范推荐的远程传输方式(旧的SSE传输已被标记为deprecated)。Server暴露一个/mcp端点,处理POST(请求)和GET+SSE(流式响应)。
10.2 Docker容器化
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
# 安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制Server代码
COPY server.py .
# 关键:stdio模式需要无缓冲输出
# HTTP模式不需要,但加了无害
ENV PYTHONUNBUFFERED=1
# 暴露HTTP端口
EXPOSE 8000
# 以非root用户运行(安全最佳实践)
RUN useradd -m mcpuser
USER mcpuser
# 启动Server(HTTP模式)
CMD ["python", "server.py"]
requirements.txt:
fastmcp>=3.0.0
构建并运行:
# 构建镜像
docker build -t devops-mcp-server .
# 运行容器
docker run -d -p 8000:8000 --name devops-mcp devops-mcp-server
# 测试是否正常
curl -X POST http://localhost:8000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
10.3 远程客户端连接
Server运行在远程后,客户端通过URL连接:
Cursor配置:
{
"mcpServers": {
"devops-remote": {
"url": "https://mcp.yourcompany.com/mcp"
}
}
}
Claude Code命令行:
claude mcp add --transport http devops-remote https://mcp.yourcompany.com/mcp
10.4 生产环境安全清单
把MCP Server放到网络上意味着任何人都能调用你的工具。以下安全措施是必须的:
TLS加密。 所有生产流量必须走HTTPS。在Nginx或负载均衡层终止TLS。不要用裸HTTP暴露工具端点,工具的输入输出经常包含敏感数据。
认证。 在/mcp端点前加认证中间件。Bearer Token或OAuth 2.0。没有认证的MCP Server等于公开的API执行入口。
CORS限制。 限制Access-Control-Allow-Origin为已知客户端。不要设为*。
速率限制。 Agent可能在错误时快速重试。在Nginx或应用层加速率限制,防止API成本失控。
输入校验。 FastMCP通过类型注解自动校验参数类型,但业务逻辑的校验需要你自己写。路径遍历、SQL注入、命令注入,这些在MCP Server里一样要防。
非root用户。 Dockerfile里加USER 1000。不要以root运行容器内的MCP Server。
日志走stderr。 stdio传输下stdout是协议通道。HTTP传输下虽然没这个限制,但保持日志走stderr是好习惯,方便容器日志收集。
来源:mcpplaygroundonline.com Docker部署指南, chatforest.com MCP Docker指南, mcpserverspot.com远程部署指南
11. 辩证看待:MCP的现实问题与陷阱
11.1 增长数字背后的质量危机
MCP的17000+公开索引服务器听起来很壮观,但Q1 2026的Nerq普查发现一个扎眼的数字:只有12.9%的服务器在文档完整性、活跃维护、可靠性信号的综合质量评分上超过70分。换句话说,大约15200个被索引的服务器是实验性的、已废弃的、或文档严重不足的。
这个质量分布对选型有实际影响。你在MCP Registry上找一个Postgres MCP Server,搜出来20个,可能只有2-3个能用于生产。其余的可能是某个开发者的周末项目,三个月没更新了,README写了一半。
来源:agentmarketcap.ai A2A vs MCP部署数据 2026.04, Nerq MCP Census Q1 2026
11.2 写一个Server和写一个好Server之间的鸿沟
MCP的入门门槛很低。FastMCP的装饰器API让一个有Python基础的开发者10分钟就能跑起来一个Server。但「跑起来」和「在生产环境可靠运行」之间的距离,比大多数人想象的远得多。
三个最常见的生产事故模式:
Tool描述写得模糊,模型在不该调的时候调了。 get_service_status的docstring如果只写「查询服务状态」,模型可能在用户说「我觉得api-gateway这个名字不错」时也去调它。这不是模型的错,是描述没有写清楚调用条件。
长链路工具调用的错误叠加。 一个Agent在一次任务中可能连续调用5-8个Tool。每个Tool 95%正确率,8步串联下来整体成功率只有66%。如果你的Tool有边界条件没处理(比如service_name带空格、返回值超过token限制),这些边界条件会在Agent的多步调用中放大。
stdio缓冲导致的假死。 这是本地开发最常见的问题。Python默认缓冲stdout,MCP客户端等在stdin读响应,Server的响应卡在stdout缓冲区里。表现是Server看起来启动了但客户端一直转圈。解决方式前面说了:PYTHONUNBUFFERED=1。
11.3 协议本身的演进风险
MCP正在经历一次重大架构变更。2026年7月28日规范候选版正在将协议从有状态改为无状态:移除initialize握手、移除协议级session、引入Multi Round-Trip Requests(MRTR)。
这意味着今天写的Server代码可能在半年后需要适配新的SDK版本。FastMCP v2 beta已经开始适配新规范:Python v2 Server在一个端点上同时回答两种协议版本,TypeScript v2拆分了包结构。但如果你直接用裸SDK写的Server,迁移成本会更高。
对生产系统来说,这意味着两件事:第一,锁定SDK版本,不要用浮动的latest标签。第二,关注MCP的 SEP(Specification Enhancement Proposals)流程,提前知道哪些变更会影响你。
来源:blog.modelcontextprotocol.io SDK Beta公告 2026.07, modelcontextprotocol.io/development roadmap
11.4 MCP不解决所有问题
MCP解决的是「工具定义的标准化和可发现性」。它不解决:
- 模型什么时候该调工具的判断能力。 这是模型本身的能力,MCP只提供接口。
- 工具调用的编排逻辑。 先调A再调B还是并行调用,这是Agent框架的事。
- 工具之间的数据流。 A的输出怎么变成B的输入,需要你的代码或Agent框架来编排。
- 安全和权限管控的完整方案。 MCP提供了三层分离的架构,但具体的认证授权策略需要你自己实现。
把MCP理解为「工具的USB-C接口」是准确的。USB-C统一了物理接口和传输协议,但你的设备能不能用好这个接口,取决于设备本身的电路设计、固件实现和安全策略。MCP也是一样。
12. 总结与下一篇预告
12.1 核心要点
MCP把Agent工具调用从「每个应用为每个工具写一套集成」升级为「一个Server被所有客户端共享」。16个月达到9700万月下载,是开发者协议史上最快的增长曲线。2025年12月捐赠给Linux基金会后,OpenAI、Google、Microsoft全部跟进,成为事实标准。
三大原语的分工要记牢。Tools是模型主动调用的执行函数,控制权在模型。Resources是应用提供的只读数据,控制权在应用。Prompts是用户调用的消息模板,控制权在用户。搞混控制权是设计MCP Server最常见的问题。
FastMCP用装饰器+类型注解消掉了裸SDK的样板代码。@mcp.tool()自动从类型注解生成JSON Schema,从docstring提取工具描述。一个完整的Server不超过200行。Tool的docstring质量直接决定模型的调用准确率,写清楚「什么时候该调」和「什么时候不该调」。
调试用MCP Inspector。npx @modelcontextprotocol/inspector python server.py,在浏览器里可视化测试Tool、Resource、Prompt,看完整的JSON-RPC消息追踪。不要跳过Inspector直接接客户端,出了问题分不清是Server还是客户端的锅。
从本地到远程的路径清晰。stdio传输适合本地开发(Claude Desktop、Cursor),Streamable HTTP适合远程部署。Docker容器化加Nginx反向代理加TLS加认证,是生产部署的标准组合。PYTHONUNBUFFERED=1是stdio模式的生命线,不加它Server会假死。
12.2 下一篇预告
第10篇:多Agent协作与A2A协议——CrewAI + LangGraph搭建AI协作团队
这篇讲清楚了单个Agent怎么通过MCP连接工具。下一篇升级到多Agent协作:为什么一个复杂任务需要多个Agent分工?CrewAI怎么用几行代码搭建一个「研究员+写手+审核员」的Agent团队?LangGraph怎么构建有状态的复杂工作流?Google主导的A2A协议v1.0怎么让不同框架的Agent互相通信?
MCP解决Agent到工具的连接,A2A解决Agent到Agent的连接。两个协议叠加,就是2026年企业Agent架构的完整图景。A2A已有150+组织支持,v1.0于2026年5月正式发布,中文深度内容几乎空白。
系列推荐阅读:
本文数据来源:MCP Dev Summit NYC 2026(2026.04,1200人参会,agentmarketcap.ai报道)、Anthropic AAIF捐赠公告(2025.12.09)、Digital Applied “MCP Adoption Statistics 2026”(2026.05.24验证更新)、Stacklok “State of MCP in Software 2026”(41%企业生产采用率)、ai2.work MCP分析(2026.03)、Nerq MCP Census Q1 2026(17,468服务器索引,12.9%质量达标率)、modelcontextprotocol.io官方文档与Roadmap(2026.03更新)、blog.modelcontextprotocol.io SDK Beta公告(2026.07)、GitHub Search API(15,926 mcp-server topic仓库,2026.05.24快照)、danilchenko.dev FastMCP指南、tutorials.technology Build MCP Server 2026、aitechconnect.in FastMCP Tutorial、firecrawl.org.cn FastMCP教程、mcpsearch.com Inspector文档、mcpplaygroundonline.com Docker部署指南、chatforest.com MCP Docker指南、mcpserverspot.com远程部署指南。
更多推荐



所有评论(0)