前言
为什么别人的大模型能随手调试代码、发邮件、接入内部 API 查报表、甚至生成 3D 模型;而你的大模型,却只能聊天?问它能不能查文件、查数据,总是回答“我做不到”?其实差距不在模型,而在于有没有一座桥,让模型接入更多工具。
这座桥,就是 MCP(Model Context Protocol)。
MCP 协议介绍
MCP 协议
中有两个角色,工具实现端(MCP Server
)跟工具调用端(MCP Client
),他们之间通过 JSON-RPC 协议 来进行交互。
用户通过聊天客户端跟大模型交互时,大模型会发送特定格式的 tool_call
指令,聊天客户端(MCP Client
)接收到这个指令后,会按照对应的参数去请求 MCP Server
,然后将执行结果反馈给大模型,最后由大模型整合,以聊天的形式返回给用户。
MCP 协议调用流程
什么是 JSON-RPC 协议?跟传统的 API 调用有什么区别?
JSON-RPC
主要是定义了消息体结构,没有规定具体的传输方式,支持 WebSocket
、HTTP
、STDIO(标准输入输出)
等形式传输。API 请求则是基于 URL 跟动作(GET、POST),来定义的资源获取方式。
// 基本形式
{
"jsonrpc": "2.0", // 固定写 "2.0"
"method": "subtract", // 要调用的接口名
"params": [42, 23], // 参数,可用数组或对象
"id": 1 // 调用 ID
}
MCP Server 的简单实现(Python 形式)
安装uv 工具
uv
(astral‑sh/uv)是一个由 Astral 团队开发、使用 Rust 编写的下一代 Python 包与项目管理工具
,目标成为 pip、pipx、poetry、virtualenv、pyenv 等工具的升级合并替代品。
# 使用 PowerShell 管理员 模式运行
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# 查看 uv 版本
uv --version
初始化 mcp 项目
# 项目初始化
uv init py-mcp
cd py-mcp
# 添加 MCP 工具依赖(新建 .venv 与锁文件)
uv add "mcp[cli]"
# 开发过程中启动 server
uv run python main.py
# 测试时安装额外工具作为 dev 依赖
uv add --dev pytest httpx
# 同步 lock 文件
uv sync
# 构建发行包(可选)
uv build
uv 生成的目录结构解析
文件 / 目录 | 用途 |
---|---|
.venv/ | 项目独立虚拟环境 |
.gitignore | 忽略虚拟环境、锁文件等不需提交的内容 |
.python-version | 指定 Python 版本(用于自动创建 .venv ) |
main.py | 默认入口脚本(项目执行入口) |
pyproject.toml | 配置项目元信息、依赖、脚本命令等 |
README.md | 项目说明文档 |
uv.lock | 锁定依赖版本,保证环境可重现 |
src/ + __init__.py (可选) | 用于构建 Python 包/库的源码入口(packaged layout) |
编写工具逻辑
修改 main.py
,实现 D盘文件查看器
工具逻辑。
import os
from mcp.server.fastmcp import FastMCP
# 创建FastMCP服务器实例
mcp = FastMCP("D盘目录浏览器")
@mcp.tool()
def list_d_drive(path: str = "") -> str:
"""列出D盘指定目录的内容
Args:
path: 要查看的目录路径(相对于D盘根目录),默认为根目录
Returns:
目录内容的格式化字符串
"""
try:
# 构建完整的D盘路径
if path:
full_path = os.path.join("D:\\", path)
else:
full_path = "D:\\"
# 检查路径是否存在
if not os.path.exists(full_path):
return f"❌ 错误:路径 '{full_path}' 不存在"
# 检查是否为目录
if not os.path.isdir(full_path):
return f"❌ 错误:'{full_path}' 不是一个目录"
# 列出目录内容
try:
items = os.listdir(full_path)
items.sort() # 按字母顺序排序
if not items:
return f"📂 目录 '{full_path}' 为空"
result = f"📂 目录 '{full_path}' 的内容:\n\n"
for item in items:
item_path = os.path.join(full_path, item)
if os.path.isdir(item_path):
result += f"📁 {item}/\n"
else:
# 获取文件大小
try:
size = os.path.getsize(item_path)
if size < 1024:
size_str = f"{size} B"
elif size < 1024 * 1024:
size_str = f"{size / 1024:.1f} KB"
elif size < 1024 * 1024 * 1024:
size_str = f"{size / (1024 * 1024):.1f} MB"
else:
size_str = f"{size / (1024 * 1024 * 1024):.1f} GB"
result += f"📄 {item} ({size_str})\n"
except OSError:
result += f"📄 {item} (无法获取大小)\n"
return result
except PermissionError:
return f"🔒 错误:没有权限访问目录 '{full_path}'"
except Exception as e:
return f"❌ 错误:读取目录时发生异常 - {str(e)}"
except Exception as e:
return f"❌ 工具执行错误:{str(e)}"
@mcp.tool()
def get_file_info(path: str) -> str:
"""获取D盘指定文件或目录的详细信息
Args:
path: 文件或目录的路径(相对于D盘根目录)
Returns:
文件或目录的详细信息
"""
try:
# 构建完整的D盘路径
full_path = os.path.join("D:\\", path) if path else "D:\\"
# 检查路径是否存在
if not os.path.exists(full_path):
return f"❌ 错误:路径 '{full_path}' 不存在"
import stat
import time
# 获取文件状态
file_stat = os.stat(full_path)
# 格式化时间
modified_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(file_stat.st_mtime))
created_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(file_stat.st_ctime))
if os.path.isdir(full_path):
# 目录信息
try:
item_count = len(os.listdir(full_path))
return f"""📁 目录信息:{full_path}
📊 包含项目:{item_count} 个
📅 修改时间:{modified_time}
📅 创建时间:{created_time}
🔑 权限:{stat.filemode(file_stat.st_mode)}"""
except PermissionError:
return f"""📁 目录信息:{full_path}
🔒 无法访问目录内容(权限不足)
📅 修改时间:{modified_time}
📅 创建时间:{created_time}
🔑 权限:{stat.filemode(file_stat.st_mode)}"""
else:
# 文件信息
size = file_stat.st_size
if size < 1024:
size_str = f"{size} B"
elif size < 1024 * 1024:
size_str = f"{size / 1024:.1f} KB"
elif size < 1024 * 1024 * 1024:
size_str = f"{size / (1024 * 1024):.1f} MB"
else:
size_str = f"{size / (1024 * 1024 * 1024):.1f} GB"
return f"""📄 文件信息:{full_path}
📏 文件大小:{size_str}
📅 修改时间:{modified_time}
📅 创建时间:{created_time}
🔑 权限:{stat.filemode(file_stat.st_mode)}"""
except Exception as e:
return f"❌ 获取文件信息时发生错误:{str(e)}"
if __name__ == "__main__":
mcp.run()
使用 CherryStudio 引入自定义 MCP 服务器
配置好 MCP Server
之后,在聊天框下方启用 MCP 功能,然后直接对话即可,AI 会自动调用服务。
总结
自定义的 MCP Server
可以让模型直接调用我们已有的功能逻辑,让 AI
参与到我们的工作流中,而不是仅限于聊天。它可以实现:
- 访问你的数据库、文件系统,按自然语言做查询或汇总
- 封装公司内部 API 或脚本,让模型能自动执行业务操作
- 连接外部服务(邮件、GitHub、Slack 等),做自动化通知、拉取数据、创建任务
- 把重复的本地流程(文件整理、日志分析、定时任务)交给模型一键处理
学会使用 MCP 协议
,发挥想象,让 AI
发挥更大的作用!!!