深入浅出:MCP 协议及应用

MCP 的诞生

AI 集成面临的问题 (M x N 问题)

大型语言模型(LLM)和AI Agent发展很快。它们在理解和生成文本方面,能力惊人。但是,AI 不应只停留在"聊天"。要让 AI 与现实世界互动、执行任务,必须连接外部工具、数据源和现有系统。

举个例子,一个 AI Agent 需要查询数据库获取用户信息,调用支付 API 完成交易,再更新库存系统。这需要与多个系统交互。

传统的集成方式,是为每个 AI 应用(M)和每个外部系统(N)单独开发连接器或适配层。这意味着,如果你的公司有 5 个 AI 应用,需要与 10 个不同的数据库、API 和服务交互,你可能要开发 5 x 10 = 50 个定制模块。这种模式,就是"M x N 问题"。

这种 M x N 的集成方式,带来很多挑战:

  • 重复开发:为不同应用或系统,编写大量相似代码。
  • 实现不一致:不同团队或开发者,集成方式不同,导致代码风格、错误处理、性能不一致。
  • 维护困难:应用和系统越多,维护定制模块越复杂耗时。
  • 扩展性差:引入新应用或系统,需要额外开发资源。

这些问题,阻碍了 AI 应用的快速开发和落地。我们需要更标准、高效的方式,连接 AI 与外部世界。

MCP 是什么?

在这种背景下,Model Context Protocol(MCP)出现了。MCP 是 Anthropic 提出并主导开发的开放标准和协议。它的核心目标是:提供通用框架,标准化 AI 模型(特别是 LLM 和 Agent)与外部工具、数据源、系统交互的方式。

可以将 MCP 比作 AI 集成领域的 USB-C 接口。USB-C 出现前,不同设备需要不同接口和线缆。有了 USB-C,一个标准接口就能连接多种设备。类似地,MCP 提供标准协议,让 AI 模型以统一方式与外部能力通信。

引入 MCP 后,M x N 问题变成了 M + N 问题。这意味着:

  • N 个 MCP Server:外部工具、数据源或系统,只需实现一个符合 MCP 标准的 Server。Server 负责按 MCP 规范,暴露自身能力(如 API 调用、数据库操作)。
  • M 个 MCP Client:AI 应用(Host),只需集成一个 MCP Client。Client 负责与符合 MCP 标准的 Server 通信,将 Server 能力提供给 AI 模型。

这样,新的 AI 应用或外部系统,只要遵循 MCP 标准,就能轻松集成。这大大降低了集成成本和复杂性。

MCP 核心概念与架构

模型上下文协议(MCP)的目标,是标准化大型语言模型(LLM)与外部工具和资源交互的方式。它的核心概念和架构设计,让它能实现更强大、更灵活的功能。

核心概念

MCP 的核心概念,是增强 LLM 的能力,让它超越训练数据和固有能力。这主要通过以下几个关键概念实现:

  • 工具集成(Tool Integration): MCP 允许 LLM 发现并使用外部工具。这些工具可以是各种服务,比如文件系统操作、网络浏览器交互、代码解释器、数据库查询,或者其他任何可执行的功能。通过集成工具,LLM 可以执行特定任务、获取实时信息,或与外部环境互动,扩展应用范围。
  • 上下文管理(Context Management): 与工具交互时,LLM 需要理解当前任务、可用工具,以及之前交互的结果。MCP 提供结构化方式,管理这些上下文信息,确保 LLM 有效利用工具,保持对话连贯。
  • 能力发现(Capability Discovery): LLM 需要知道哪些工具可用,以及如何使用它们。MCP 包含一种机制,允许 LLM 查询可用工具及其功能(比如,通过 JSON Schema 描述输入/输出参数),从而动态适应不同环境和任务。
  • 权限控制(Permission Control): 与外部系统交互存在安全风险。MCP 设计包含权限控制机制,确保 LLM 只能访问和使用被授权的工具和资源,保护用户数据和系统安全。
  • 迭代执行(Iterative Execution): 复杂的任务通常需要一系列步骤和工具调用。MCP 支持迭代执行流程,LLM 可以根据工具输出,调整后续行动,逐步完成任务。

架构

MCP 核心采用客户端-服务器架构,主机应用可以连接多个服务器:

  • MCP Hosts: 如 Claude Desktop、IDE 或 AI 工具,希望通过 MCP 访问数据的程序
  • MCP Clients: 维护与服务器一对一连接的协议客户端
  • MCP Servers: 轻量级程序,通过标准的 Model Context Protocol 提供特定能力
  • 本地数据源: MCP 服务器可安全访问的计算机文件、数据库和服务
  • 远程服务: MCP 服务器可连接的互联网上的外部系统(如通过 APIs)

MCP 服务器与客户端实战

MCP 服务器

MCP 服务器是 MCP 世界与外部系统特定功能(例如 API、数据库、本地文件等)之间的桥梁/API。它们本质上是根据 MCP 规范暴露这些外部功能的包装器。

只要服务器能够通过支持的传输协议进行通信,就可以使用各种语言(Python、TypeScript、Java、Rust 等)构建服务器。服务器主要通过两种方式与客户端通信:

  • stdio(标准输入/输出): 当客户端和服务器在同一台机器上运行时使用。这对于本地集成(例如访问本地文件或运行本地脚本)来说简单有效。
  • 通过 SSE(服务器发送事件)的 HTTP: 客户端通过 HTTP 连接到服务器。初始设置后,服务器可以使用 SSE 标准通过长连接向客户端推送消息(事件)。

MCP 服务示例

环境准备

bash 复制代码
mkdir mcp-demo
uv init .
uv add "mcp[cli]" # 添加依赖
uv venv
source .venv/bin/activate

MPC Server demo 代码: demo_server.py

python 复制代码
from mcp.server import FastMCP
# Create an MCP server
app = FastMCP("Demo")

# Add a tool, will be converted into JSON spec for function calling
@app.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

# Specific prompt templates for better use
@app.prompt()
def review_code(code: str) -> str:
    return f"Please review this code:\n\n{code}"

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

运行 MCP Server 以及测试服务

csharp 复制代码
# 直接运行
uv run demo_server.py

# 使用 mcp 调试工具运行
mcp dev demo_server.py
(mcp-demo) ➜  mcp-demo git:(main) ✗ mcp dev demo.py
Starting MCP inspector...
⚙️ Proxy server listening on port 6277
🔍 MCP Inspector is up and running at http://127.0.0.1:6274 🚀

MPC 客户端

MCP 客户端是主机应用程序(IDE、聊天机器人等)的一部分,用于管理与特定 MCP 服务器的通信。

  • 角色: 根据 MCP 规范处理连接管理、功能发现、请求转发和响应处理。
  • 主机/客户端示例:
    • UI 应用程序:Claude Desktop、Microsoft Copilot Studio、LibreChat、Claude Code
    • IDE:Cursor、Windsurf、Continue、Zed、Cline
    • 自定义代理(Python / TypeScript):
      • Firebase Genkit
      • OpenAI代理SDK
      • ....
python 复制代码
import asyncio

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

# 为 stdio 连接创建服务器参数
server_params = StdioServerParameters(
    command='uv',
    args=['run', 'demo_server.py'],
)

async def main():
    # 创建 stdio 客户端
    async with stdio_client(server_params) as (stdio, write):
        # 创建 ClientSession 对象
        async with ClientSession(stdio, write) as session:
            # 初始化 ClientSession
            await session.initialize()

            # 列出可用的工具
            response = await session.list_tools()
            print(response)

            # 调用工具
            response = await session.call_tool('add', {'a': '1', 'b': '2'})
            print(response)


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

LLM 与 MCP 的交互流程

交互流程整体流程

工具定义

在 MCP 里,工具的定义非常关键。它让 LLM 能够理解工具的功能、需要什么输入,以及会得到什么输出。通常,工具通过以下几个方面来定义和描述:

  1. 工具名称 (Tool Name): 每个工具都有一个独一无二的名字。LLM 就用这个名字来引用和调用工具。
  2. 工具描述 (Tool Description): 简单说明工具是做什么的。这帮助 LLM 理解工具的作用和适用场景。
  3. 输入参数 (Input Parameters): 用结构化格式(比如 JSON Schema)详细描述工具接受哪些输入参数。包括参数的名字、类型、是否必须、描述,以及可能的取值范围。这保证 LLM 能用正确的格式和内容给工具提供输入。
  4. 输出描述 (Output Description): 描述工具执行完后,可能返回什么结果。可以是对输出数据结构的描述,也可以说明不同执行结果(成功、失败、错误)的情况。虽然不像输入参数那样强制用严格的 Schema,但清晰的输出描述,能帮助 LLM 正确理解和使用工具返回的信息。
  5. 权限要求 (Permission Requirements): 定义使用这个工具需要什么权限。这与 MCP 的权限控制机制相关,确保只有被授权的 LLM 或用户,才能调用敏感工具。

通过这种标准化的定义方式,MCP 适配器可以把可用的工具列表和详细信息,暴露给 LLM。LLM 就能根据这些信息,动态地选择和使用工具,而不需要提前"知道"某个特定工具的细节。

交互流程

LLM 通过 MCP 与工具的交互,是一个多步骤的过程。通常包括以下几个阶段:

  1. 解析用户请求: LLM 收到用户的请求。它先分析请求,理解用户的意图和要完成的任务。
  2. 选择工具: 根据对用户请求的理解,以及 MCP 适配器提供的工具列表,LLM 判断是否需要外部工具。如果需要,就选择一个或多个最合适的工具。
  3. 生成参数: 确定要使用的工具后,LLM 会根据工具的输入参数定义,从用户请求或自身知识中,提取或生成必要的参数值。
  4. 发起工具调用: LLM 构建一个标准的工具调用请求,包含工具名称和生成的参数,发送给 MCP 适配器。
  5. 路由与执行: MCP 适配器收到请求。它会验证请求是否有效(比如参数是否符合要求),检查 LLM 是否有权限调用这个工具。然后,将请求转发给相应的工具服务器。工具服务器接收请求,执行实际的功能。
  6. 返回结果: 工具执行完毕,工具服务器将结果(包括输出数据、状态或错误信息)返回给 MCP 适配器。
  7. 处理结果与生成响应: MCP 适配器将工具返回的结果传回 LLM。LLM 处理工具的输出,将其整合到当前上下文。根据工具结果,LLM 可能决定再次调用工具(处理复杂任务),或者生成最终的用户响应。
  8. 用户响应: LLM 生成最终的、连贯的用户响应。响应中可能包含工具执行的结果,或基于结果得出的结论。

这个交互过程是迭代的。LLM 可以根据工具的输出,多次调用工具,直到任务完成。这种流程,让 LLM 能以一种可控、可理解的方式,与外部环境进行复杂的互动。

OpenAI 的接口案例

目前不同 LLM 的标准不同,我们以 OpenAI 的 API 为例,看看如何让 LLM 选择使用工具,以及传入参数。

我们之前已经开放了一个web_search 的工具,如何让 LLM使用工具?

python 复制代码
# ....
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from openai import OpenAI

# .... 

class MCPClient:
    def __init__(self):
        self.session: Optional[ClientSession] = None
        self.exit_stack = AsyncExitStack()
        self.client = OpenAI()
    
    async def connect_to_server(self):
        server_params = StdioServerParameters(
            command='uv',
            args=['run', 'web_search.py'],
            env=None
        )

        stdio_transport = await self.exit_stack.enter_async_context(
            stdio_client(server_params))
        stdio, write = stdio_transport
        # mcp client session
        self.session = await self.exit_stack.enter_async_context(
            ClientSession(stdio, write))

        await self.session.initialize()

    async def process_query(self, query: str) -> str:
        # 利用系统提示词约束 LLM 使用工具,否则会出现不调用工具,自己乱回答的情况
        system_prompt = (
            "You are a helpful assistant."
            "You have the function of online search. "
            "Please MUST call web_search tool to search the Internet content before answering."
            "Please do not lose the user's question information when searching,"
            "and try to maintain the completeness of the question content as much as possible."
            "When there is a date related question in the user's question," 
            "please use the search function directly to search and PROHIBIT inserting specific time."
        )
        
        messages = [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": query}
        ]

        # 获取所有 mcp 服务器 工具列表信息
        response = await self.session.list_tools()
        # 生成 function call 的描述信息(工具定义)
        available_tools = [{
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "input_schema": tool.inputSchema
            }
        } for tool in response.tools]

        # 请求 deepseek,function call 的描述信息通过 tools 参数传入
        response = self.client.chat.completions.create(
            model=os.getenv("OPENAI_MODEL"),
            messages=messages,
            tools=available_tools
        )

        # 处理返回的内容
        content = response.choices[0]
        if content.finish_reason == "tool_calls":
            # 如何是需要使用工具,就解析工具
            tool_call = content.message.tool_calls[0]
            tool_name = tool_call.function.name
            tool_args = json.loads(tool_call.function.arguments)

            # 执行工具
            result = await self.session.call_tool(tool_name, tool_args)
            print(f"\n\n[Calling tool {tool_name} with args {tool_args}]\n\n")

            # 将 deepseek 返回的调用哪个工具数据和工具执行完成后的数据都存入messages中
            messages.append(content.message.model_dump())
            messages.append({
                "role": "tool",
                "content": result.content[0].text,
                "tool_call_id": tool_call.id,
            })

            # 将上面的结果再返回给 deepseek 用于生产最终的结果
            response = self.client.chat.completions.create(
                model=os.getenv("OPENAI_MODEL"),
                messages=messages,
            )
            return response.choices[0].message.content

        return content.message.content

    async def chat_loop(self):
        while True:
            try:
                query = input("\nQuery: ").strip()
                if query.lower() == 'quit':
                    break

                response = await self.process_query(query)
                print("\n" + response)

            except Exception as e:
                import traceback
                traceback.print_exc()

    async def cleanup(self):
        """Clean up resources"""
        await self.exit_stack.aclose()

async def main():
    client = MCPClient()
    try:
        await client.connect_to_server()
        await client.chat_loop()
    finally:
        await client.cleanup()


if __name__ == "__main__":
    import sys
    asyncio.run(main())
相关推荐
进来有惊喜12 分钟前
循环神经网络RNN---LSTM
人工智能·rnn·深度学习
Chrome深度玩家12 分钟前
如何下载Google Chrome适用于AI语音交互的特制版
前端·人工智能·chrome
Xiaoxiaoxiao020913 分钟前
GAEA情感坐标背后的技术原理
人工智能·web3·区块链
崔高杰20 分钟前
On the Biology of a Large Language Model——Claude团队的模型理解文章【论文阅读笔记】其一CLT与LLM知识推理
论文阅读·人工智能·笔记·语言模型·自然语言处理
陈随易37 分钟前
长跑8年,Node.js框架Koa v3.0终发布
前端·后端·程序员
lovebugs39 分钟前
Redis的高性能奥秘:深入解析IO多路复用与单线程事件驱动模型
redis·后端·面试
bug菌43 分钟前
面十年开发候选人被反问:当类被标注为@Service后,会有什么好处?我...🫨
spring boot·后端·spring
ICT_SOLIDWORKS43 分钟前
智诚科技苏州SOLIDWORKS授权代理商的卓越之选
大数据·人工智能·科技·软件工程
田园Coder1 小时前
Spring之IoC控制反转
后端
新知图书1 小时前
OpenCV彩色图像分割
人工智能·opencv·计算机视觉