引言

大语言模型(LLM)的能力已经不仅仅局限于对话,越来越多的AI应用需要与外部工具、API和数据源交互。但长期以来,让模型调用自定义工具的过程充满碎片化——每个平台都有自己的函数调用格式,开发者不得不为 ChatGPT、Claude、开源模型分别实现不同的适配逻辑。

Anthropic 推出的 Model Context Protocol(MCP) 正在改变这一局面。它提供了一套开放标准,让 AI 应用能够通过统一的协议发现和调用工具,无论是本地脚本、远程 API 还是数据库查询,都可以被封装成 MCP 插件(MCP Server)。这意味着你只需编写一次工具,就可以被任何支持 MCP 的客户端(如 Claude Desktop、VS Code 扩展)直接使用。

本文将以实战为导向,带你从零开发一个完整的 MCP 插件。我们会先理解 MCP 的核心架构,然后用 Python 编写一个可运行的天气查询插件,最后探讨生产环境中的常见问题与最佳实践。读完你会发现,构建一个 MCP 插件比你想象的要简单得多。

核心概念:MCP 是如何工作的?

在动手编码前,有必要先理清 MCP 的几个关键角色和通信方式。

MCP 的基本架构

MCP 采用经典的客户端-服务器模型,但针对 AI 场景进行了优化:

  • MCP Host:运行 AI 模型的主程序,比如 Claude Desktop、IDE 插件。
  • MCP Client:运行在 Host 内部的协议客户端,负责与 MCP Server 建立连接、管理生命周期。
  • MCP Server:你将要开发的插件,它对外暴露一组工具(Tools)、资源(Resources)、提示词(Prompts)。

其中,工具(Tool)是 MCP 中最常用的概念,对应一个可以被模型调用的具体功能(例如 get_weather)。当用户向 AI 询问“北京今天天气怎么样?”,Host 会通过 MCP Client 请求 Server,Server 执行工具并返回结果,LLM 再将结果组织成自然语言回复。

两种传输方式

MCP 支持两种底层传输协议:

  1. stdio(标准输入/输出):Server 作为子进程启动,通过标准 I/O 与 Client 通信。适合本地开发、个人工具。
  2. SSE(Server-Sent Events):基于 HTTP,支持远程 Server,允许多个 Client 共享。适合团队或生产环境。

本文的示例将使用 stdio 传输,因为它零配置、最适合快速上手。

工具定义的要素

一个 MCP 工具需要包含以下信息:

  • 名称:唯一标识,如 get_weather
  • 描述:自然语言说明,模型会依据描述决定何时调用
  • 输入参数:JSON Schema 定义,包含参数名、类型、是否必填等
  • 执行逻辑:具体的业务代码,返回字符串或结构化数据

清楚了这些之后,我们立刻开始实战。

实战示例:开发一个天气查询 MCP 插件

环境准备

确保你的开发环境满足以下条件:

  • Python 3.10 或更高版本
  • pip 包管理器
  • 一个支持 MCP 的客户端用于测试,推荐使用官方 MCP Inspector 或 Claude Desktop

(如果你还没有 Claude Desktop,可以先用 Inspector 完成测试,安装方法后面会介绍。)

第一步:安装 MCP Python SDK

Anthropic 提供了官方 Python SDK mcp,它内置了 FastMCP 高层封装,让我们可以像写普通函数一样定义工具。

打开终端,创建项目目录并安装依赖:

mkdir weather-mcp-server
cd weather-mcp-server
python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate
pip install mcp

此时你的环境中就拥有了开发 MCP 插件的全部能力。

第二步:编写 Server 代码

新建文件 weather_server.py,输入以下完整代码:

from mcp.server.fastmcp import FastMCP

# 初始化 FastMCP 实例,参数为插件名称
mcp = FastMCP("Weather")

# 使用 @mcp.tool() 装饰器定义工具
@mcp.tool()
def get_weather(city: str) -> str:
    """
    获取指定城市的天气信息。
    参数 city: 城市名称,例如 北京、上海。
    """
    # 模拟天气数据,实际项目中可以调用天气 API
    weather_data = {
        "北京": "晴,气温 25°C,湿度 40%",
        "上海": "多云转阴,气温 28°C,湿度 65%",
        "深圳": "阵雨,气温 30°C,湿度 80%",
        "纽约": "晴,气温 22°C,湿度 55%",
    }
    result = weather_data.get(city)
    if result is None:
        return f"抱歉,未找到城市“{city}”的天气信息"
    return f"{city}天气:{result}"

if __name__ == "__main__":
    # 启动服务器,默认使用 stdio 传输
    mcp.run()

代码说明:

  • FastMCP("Weather") 创建了一个名为 Weather 的 MCP Server,它将被客户端识别。
  • @mcp.tool() 将下方的函数注册为一个工具。函数名 get_weather 自动成为工具名。
  • 函数文档字符串(docstring)会作为工具的描述信息,模型依赖该描述理解工具功能。
  • 参数 city: str 定义了输入模式,MCP SDK 会自动生成 JSON Schema,无需手动编写。
  • 主程序入口调用 mcp.run() 启动 stdio 服务器,监听来自客户端的请求。

这就是一个完整的 MCP 插件!没有复杂的配置,没有额外的注册步骤,你只需专注业务逻辑。

第三步:测试你的插件

使用 MCP Inspector 测试

MCP Inspector 是官方提供的调试工具,可以图形化地测试本地的 MCP Server。

首先全局安装 Inspector:

npx @modelcontextprotocol/inspector

(需要 Node.js 环境,如果没有请先安装。)

启动 Inspector 并连接到你的 Server:

npx @modelcontextprotocol/inspector python weather_server.py

浏览器会自动打开 http://localhost:5173,你将看到一个交互界面:
- 左侧列出了检测到的工具 get_weather
- 点击工具,填写参数 {"city": "北京"},点击执行
- 右侧会显示返回的结果

看到预期的天气信息,说明你的插件工作正常。

集成到 Claude Desktop

如果你安装了 Claude Desktop(目前支持 macOS 和 Windows),可以通过配置文件将我们的插件加入。

找到 Claude Desktop 的配置文件:
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
- Windows: %APPDATA%\Claude\claude_desktop_config.json

如果没有该文件,手动创建一个。加入以下内容:

{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["/你的路径/weather_server.py"]
    }
  }
}

将路径替换为你的 weather_server.py 绝对路径。重启 Claude Desktop,然后尝试在对话中输入:“北京今天天气怎么样?”,模型会自动调用 get_weather 工具并给出回复。

进阶:开发一个支持异步和错误处理的生产级插件

上面的示例虽然能跑,但在真实项目中你可能需要调用外部 HTTP API、处理超时、优雅地返回错误。下面展示一个更健壮的版本,它使用 httpx 异步请求真实天气 API(以 OpenWeatherMap 为例,你需要注册获取 API Key)。

安装额外依赖:

pip install httpx python-dotenv

创建 weather_pro_server.py

import os
import httpx
from mcp.server.fastmcp import FastMCP
from dotenv import load_dotenv

load_dotenv()  # 加载 .env 中的 API_KEY

mcp = FastMCP("WeatherPro")

@mcp.tool()
async def get_weather(city: str, units: str = "metric") -> str:
    """
    获取指定城市的实时天气数据。
    city: 城市名,支持英文如 Beijing、London
    units: 温度单位,metric(摄氏度) 或 imperial(华氏度)
    """
    api_key = os.getenv("OPENWEATHER_API_KEY")
    if not api_key:
        return "错误:未配置天气 API Key,请在 .env 文件中设置 OPENWEATHER_API_KEY"

    url = "https://api.openweathermap.org/data/2.5/weather"
    params = {
        "q": city,
        "appid": api_key,
        "units": units,
        "lang": "zh_cn"
    }

    try:
        async with httpx.AsyncClient(timeout=10.0) as client:
            resp = await client.get(url, params=params)
            resp.raise_for_status()
            data = resp.json()
            desc = data["weather"][0]["description"]
            temp = data["main"]["temp"]
            humidity = data["main"]["humidity"]
            return f"{city}天气:{desc},温度{temp}°,湿度{humidity}%"
    except httpx.HTTPStatusError as e:
        if e.response.status_code == 404:
            return f"城市“{city}”未找到"
        return f"API 请求失败,状态码:{e.response.status_code}"
    except Exception as e:
        return f"获取天气时发生异常:{str(e)}"

if __name__ == "__main__":
    mcp.run()

这个版本展示了:
- 异步工具:函数声明为 async def,MCP SDK 原生支持异步执行,不会阻塞其他请求。
- 环境变量管理:使用 .env 存储敏感信息,避免硬编码。
- 结构化错误处理:针对 HTTP 错误、城市未找到、超时等情况返回有意义的提示,而不是抛出异常让客户端报错。

常见问题与注意事项

1. 工具函数的类型提示是必须的吗?

强烈建议加上。MCP SDK 会利用 Python 的类型注解自动生成 JSON Schema。如果不加类型提示,所有参数都会被当作 any 类型,客户端无法正确校验参数,模型的调用准确性也会下降。

2. 如何调试错误?

  • 查看 Server 的 stderr 输出:当使用 stdio 传输时,打印到 sys.stderr 的信息会显示在启动它的终端或客户端日志中。因此建议使用 logging 模块输出到 stderr。
  • 使用 MCP Inspector:它会在页面上显示任何工具执行时抛出的异常。
  • 增加详细的返回值:在开发阶段,让工具返回更详细的错误信息,帮助快速定位问题。

3. 可以选择 SSE 传输替代 stdio 吗?

可以。如果你的工具需要部署到服务器并被多个客户端远程调用,使用 SSE 更合适。修改方式很简单,将 mcp.run() 替换为:

mcp.run(transport="sse", host="0.0.0.0", port=8000)

然后客户端通过 http://your-server:8000/sse 连接。注意 SSE 需要处理跨域和鉴权,生产环境下建议在前面加一层反向代理(如 Nginx)。

4. 如何限制工具的调用权限?

MCP 协议本身不提供细粒度的权限模型。安全控制通常在这些层面实现:
- 在 Server 内部检查调用来源或进行用户认证(例如通过自定义 Header)。
- 如果你使用 Claude Desktop 等 Host,确保配置文件中的 command 和 args 都是安全可信的,防止任意代码执行。
- 对于敏感操作(如删除文件、执行命令),在工具内再次要求用户确认或记录审计日志。

5. 工具可以返回图片、文件等非文本内容吗?

MCP 资源(Resources)专门用于暴露文件、图片、数据库表等结构化内容。工具通常返回文本,但资源可以返回二进制数据。你可以同时暴露工具和资源,让模型根据需求选择调用。示例:

from mcp.server.fastmcp import FastMCP
from mcp.types import Resource

mcp = FastMCP("ImageProvider")

@mcp.resource("image://{filename}")
def get_image(filename: str) -> bytes:
    with open(f"./images/{filename}", "rb") as f:
        return f.read()

资源定义使用 mcp.resource() 装饰器,路径可以是 URI 模板。

总结

通过本文,我们完整地走过了从概念到代码的 MCP 插件开发流程。你学到了:

  • MCP 的客户端-服务器架构及 stdio、SSE 两种传输方式
  • 使用 FastMCP 定义工具、资源和异步处理
  • 用 MCP Inspector 和 Claude Desktop 进行测试
  • 生产环境中处理错误、安全、远程部署的注意事项

MCP 正在成为 AI Agent 与外部世界交互的“USB 接口”,越来越多的工具和平台开始原生支持。现在,你可以将任何 Python 函数封装成标准化的 MCP 工具,无论是本地脚本、企业内部 API 还是云端服务,都能无缝接入 AI 工作流。

下一步,你可以尝试将手头常用的脚本(数据库查询、文件操作、通知推送)改造成 MCP 插件,或者为团队开发一套标准化的研发工具集。整个协议是开放的,生态也在快速成长,早一步掌握 MCP 开发,就能早一步享受到统一接口带来的效率红利。

完整代码已同步在 GitHub Gist,欢迎取用并部署你的第一个 MCP 插件!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐