MCP 协议从入门到实战:让大模型拥有调用本地工具的能力
本文基于实际项目经验,从零讲解 MCP(Model Context Protocol)协议的完整实现,包含服务端开发、客户端调用、异步通信流程,以及通义千问大模型的实际对接。
为什么需要 MCP?
大语言模型(LLM)本身有一个根本限制:它只能基于训练数据进行文本生成,无法直接访问外部工具、本地文件或实时数据。
比如,你问 ChatGPT"现在几点了?",它只能根据训练数据猜测,因为模型没有获取实时时间的能力。
MCP(Model Context Protocol)就是为了解决这个问题而生的------它是一套标准化的协议,让大模型能够安全、规范地调用外部工具。
类比一下:
- 没有 MCP:大模型是一个"闭门造车"的专家,什么都能聊但什么都做不了
- 有了 MCP:大模型长出了"双手",可以操作计算器、查天气、读文件、调用 API
一、MCP 是什么?
MCP 是 Anthropic 提出的开放协议,定义了大模型与外部工具之间的通信标准。核心概念:
- MCP Server:提供工具的服务端,注册各种可调用功能
- MCP Client:与大模型对接的客户端,负责将工具暴露给模型
- stdio 通信:默认通过标准输入输出进行双向通信
arduino
┌─────────────┐ stdio ┌──────────────┐
│ LLM (模型) │ ←────────────→ │ MCP Client │
└─────────────┘ └──────┬───────┘
│ stdio
┌───────┴───────┐
│ MCP Server │
│ ┌──────────┐ │
│ │ Tool 1 │ │
│ │ Tool 2 │ │
│ │ Tool 3 │ │
│ └──────────┘ │
└────────────────┘
二、快速上手:写一个 MCP 服务端
环境准备
bash
pip install mcp
第一个 MCP Server
使用 FastMCP(官方推荐的高级 API,类似 FastAPI 的装饰器语法):
python
from mcp.server.fastmcp import FastMCP
from datetime import datetime
# 1. 初始化 MCP 服务器
mcp_server = FastMCP("my-first-mcp-server")
# 2. 注册工具:加法计算器
@mcp_server.tool()
async def calculate_add(a: float, b: float) -> str:
"""计算两个数字的加法"""
result = a + b
return f"加法计算结果:{a} + {b} = {result}"
# 3. 注册工具:获取当前时间
@mcp_server.tool()
async def get_current_time() -> str:
"""获取当前系统的本地时间"""
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return f"当前系统时间:{now}"
# 4. 注册工具:天气查询
@mcp_server.tool()
async def query_weather(city: str) -> str:
"""查询指定城市的天气"""
weather_data = {
"北京": "晴,25℃",
"上海": "多云,28℃",
"广州": "小雨,30℃"
}
result = weather_data.get(city, f"未找到{city}的天气数据")
return f"{city}天气:{result}"
# 5. 启动服务
if __name__ == "__main__":
print("✅ MCP 服务已启动,等待客户端连接...")
mcp_server.run() # 默认启动 stdio 通信
关键点:
- 用
@mcp_server.tool()装饰器注册工具 - 工具函数必须是
async的 - 返回值必须是
str(FastMCP 会自动包装为TextContent) - 函数的 docstring 会被大模型用来理解工具用途
三、客户端调用:让大模型自动使用工具
服务端写好了,现在需要一个客户端把工具"绑定"给大模型。
完整客户端代码
python
import asyncio
import os
from dotenv import load_dotenv
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
# 加载环境变量
load_dotenv()
api_key = os.getenv('DASHSCOPE_API_KEY', '')
base_url = os.getenv('DASHSCOPE_API_BASE', '')
# 初始化大模型(以通义千问为例,兼容 OpenAI 接口)
llm = ChatOpenAI(
base_url=base_url,
model='qwen3.5-plus',
api_key=api_key,
)
async def main():
# 1. 配置 MCP 服务端连接参数
server_params = StdioServerParameters(
command="python",
args=["my_mcp_tools/weather_server.py"]
)
# 2. 建立 stdio 连接
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
print("✅ 已成功连接到 MCP Server!")
# 3. 获取服务端所有可用工具
tools_response = await session.list_tools()
print(f"🛠️ 发现工具:{[t.name for t in tools_response.tools]}")
# 4. 将 MCP 工具格式转换为 OpenAI 函数格式
lc_tools = []
for t in tools_response.tools:
lc_tools.append({
"type": "function",
"function": {
"name": t.name,
"description": t.description,
"parameters": t.inputSchema
}
})
# 5. 将工具绑定到 LLM
llm_with_tools = llm.bind_tools(lc_tools)
# 6. 向大模型提问(让它自己决定用什么工具)
query = "看下现在的时间,然后帮我查北京天气,再算一下 15.5 + 20.3"
print(f"🧑 提问: {query}")
response = llm_with_tools.invoke([HumanMessage(content=query)])
# 7. 执行工具调用
if response.tool_calls:
for tool_call in response.tool_calls:
print(f"\n🤖 大模型调用工具: {tool_call['name']}")
print(f"📦 参数: {tool_call['args']}")
result = await session.call_tool(
tool_call['name'],
arguments=tool_call['args']
)
print(f"🎯 结果: {result.content[0].text}")
else:
print(f"\n🤖 大模型回答: {response.content}")
if __name__ == "__main__":
asyncio.run(main())
运行结果示例
css
✅ 已成功连接到 MCP Server!
🛠️ 发现工具:['calculate_add', 'get_current_time', 'query_weather']
🧑 提问: 看下现在的时间,然后帮我查北京天气,再算一下 15.5 + 20.3
🤖 大模型调用工具: get_current_time
📦 参数: {}
🎯 结果: 当前系统时间:2026-05-13 14:30:00
🤖 大模型调用工具: query_weather
📦 参数: {'city': '北京'}
🎯 结果: 北京天气:晴,25℃
🤖 大模型调用工具: calculate_add
📦 参数: {'a': 15.5, 'b': 20.3}
🎯 结果: 加法计算结果:15.5 + 20.3 = 35.8
大模型自主决定了调用哪几个工具、传什么参数、按什么顺序调用------你只需要注册工具,剩下的交给模型。
四、核心流程解析
整个 MCP 调用链路分为 6 步:
markdown
1. 启动 MCP Server → 注册工具
2. Client 建立 stdio 连接
3. Client 拉取工具列表(list_tools)
4. 工具格式转换 → MCP Schema → OpenAI Function Schema
5. 绑定工具到 LLM(bind_tools)
6. 用户提问 → LLM 自主决策 → 调用工具 → 返回结果
关键知识点
| 概念 | 说明 |
|---|---|
| stdio 通信 | MCP 默认通过标准输入输出通信,类似管道 |
| 工具 Schema | 每个工具有 name、description、inputSchema 三个字段 |
| 格式转换 | MCP 工具格式 → OpenAI 函数格式,让大模型能理解 |
| 自主决策 | 大模型自己判断是否需要调用工具、调用哪个、传什么参数 |
| 异步编程 | 所有 MCP 通信都是非阻塞的 async/await |
五、实战:PaddleOCR MCP 部署
理论讲完了,来看一个真实场景:把 PaddleOCR 部署为 MCP 工具,让大模型能"看懂"图片。
场景
本地有一台 GPU 服务器,部署了 PaddleOCR-VL-1.5。现在想让本机的大模型应用(如 Cursor、Claude Desktop)能调用 OCR 能力。
服务端核心代码
python
from mcp.server.fastmcp import FastMCP
from paddleocr import PaddleOCR
mcp_server = FastMCP("paddleocr-server")
ocr = PaddleOCR(use_angle_cls=True, lang='ch')
@mcp_server.tool()
async def ocr_image(image_path: str) -> str:
"""对图片进行 OCR 识别,返回文字内容"""
result = ocr.ocr(image_path)
if result and result[0]:
texts = [line[1][0] for line in result[0]]
return "\n".join(texts)
return "未识别到文字"
@mcp_server.tool()
async def ocr_layout(image_path: str) -> str:
"""分析图片的版面结构"""
# 返回版面分析结果(标题、段落、表格等区域)
...
if __name__ == "__main__":
mcp_server.run()
部署要点
| 维度 | 说明 |
|---|---|
| 显存需求 | PaddleOCR-VL-1.5 约需 2-4GB 显存 |
| 跨局域网 | 通过 MCP stdio 隧道转发,或改用 HTTP 传输 |
| systemd 部署 | 写一个 service 文件,开机自启 |
| Docker 部署 | 用 NVIDIA Container Toolkit 挂载 GPU |
六、MCP 的广阔前景
MCP 的价值远不止"让大模型查个天气":
- 本地文件操作:读文件、写文件、搜索代码
- 数据库查询:让大模型直接查询你的业务数据
- API 调用:把任何 REST API 封装为 MCP 工具
- 系统命令:让大模型执行 shell 命令(注意安全)
- 多模态:图像识别、语音转写、视频分析
- Agent 协作:多个 MCP Server 组成工具生态
核心思路:你只需要写工具,大模型会自动学会使用它们。
总结
MCP 协议让大模型从"只能聊天"变成了"能干活"。本文从零讲解了:
- MCP 是什么、为什么需要它
- 用 FastMCP 写一个服务端(3 个工具)
- 用 LangChain + 通义千问做客户端调用
- 完整的 6 步调用链路
- PaddleOCR MCP 部署实战
下一步你可以:
- 把自己的业务工具封装为 MCP Server
- 探索 MCP 的 HTTP 传输模式(适合远程部署)
- 关注 MCP 官方生态(github.com/modelcontextprotocol)
本文基于实际项目经验编写,代码均可直接运行。如有疑问,欢迎在评论区交流。