[TOC]
介绍
帮助小白快速认识、开发和使用MCP。其实我也是小白哦,若有问题或错误欢迎指出,感谢
1. 什么是MCP
MCP(Model Context Protocol)即模型上下文协议,是由 Anthropic 公司于 2024 年底开源发布, 用于标准化应用程序如何向大型语言模型(LLMs)提供上下文。将 MCP 想象成 AI 应用的 USB-C 接口。就像 USB-C 提供了一种标准化的方式来连接设备到各种外设和配件一样,MCP 提供了一种标准化的方式来连接 AI 模型到不同的数据源和工具。
2. 为什么用MCP
MCP 帮助你在 LLMs 上构建代理和复杂的工作流。LLMs 经常需要与数据和工具集成,而 MCP 提供了:
- 一系列不断增长的预构建集成,你的 LLM 可以直接插入
- MCP 对各个服务的接口进行了统一,这样 M 个 Agent 可以直接使用这 N 个服务,大幅降低重复开发和适配的成本
- 确保你的数据在你的基础设施内最佳实践
- 在 LLM 提供者和供应商之间切换的灵活性
3. MCP、智能体、大模型 关系
4. MCP 架构与组件
- 主机(Host):与大模型直接交互,处理大模型输入输出,保障数据准确完整,执行安全策略,处理延迟可降低至平均 50 毫秒以内。通常是 AI 应用(Agent),比如 Anthropic Claude Desktop、Cursor、Cline 等,负责选择并调用 MCP Client,以便使用各种 MCP Server 提供的能力。
- 客户端(Client):位于外部系统,将外部需求转为 MCP 协议格式请求,再把主机响应转换为外部系统能理解的格式 ,请求准备时间可缩短至平均 30 毫秒以内,支持多种编程语言和平台。
- 服务器(Server):处理主机请求并提供服务,可提供数据查询、文件操作等服务 ,处理能力可达每秒处理 1000 个请求以上,具备灵活扩展机制。提供资源、工具或 Prompts 的服务,如文件系统、数据库、API 等。
Client 和 Server 之间使用双向的 JSON-RPC 2.0 进行通信。当前支持 stdio 和 Streamable HTTP 两种传输机制。
5. MCP关键概念
Resources 资源:将服务器中的数据和内容公开给 LLM,
它允许服务器公开可由客户端读取并用作 LLM 交互上下文的数据和内容。
Tools 工具:使 LLM 能够通过您的服务器执行操作
它使服务器能够向客户端公开可执行功能。通过工具,LLM 可以与外部系统交互、执行计算并在现实世界中采取行动。(执行者,API)
Prompts 提示: 创建可重用的提示模板和工作流
使服务器能够定义可重用的提示模板和工作流,客户端可以轻松地向用户和 LLM 显示这些模板和工作流。它们提供了一种强大的方法来标准化和共享常见的 LLM 交互。
Sampling 采样:让您的服务器从 LLM 请求完成
它允许服务器通过客户端请求 LLM 完成,从而在维护安全性和隐私性的同时实现复杂的代理行为。
MCP Server 可以发起请求到 MCP Client,再MCP Client 接收到请求后,执行相应的动作,比如:人工确认或者是调用大模型等。
Roots 根:用于定义服务器可以运行的边界。它们为客户端提供了一种将相关资源及其位置通知服务器的方法。
根是客户端建议服务器应该关注的 URI。当客户端连接到服务器时,它会声明服务器应该使用哪些根。虽然 root 主要用于文件系统路径,但 root 可以是任何有效的 URI,包括 HTTP URL。
- Project directories 项目目录
- Repository locations 存储库位置
- API endpoints API 终端节点
- Configuration locations 配置位置
- Resource boundaries 资源边界
Transports 运输:模型上下文协议 (MCP) 中的传输为客户端和服务器之间的通信提供了基础。传输处理消息发送和接收方式的基本机制。
MCP 使用 JSON-RPC 2.0 作为其传输格式。传输层负责将 MCP 协议消息转换为 JSON-RPC 格式进行传输,并将收到的 JSON-RPC 消息转换回 MCP 协议消息。
6. 通信方式(Transport)
通信方式 | 介绍 | 场景 |
---|---|---|
STDIO | 标准输入/输出 | - 本地集成和命令行工具特别有用;- 构建命令行工具;- 本地集成;- 简单的通信;- 使用 shell 脚本 |
Server-Sent-Events | 服务端推流,单向 | - 需要服务端到客户端的流式传输;- 网络受限 |
Streamable HTTP | 流式 Http,将替换 SSE。原因 | 高效灵活,支持更大模块的分布式部署 |
实战
1. 构建MCP
1.1 前置准备
开发工具:
- vscode
开发环境:
- py 3.13
- nodejs 18
调试工具:
- modelcontextprotocol/inspector@0.6.0 (依赖node18+)
主机
- Cherry studio
- Vscode
1.2 环境搭建
👉 推荐使用uv https://docs.astral.sh/uv/getting-started/installation/
1.2.1 初始化工程
# 初始化框架
uv init mcp-math-demo
# 切换目录
cd mcp-math-demo
# 创建目录(为什么 —> 因为据说是python工程规范)
mkdir -p src/example
# 将生成的main移动到example
mv main.py src/example
1.2.2 创建py虚拟环境
# 创建虚拟环境(工程中会多一个.venv 文件),并激活环境
uv venv
# 激活环境,mac和window 有点差异,需注意下
source .venv/bin/activate
1.2.3 配置pyproject.toml
....其他已经存在的配置....
dependencies = [
"fastmcp>=2.3.4",
"mcp[cli]>=1.6.0"
]
[project.scripts]
example = "example.main:main"
[[tool.uv.index]]
name = "mypypi"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
publish-url = "http://your.pypi/"
default = true
....其他已经存在的配置....
1.2.4 安装依赖
uv pip install -e .
1.2.5 运行代码
# 执行工程代码
uv run example
# 若成功则打印结果:Hello from mcp-math-demo!
1.3 快速编码
一下两种方式任选一种,其中Sse方式需要提前启动
1.3.1 Stdio方式
启动命令测试:uv run src/example/server-math-stdio.py
# server-math-stdio.py
from mcp.server.fastmcp import FastMCP
import logging
# 配置日志记录器
logging.basicConfig(
level=logging.INFO, # 设置日志级别为 INFO
format="%(asctime)s - %(levelname)s - %(message)s" # 日志格式
)
logger = logging.getLogger(__name__)
# 创建 FastMCP 实例
mcp = FastMCP("Math")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
logger.info("The add method is called: a=%d, b=%d", a, b) # 记录加法调用日志
return a + b
@mcp.tool()
def multiply(a: int, b: int) -> int:
"""Multiply two numbers"""
logger.info("The multiply method is called: a=%d, b=%d", a, b) # 记录乘法调用日志
return a * b
if __name__ == "__main__":
logger.info("Start math server through MCP-STDIO") # 记录服务启动日志
mcp.run(transport="stdio") # 启动服务并使用标准输入输出通信
1.3.2 Sse方式
👉 该方式需要先启动服务,然后在使用HOST取连接
启动命令测试:uv run src/example/server-math-stdio.py
# server-math-sse.py
from mcp.server.fastmcp import FastMCP
import logging
# 配置日志记录器
logging.basicConfig(
level=logging.INFO, # 设置日志级别为 INFO
format="%(asctime)s - %(levelname)s - %(message)s" # 日志格式
)
logger = logging.getLogger(__name__)
# 创建 FastMCP 实例
mcp = FastMCP("Math")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
logger.info("The add method is called: a=%d, b=%d", a, b) # 记录加法调用日志
return a + b
@mcp.tool()
def multiply(a: int, b: int) -> int:
"""Multiply two numbers"""
logger.info("The multiply method is called: a=%d, b=%d", a, b) # 记录乘法调用日志
return a * b
if __name__ == "__main__":
logger.info("Start math server through MCP-SSE") # 记录服务启动日志
mcp.run(transport="sse") # 启动服务并使用标准输入输出通信
#mcp.run(transport="streamable-http") # 启动服务并使用标准输入输出通信
1.4 调试MCP
方式一:单独运行调试工具
- 按照插件(需要按照node18+,建议使用nvm)
# 打开命令行执行脚本
npx @modelcontextprotocol/inspector@0.6.0
Sse
Stdio
方式二:通过mcp进行调试(实则方式一)
## 这种本质上就是方式一
uv run mcp dev server-math-stdio.py
2. 使用MCP
以非开发、开发人员视角演示使用,下面是常用标准的MCP配置
{
## 数学计算 方式一:stdio --> server-math-stdio.py
"math-stdio": {
# 这个也可以是 .venv/bin/python3
"command": "uv",
# Replace with absolute path to your server-math-stdio.py file
"args": ["run","xxxx/src/example/server-math-stdio.py"],
"transport": "stdio",
},
## 数学计算 方式二:sse --> server-math-sse.py
"math-sse": {
"url":"http://127.0.0.1:8000/sse",
"transport": "sse"
},
}
2.1 Cherry studio
2.1.1 配置模型
这里可以点击硅基流动进行注册获取(新用户有免费额度),或者根据自己实际情况配置
2.1.2 配置MCP服务
2.1.2.1 方式一:stdio
--directory
xxx/mcp-math-demo/src/example/
run
server-math-stdio.py
2.1.2.2 方式二:sse
先启动服务:
在配置
2.1.2.3 查看MCP具体工具
2.1.3 选择MCP服务和大模型
2.1.4 对话使用MCP
2.2 Vscode
2.2.1 下载插件
我用的Roo Code,大家可以下载其他插件: Cline ...
2.2.2 配置模型
我这边注册的DeepSeek
2.2.3 配置mcp
注意:
comand:指定可以运行脚本的命令,环境不要混了
args:参数指定绝对路径
transport: 依据实际, stdio|sse
2.2.4 对话 (过程中自动批准MCP)
输入3*3
2.3 Code 代码调用
2.3.1 需要安装依赖
uv add langchain_mcp_adapters langgraph langchain_deepseek
2.3.2 客户端代码 client-langchain.py
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from example.utils import init_model
## 模型
llm = init_model()
async def main():
client = MultiServerMCPClient(
{
# # 数学计算 sse --> server-math-sse.py
"math-sse": {
"url":"http://127.0.0.1:8000/sse",
"transport": "sse"
},
# 数学计算 stdio --> server-math-stdio.py
# "math-stdio": {
# "command": "python",
# "args": ["src/example/server-math-stdio.py"],
# "transport": "stdio",
# }
}
)
tools = await client.get_tools()
agent = create_react_agent(
llm,
tools,
debug=True
)
# 循环接收用户输入
while True:
try:
# 提示用户输入问题
user_input = input("\n请输入您的问题(或输入 'exit' 退出):")
if user_input.lower() == "exit":
print("感谢使用!再见!")
break
# 调用代理处理问题
agent_response = await agent.ainvoke({"messages": [{"role": "user", "content": user_input}]})
print("\n>>>>>>>>>>>>>>>>>>>>>>> 开始输出结果 >>>>>>>>>>>>>>>>>>>>>>>\n")
# 调用抽取的方法处理输出结果
print_optimized_result(agent_response)
print("\n<<<<<<<<<<<<<<<<<<<<<<< 结果输出完成 <<<<<<<<<<<<<<<<<<<<<<<\n")
except Exception as e:
print(f"发生错误:{e}")
continue
# 解析并输出结果
def print_optimized_result(agent_response):
"""
解析代理响应并输出优化后的结果。
:param agent_response: 代理返回的完整响应
"""
messages = agent_response.get("messages", [])
steps = [] # 用于记录计算步骤
final_answer = None # 最终答案
for message in messages:
if hasattr(message, "additional_kwargs") and "tool_calls" in message.additional_kwargs:
# 提取工具调用信息
tool_calls = message.additional_kwargs["tool_calls"]
for tool_call in tool_calls:
tool_name = tool_call["function"]["name"]
tool_args = tool_call["function"]["arguments"]
steps.append(f"调用工具: {tool_name}({tool_args})")
elif message.type == "tool":
# 提取工具执行结果
tool_name = message.name
tool_result = message.content
steps.append(f"{tool_name} 的结果是: {tool_result}")
elif message.type == "ai":
# 提取最终答案
final_answer = message.content
# 打印优化后的结果
print("\n***执行过程***:")
for step in steps:
print(f"- {step}")
if final_answer:
print(f"\n***最终结果***\n> {final_answer}\n")
if __name__ == "__main__":
asyncio.run(main())
2.3.3 模型工具类utils.py
from langchain_deepseek import ChatDeepSeek
#from langchain.chat_models import init_chat_model
#from langchain_core.language_models import BaseChatModel
def init_model():
## 公网-通义模型
# from langchain_community.chat_models.tongyi import ChatTongyi #dashscope
# llm = ChatTongyi(
# temperature=0,
# api_key="sk-XXXX",
# )
# 公网-Deepseek模型
llm = ChatDeepSeek(
model="deepseek-chat", # 尝试不同的模型名称
api_key="sk-xxxx"
)
## 不行坑比较多,可能是版本功能问题,初始化后的llm 总是报各种错误
# llm = load_chat_model(f"{model_provider}/{model_name}",{
# "api_key": api_key,
# "base_url": base_url,
# # enable_auto_tool_choice: True,
# # tool_call_parser:True
# })
return llm;
2.3.4 调试过程
- 启动MCP服务端
- 启动MCP客户端并输入问题:3*4
调用流程分析:
3. 常见问题
python虚拟环境
- 环境混乱导致包不兼容,命令行中的环境生效
- 调试工具中的uv环境和工程实际环境不对应
# 推荐(局部) uv --directory xxx/mcp-math-demo/src/example run server-math-stdio.py # 不推荐(依赖全局) uv run xxx/mcp-math-demo/src/example/server-math-stdio.py
模型支持
mcp 在对话中未调用工具(换模型)基于
langchain_mcp_adapters
代码实现来,model="deepseek-chat" # 成功
model="qwen-turbo" # 成功
自己搭建的模型不行
可以检查一下模型是否支持工具,是否开启工具
权限问题
window中通过uv执行代码虚拟环境共用出错
可以单独创建,就是不要同时在一个虚拟环境中启动两个MCP
4. 相关文档
- MCP
官网:https://modelcontextprotocol.io/introduction
- Uv/Uvx:一个帮你高效管理python虚拟环境的工具
官网:https://docs.astral.sh/uv/
- SpringAI:提供AI编程框架和解决方案,完美整合Spring体系(java17+)
- npx:让你可以无需安装就能运行npm包里的命令,方便快捷地使用各种Node.js工具和库。(node18+)
这里用于启动调试工具
npx @modelcontextprotocol/inspector@0.6.0
- Nvm: nvm(Node Version Manager)是一个用于管理Node.js版本的工具,可以方便地在不同项目之间切换Node.js版本,确保开发环境的一致性
- unix/linux: https://github.com/nvm-sh/nvm
- Window: https://github.com/coreybutler/nvm-windows (记得申请管理员权限,不然安装不了)
5. 总结
简单认识MCP
- MCP 解决了大模型与外部工具和数据源集成的标准化问题,类似于硬件领域的 USB-C 标准
- MCP 架构主要包含三个核心组件:主机(Host)、客户端(Client)和服务器(Server),它们通过 JSON-RPC 协议进行通信
- MCP 提供了四种关键能力:资源(Resources)、工具(Tools)、提示(Prompts)和采样(Sampling),使大模型能够更高效地与外部系统交互
- MCP 支持多种通信方式,包括标准输入/输出(STDIO)、服务端推流(SSE)和流式 HTTP,适用于不同的使用场景
MCP快速入门实战
- 我们通过构建简单的数学计算 MCP 服务,展示了如何快速开发和部署 MCP 服务,并通过多种方式(Cherry studio、VSCode、自定义代码)调用这些服务。这为开发者提供了清晰的入门指导,帮助理解 MCP 的实际应用流程。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。