随着AI代理从原型到生产的竞赛日益激烈,迫切需要一种标准化的方式,使代理能够跨不同提供商调用工具。这种向标准化工具调用方法的过渡,类似于我们之前看到的REST API的普及。在REST API出现之前,开发者不得不处理各种专有协议,仅仅是为了从不同服务中提取数据。REST带来了秩序,使系统能够以一致的方式相互通信。MCP(模型上下文协议)的目标是,正如其名,为AI模型提供标准化的上下文。没有它,我们将陷入工具调用的混乱中,多个不兼容的“标准化”工具调用版本将层出不穷,仅仅因为代理没有共享的方式来组织、共享和调用工具。MCP为我们提供了一种共享语言,实现了工具调用的民主化。
我个人感到兴奋的是,像MCP这样的工具调用标准实际上可以使AI系统更安全。通过更容易地访问经过良好测试的工具,更多的公司可以避免重复造轮子,从而减少安全风险并最小化恶意代码的可能性。随着AI系统在2025年开始扩展,这些都是值得关注的问题。
当我深入研究MCP时,我意识到文档中存在一个巨大的空白。有很多高层次的“它做什么”的内容,但当你真正想了解它是如何工作时,资源就开始不足了——尤其是对于那些非原生开发者来说。要么是高层次的解释,要么是深入到源代码中。
在这篇文章中,我将为更广泛的受众拆解MCP——使概念和功能清晰易懂。如果你有能力,可以跟随代码部分,如果没有,代码片段上方的自然语言解释也会很好地说明。
理解MCP的类比:餐厅
让我们将MCP的概念想象成一个餐厅,其中:
= 餐厅建筑(代理运行的环境)
= 厨房(工具所在的地方)
= 服务员(发送工具请求的人)
= 顾客(决定使用什么工具的人)
= 食谱(执行的代码)
MCP的组成部分
主机
这是代理运行的地方。在我们的类比中,它是餐厅建筑;在MCP中,它是你的代理或LLM实际运行的地方。如果你在本地使用Ollama,你就是主机。如果你使用Claude或GPT,那么Anthropic或OpenAI就是主机。
客户端
这是从代理发送工具调用请求的环境。把它想象成服务员,他接受你的订单并将其送到厨房。实际上,它是你的代理运行的应用程序或界面。客户端使用MCP将工具调用请求传递给服务器。
服务器
这是厨房,食谱或工具所在的地方。它集中了工具,以便代理可以轻松访问它们。服务器可以是本地的(由用户启动)或远程的(由提供工具的公司托管)。服务器上的工具通常按功能或集成分组。例如,所有与Slack相关的工具都可以放在“Slack服务器”上,或者所有消息工具都可以分组在“消息服务器”上。这个决定基于架构和开发者的偏好。
代理
操作的“大脑”。由LLM驱动,它决定调用哪些工具来完成一项任务。当它确定需要一个工具时,它会向服务器发起请求。代理不需要原生理解MCP,因为它通过每个工具关联的元数据学习如何使用它。与每个工具关联的元数据告诉代理调用工具的协议和执行方法。但需要注意的是,平台或代理需要支持MCP,以便它自动处理工具调用。否则,开发者需要编写复杂的翻译逻辑,如何从模式中解析元数据,以MCP格式形成工具调用请求,将请求映射到正确的函数,执行代码,并以MCP兼容的格式将结果返回给代理。
工具
这些是执行工作的函数,例如调用API或自定义代码。工具位于服务器上,可以是:
你创建并托管在本地服务器上的自定义工具。
由他人在远程服务器上托管的预制工具。
由他人创建但由你托管在本地服务器上的预制代码。
组件如何协同工作
服务器注册工具
每个工具都定义了一个名称、描述、输入/输出模式、一个函数处理程序(运行的代码)并注册到服务器。这通常涉及调用一个方法或API来告诉服务器“嘿,这是一个新工具,这是你使用它的方式”。
服务器暴露元数据
当服务器启动或代理连接时,它通过MCP暴露工具元数据(模式、描述)。
代理发现工具
代理查询服务器(使用MCP)以查看可用的工具。它从工具元数据中了解如何使用每个工具。这通常发生在启动时或添加工具时。
代理计划工具使用
当代理确定需要一个工具时(基于用户输入或任务上下文),它形成一个标准化的MCP JSON格式的工具调用请求,其中包括工具名称、与工具输入模式匹配的输入参数以及任何其他元数据。客户端充当传输层,并通过HTTP将MCP格式的请求发送到服务器。
翻译层执行
翻译层获取代理的标准化工具调用(通过MCP),将请求映射到服务器上的相应函数,执行函数,将结果格式化为MCP,并将其发送回代理。一个为你抽象MCP的框架会在不需要开发者编写翻译层逻辑的情况下完成所有这些工作(这听起来很头疼)。
代码示例:使用MCP Brave搜索服务器的Re-Act代理
为了理解MCP在应用时的样子,让我们使用IBM的beeAI框架,它原生支持MCP并为我们处理翻译逻辑。
如果你计划运行此代码,你需要:
克隆beeai框架仓库以访问此代码中使用的帮助类。
创建一个免费的Brave开发者账户并获取你的API密钥。有免费订阅可用(需要信用卡)。
创建一个OpenAI开发者账户并创建一个API密钥。
将你的Brave API密钥和OpenAI密钥添加到仓库的python文件夹级别的.env文件中。
确保你已安装npm并正确设置了路径。
示例.env文件
BRAVE_API_KEY= ""
BEEAI_LOG_LEVEL=INFO
OPENAI_API_KEY= ""
示例mcp_agent.ipynb
导入必要的库
import asyncio
import logging
import os
import sys
import traceback
from typing import Any
from beeai_framework.agents.react.runners.default.prompts import SystemPromptTemplate
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from beeai_framework import Tool
from beeai_framework.agents.react.agent import ReActAgent
from beeai_framework.agents.types import AgentExecutionConfig
from beeai_framework.backend.chat import ChatModel, ChatModelParameters
from beeai_framework.emitter.emitter import Emitter, EventMeta
from beeai_framework.errors import FrameworkError
from beeai_framework.logger import Logger
from beeai_framework.memory.token_memory import TokenMemory
from beeai_framework.tools.mcp_tools import MCPTool
from pathlib import Path
from beeai_framework.adapters.openai.backend.chat import OpenAIChatModel
from beeai_framework.backend.message import SystemMessage
加载环境变量并设置系统路径(如果需要)
import os
from dotenv import load_dotenv
# 你的.env文件的绝对路径
# 有时系统可能无法定位.env文件
env_path =
# 加载它
load_dotenv(dotenv_path=env_path)
# 获取当前工作目录
path = #...beeai-framework/python'
# 追加到sys.path
sys.path.append(path)
配置日志记录器
# 配置日志记录 - 使用DEBUG而不是trace
logger = Logger("app", level=logging.DEBUG)
加载帮助函数如process_agent_events、observer,并创建ConsoleReader的实例
# 加载控制台读取器
from examples.helpers.io import ConsoleReader
# 这是一个帮助函数,使助手聊天更容易阅读
reader = ConsoleReader()
def process_agent_events(data: dict[str, Any], event: EventMeta) -> None:
"""处理代理事件并适当记录"""
if event.name == "error":
reader.write("Agent : ", FrameworkError.ensure(data["error"]).explain())
elif event.name == "retry":
reader.write("Agent : ", "retrying the action...")
elif event.name == "update":
reader.write(f"Agent({data['update']['key']}) : ", data["update"]["parsedValue"])
elif event.name == "start":
reader.write("Agent : ", "starting new iteration")
elif event.name == "success":
reader.write("Agent : ", "success")
else:
print(event.path)
def observer(emitter: Emitter) -> None:
emitter.on("*.*", process_agent_events)
设置Brave API密钥和服务器参数。
brave_api_key = os.environ["BRAVE_API_KEY"]
brave_server_params = StdioServerParameters(
command="/opt/homebrew/bin/npx", # 完整路径以确保安全
args=[
"-y",
"@modelcontextprotocol/server-brave-search"
],
env={
"BRAVE_API_KEY": brave_api_key,
"x-subscription-token": brave_api_key
},
)
创建brave工具,启动与MCP服务器的连接,发现工具,并将发现的工具返回给代理,以便它决定为给定任务调用哪个工具。
async def brave_tool() -> MCPTool:
brave_env = os.environ.copy()
brave_server_params = StdioServerParameters(
command="/opt/homebrew/bin/npx",
args=["-y", "@modelcontextprotocol/server-brave-search"],
env=brave_env
)
print("Starting MCP client...")
try:
async with stdio_client(brave_server_params) as (read, write), ClientSession(read, write) as session:
print("Client connected, initializing...")
await asyncio.wait_for(session.initialize(), timeout=10)
print("Initialized! Discovering tools...")
bravetools = await asyncio.wait_for(
MCPTool.from_client(session, brave_server_params),
timeout=10
)
print("Tools discovered!")
return bravetools
except asyncio.TimeoutError as e:
print("❌ Timeout occurred during session initialization or tool discovery.")
except Exception as e:
print("❌ Exception occurred:", e)
traceback.print_exc()
(可选)检查与MCP服务器的连接,并确保它在提供给代理之前返回所有可用的工具。
tool = await brave_tool()
print("Discovered tools:", tool)
for tool in tool:
print(f"Tool Name: {tool.name}")
print(f"Description: {getattr(tool, 'description', 'No description available')}")
print("-" * 30)
输出:
Starting MCP client...
Client connected, initializing...
Initialized! Discovering tools...
Tools discovered!
Discovered tools: [, ]
Tool Name: brave_web_search
Description: Performs a web search using the Brave Search API, ideal for general queries, news, articles, and online content. Use this for broad information gathering, recent events, or when you need diverse web sources. Supports pagination, content filtering, and freshness controls. Maximum 20 results per request, with offset for pagination.
------------------------------
Tool Name: brave_local_search
Description: Searches for local businesses and places using Brave's Local Search API. Best for queries related to physical locations, businesses, restaurants, services, etc. Returns detailed information including:
- Business names and addresses
- Ratings and review counts
- Phone numbers and opening hours
Use this when the query implies 'near me' or mentions specific locations. Automatically falls back to web search if no local results are found.
编写创建代理的函数:
async def create_agent() -> ReActAgent:
"""创建并配置带有工具和LLM的代理"""
# 使用openai api代替
llm = OpenAIChatModel(model_id="gpt-4o")
# 配置工具
tools: list[Tool] = await brave_tool()
#tools: list[Tool] = [await brave_tool()]
# 创建带有内存和工具的代理
agent = ReActAgent(llm=llm, tools=tools, memory=TokenMemory(llm), )
await agent.memory.add(SystemMessage(content="You are a helpful assistant. If you need to use the brave_tool you must use a count of 5."))
return agent
创建主函数
import asyncio
import traceback
import sys
# 你的异步主函数
async def main() -> None:
"""主应用程序循环"""
# 创建代理
agent = await create_agent()
# 与用户输入的主要交互循环
for prompt in reader:
# 退出条件
if prompt.strip().lower() in {"exit", "quit"}:
reader.write("Session ended by user. Goodbye! n")
break
# 使用提示运行代理
try:
response = await agent.run(
prompt=prompt,
execution=AgentExecutionConfig(max_retries_per_step=3, total_max_retries=10, max_iterations=20),
).observe(observer)
reader.write("Agent : ", response.result.text)
except Exception as e:
reader.write("An error occurred: ", str(e))
traceback.print_exc()
# 运行main()并处理错误
try:
await main()
except FrameworkError as e:
traceback.print_exc()
sys.exit(e.explain())
输出:
Starting MCP client...
Client connected, initializing...
Initialized! Discovering tools...
Tools discovered!
Interactive session has started. To escape, input 'q' and submit.
Agent : starting new iteration
Agent(thought) : I will use the brave_local_search function to find the open hours for La Taqueria on Mission St in San Francisco.
Agent(tool_name) : brave_local_search
Agent(tool_input) : {'query': 'La Taqueria Mission St San Francisco'}
Agent(tool_output) : [{"annotations": null, "text": "Error: Brave API error: 422 Unprocessable Entityn{"type":"ErrorResponse","error":{"id":"ddab2628-c96e-478f-80ee-9b5f8b1fda26","status":422,"code":"VALIDATION","detail":"Unable to validate request parameter(s)","meta":{"errors":[{"type":"greater_than_equal","loc":["query","count"],"msg":"Input should be greater than or equal to 1","input":"0","ctx":{"ge":1}}]}},"time":1742589546}", "type": "text"}]
Agent : starting new iteration
Agent(thought) : The function call resulted in an error. I will try again with a different approach to find the open hours for La Taqueria on Mission St in San Francisco.
Agent(tool_name) : brave_local_search
Agent(tool_input) : {'query': 'La Taqueria Mission St San Francisco', 'count': 5}
Agent(tool_output) : [{"annotations": null, "text": "Title: LA TAQUERIA - Updated May 2024 - 2795 Photos & 4678 Reviews - 2889 Mission St, San Francisco, California - Mexican - Restaurant Reviews - Phone Number - YelpnDescription: LA TAQUERIA, 2889 Mission St, San Francisco, CA 94110, 2795 Photos, Mon - Closed, Tue - Closed, Wed - 11:00 am - 8:45 pm, Thu - 11:00 am - 8:45 pm, Fri - 11:00 am - 8:45 pm, Sat - 11:00 am - 8:45 pm, Sun - 11:00 am - 7:45 pmnURL: https://www.yelp.com/biz/la-taqueria-san-francisco-2nnTitle: La Taqueria: Authentic Mexican Cuisine for Every TastenDescription: La Taqueria - Mexican Food Restaurant welcomes you to enjoy our delicious. La Taqueria provides a full-service experience in a fun casual atmosphere and fresh flavors where the customer always comes first!nURL: https://lataqueria.gotoeat.net/nnTitle: r/sanfrancisco on Reddit: Whats so good about La Taqueria in The Mission?nDescription: 182 votes, 208 comments. Don't get me wrong its good but I failed to see the hype. I waited in a long line and once I got my food it just tastes like…nURL: https://www.reddit.com/r/sanfrancisco/comments/1d0sf5k/whats_so_good_about_la_taqueria_in_the_mission/nnTitle: LA TAQUERIA, San Francisco - Mission District - Menu, Prices & Restaurant Reviews - TripadvisornDescription: La Taqueria still going strong. Historically the most well known Burrito home in the city and Mission District. Everything is run like a clock. The fillings are just spiced and prepared just right. Carnitas, chicken, asada, etc have true home made flavors. The Tortillas both are super good ...nURL: https://www.tripadvisor.com/Restaurant_Review-g60713-d360056-Reviews-La_Taqueria-San_Francisco_California.htmlnnTitle: La Taqueria – San Francisco - a MICHELIN Guide RestaurantnDescription: San Francisco Restaurants · La Taqueria · 4 · 2889 Mission St., San Francisco, 94110, USA · $ · Mexican, Regional Cuisine · Visited · Favorite · Find bookable restaurants near me · 2889 Mission St., San Francisco, 94110, USA · $ · Mexican, Regional Cuisine ·nURL: https://guide.michelin.com/us/en/california/san-francisco/restaurant/la-taqueria", "type": "text"}]
Agent : starting new iteration
Agent(thought) : I found the open hours for La Taqueria on Mission St in San Francisco. I will provide this information to the user.
Agent(final_answer) : La Taqueria, located at 2889 Mission St, San Francisco, CA 94110, has the following opening hours:
- Monday: Closed
- Tuesday: Closed
- Wednesday to Saturday: 11:00 AM - 8:45 PM
- Sunday: 11:00 AM - 7:45 PM
For more details, you can visit their [Yelp page](https://www.yelp.com/biz/la-taqueria-san-francisco-2).
Agent : success
Agent : success
run.agent.react.finish
Agent : La Taqueria, located at 2889 Mission St, San Francisco, CA 94110, has the following opening hours:
- Monday: Closed
- Tuesday: Closed
- Wednesday to Saturday: 11:00 AM - 8:45 PM
- Sunday: 11:00 AM - 7:45 PM
For more details, you can visit their [Yelp page](https://www.yelp.com/biz/la-taqueria-san-francisco-2).
结论、挑战和MCP的未来
在这篇文章中,你已经看到了MCP如何为代理提供一种标准化的方式来发现MCP服务器上的工具,然后与它们交互,而无需开发者指定工具调用的实现细节。MCP提供的抽象级别非常强大。这意味着开发者可以专注于创建有价值的工具,而代理可以通过标准协议无缝地发现和使用它们。
我们的餐厅示例帮助我们可视化了MCP概念,如主机、客户端、服务器、代理和工具如何协同工作——每个都有其重要的角色。代码示例中,我们使用了Beeai框架中的Re-Act代理,它原生处理MCP工具调用,调用Brave MCP服务器并访问两个工具,提供了MCP在实践中如何使用的真实世界理解。