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


目录

  1. MCP协议回顾:为什么Agent世界需要一个USB-C
  2. 三大原语:Resources、Tools、Prompts的分工
  3. 环境准备:FastMCP与项目搭建
  4. 实战第一步:写一个Tool(可执行的工具)
  5. 实战第二步:写一个Resource(可读取的数据源)
  6. 实战第三步:写一个Prompt(可复用的提示模板)
  7. 完整Server:把它们组合起来
  8. 调试:用MCP Inspector可视化测试
  9. 接入Claude Desktop和Cursor
  10. 从本地到远程:Docker部署与HTTP传输
  11. 辩证看待:MCP的现实问题与陷阱
  12. 总结与下一篇预告

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的六条规则

  1. 名字短、动宾结构。 get_service_status,不要perform_service_status_query_operation
  2. docstring写给一个看不到代码的同事。 模型只看到name、description、parameters,看不到你的实现。
  3. 返回可序列化的值。 str、dict、list。FastMCP自动处理。
  4. 错误返回描述性消息,不要抛异常。 让模型能把问题转达给用户。
  5. description里写明调用时机。 "什么时候该调"和"什么时候不该调"都写上。这不是可选优化,是必须。
  6. 参数用类型注解。 service_name: strservice_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-serviceseverity=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 UIhttp://localhost:6274(浏览器打开这个)
  • Inspector Proxyhttp://localhost:6277(自动连接你的Server)

8.2 在Inspector中测试

打开http://localhost:6274后:

测试Tool。 左侧选择Tools标签,看到三个Tool:get_service_statuslist_all_servicesrestart_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-serviceseverity=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远程部署指南。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐