引言

Claude 是当下最强大的大语言模型之一,但你有没有遇到过这些场景:需要它读取本地文件、查询实时数据库、调用公司内部 API,或者只是简单地获取当前时间?由于安全和架构限制,Claude 本身无法直接访问外部资源。而 Model Context Protocol (MCP) 正是 Anthropic 推出的标准解决方案——它定义了一套通用协议,让 AI 应用(如 Claude Desktop)能够安全、标准地连接你提供的工具、资源和提示模板。

本文将带你从零开始,掌握 MCP 的核心概念,并通过一个完整可运行的 Python 示例,构建你自己的 Claude 自定义工具。读完你会发现,扩展 Claude 的能力其实比你想象的简单得多。

一、MCP 核心概念:为什么它如此重要?

在 MCP 出现之前,如果你想让大模型集成某个工具,通常需要针对不同模型写不同的插件,或者通过 LangChain 等框架做各种适配。这种碎片化让工具开发者疲于奔命,也让用户难以复用。

MCP 的出现改变了这一切,它像一个“USB-C 接口”:
- 统一标准:无论是 OpenAI、Claude 还是其他模型,只要支持 MCP,就能使用同一个工具服务器。
- 安全隔离:工具运行在独立的进程中,模型只能通过标准协议调用,无法随意执行系统命令。
- 可插拔:你可以在 Claude Desktop 中随时添加或移除 MCP 服务器,无需修改应用本体。

MCP 的核心架构由三个角色构成:
- 主机(Host):即 AI 应用本身,例如 Claude Desktop。
- 客户端(Client):由 Host 内部创建,与指定的 MCP 服务器维持 1:1 连接。
- 服务器(Server):你编写的轻量级程序,负责暴露具体的“能力”,比如工具函数、数据资源、提示模板。

你现在需要的就是开发一个 MCP 服务器,然后将它配置到 Claude Desktop 中。开发一个服务器只需要遵循 MCP 协议,通过标准传输方式(如 stdio)与客户端通信即可。下面,上实战!

二、实战示例:构建一个“系统助手” MCP 服务器

我们将用 Python 编写一个 MCP 服务器,提供三个工具:
- get_current_time:返回当前系统时间。
- add_numbers:计算两个数的和。
- echo:回显输入的字符串(验证工具通信)。

最终效果:在 Claude Desktop 中,可以让 Claude 调用这些工具,比如问它“现在几点”或“帮我算 123+456”。

2.1 环境准备

确保你的 Python 版本 ≥ 3.10,然后安装 MCP 的 Python SDK:

pip install mcp

该库提供了快速构建 MCP 服务器的装饰器和基础类。

2.2 编写服务器代码

新建一个文件 system_assistant.py,内容如下:

#!/usr/bin/env python3
"""
一个简单的 MCP 服务器,暴露三个工具:
- get_current_time: 获取当前时间
- add_numbers: 两数相加
- echo: 回显字符串
"""

import datetime
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationCapabilities
from mcp.server.stdio import stdio_server
import mcp.types as types

# 创建服务器实例
server = Server("system-assistant")

@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
    """返回工具列表,Claude 会根据工具描述决定何时调用"""
    return [
        types.Tool(
            name="get_current_time",
            description="获取当前系统时间,返回 ISO 格式字符串",
            inputSchema={
                "type": "object",
                "properties": {},  # 无需参数
            },
        ),
        types.Tool(
            name="add_numbers",
            description="将两个数字相加,返回计算结果",
            inputSchema={
                "type": "object",
                "properties": {
                    "a": {"type": "number", "description": "第一个数"},
                    "b": {"type": "number", "description": "第二个数"},
                },
                "required": ["a", "b"],
            },
        ),
        types.Tool(
            name="echo",
            description="回显用户输入的文本,可用于测试",
            inputSchema={
                "type": "object",
                "properties": {
                    "message": {"type": "string", "description": "要回显的内容"},
                },
                "required": ["message"],
            },
        ),
    ]

@server.call_tool()
async def handle_call_tool(
    name: str, arguments: dict
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
    """当 Claude 实际调用工具时,执行对应逻辑"""
    if name == "get_current_time":
        now = datetime.datetime.now().isoformat()
        return [types.TextContent(type="text", text=f"当前时间:{now}")]

    elif name == "add_numbers":
        a = arguments["a"]
        b = arguments["b"]
        result = a + b
        return [types.TextContent(type="text", text=f"{a} + {b} = {result}")]

    elif name == "echo":
        msg = arguments["message"]
        return [types.TextContent(type="text", text=f"ECHO: {msg}")]

    # 未知工具
    raise ValueError(f"未知工具: {name}")

async def main():
    # 启动基于标准输入输出的 MCP 服务器
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationCapabilities(
                sampling={},  # 无需采样能力
                experimental={}
            ),
        )

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

代码解读
- @server.list_tools() 注册一个函数,返回可用工具的定义。每个工具包含名称、描述和符合 JSON Schema 的输入参数规范,这能让 Claude 在对话时自动判断何时调用哪个工具。
- @server.call_tool() 是真正的执行逻辑,当 Claude 决定调用某个工具时,MCP 客户端会将工具名称和实参传入该函数,你需要在此完成具体计算,并将结果封装为 TextContent 返回。
- 最后使用 stdio_server() 启动服务器,通过标准输入/输出与 Claude Desktop 通信。这也是的推荐的本地连接方式,简单可靠。

2.3 配置 Claude Desktop

Claude Desktop 支持通过配置文件添加 MCP 服务器。

  1. 找到配置文件位置:
    - macOS~/Library/Application Support/Claude/claude_desktop_config.json
    - Windows%APPDATA%\Claude\claude_desktop_config.json
    - Linux~/.config/Claude/claude_desktop_config.json

  2. 编辑该文件,添加如下内容(如果文件不存在则新建):

{
  "mcpServers": {
    "system-assistant": {
      "command": "python",
      "args": [
        "/完整路径/system_assistant.py"
      ]
    }
  }
}

/完整路径/ 替换为你刚刚保存 system_assistant.py 的绝对路径。
如果你使用虚拟环境,请确保 python 指向虚拟环境中的 Python 解释器,或者使用 uv 等工具管理。

  1. 重启 Claude Desktop。此时,Claude 会自动连接该 MCP 服务器。

2.4 测试效果

重启后,在 Claude Desktop 的聊天框中检查“可用工具”图标(一个小锤子),你会看到 system-assistant 已经连接。然后可以这样测试:

  • 输入:“现在几点了?” → Claude 会调用 get_current_time,回复时间。
  • 输入:“帮我算 998 + 254” → 调用 add_numbers,返回计算结果。
  • 输入:“请回显 Hello MCP” → 调用 echo,返回 ECHO: Hello MCP

恭喜!你已经成功让 Claude 突破了内置限制,获得了自定义外部能力。

三、常见问题与注意事项

1. 工具描述的重要性

Claude 对工具的调用完全是基于你提供的名称、描述和参数 schema 进行的。如果描述模糊,Claude 可能不会触发调用,或调用时参数传递错误。例如,add_numbers 的描述若写成“计算”,Claude 可能不理解其用途。务必在描述中明确“两个数字相加”、“返回计算结果”等关键词。

2. 传输方式的选择

上述示例使用 stdio 传输,因为大多数本地服务器用这个最简单。MCP 还支持 SSE (Server-Sent Events) 通信,适合远程部署或需要多客户端的场景。但初学者从 stdio 入手即可。

3. 服务器进程管理

当你关闭 Claude Desktop 时,它启动的 MCP 服务器也会终止。但如果你手动杀掉了服务器进程,Claude 的对应能力会变为不可用,需要重启客户端才能恢复。调试期间,直接在终端运行 python system_assistant.py 可以单独测试服务器。

4. 安全考量

  • 切勿在工具中执行危险的系统命令或暴露敏感数据,除非你完全清楚风险。
  • MCP 协议本身不提供鉴权,若服务器通过 SSE 暴露在网络上,需要自行添加认证层。本地 stdio 模式则相对安全。
  • 工具返回的信息会原样展示给用户,注意不要意外泄露密钥等信息。

5. 复杂工具的扩展

当你的工具需要访问数据库、调用 API 时,只需在 handle_call_tool 中完成操作。例如,接入一个天气 API:定义 get_weather 工具,参数为城市名,内部用 httpx 发起请求,将结果格式化返回。MCP 让集成难度大幅降低,你只需专注业务逻辑。

四、总结

本文通过一个极简的 Python MCP 服务器,演示了如何为 Claude Desktop 注入自定义能力。MCP 的价值在于 标准化:你编写的工具不再绑定特定模型,一次开发,多处使用(未来任何支持 MCP 的客户端都能连接)。这种思路极大提升了 AI 应用的扩展性和生态活力。

学习 MCP 不意味着要抛弃现有框架,而是为你的项目增加一种更原生、更通用的集成方式。你可以基于这个示例继续添加更多工具,比如文件操作、知识库查询、甚至控制智能家居。AI 的边界,正由你来定义。

现在,打开你的编辑器,开始构建你的第一个 MCP 工具吧!

延伸阅读
- MCP 官方文档
- Anthropic 的 Claude 工具使用指南

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐