MCP 服务开发入门

MCP 服务开发入门
AI Agent 工程实践教程 · 第 16 章
理解 MCP 如何把工具、资源和提示词统一接给 Agent,并从 Server、Tool、Resource、Prompt 和 Client 调用链路入门。
第十六天:MCP 服务开发入门
前面我们已经学习了工具调用和 Agent。
工具调用解决的是:
让模型知道:什么时候该调用工具、调用哪个工具、参数是什么。
Agent 解决的是:
自动完成“模型判断 -> 调用工具 -> 把结果交回模型 -> 生成最终回答”的流程。
但是这里还有一个问题:
如果我们有很多工具,比如:
- 查询课程。
- 查询订单。
- 查询学生。
- 读取知识库文档。
- 读取项目配置。
- 生成某类固定提示词。
这些工具应该怎么统一接给不同的 Agent 客户端?
比如今天接 Claude Desktop,明天接 Cursor,后天接 Codex,难道每个客户端都写一套工具适配代码吗?
这就是 MCP 要解决的问题。
一句话:
MCP 是一套把工具、资源、提示词统一接给 Agent 的协议。
这里的“统一接给 Agent”要重点理解一下。
它不是说把工具、资料和提示词直接塞进大模型参数里。
它的意思是:
我们把工具、资源、提示词放到 MCP Server 里。
Agent 客户端通过 MCP 协议连接这个 Server。
连接成功后,Agent 客户端就能发现和使用这些能力。
所以 MCP 更像一个标准插座。
工具、文档、提示词不用分别为 Claude、Cursor、Codex 各写一套接入逻辑,只要按 MCP 协议暴露出来,支持 MCP 的客户端就可以连接。
1. 先用一句话理解 MCP
MCP 全称是 Model Context Protocol。
可以先不用记全称,先记住这个理解:
MCP = 给 Agent 插工具、插资料、插提示词的一套标准接口。
它不是大模型。
它也不是 Agent。
它更像一个中间层:
Agent 客户端
|
| MCP 协议
v
MCP Server
|
v
你的工具 / 文档 / 业务系统
以前我们在 LangChain 里写工具,通常是直接把 Python 函数绑定给模型。
MCP 的思路是:
把工具放到一个独立的 MCP Server 里。
Agent 客户端通过 MCP 协议发现和调用这些工具。
这样工具就不只属于某一个 Python 程序,而是可以被支持 MCP 的客户端复用。
2. MCP Server 里能放什么
MCP Server 主要暴露三类能力。
可以先看这张图:
注意:
Prompt 不是比 Tool、Resource 更高的一层。
在 MCP 里,Tools、Resources、Prompts 是 MCP Server 并列暴露给 Agent 客户端的三类能力。
它们只是用途不同:
Tool 负责执行动作。
Resource 负责读取资料。
Prompt 负责提供可复用提示词模板。
2.1 Tools:工具
Tool 是让 Agent 做事的能力。
比如:
query_course 查询课程
get_current_time 获取当前时间
query_order 查询订单
search_knowledge 查询知识库
Tool 通常有输入参数,也有返回结果。
例如:
工具名:query_course
参数:keyword
结果:课程列表
2.2 Resources:资源
Resource 是让 Agent 读取资料的能力。
比如:
yanque://course/mcp
yanque://config/chat
yanque://doc/project-readme
Resource 更像“可读取的数据地址”。
Agent 可以根据资源 URI 读取内容。
2.3 Prompts:提示词模板
Prompt 是预设好的提示词模板。
比如:
course_study_plan
code_review_prompt
sql_analysis_prompt
它的作用是:
把常用提示词做成可复用模板,让 Agent 客户端可以按参数生成提示词。
3. 本节要做什么
这一节我们不直接写复杂业务。
我们先做一个最小 MCP 服务。
它包含:
1. 一个空 MCP Server。
2. 一个只包含 Tool 的 MCP Server。
3. 一个只包含 Resource 的 MCP Server。
4. 一个只包含 Prompt 的 MCP Server。
5. 一个完整 MCP Server。
6. 一个本地 Client,用来测试完整 MCP Server。
7. 一个接真实业务 API 的写法示例。
本节 demo 放在 AI 项目的 test/mcp_demo 下:
YanQue-AI
└── test
└── mcp_demo
├── 00_empty_server.py
├── 01_tool_server.py
├── 02_resource_server.py
├── 03_prompt_server.py
├── 04_full_server.py
├── 05_stdio_client.py
├── 06_business_api_style.py
├── 07_streamable_http_server.py
├── 08_streamable_http_client.py
├── 09_load_mcp_tools.py
├── 10_agent_with_mcp_tools.py
└── README.md
为什么要拆这么多文件?
因为 MCP 初学时最容易卡在:
一上来既有 Tool,又有 Resource,又有 Prompt,又有 Client。
看起来代码不多,但概念会混在一起。
所以本节按下面的顺序逐步展开:
00_empty_server.py 先知道 MCP Server 怎么启动
01_tool_server.py 再知道 Tool 怎么注册
02_resource_server.py 再知道 Resource 怎么读取
03_prompt_server.py 再知道 Prompt 怎么生成
04_full_server.py 最后把三类能力合在一起
05_stdio_client.py 用 Client 测试完整调用链路
06_business_api_style.py 看真实项目里怎么接 Java 后端 API
07_streamable_http_server.py 看 MCP Server 如何用 HTTP 方式启动
08_streamable_http_client.py 看 MCP Client 如何通过 URL 连接 HTTP MCP Server
09_load_mcp_tools.py 把 MCP Tools 加载成 LangChain Tools,并直接调用
10_agent_with_mcp_tools.py Agent 接入 MCP Server 工具的完整示例
先不要急着接真实数据库。
我们先用内存数据模拟课程。
等流程跑通以后,再把内存数据替换成:
- HTTP API 调用。
- 数据库查询。
- Java 后端接口。
- 已有 Python service。
4. 安装 MCP SDK
进入 YanQue-AI 目录:
cd /Users/huangjin/IdeaProjects/YanQue/YanQue-AI
安装 MCP Python SDK:
uv add mcp
安装完成后,pyproject.toml 里会出现类似依赖:
dependencies = [
"mcp>=1.28.1",
]
5. 第一步:创建一个空 MCP Server
先创建文件:
test/mcp_demo/00_empty_server.py
写入最小代码:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("yanque-course-demo")
if __name__ == "__main__":
mcp.run(transport="stdio")
这里先解释三个东西。
5.1 FastMCP 是什么
FastMCP 是 Python MCP SDK 提供的快速开发工具。
可以把它理解成:
FastAPI 帮我们快速写 HTTP API。
FastMCP 帮我们快速写 MCP Server。
如果不用 FastMCP,我们要自己处理很多底层事情:
- 解析 MCP 请求。
- 判断客户端要调用哪个工具。
- 校验参数。
- 返回结果。
- 注册资源。
- 注册提示词。
- 处理 stdio 通信。
用了 FastMCP 以后,我们只需要关心:
我要暴露哪些工具、资源和提示词。
5.2 transport 是什么
transport 可以理解成:
MCP Client 和 MCP Server 之间用什么方式通信。
也就是:
请求怎么发过去,结果怎么传回来。
在当前 Python MCP SDK 里,mcp.run(...) 常见写法有:
mcp.run(transport="stdio")
mcp.run(transport="streamable-http")
mcp.run(transport="sse")
它们的区别不是“工具能力不同”。
工具、资源、提示词还是那些。
区别只是:
客户端怎么连接服务端。
可以先记住这张对比表:
stdio:
客户端启动 MCP Server 进程。
客户端通过标准输入输出和服务端通信。
适合本地开发、本地客户端、Claude Desktop、Cursor 这类 command + args 配置。
streamable-http:
MCP Server 自己作为 HTTP 服务启动。
客户端通过 URL 连接它。
适合远程部署、团队共享、服务化接入。
sse:
也是 HTTP 相关方式,使用 Server-Sent Events 建立消息通道。
一些早期 MCP 客户端或旧接入方式会看到它。
5.3 stdio 是什么
这里的:
mcp.run(transport="stdio")
表示 MCP Server 使用标准输入输出通信。
它不是 Web 服务。
所以它不会显示:
http://localhost:8000
stdio 模式的意思是:
MCP 客户端启动这个 Python 进程。
客户端通过标准输入输出和它通信。
这也是本地 MCP Server 最常见的入门方式。
我们前面的 00_empty_server.py 到 06_business_api_style.py 都使用 stdio。
本地运行时,stdio 最简单:
不用开端口。
不用处理 HTTP 地址。
客户端直接启动服务端进程。
可以先用下面的方式理解:
普通命令行程序:
人在终端输入内容
-> 程序从标准输入 stdin 读到内容
程序 print 输出内容
-> 内容出现在终端屏幕上
MCP stdio 模式:
MCP 客户端把请求写给 MCP Server
-> MCP Server 从标准输入 stdin 读到请求
MCP Server 把结果写出来
-> MCP 客户端从标准输出 stdout 读到结果
先记住一句话:
stdio 就是不用 URL,而是用“输入通道”和“输出通道”让客户端和服务端说话。
在我们的 demo 里:
async with stdio_client(server_params) as (read_stream, write_stream):
这行代码主要完成两件事:
stdio_client 做了两件事:
1. 启动 04_full_server.py 这个 MCP Server。
2. 建立两条通道:
write_stream 用来发请求。
read_stream 用来读结果。
后面的:
async with ClientSession(read_stream, write_stream) as session:
可以理解成:
把这两条通道包装成 MCP 会话。
之后就可以用 list_tools、call_tool、read_resource、get_prompt 这些方法了。
5.4 streamable-http 是什么
stdio 的特点是:
客户端启动服务端进程。
而 streamable-http 的特点是:
服务端先自己启动,监听一个 HTTP 地址。
客户端再通过 URL 连接这个 MCP Server。
也就是说,streamable-http 更像我们平时写后端服务:
先启动服务:
uv run python test/mcp_demo/07_streamable_http_server.py
再访问地址:
http://127.0.0.1:8000/mcp
在代码里,核心区别是:
mcp = FastMCP(
"yanque-http-demo",
host="127.0.0.1",
port=8000,
streamable_http_path="/mcp",
)
mcp.run(transport="streamable-http")
这个示例放在:
test/mcp_demo/07_streamable_http_server.py
它和 04_full_server.py 暴露的能力类似,也包含:
1. query_course 工具。
2. get_current_time 工具。
3. yanque://course/{course_id} 资源。
4. course_study_plan 提示词。
简化代码如下:
from datetime import datetime
from typing import Any
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(
"yanque-http-demo",
host="127.0.0.1",
port=8000,
streamable_http_path="/mcp",
)
COURSES: dict[str, dict[str, Any]] = {
"langchain": {
"id": "langchain",
"name": "LangChain 框架实战",
"teacher": "燕雀 AI 教研组",
"date": "2026-07-06",
"summary": "学习模型调用、提示词模板、工具调用和 Agent 基础流程。",
},
"mcp": {
"id": "mcp",
"name": "MCP 服务开发入门",
"teacher": "燕雀 AI 教研组",
"date": "2026-07-23",
"summary": "学习 MCP Server 如何把工具、资源和提示词暴露给 Agent。",
},
}
@mcp.tool()
def query_course(keyword: str) -> list[dict[str, Any]]:
"""按课程 ID、课程名或简介关键词查询课程。"""
normalized_keyword = keyword.strip().lower()
if not normalized_keyword:
return []
return [
course
for course in COURSES.values()
if normalized_keyword in course["id"].lower()
or normalized_keyword in course["name"].lower()
or normalized_keyword in course["summary"].lower()
]
@mcp.tool()
def get_current_time() -> str:
"""返回 MCP HTTP 服务端当前时间。"""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@mcp.resource("yanque://course/{course_id}", mime_type="application/json")
def get_course_resource(course_id: str) -> dict[str, Any]:
"""读取指定课程的完整资料。"""
course = COURSES.get(course_id)
if course is None:
return {
"error": "COURSE_NOT_FOUND",
"message": f"未找到课程:{course_id}",
}
return course
@mcp.prompt()
def course_study_plan(course_name: str, student_level: str = "初学者") -> str:
"""生成课程学习计划提示词。"""
return f"请为 {student_level} 学员制定《{course_name}》学习计划。"
if __name__ == "__main__":
mcp.run(transport="streamable-http")
启动服务端:
uv run python test/mcp_demo/07_streamable_http_server.py
启动后它监听的地址是:
http://127.0.0.1:8000/mcp
注意:
这个地址不是普通网页。
它是 MCP 客户端要连接的 HTTP MCP endpoint。
所以你用浏览器打开它,不一定会看到一个漂亮页面。
我们再写一个 HTTP 客户端连接它:
test/mcp_demo/08_streamable_http_client.py
核心代码是:
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
async def main() -> None:
async with streamable_http_client("http://127.0.0.1:8000/mcp") as (
read_stream,
write_stream,
_get_session_id,
):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()
tools = await session.list_tools()
for tool in tools.tools:
print(f"- {tool.name}: {tool.description}")
course_result = await session.call_tool(
"query_course",
{"keyword": "MCP"},
)
print(course_result.content)
if __name__ == "__main__":
asyncio.run(main())
这里要注意:
stdio_client(...):
根据 command + args 启动 MCP Server。
streamable_http_client(...):
不启动服务端,只连接已经启动好的 URL。
所以 HTTP 版本要开两个终端。
第一个终端启动服务端:
uv run python test/mcp_demo/07_streamable_http_server.py
第二个终端启动客户端:
uv run python test/mcp_demo/08_streamable_http_client.py
5.5 stdio 和 streamable-http 的区别
可以用下面这张表对比:
对比项 stdio streamable-http
谁启动服务端 客户端启动 服务端自己先启动
怎么连接 stdin / stdout 输入输出通道 HTTP URL
有没有端口 没有 有,例如 8000
适合哪里 本地工具、本地开发、个人使用 远程部署、团队共享、正式服务
客户端配置 command + args url
是否像 Web 服务 不像 更像
浏览器能不能看 不能当网页看 endpoint 也不是普通网页
初学和本地演示时,优先使用:
mcp.run(transport="stdio")
原因是:
配置少,最容易跑通。
如果你要把 MCP Server 部署成一个独立服务,或者让多个客户端通过网络连接,再考虑:
mcp.run(transport="streamable-http")
可以这样判断:
本地工具型 MCP:优先 stdio。
远程服务型 MCP:优先 streamable-http。
最小 demo:优先 stdio。
YanQue 业务服务化:更适合 streamable-http。
6. 第二步:添加一个 Tool
现在我们给 MCP Server 加一个工具。
这一步建议单独创建文件:
test/mcp_demo/01_tool_server.py
这个工具叫:
query_course
作用是按关键词查询课程。
完整代码变成:
from typing import Any
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("yanque-course-demo")
COURSES: dict[str, dict[str, Any]] = {
"langchain": {
"id": "langchain",
"name": "LangChain 框架实战",
"teacher": "燕雀 AI 教研组",
"date": "2026-07-06",
"summary": "学习模型调用、提示词模板、工具调用和 Agent 基础流程。",
},
"mcp": {
"id": "mcp",
"name": "MCP 服务开发入门",
"teacher": "燕雀 AI 教研组",
"date": "2026-07-23",
"summary": "学习 MCP Server 如何把工具、资源和提示词暴露给 Agent。",
},
}
@mcp.tool()
def query_course(keyword: str) -> list[dict[str, Any]]:
"""按课程 ID、课程名或简介关键词查询课程。"""
normalized_keyword = keyword.strip().lower()
if not normalized_keyword:
return []
return [
course
for course in COURSES.values()
if normalized_keyword in course["id"].lower()
or normalized_keyword in course["name"].lower()
or normalized_keyword in course["summary"].lower()
]
if __name__ == "__main__":
mcp.run(transport="stdio")
重点看这段:
@mcp.tool()
def query_course(keyword: str) -> list[dict[str, Any]]:
...
它的意思是:
把 query_course 这个 Python 函数注册成 MCP Tool。
注册以后,Agent 客户端就能看到这个工具。
函数的注释:
"""按课程 ID、课程名或简介关键词查询课程。"""
也很重要。
因为 Agent 会根据工具说明判断:
什么时候应该调用这个工具。
所以工具说明要写清楚,不要太随意。
7. 第三步:再添加一个获取时间的 Tool
再加一个更简单的工具:
from datetime import datetime
@mcp.tool()
def get_current_time() -> str:
"""返回 MCP 服务端当前时间。"""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
这个工具虽然简单,但很适合说明一个概念:
大模型自己并不知道程序运行机器上的真实当前时间。
如果问题需要实时信息,就应该通过工具获取。
8. 第四步:添加一个 Resource
Tool 是“执行动作”。
Resource 更像“读取资料”。
这一步建议单独创建文件:
test/mcp_demo/02_resource_server.py
我们添加一个课程详情资源:
@mcp.resource("yanque://course/{course_id}", mime_type="application/json")
def get_course_resource(course_id: str) -> dict[str, Any]:
"""读取指定课程的完整资料。"""
course = COURSES.get(course_id)
if course is None:
return {
"error": "COURSE_NOT_FOUND",
"message": f"未找到课程:{course_id}",
}
return course
这里的资源地址是:
yanque://course/{course_id}
如果客户端读取:
yanque://course/mcp
服务端就会调用:
get_course_resource(course_id="mcp")
然后返回 MCP 课程详情。
Resource 适合放这些内容:
- 项目说明文档。
- 数据库表结构。
- 课程详情。
- 配置信息。
- 知识库片段。
9. 第五步:添加一个 Prompt
Prompt 是提示词模板。
这一步建议单独创建文件:
test/mcp_demo/03_prompt_server.py
我们添加一个生成课程学习计划的提示词:
@mcp.prompt()
def course_study_plan(course_name: str, student_level: str = "初学者") -> str:
"""生成课程学习计划提示词。"""
return f"""
你是燕雀 AI 课程助教,请为 {student_level} 学员制定《{course_name}》学习计划。
要求:
1. 先用一句话说明这门课适合解决什么问题。
2. 按课前、课中、课后三个阶段安排学习任务。
3. 给出 3 个可实践的小练习。
4. 最后提醒学员哪些地方容易踩坑。
""".strip()
这个函数不是直接调用模型。
它只是返回一段提示词。
Agent 客户端拿到这段提示词以后,可以再交给模型使用。
Prompt 适合沉淀固定任务模板。
比如:
生成学习计划
代码审查
SQL 分析
面试复盘
日志诊断
10. 第六步:写一个本地 Client 测试它
只写 MCP Server 不容易看效果。
前面 01_tool_server.py、02_resource_server.py、03_prompt_server.py 是为了分开理解概念。
真正测试完整链路时,可以把它们合成一个完整服务端:
test/mcp_demo/04_full_server.py
然后再写一个客户端文件:
test/mcp_demo/05_stdio_client.py
代码如下:
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main() -> None:
server_params = StdioServerParameters(
command="uv",
args=["run", "python", "test/mcp_demo/04_full_server.py"],
)
async with stdio_client(server_params) as (read_stream, write_stream):
# stdio_client 会启动上面的命令,并拿到服务端进程的输入输出通道。
# write_stream:往 MCP Server 的 stdin 写请求。
# read_stream:从 MCP Server 的 stdout 读响应。
async with ClientSession(read_stream, write_stream) as session:
# ClientSession 把 stdin/stdout 这种底层读写,封装成 list_tools、call_tool 等方法。
await session.initialize()
print("1. 服务端暴露的工具:")
tools = await session.list_tools()
for tool in tools.tools:
print(f"- {tool.name}: {tool.description}")
print()
print("2. 调用工具 query_course:")
course_result = await session.call_tool(
"query_course",
{"keyword": "MCP"},
)
print(course_result.content)
print()
print("3. 调用工具 get_current_time:")
time_result = await session.call_tool("get_current_time", {})
print(time_result.content)
print()
print("4. 读取资源 yanque://course/mcp:")
resource_result = await session.read_resource("yanque://course/mcp")
for content in resource_result.contents:
print(content.text)
print()
print("5. 获取提示词 course_study_plan:")
prompt_result = await session.get_prompt(
"course_study_plan",
{
"course_name": "MCP 服务开发入门",
"student_level": "Java 后端工程师",
},
)
for message in prompt_result.messages:
print(message.content.text)
if __name__ == "__main__":
asyncio.run(main())
这段代码做了四件事:
1. 启动 MCP Server。
2. 查询服务端有哪些工具。
3. 调用 query_course 工具。
4. 调用 get_current_time 工具。
5. 读取课程资源。
6. 获取课程学习计划提示词。
注意这里:
server_params = StdioServerParameters(
command="uv",
args=["run", "python", "test/mcp_demo/04_full_server.py"],
)
它的意思是:
客户端负责启动 MCP Server。
所以我们运行客户端时,不需要提前手动启动服务端。
整个 stdio demo 的运行关系可以画成这样:
11. 第七步:运行 demo
进入 AI 项目:
cd /Users/huangjin/IdeaProjects/YanQue/YanQue-AI
运行客户端:
uv run python test/mcp_demo/05_stdio_client.py
如果成功,会看到类似输出:
1. 服务端暴露的工具:
- query_course: 按课程 ID、课程名或简介关键词查询课程。
- get_current_time: 返回 MCP 服务端当前时间。
2. 调用工具 query_course:
...
3. 调用工具 get_current_time:
...
4. 读取资源 yanque://course/mcp:
...
5. 获取提示词 course_study_plan:
...
这就说明:
MCP Server 已经可以把 Tool、Resource、Prompt 暴露给 MCP Client。
12. 单独启动服务端为什么没反应
如果你运行:
uv run python test/mcp_demo/04_full_server.py
你可能会发现终端没有明显输出。
这是正常的。
因为它是 stdio 模式,不是 HTTP 模式。
它启动后就在等待 MCP 客户端通过标准输入输出发送消息。
所以学习阶段建议先运行:
uv run python test/mcp_demo/05_stdio_client.py
这样客户端会自动启动服务端,并演示完整调用过程。
13. 接入 MCP 客户端时怎么配置
不同客户端配置文件位置不一样,但核心配置长得差不多。
大概是:
{
"mcpServers": {
"yanque-course-demo": {
"command": "uv",
"args": [
"run",
"python",
"test/mcp_demo/04_full_server.py"
]
}
}
}
如果客户端不是在 YanQue-AI 目录下启动,建议把 server 文件写成绝对路径:
{
"mcpServers": {
"yanque-course-demo": {
"command": "uv",
"args": [
"run",
"python",
"/Users/huangjin/IdeaProjects/YanQue/YanQue-AI/test/mcp_demo/04_full_server.py"
]
}
}
}
14. MCP 和普通工具调用有什么区别
前面我们在 LangChain 里写过工具:
@tool
def get_student_day_schedule(date: str) -> str:
...
这种工具通常只在当前 Python 程序里使用。
MCP 工具则是通过 MCP Server 暴露出去:
@mcp.tool()
def query_course(keyword: str):
...
差别可以这样理解:
LangChain Tool:
工具直接绑定在当前 Agent 程序里。
MCP Tool:
工具放在独立 MCP Server 里。
支持 MCP 的 Agent 客户端都可以连接它。
所以 MCP 更适合做统一工具入口。
比如公司可以做一个:
yanque-business-mcp-server
里面统一放:
- 查询课程工具。
- 查询学生工具。
- 查询订单工具。
- 查询知识库资源。
- 生成运营分析提示词。
然后不同 Agent 客户端都可以接这个服务。
15. Agent 怎么接入 MCP Server
前面 05_stdio_client.py 只是普通 MCP Client。
它能证明:
MCP Client 可以连接 MCP Server,并调用工具、资源、提示词。
但真实项目里,我们更关心的是:
Agent 能不能使用 MCP Server 暴露的工具?
可以。
这里需要一个适配器:
uv add langchain-mcp-adapters
这个依赖的作用是:
把 MCP Server 暴露的 Tools 加载成 LangChain 可以使用的 Tools。
这里会用到一个类:
from langchain_mcp_adapters.client import MultiServerMCPClient
MultiServerMCPClient 可以理解成:
LangChain 连接 MCP Server 的客户端适配器。
它本身不是 Agent。
它也不负责调用大模型。
它主要做三件事:
1. 根据配置连接一个或多个 MCP Server。
2. 从 MCP Server 读取工具列表。
3. 把 MCP Tool 转成 LangChain Agent 可以使用的 Tool。
名字里的 MultiServer 表示它可以同时连接多个 MCP Server。
比如以后可以这样配置:
client = MultiServerMCPClient(
{
"course_server": {
"url": "http://127.0.0.1:8000/mcp",
"transport": "streamable_http",
},
"order_server": {
"url": "http://127.0.0.1:8001/mcp",
"transport": "streamable_http",
},
}
)
这段配置的意思是:
同时连接课程 MCP Server 和订单 MCP Server。
然后把两个服务里的工具一起加载出来。
本节 demo 只连接一个 MCP Server:
client = MultiServerMCPClient(
{
"yanque_course": {
"url": "http://127.0.0.1:8000/mcp",
"transport": "streamable_http",
}
}
)
这里的配置可以这样读:
yanque_course:
给这个 MCP Server 起一个本地名字。
url:
MCP Server 的 HTTP 地址。
transport:
使用 streamable_http 方式连接。
后面的:
tools = await client.get_tools()
意思是:
连接 MCP Server,读取它暴露的 Tools,并转换成 LangChain Tools。
整体关系可以看这张图:
15.1 先加载 MCP Tools
先看文件:
test/mcp_demo/09_load_mcp_tools.py
核心代码:
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
async def main() -> None:
client = MultiServerMCPClient(
{
"yanque_course": {
"url": "http://127.0.0.1:8000/mcp",
"transport": "streamable_http",
}
}
)
tools = await client.get_tools()
print("从 MCP Server 加载到的 LangChain 工具:")
for tool in tools:
print(f"- {tool.name}: {tool.description}")
print()
tool_map = {tool.name: tool for tool in tools}
query_course = tool_map["query_course"]
print("直接调用 MCP 工具 query_course:")
result = await query_course.ainvoke({"keyword": "MCP"})
print(result)
if __name__ == "__main__":
asyncio.run(main())
运行:
先启动 HTTP MCP Server:
uv run python test/mcp_demo/07_streamable_http_server.py
再打开另一个终端运行:
uv run python test/mcp_demo/09_load_mcp_tools.py
这一步还没有创建 Agent。
它只是证明:
MCP Server 里的 query_course、get_current_time
已经变成了 LangChain Tool。
15.2 再把 MCP Tools 交给 Agent
再看文件:
test/mcp_demo/10_agent_with_mcp_tools.py
核心流程是:
1. 连接 MCP Server。
2. 加载 MCP Tools。
3. 创建 LangChain Agent。
4. 把 MCP Tools 作为 tools 传给 Agent。
5. 用户提问后,Agent 自己决定是否调用 MCP 工具。
核心代码:
import asyncio
from langchain.agents import create_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
from test.langchain_demo.demo_model import build_demo_model
SYSTEM_PROMPT = (
"你是一个燕雀课程助手。"
"如果用户询问课程信息,优先使用 MCP 工具查询真实课程数据。"
"回答要简洁、自然,适合初学者理解。"
)
async def main() -> None:
client = MultiServerMCPClient(
{
"yanque_course": {
"url": "http://127.0.0.1:8000/mcp",
"transport": "streamable_http",
}
}
)
mcp_tools = await client.get_tools()
agent = create_agent(
model=build_demo_model(),
tools=mcp_tools,
system_prompt=SYSTEM_PROMPT,
)
result = await agent.ainvoke(
{
"messages": [
{
"role": "user",
"content": "帮我查一下 MCP 课程是什么时候上,课程主要讲什么?",
}
]
}
)
print(result["messages"][-1].content)
if __name__ == "__main__":
asyncio.run(main())
运行:
先启动 HTTP MCP Server:
uv run python test/mcp_demo/07_streamable_http_server.py
再打开另一个终端运行:
uv run python test/mcp_demo/10_agent_with_mcp_tools.py
这个文件需要 .env 中已经配置大模型参数。
如果运行成功,会看到 Agent 先调用 MCP 工具:
工具名:query_course
参数:{"keyword": "MCP"}
然后模型根据工具结果回答:
《MCP 服务开发入门》的上课时间是 2026-07-23,主要讲 MCP Server 如何把工具、资源和提示词暴露给 Agent。
到这里,完整链路就变成了:
用户问题
|
v
LangChain Agent
|
v
MCP Tool Adapter
|
v
MCP Server
|
v
query_course 工具
这就是 Agent 接入 MCP Server 的核心方式。
16. 真实项目里怎么接 YanQue 业务
当前 demo 使用的是内存数据:
COURSES = {
...
}
真实项目里一般不会这么写。
可以把工具函数改成调用后端接口。
我们单独放了一个写法示例:
test/mcp_demo/06_business_api_style.py
这个文件是接真实业务的写法示例,不需要直接运行。
因为它依赖:
1. 本地 YanQue Java 后端已经启动。
2. 后端接口路径真实存在。
3. 接口鉴权、签名、登录态已经处理好。
它主要说明:
MCP Tool 里面不一定写死内存数据。
MCP Tool 可以调用已有业务系统 API。
示例代码大概是:
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("yanque-business-api-demo")
YANQUE_ADMIN_BASE_URL = "http://localhost:8080"
@mcp.tool()
def query_course_from_admin(keyword: str) -> dict[str, Any]:
"""演示如何把 MCP Tool 改造成调用 YanQue Java 后端 API。"""
response = httpx.get(
f"{YANQUE_ADMIN_BASE_URL}/api/course/page",
params={"keyword": keyword},
timeout=10,
)
response.raise_for_status()
return response.json()
这段代码只是一个方向。
真实落地时,不能只写一个 httpx.get 就完事。
还要补:
1. 登录态或服务端 token。
2. 请求签名。
3. 权限校验。
4. 超时设置。
5. 错误码转换。
6. 敏感字段脱敏。
7. 返回结构整理。
如果 YanQue 的业务逻辑主要在 Java Spring Boot 后端里,推荐方式是:
MCP Server
|
| HTTP
v
YanQue-Admin / YanQue 后端 API
|
v
Service / Mapper / Database
这样 MCP Server 不需要重复写一套业务逻辑。
它只是把已有后端能力包装成 Agent 可以调用的工具。
17. 写 MCP 工具时要注意什么
MCP 看起来只是把函数暴露出去。
但真实项目里要特别注意安全。
17.1 工具参数不要太随意
比如学生查课表工具,不建议让模型传:
studentId
classId
更合理的是只让模型传:
date
当前学生是谁,应该由登录态或服务端上下文决定。
这样可以避免模型传错 ID,甚至查到别人的数据。
17.2 写操作要谨慎
查询类工具风险较低。
但如果工具会执行写操作,比如:
创建订单
删除课程
修改学生信息
发送通知
就必须增加确认和权限控制。
不要让 Agent 在没有确认的情况下直接执行高风险操作。
17.3 返回结果要结构化
尽量返回清晰结构:
{
"id": "mcp",
"name": "MCP 服务开发入门",
"date": "2026-07-23"
}
不要只返回一大段混乱字符串。
结构化结果更方便 Agent 理解,也更方便后续调试。
17.4 错误信息要明确
不要只返回:
失败了
更好的错误是:
{
"error": "COURSE_NOT_FOUND",
"message": "未找到课程:xxx"
}
这样 Agent 才知道下一步该怎么处理。
18. 本节小结
这一节我们学习了 MCP 的最小闭环。
重点记住:
MCP Server 是给 Agent 提供能力的服务。
Tool 让 Agent 做事。
Resource 让 Agent 读资料。
Prompt 让 Agent 获取提示词模板。
FastMCP 帮我们快速创建 MCP Server。
transport 决定 MCP Client 和 MCP Server 怎么通信。
stdio 模式适合本地 MCP 客户端启动和通信。
streamable-http 模式适合把 MCP Server 当成独立 HTTP 服务部署。
本节最重要的一句话:
MCP 不是让模型变聪明,而是让 Agent 可以用标准方式连接你的业务能力。
下一步可以继续做两件事:
- 把
query_course从内存数据改成调用 YanQue 后端接口。 - 把课程、订单、学生等查询能力整理成一个正式的 YanQue MCP Server。







