2024 年 11 月 25 日,Anthropic,就是 Claude 背后的那家公司,推出了一个名为 MCP 的开放协议,它的全称为 Model Context Protocol(模型上下文协议),用于标准化大模型与各类外部工具和数据源之间的交互。

这个协议自推出以来,在 AI 圈一直不温不火,很多人认为 MCP 只是套壳的 API,并没有什么特别之处。但是近期,随着 Manus 的爆火,MCP 的概念在社区中又逐渐流行开来,使用 MCP 打造一款本地的 Manus 成了大家乐此不疲的话题。

MCP 架构

为了搞清楚什么是 MCP,让我们先来了解下它的基本架构。下面这张图非常形象地展示了 MCP 的基本架构(图片来源):

mcp-overview.png

可以看到 MCP 采用了非常经典的 C/S 架构(客户端/服务器),主要包括三个部分:

  • 主机(Host): 一般是基于大模型的 AI 应用,比如 Claude Desktop、ChatGPT Desktop、Cursor 等桌面应用,需要访问外部数据或工具;
  • 客户端(Client):内置在应用中,与 MCP 服务器建立一对一的连接;
  • 服务器(Server):连接本地或远程的数据源,提供特定功能;

    • 本地数据源:文件或数据库;
    • 远程服务:外部 API 或互联网服务;

MCP 协议将所有的外部数据或工具以一种统一的方式接入 AI 应用,这就好比 USB-C 接口,将各种不同的电子设备统一成一种接口,从而让用户不再为准备各种各样不同的线缆插头而烦恼。简单说,MCP 就像一座桥梁,它本身不处理复杂逻辑,只负责协调 AI 应用与外部资源之间的信息流动。

MCP 和 API 的区别

在推出 MCP 之前,AI 应用如果要对接外部工具,通常需要单独整合多个不同的 API,每个 API 的接口可能都各不相同,认证方式和错误处理也可能不同,极大地增加了开发复杂度和维护成本。

所以说,传统 API 就像不同的门,每扇门都有一把不同的钥匙,而 MCP 像一把万能钥匙,AI 应用开发者只要集成了这个万能钥匙,就可以打开任意的门。下面是 MCP 和传统 API 的对比:

mcp-vs-api.png

MCP 初体验

接下来,让我们在 Claude Desktop 中体验下 MCP 是如何工作的。

首先,下载并安装 Claude for Desktop,注册并登录账号后,确保 Claude 能正常对话:

claude-hi.png

然后,打开 Settings -> Developer 配置页面:

claude-settings.png

点击 Edit Config 按钮,进入 Claude 配置文件所在目录,打开 claude_desktop_config.json 配置文件,输入如下内容:

{
    "mcpServers": {
        "filesystem": {
            "command": "npx",
            "args": [
                "-y",
                "@modelcontextprotocol/server-filesystem",
                "/Users/aneasystone/Downloads/demo"
            ]
        }
    }
}

这是官方开发的 Filesystem MCP Server,用于操作你的本地文件,比如读取、编辑、搜索等。最后一个参数是文件路径,表示只允许 Claude 访问这个目录,可以添加一个或多个。

注意这里通过 npx 命令启动 MCP Server,所以需要提前安装 Node.js,使用 node --version 确认你的电脑上是否具备 Node.js 环境。

配置好 Filesystem MCP Server 之后,重启 Claude Desktop 应用,Claude Desktop 在启动时会自动加载所有的 MCP Server(其实就是为每个 Server 启动一个独立的进程,运行配置文件中的命令)。加载成功后,在对话框右下角会看到一个小锤子的图标:

claude-mcp-hammer.png

点击小图标,可以看到 Filesystem MCP Server 自带的所有工具列表:

claude-mcp-tools.png

这时我们就可以在对话时让 Claude 调用这些工具了:

claude-file-list.png

Claude 在调用工具之前会提醒用户,只有当用户确认允许后才会真正执行相应操作。

运行 npx 默认是从 Node.js 官方仓库下载包,有时会非常慢,导致 Claude 加载 MCP Server 失败,可以通过环境变量将仓库地址改为国内的源:

{
    "mcpServers": {
        "filesystem": {
            "command": "npx",
            "args": ...
            "env": {
                "NPM_CONFIG_REGISTRY": "https://mirrors.huaweicloud.com/repository/npm/"
            }
        }
    }
}

MCP 开发者指南

上一节我们从用户视角体验了一把 MCP,直观的感受了 MCP 是如何将外部资源集成到 AI 应用的。接下来,我们将从开发者视角,介绍如何开发自己的 MCP Server 以及如何将 MCP 集成到自己的 AI 应用中。

开发 MCP Server

这一节我们将通过 MCP 官方提供的 SDK 实现一个简单的天气查询 MCP Server。

官方目前提供了 Python、TypeScript、Java 和 Kotlin 四种 SDK 供开发者选择,这里我们使用 Pyhon SDK。

MCP Server 可以提供三种主要类型的能力:

这里我们暂时只关注工具。

首先,安装 mcp[cli] 依赖:

$ pip install --index-url https://pypi.tuna.tsinghua.edu.cn/simple/ mcp[cli]

然后,创建 mcp-server-weather.py 文件:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")

@mcp.tool()
async def get_weather(city: str, date: str) -> str:
    """查询某个城市某个日期的天气.

    Args:
        city: 城市名称
        date: 日期
    """
    return '天气晴,气温25摄氏度'

if __name__ == "__main__":
    mcp.run(transport='stdio')

上面的代码可以说非常简单,先通过 FastMCP 初始化 MCP Server,然后通过 @mcp.tool() 注解定义工具,最后通过 mcp.run() 启动 MCP Server。

其中 transport 参数表示传输协议的类型,决定了客户端如何和 MCP Server 通信,MCP 默认支持两种传输协议:

  • 标准输入和输出 (stdio):通过标准输入和输出流进行通信,这对本地集成和命令行工具特别有用;
  • 服务器发送事件 (SSE):通过 HTTP POST 请求支持客户端与服务器之间的流式通信;

这里 transport='stdio' 表示使用标准输入和输出流进行通信,至此,一个简单的 MCP Server 就开发好了。

接下来,打开 Claude 的配置文件 claude_desktop_config.json,添加 MCP Server 配置如下:

{
    "mcpServers": {
        "weather": {
            "command": "python3",
            "args": [
                "/path/to/mcp-server-weather.py"
            ]
        }
    }
}

重启 Claude for Desktop,在对话框右下角可以看到我们的工具已成功加载:

claude-mcp-my-tools.png

测试下效果:

claude-using-my-tools.png

探索 MCP Server

除了上面介绍的 Filesystem MCP Server 和自己开发之外,网上还有大量的 MCP Server 开箱即用,可以用于文件处理、数据分析、软件开发、浏览器自动化、沟通提效等等,这些官网提供的 例子 可以作为很好的入门。

此外,社区也有不少人将各种 MCP Server 收集在一起,比如 Model Context Protocol Servers、Awesome MCP Servers、MCP.so 和 Smithery,感兴趣的同学可以逛逛看。

开发 MCP Client

上面我们体验了在 Claude Desktop 中通过 MCP Server 让大模型调用外部工具,实现更强大的功能,这很容易让人想起智能体的概念。智能体是目前最热门最前沿的研究方向之一,我曾在之前的博客中多次学习过相关的技术,比如在 大模型应用开发框架 LangChain 学习笔记(二) 中学习 OpenAI 的 Function Call 功能以及 LangChain 内置的几种 Agent 的使用,在 基于 LangGraph 创建智能体应用 中学习如何使用 LangGraph 开发智能体应用。

之前开发智能体应用时,我们要对不同的工具分别对接,现在有了 MCP,我们只需要对接 MCP 协议,将所有工具的对接统一化。这一节,我们将学习如何构建一个连接到 MCP 服务器的 AI 应用。

首先,安装 mcp[cli] 和 openai 依赖:

$ pip install --index-url https://pypi.tuna.tsinghua.edu.cn/simple/ mcp[cli] openai

然后,创建 app.py 文件,程序大致的结构如下:

class MCPClient:
    ''' 客户端逻辑
    '''
    
async def main():
    client = MCPClient()
    try:
        await client.connect_to_server()
        await client.chat("查下合肥和杭州明天的天气")
    finally:
        await client.cleanup()

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

其中 MCPClient 类实现了客户端的主要逻辑,包括两个核心方法,第一个是 connect_to_server() 方法:

    async def connect_to_server(self):

        # 通过 stdio 连接 MCP Server
        server_params = StdioServerParameters(
            command = "python",
            args = ["mcp-server-weather.py"],
            env = None
        )
        self.stdio, self.write = await self.exit_stack.enter_async_context(stdio_client(server_params))
        self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
        await self.session.initialize()

        # 列出所有工具
        response = await self.session.list_tools()
        print("\nConnected to server with tools:", [tool.name for tool in response.tools])

上面的代码通过 MCP SDK 提供的 stdio_client() 方法让我们以 stdio 方式连接到上一节开发的天气查询 MCP Server 上,并创建一个 ClientSession 会话对象;有了会话之后,我们就可以通过 self.session.initialize() 对会话进行初始化,通过 self.session.list_tools() 查询 MCP Server 的工具列表。

注意,在 MCP SDK 中大量使用了 async 关键字定义异步函数,异步编程允许你编写非阻塞的代码,从而提高程序的效率,尤其是在处理 I/O 操作时(如网络请求、文件读取等)。在调用这些异步函数时需要使用 await 关键字,同时我们的函数本身也得定义成异步函数。或者用 asyncio.run() 运行异步函数,它会启动一个事件循环,直到异步函数运行完成。

第二个是 chat() 方法:

    async def chat(self, query: str) -> str:
        
        # 组装 function call 参数
        response = await self.session.list_tools()
        available_tools = [{
            "type": "function",
            "function": {    
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.inputSchema
            }
        } for tool in response.tools]

        messages = [{
            "role": "user",
            "content": query,
        }]
        while True:
            print(f"Sending messages {messages}")
            completion = self.openai.chat.completions.create(
                model="gpt-4o-mini",
                messages=messages,
                tools=available_tools
            )
            tool_calls = completion.choices[0].message.tool_calls
            if tool_calls == None:
                print(completion.choices[0].message.content)
                break
            
            messages.append({
                "role": "assistant",
                "content": "",
                "tool_calls": tool_calls
            })
            
            # 调用工具
            for tool_call in tool_calls:
                tool_name = tool_call.function.name
                tool_args = tool_call.function.arguments
                result = await self.session.call_tool(tool_name, json.loads(tool_args))
                print(f"[Calling tool {tool_name} with args {tool_args}]")
                print(f"[Calling tool result {result.content}]")
            
                messages.append({
                    "role": "tool",
                    "content": result.content,
                    "tool_call_id": tool_call.id
                })

这个方法比较简单,调用 OpenAI 接口进行对话,通过 Function calling 功能实现 MCP Server 的调用。

运行结果如下:

Connected to server with tools: ['get_weather']

[Calling tool get_weather with args {"city": "合肥", "date": "明天"}]
[Calling tool result [TextContent(type='text', text='天气晴,气温25摄氏度', annotations=None)]]
[Calling tool get_weather with args {"city": "杭州", "date": "明天"}]
[Calling tool result [TextContent(type='text', text='天气晴,气温25摄氏度', annotations=None)]]

明天合肥和杭州的天气都是晴天,气温大约在25摄氏度。

目前已经有很多客户端集成了 MCP,比如 Cursor、Cline、LibreChat、Roo Code、Windsurf Editor 等,官方这里有一个 已集成 MCP 的应用列表,其中不乏有一些著名的开源项目,当我们实现 MCP Client 遇到问题时,不妨参考下他们的实现。

深入 MCP 原理

MCP Inspector 是一个用于测试和调试 MCP Server 的交互式开发工具,使用 MCP Inspector 可以让我们对 MCP 的工作原理有一个更深入的了解。

使用下面的命令启动 MCP Inspector:

$ npx -y @modelcontextprotocol/inspector python3 /path/to/mcp-server-weather.py

注意参数的后面是我们的 MCP Server 的启动命令。启动成功后,在浏览器输入 http://localhost:5173 就可以访问了:

mcp-inspector.png

在这里可以查看 MCP Server 的工具列表,也可以对工具进行调用。除了工具,也支持资源、提示以及其他功能的调试。

在页面左下方,可以查看调试的历史记录:

mcp-inspector-history.png

这里可以更深入的了解 MCP 的底层细节。MCP Client 和 MCP Server 之间的所有消息都遵循 JSON-RPC 2.0 格式,比如所有的请求格式如下:

{
  jsonrpc: "2.0";
  id: string | number;
  method: string;
  params?: {
    [key: string]: unknown;
  };
}

所有的响应格式如下:

{
  jsonrpc: "2.0";
  id: string | number;
  result?: {
    [key: string]: unknown;
  }
  error?: {
    code: number;
    message: string;
    data?: unknown;
  }
}

所以我们也可以在启动 MCP Server 之后,直接在 stdio 上输入这个格式的请求来调试。比如查询工具列表:

{"method":"tools/list","params":{},"jsonrpc":"2.0","id":1}

不过如果我们直接输入上面的请求,MCP Server 可能会报如下这个错误:

      | Traceback (most recent call last):
      |   File "/usr/local/lib/python3.13/site-packages/mcp/shared/session.py", line 326, in _receive_loop
      |     await self._received_request(responder)
      |   File "/usr/local/lib/python3.13/site-packages/mcp/server/session.py", line 148, in _received_request
      |     raise RuntimeError(
      |         "Received request before initialization was complete"
      |     )
      | RuntimeError: Received request before initialization was complete
      +------------------------------------

根据 MCP 的规范,为了确保适当的功能协商和状态管理,MCP Client 和 MCP Server 之间的通信遵循严格的 生命周期,整个生命周期可以分成三个阶段:初始化阶段、操作阶段和关闭阶段,如下图所示:

mcp-lifecycle.png

可以看出 MCP Client 向 MCP Server 发送请求,属于操作阶段,在进入操作阶段之前,我们必须先执行初始化。整个初始化的过程有点类似 TCP 的三次握手,首先,发送初始化消息:

{"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-ai","version":"0.1.0"}},"jsonrpc":"2.0","id":0}

如果初始化成功,我们可以得到类似这样的消息:

{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"weather","version":"1.4.1"}}}

接着再发送初始化完成消息:

{"method":"notifications/initialized","jsonrpc":"2.0"}

三次握手之后,就算初始化完成了。接下来我们就可以发送请求了,比如调用工具:

{"method":"tools/call","params":{"name":"get_weather","arguments":{"city":"合肥", "date":"明天"}},"jsonrpc":"2.0","id":2}

参考

更多

Spring AI + MCP

打造本地 Manus

通过内置深度思考,文件处理,联网搜索,浏览器,代码解释器等技能实现类 Manus 智能体。