MCP
MCP: 模型上下文协议
无mcp时常见痛点:
-
每个 AI 应用都要重复接一遍外部系统
例如同样是接 GitHub、Slack、数据库、文件系统,Cursor 要写一套,Claude Desktop 要写一套,自研 Agent 平台又要写一套。 -
每个框架和宿主各有自己的接法
即使大家都支持 Tool / Function Calling,真正落地时仍然要处理:服务如何发现、参数 schema 怎么描述、鉴权怎么传、进程怎么启动、结果怎么返回。 -
工具很难复用成“生态能力”
没有统一协议时,一个工具即使写得很好,也往往只能服务于某个特定应用,迁移和复用成本很高。
MCP 出现的背景,不是“以前没人会写工具”,而是:
大家都在写工具,但缺少一套跨应用、跨框架、跨宿主都能复用的统一连接标准
一句话:让“外部工具、资源、提示词模板”等能力,能够按统一协议被不同 AI 应用发现和使用。
定义: 是一套开放的标准协议,用于规范 AI 应用 / Agent / IDE / 聊天客户端 如何与 外部工具、资源和上下文提供方 交互
区别:
| 概念 | 解决什么问题 | 典型关注点 |
| Tool / Function Calling | 模型如何调用一个具体工具 | 工具 schema、参数、调用结果 |
| RAG | 模型如何拿到外部知识上下文 | 文档加载、切块、检索、上下文拼接 |
| MCP | 外部能力如何被标准化暴露与接入 | Host / Client / Server、协议、传输、发现 |
| Agent | 谁来规划、决策、调用这些能力 | 推理、编排、记忆、执行闭环 |
- Tool 解决“能不能调用”
- RAG 解决“能不能拿到知识”
- MCP 解决“怎么统一接入”
- Agent 解决“谁来决定何时调用”
MCP 不是在替代 Tool,而是在 Tool 之上再向上抽象了一层“协议层”。
MCP的核心能力
| 类型 | 作用 | 控制方式 |
| Tools | 可执行动作,例如查天气、查数据库、发请求 | 模型可触发 |
| Resources | 可读取内容,例如文件、配置、数据库 schema、API 响应 | 应用 / 宿主决定如何使用 |
| Prompts | 可复用的提示词模板 / 工作流模板 | 用户显式选择更常见 |
其他能力
| 类型 | 作用 |
| Sampling | 服务器通过客户端向宿主侧的 LLM 请求一次生成(把“算力在哪一侧”也纳入协议协作) |
| Elicitation | 服务器通过客户端向用户请求补充信息 |
| Logging | 服务器向客户端发送结构化日志 |
| Progress / Notifications | 长任务过程中的进度和通知 |
MCP架构知识
| 角色 | 含义 |
| MCP Host(MCP 主机) | 用户真正交互的应用,例如 IDE、桌面客户端、聊天应用、自研 AI 平台 |
| MCP Client(MCP 客户端) | Host 内部负责和某个 MCP Server 建立协议连接的组件 |
| MCP Server(MCP 服务器) | 对外暴露 Tools / Resources / Prompts 等能力的服务 |
- Host 是你在用的应用
- Client 是 Host 内部的协议连接组件
主线:
- Host / Client 发起连接
- Client 和 Server 做初始化
- 双方声明各自支持的 capabilities
- 客户端发现服务器提供的 tools / resources / prompts
- 按需调用或读取
- 结果返回给 Host,再由模型 / UI 使用
一次完整的MCP调用:
-
握手与能力发现(Handshake & Discovery)
Host 启动后,会根据配置连接 MCP Server,并完成初始化。此时客户端会知道:这台服务器提供了哪些 Tools、Resources、Prompts,以及它支持哪些 capabilities。 -
用户提问与上下文注入(Context Injection)
用户提出问题后,Host 会把“用户问题 + 已发现的工具说明 / 资源信息 / 提示词信息”一并提供给模型或应用逻辑。 -
模型或应用做决策(Reasoning / Decision)
模型决定是否需要调用某个 Tool,或者应用决定是否读取某个 Resource、选用某个 Prompt。 -
路由与执行(Routing & Execution)
Host / Client 按协议把请求发给 MCP Server。Server 在自己的进程或远端服务中执行真正的逻辑,例如查天气、读文件、查数据库。 -
结果回传与继续生成(Result Feedback)
Server 把结果返回给 Client,Client 再把结果交回 Host,由 Host 继续让模型生成最终回答,或者直接展示给用户。
两类常见传输
- stdio
- Streamable HTTP
更准确的理解应该是:
- STDIO:本地子进程通信
- Streamable HTTP:独立服务进程,通过 HTTP 通信,必要时可配合 SSE 流式返回
安全边界
- 写操作要有人确认:删除、外呼、支付、批量修改这类 Tool,不要默认让模型静默执行。
- HTTP 服务要做鉴权和来源校验:至少要考虑 token、会话身份、Origin / 来源校验,而不是“能连上就算接入成功”。
- 本地服务尽量收口暴露范围:能只监听本机就不要默认对公网开放,避免把调试用 MCP Server 直接变成外网入口。
- 日志与业务数据要分级处理:进度、错误、调试日志很有用,但不要把敏感配置、私有数据原样暴露给模型或不可信客户端。
了解FASTAPI用法:
# 创建服务实例
from mcp.serve.fastmcp import FastMCP
mcp = FastMCP("Demo")
# 注册Tool
@mcp.tool()
def add(a: int, b: int)-> int:
return a + b
# 注册Resource
@mcp.resource("greeting://default")
def get_greeting() -> str:
return "Hello from static resource!"
# 注册Prompt
@mcp.prompt()
def greet_user(name: str, style:str = "friendly") -> str:
return f"为{name}生成问候语"
# 启动服务
mcp.run(transport="stdio")
# 或者
mcp.run(transport="streamable-http")
# 历史写法
# mcp.run(transport="sse", host="127.0.0.1", port=8000)
从底层SDK视角看客户端
以官方 SDK 思路来看,一个 MCP 客户端的典型动作通常是:
- 建立传输连接
- 创建
ClientSession - 调用
initialize() list_tools()/list_resources()/list_prompts()call_tool()/read_resource()/get_prompt()
STDIO
import asyncio
from mcp import ClientSession,StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
server_params = StdioServerParameters(
command="python",
args= ["mcp_server_stdio.py"]
)
async with stdio_client(server_params) as (read,write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
result = await session.call_tool("add", {"a": 1, "b": 2})
print(tools)
print(result)
asyncio.run(main())
Streamable HTTP 差异主要在“连接方式”变成了远程 URL
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
async def main():
async with streamable_http_client("http://127.0.0.1:8000/mcp") as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print(tools)
asyncio.run(main())
完整调用过程理解
把协议层想象成一条完整链路,会更容易理解:
- 用户在 Host 中发起请求
- Host 内部某个 MCP Client 与对应 Server 建立会话
- Client 发现 Server 暴露的能力
- 模型或应用决定是否调用某个 Tool / 读取某个 Resource / 获取某个 Prompt
- Server 执行或返回结果
- Host 把结果展示给用户,或继续交给模型推理
流程再落到 LangChain Agent 里,就会变成:
MultiServerMCPClient连接 MCP 服务get_tools()获取工具- Agent 拿到工具列表
- 用户提问
- Agent 选择工具
- 工具返回结果
- 模型基于结果继续生成最终回答
获取天气案例
服务端:
import json
import os
# pip install mcp httpx python-dotenv
from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP
import httpx
load_dotenv()
# 构造函数只接受「服务名」;网络绑定信息在 run() 时再指定
mcp = FastMCP(
"WeatherServerSSE"
) # "WeatherServerSSE" 就是你自己起的名,可改成 "MyWeather" 等
@mcp.tool()
def get_weather(city: str) -> str:
"""查询指定城市的即时天气信息。city 为城市英文名,如 Beijing、Shanghai。"""
url = "https://api.openweathermap.org/data/2.5/weather"
params = {
"q": city,
"appid": os.getenv("OPENWEATHER_API_KEY"),
"units": "metric",
"lang": "zh_cn",
}
resp = httpx.get(url, params=params, timeout=10)
data = resp.json()
return json.dumps(data, ensure_ascii=False)
if __name__ == "__main__":
# host、port 在 run() 时传入,不是构造函数。
# 这里启动后,mcp.json 中的 weather 服务就可以按约定地址连到它。
mcp.run(transport="sse", host="127.0.0.1", port=8000)
客户端:
- """从同目录的 mcp.json 加载 MCP 服务配置,使用 langchain_mcp_adapters 的 MultiServerMCPClient 连接多台
MCP 服务器并获取工具列表,再交给 LangChain 的 create_tool_calling_agent + AgentExecutor,形成
「LLM + MCP 工具」的对话 Agent。这也是第 21 章里“外部工具接入 Agent”的代表案例。
- mcp.json 是“客户端侧的连接配置约定”,不是 MCP 协议本身。它描述的是“有哪些服务、分别怎么连”,
例如本仓库里既有网络方式的 weather 服务,也有 stdio 方式的 fetch 服务。
- 流程:加载 mcp.json → 初始化 MultiServerMCPClient → 异步获取 MCP Tools → 创建 DeepSeek 模型与
提示模板 → 组装 Agent 与 AgentExecutor → 启动命令行聊天循环(输入 quit 退出)。
- 本案例重点展示“把 MCP Tools 交给 LangChain Agent”;Resources 和 Prompts 虽然也是 MCP 能力,
但这里没有作为主线展开。
- 这个文件延续了仓库里更容易教学的 classic Agent 路线;如果改走更偏 1.x 的直接路线,也常见
`await client.get_tools()` 之后把工具交给 `create_agent`,再配合 `ainvoke()` / `astream()` 使用。
- 依赖:pip install langchain-mcp-adapters langchain-openai langchain-classic loguru;部分适配器要求 Python 3.12 及以下。需配置环境变量 deepseek-api(或改用其他兼容 OpenAI 的 api_key/base_url)。
"""
import asyncio
import json
import os
from pathlib import Path
from loguru import logger
# 默认 mcp.json 路径(与本文件同目录)
_MCP_JSON_PATH = Path(__file__).resolve().parent / "mcp.json"
def load_servers(file_path: str | Path | None = None) -> dict:
"""
加载 MCP 服务器配置。
:param file_path: 配置文件路径,默认使用同目录下的 mcp.json
:return: 完整配置字典,如 {"mcpServers": {"weather": {...}, "fetch": {...}}}
这里读取的是“客户端如何连接服务”的约定配置,而不是协议本体。
"""
path = Path(file_path) if file_path else _MCP_JSON_PATH
if not path.exists():
logger.warning(f"未找到 mcp 配置文件: {path}")
return {"mcpServers": {}}
with open(path, "r", encoding="utf-8") as f:
config = json.load(f)
logger.info(
f"已加载 mcp 配置: {path},共 {len(config.get('mcpServers', {}))} 个服务"
)
return config
async def run_chat_loop(config_path: str | Path | None = None) -> None:
"""
启动并运行一个基于 MCP 工具的聊天 Agent 循环。
该函数会:1)加载 MCP 服务器配置;2)初始化 MCP 客户端并获取工具;
3)创建基于 DeepSeek 的语言模型和 Agent;4)启动命令行聊天循环;5)退出时清理资源。
"""
try:
from langchain_mcp_adapters.client import MultiServerMCPClient
except ImportError as e:
logger.error(
"请先安装 langchain-mcp-adapters: pip install langchain-mcp-adapters(部分环境需 Python 3.12 及以下)"
)
raise e
from langchain_openai import ChatOpenAI
from langchain_classic.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
config = load_servers(config_path)
servers = config.get("mcpServers", {})
if not servers:
logger.warning("mcp.json 中未配置任何服务,无法获取 MCP 工具")
return
# 初始化 MCP 客户端:connections 就是 mcp.json 中的 mcpServers 字典
# 每个条目描述一台 MCP 服务该如何连接,例如 stdio 子进程或 HTTP/SSE 地址
client = MultiServerMCPClient(connections=servers)
# 按官方默认用法,MultiServerMCPClient 是无状态的;获取工具时使用异步接口即可
tools = await client.get_tools()
if not tools:
logger.warning(
"未从 MCP 服务获取到任何工具,请确认服务已启动且 mcp.json 配置正确"
)
return
logger.info(f"已获取 {len(tools)} 个 MCP 工具: {[t.name for t in tools]}")
# 语言模型(DeepSeek,与截图一致;可改为其他 OpenAI 兼容接口)
llm = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("deepseek-api"),
base_url="https://api.deepseek.com",
)
# 对话提示:系统提示要求使用工具完成用户请求,agent_scratchpad 供 Executor 填入中间步骤
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个有用的助手,需要使用提供的工具来完成用户请求。"),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)
agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors="解析用户请求失败,请重新输入清晰的指令",
)
logger.info("\n MCP Agent 已启动,请先输入一个提问给(LLM+MCP),输入 'quit' 退出")
while True:
try:
user_input = input("\n您: ").strip()
if not user_input:
continue
if user_input.lower() == "quit":
logger.info("已退出")
break
result = agent_executor.invoke({"input": user_input})
output = result.get("output", result)
print(f"\nAgent: {output}")
except KeyboardInterrupt:
logger.info("已退出")
break
def main() -> None:
asyncio.run(run_chat_loop())
if __name__ == "__main__":
main()
这个文件最值得学习的地方有两个:
第一,它说明 MCP 和 Agent 是怎么接起来的。
MCP 不负责帮你规划,也不负责帮你推理;它负责把工具接进来。真正决定“什么时候调用天气工具”的,是 Agent。
第二,它说明 LangChain 对 MCP 的支持已经不只停留在工具发现。
根据 LangChain 官方文档,除了 get_tools(),现在还可以:
get_resources()get_prompt()- 使用 stateful session
- 处理 logging / elicitation / structured content
mcp.json:
某些 MCP Host / Client / 适配器常用的客户端连接配置文件。
也就是说,它解决的是:
- 要连接哪几台服务器
- 每台服务器用什么 transport
- URL / command / args 是什么
这里的 mcp.json,可以这样理解:
weather- 走
sse - 指向本地天气服务
- 走
fetch- 走
stdio - 通过命令启动一个本地 MCP Server
- 走
{
"mcpServers": {
"weather": {
"url": "http://127.0.0.1:8000/sse",
"transport": "sse"
},
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"],
"transport": "stdio"
}
}
}

浙公网安备 33010602011771号