深入理解 MCP 协议:从底层通信到 MySQL 实战接入
最近在做一个课设,想让 AI 能直接查询我本地的 MySQL 数据库。网上翻了一圈,全是"MCP 是 AI 的 USB-C 接口"这类比喻,看完还是不知道怎么用。花了三天踩坑,把底层逻辑搞清楚了,写下来给自己留个记录。
先说结论:MCP 解决的是一个被忽视已久的根本问题
为什么需要 MCP?
在 MCP 出现之前,如果你想让 ChatGPT 或者任何 LLM 能查你的数据库,你得这么做:
- 自己写一套 Function Calling 的接口定义
- 把数据库操作封装成函数
- 把函数 schema 塞进 prompt
- 解析模型返回的 JSON,手动执行
每接入一个新工具,就要重新写一套。换个模型(比如从 GPT 换成 DeepSeek),又要重新适配。
这就好比家里每个电器都有一个独立遥控器,需要逐个编写接口;而 MCP 则像全屋智能中枢,通过统一协议接入所有设备。
听起来是个比喻,但工程上的含义很实在:写一次 MCP Server,任何支持 MCP 的 AI 客户端都能直接用。
MCP 的三层架构,用人话说清楚
MCP 的核心思想是将 LLM 的核心推理能力与外部功能的实现细节解耦,通过定义清晰的主机(Host)、客户端(Client)和服务器(Server)架构实现灵活、可扩展且安全的 LLM 应用生态。
Host / Client / Server,三个词一出来,大多数人就晕了。我当时也是。后来我用一个具体场景理解了:
你(用户) ↓ 提问 Claude Desktop(Host,宿主应用) ↓ 转发请求 MCP Client(内嵌在 Host 里,负责通信) ↓ 标准化协议 MCP Server(你自己写的,封装了数据库/文件/API) ↓ 执行 MySQL / 本地文件 / 第三方 API关键点:MCP Server 是你来写的,是把你的工具"翻译"成 AI 能懂的标准接口。
Host 是 Claude Desktop、Cursor 这类应用,它们已经帮你实现了 MCP Client,你不需要管。你只需要写 Server,告诉 AI “我有哪些工具、每个工具怎么用”。
底层通信:JSON-RPC 2.0,没有那么神秘
我踩的第一个坑就是以为 MCP 是某种复杂的私有协议。
其实不是。MCP 协议使用 JSON-RPC 2.0 作为消息传输格式。
JSON-RPC 2.0 是一个极简的远程调用协议,一条请求长这样:
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"query_database","arguments":{"sql":"SELECT * FROM users WHERE id = 1"}}}Server 返回:
{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"[{\"id\": 1, \"name\": \"张三\", \"email\": \"zhangsan@example.com\"}]"}]}}就这么简单。AI 说"我要调用 query_database,参数是这条 SQL",Server 执行完把结果返回去,AI 再把结果转成自然语言告诉你。
理解了这一层,后面的东西就都清晰了。
我踩的坑:传输方式的选择
MCP 支持两种传输方式,这里是我卡了最久的地方。
1. stdio(标准输入输出)
Server 作为一个本地子进程运行,Host 通过标准输入输出和它通信。
# Server 启动方式if__name__=="__main__":importasynciofrommcp.server.stdioimportstdio_server asyncio.run(stdio_server(server))优点:配置简单,本地开发首选。
缺点:只能本地用,无法远程访问。
2. HTTP + SSE(Streamable HTTP)
新的传输规范中,服务端作为独立进程运行,支持多客户端连接,基于 HTTP POST/GET 请求实现,可选用服务器推送事件(SSE)实现多消息流式传输。
# Server 以 HTTP 方式启动if__name__=="__main__":importuvicornfrommcp.server.fastmcpimportFastMCP app=FastMCP("my-server")uvicorn.run(app.get_asgi_app(),host="0.0.0.0",port=8000)我踩的坑:我一开始用的是老版本的 SSE 实现,结果发现协议已经改了,连接一直断。后来看了官方 changelog 才知道,2025年初协议把传输层做了破坏性更新,旧的 SSE 方案已经废弃,要用新的 Streamable HTTP。
教训:用 MCP 之前先确认 SDK 版本,pip show mcp看一眼,0.9.0 以上才支持新传输协议。
写一个真实可用的 MCP Server(查 MySQL)
说了这么多,来看实际代码。这是我课设里用的版本,精简过,核心逻辑都在。
环境准备
pipinstallmcp pymysql完整代码
importpymysqlimportjsonfrommcp.server.fastmcpimportFastMCP# 初始化 MCP Servermcp=FastMCP("mysql-assistant")# 数据库配置DB_CONFIG={"host":"localhost","port":3306,"user":"root","password":"your_password","database":"your_db","charset":"utf8mb4"}defget_connection():returnpymysql.connect(**DB_CONFIG)# ========== 定义工具 ==========@mcp.tool()defquery_data(sql:str)->str:""" 执行 SELECT 查询语句,返回查询结果。 只允许 SELECT,不允许增删改操作。 Args: sql: 要执行的 SELECT SQL 语句 Returns: JSON 格式的查询结果 """# 安全校验:只允许 SELECTsql_upper=sql.strip().upper()ifnotsql_upper.startswith("SELECT"):return"错误:只允许执行 SELECT 查询语句"try:conn=get_connection()cursor=conn.cursor(pymysql.cursors.DictCursor)cursor.execute(sql)results=cursor.fetchall()cursor.close()conn.close()ifnotresults:return"查询结果为空"returnjson.dumps(results,ensure_ascii=False,default=str)exceptpymysql.Errorase:returnf"数据库错误:{str(e)}"@mcp.tool()defget_table_schema(table_name:str)->str:""" 获取指定数据表的字段结构信息。 在写 SQL 之前先调用这个工具了解表结构。 Args: table_name: 表名 Returns: 表的字段名、类型、是否为空等信息 """try:conn=get_connection()cursor=conn.cursor()cursor.execute(f"DESCRIBE `{table_name}`")columns=cursor.fetchall()cursor.close()conn.close()result=f"表{table_name}的结构:\n"result+="字段名 | 类型 | 允许空 | 键 | 默认值\n"result+="-"*60+"\n"forcolincolumns:result+=" | ".join(str(c)forcincol)+"\n"returnresultexceptpymysql.Errorase:returnf"获取表结构失败:{str(e)}"@mcp.tool()deflist_tables()->str:""" 列出当前数据库中所有的表名。 不知道有哪些表时,先调用这个。 """try:conn=get_connection()cursor=conn.cursor()cursor.execute("SHOW TABLES")tables=[row[0]forrowincursor.fetchall()]cursor.close()conn.close()return"数据库中的表:\n"+"\n".join(f"-{t}"fortintables)exceptpymysql.Errorase:returnf"获取表列表失败:{str(e)}"# ========== 启动 ==========if__name__=="__main__":mcp.run()# 默认 stdio 模式,用于本地 Claude Desktop配置到 Claude Desktop
在claude_desktop_config.json里加上:
{"mcpServers":{"mysql-assistant":{"command":"python","args":["/绝对路径/mysql_mcp_server.py"]}}}重启 Claude Desktop,然后直接问它:
“帮我看看 users 表里注册时间最早的 10 个用户”
它会自动调用list_tables→get_table_schema→query_data,最后给你一个完整答案。不需要你写任何 SQL。
一个容易忽视的设计细节:工具描述比代码更重要
我刚开始写的时候,工具描述就一句话,结果 AI 经常调错工具或者传错参数。
后来我意识到:AI 是通过读你写的 docstring 来决定"该不该调用这个工具、怎么调用"的。描述越精确,AI 的行为越准确。
# ❌ 模糊的描述@mcp.tool()defquery(sql:str)->str:"""执行SQL"""...# ✅ 清晰的描述@mcp.tool()defquery_data(sql:str)->str:""" 执行 SELECT 查询语句,返回查询结果。 只允许 SELECT,不允许增删改操作。 在查询前请先用 get_table_schema 了解表结构,避免写出错误的字段名。 Args: sql: 完整的 SELECT SQL 语句,例如:SELECT * FROM users LIMIT 10 """...这个细节在所有 MCP 教程里几乎没人提,但它直接决定了 Agent 的实际可用性。
MCP 和 Function Calling 的本质区别
有人会问:这和 OpenAI 的 Function Calling 有什么区别?
本质区别只有一个:Function Calling 是模型私有的,MCP 是跨模型的标准。
| 维度 | Function Calling | MCP |
|---|---|---|
| 标准化程度 | 各家模型实现不同 | 统一开放协议 |
| 复用性 | 换模型要重写 | 一次编写,到处使用 |
| 工具发现 | 需要手动在 prompt 中声明 | Server 自动暴露工具列表 |
| 部署方式 | 嵌入应用代码 | 独立进程,可远程部署 |
OpenAI 于 2025 年 3 月 27 日宣布其 Agents SDK 已正式支持 MCP,这标志着该协议正式成为大模型与外部数据交互的行业标准。
连 OpenAI 都跟进了,这件事基本盖棺定论:MCP 会成为 AI 工具调用的基础设施标准,就像 HTTP 之于 Web。
最后说几句
MCP 本身不复杂,复杂的是周边生态还在快速变化——协议在更新、SDK 在迭代、各家客户端的支持程度也不一样。
我觉得现在学 MCP 最值的投资是:把"写 MCP Server"这个技能练熟。不管 AI 工具链怎么变,能给 LLM 快速接入各种数据源的人,在接下来几年会非常抢手。
有问题欢迎评论区讨论,如果帮到你了点个赞,我会继续更新这个系列。