深入理解 MCP 协议:从底层通信到 MySQL 实战接入
深入理解 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 DesktopHost宿主应用 ↓ 转发请求 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\: \zhangsanexample.com\}]}]}}就这么简单。AI 说我要调用 query_database参数是这条 SQLServer 执行完把结果返回去AI 再把结果转成自然语言告诉你。理解了这一层后面的东西就都清晰了。我踩的坑传输方式的选择MCP 支持两种传输方式这里是我卡了最久的地方。1. stdio标准输入输出Server 作为一个本地子进程运行Host 通过标准输入输出和它通信。# Server 启动方式if__name____main__:importasynciofrommcp.server.stdioimportstdio_server asyncio.run(stdio_server(server))优点配置简单本地开发首选。缺点只能本地用无法远程访问。2. HTTP SSEStreamable HTTP新的传输规范中服务端作为独立进程运行支持多客户端连接基于 HTTP POST/GET 请求实现可选用服务器推送事件SSE实现多消息流式传输。# Server 以 HTTP 方式启动if__name____main__:importuvicornfrommcp.server.fastmcpimportFastMCP appFastMCP(my-server)uvicorn.run(app.get_asgi_app(),host0.0.0.0,port8000)我踩的坑我一开始用的是老版本的 SSE 实现结果发现协议已经改了连接一直断。后来看了官方 changelog 才知道2025年初协议把传输层做了破坏性更新旧的 SSE 方案已经废弃要用新的 Streamable HTTP。教训用 MCP 之前先确认 SDK 版本pip show mcp看一眼0.9.0 以上才支持新传输协议。写一个真实可用的 MCP Server查 MySQL说了这么多来看实际代码。这是我课设里用的版本精简过核心逻辑都在。环境准备pipinstallmcp pymysql完整代码importpymysqlimportjsonfrommcp.server.fastmcpimportFastMCP# 初始化 MCP ServermcpFastMCP(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_uppersql.strip().upper()ifnotsql_upper.startswith(SELECT):return错误只允许执行 SELECT 查询语句try:connget_connection()cursorconn.cursor(pymysql.cursors.DictCursor)cursor.execute(sql)resultscursor.fetchall()cursor.close()conn.close()ifnotresults:return查询结果为空returnjson.dumps(results,ensure_asciiFalse,defaultstr)exceptpymysql.Errorase:returnf数据库错误{str(e)}mcp.tool()defget_table_schema(table_name:str)-str: 获取指定数据表的字段结构信息。 在写 SQL 之前先调用这个工具了解表结构。 Args: table_name: 表名 Returns: 表的字段名、类型、是否为空等信息 try:connget_connection()cursorconn.cursor()cursor.execute(fDESCRIBE {table_name})columnscursor.fetchall()cursor.close()conn.close()resultf表{table_name}的结构\nresult字段名 | 类型 | 允许空 | 键 | 默认值\nresult-*60\nforcolincolumns:result | .join(str(c)forcincol)\nreturnresultexceptpymysql.Errorase:returnf获取表结构失败{str(e)}mcp.tool()deflist_tables()-str: 列出当前数据库中所有的表名。 不知道有哪些表时先调用这个。 try:connget_connection()cursorconn.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 CallingMCP标准化程度各家模型实现不同统一开放协议复用性换模型要重写一次编写到处使用工具发现需要手动在 prompt 中声明Server 自动暴露工具列表部署方式嵌入应用代码独立进程可远程部署OpenAI 于 2025 年 3 月 27 日宣布其 Agents SDK 已正式支持 MCP这标志着该协议正式成为大模型与外部数据交互的行业标准。连 OpenAI 都跟进了这件事基本盖棺定论MCP 会成为 AI 工具调用的基础设施标准就像 HTTP 之于 Web。最后说几句MCP 本身不复杂复杂的是周边生态还在快速变化——协议在更新、SDK 在迭代、各家客户端的支持程度也不一样。我觉得现在学 MCP 最值的投资是把写 MCP Server这个技能练熟。不管 AI 工具链怎么变能给 LLM 快速接入各种数据源的人在接下来几年会非常抢手。有问题欢迎评论区讨论如果帮到你了点个赞我会继续更新这个系列。