掘金 人工智能 04月28日 13:42
深入浅出:MCP 协议及应用
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

文章介绍了Model Context Protocol (MCP),这是一个由Anthropic开发的开放标准,旨在标准化AI模型与外部工具、数据源和系统交互的方式。MCP通过提供通用框架,解决了传统AI集成中“M x N 问题”带来的挑战,降低了集成成本和复杂性,促进了AI应用的快速开发和落地。文章还探讨了MCP的核心概念、架构以及与LLM的交互流程,并以OpenAI的API为例进行了说明。

💡 传统AI集成面临“M x N 问题”,即每个AI应用需要单独连接每个外部系统,导致重复开发、实现不一致、维护困难和扩展性差的问题,阻碍了AI应用的快速开发和落地。

💡 MCP 解决了“M x N 问题”,通过提供类似USB-C接口的通用框架,标准化AI模型与外部工具的交互方式,降低了集成成本和复杂性,使得新的AI应用或外部系统能够轻松集成。

💡 MCP 核心概念包括工具集成、上下文管理、能力发现、权限控制和迭代执行。MCP采用客户端-服务器架构,其中MCP服务器是外部系统与MCP世界之间的桥梁,MCP客户端则负责管理与服务器的通信。

💡 LLM 通过 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 问题。这意味着:

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

MCP 核心概念与架构

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

核心概念

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

架构

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

MCP 服务器与客户端实战

MCP 服务器

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

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

MCP 服务示例

环境准备

mkdir mcp-demouv init .uv add "mcp[cli]" # 添加依赖uv venvsource .venv/bin/activate

MPC Server demo 代码: demo_server.py

from mcp.server import FastMCP# Create an MCP serverapp = 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 以及测试服务

# 直接运行uv run demo_server.py# 使用 mcp 调试工具运行mcp dev demo_server.py(mcp-demo) ➜  mcp-demo git:(main) ✗ mcp dev demo.pyStarting 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 服务器的通信。

import asynciofrom mcp import ClientSession, StdioServerParametersfrom 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 能够理解工具的功能、需要什么输入,以及会得到什么输出。通常,工具通过以下几个方面来定义和描述:

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

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

交互流程

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

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

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

OpenAI 的接口案例

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

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

# ....from mcp import ClientSession, StdioServerParametersfrom mcp.client.stdio import stdio_clientfrom 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())

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

MCP AI集成 LLM 工具调用
相关文章