1. MCP 到底解决了什么问题第一次听到 MCP 这个词是在一个做 AI 应用的朋友群里。有人甩了张架构图说以后大模型接工具不用再一家一家写适配了统一走 MCP 就行。当时我的第一反应是又是一个协议标准这年头协议还少吗但仔细看完 Anthropic 放出来的文档再动手跑通几个 MCP Server 之后我改主意了——这东西确实踩在了痛点上。MCP全称Model Context Protocol翻译过来叫“模型上下文协议”。名字听着学术其实干的事情很朴素给大语言模型LLM装一个标准化的“外设接口”。你可以把它理解成 USB-C。以前每个外设厂商都有自己的接口键盘一个口、鼠标一个口、打印机一个口电脑上插得满满当当。USB-C 出来之后不管什么设备统一插同一个口就行。MCP 想做的就是 LLM 世界里的那个 USB-C。那在 MCP 之前LLM 应用是怎么接外部能力的主流做法是Function Calling。你在调用模型的时候把一堆函数定义塞进请求里模型判断该调哪个返回一个 JSON你的代码再去执行。这套机制能用但问题很明显函数定义是跟应用绑死的。你给 A 应用写了一套查数据库的函数换到 B 应用想复用得把定义和实现全部搬过去接口格式还不一定对得上。更麻烦的是当工具数量涨到几十个每次请求都要把全部定义塞进上下文token 消耗直接起飞。MCP 的思路是把“提供能力”这件事从应用里抽出来变成一个独立的MCP Server。这个 Server 对外声明自己有哪些工具、哪些资源、哪些提示模板LLM 应用作为MCP Client去连接它动态发现能力。这样一来一个数据库查询 Server 写一次所有支持 MCP 的客户端都能用。工具的定义不再占用每次请求的上下文而是通过协议在连接时协商。这个设计带来的直接好处有三个。第一是解耦能力的提供方和消费方彻底分开各管各的。第二是复用社区里已经有人把 Figma、Playwright、Blender、Unity 这些工具都封装成了 MCP Server你拿来就能接。第三是动态性Server 可以随时增减工具Client 不需要重新编译或改代码。适合谁来了解这套东西如果你只是拿大模型聊聊天那暂时用不上。但如果你在做 AI Agent、在搭 RAG 系统、在把 LLM 往业务系统里嵌或者你手里有一堆内部工具想让 AI 直接调用那 MCP 值得花时间研究。它不复杂但能省掉大量重复的适配工作。2. MCP 的核心架构与协议设计2.1 三个角色Host、Client、ServerMCP 的架构里一共有三个角色理解清楚它们的分工后面看代码就不会晕。Host是宿主应用也就是用户直接交互的那个程序。比如一个 IDE 插件、一个聊天客户端、一个桌面 Agent。Host 负责管理整个会话决定什么时候去连 Server什么时候把工具列表给模型看。Client是 Host 内部的一个组件负责跟 Server 建立连接、发送请求、接收响应。一个 Host 可以同时跑多个 Client每个 Client 连一个 Server。Client 和 Server 之间是一对一的连接关系。Server是能力提供方。它对外暴露三类东西Tools可执行的函数、Resources可读取的数据、Prompts预定义的提示模板。Server 不关心谁在调它只负责按协议响应请求。这三者的关系可以类比成Host 是电脑主机Client 是 USB 控制器Server 是 U 盘或打印机。主机通过控制器去识别和操作外设外设只管按标准协议应答。2.2 传输层stdio 与 SSEMCP 目前支持两种传输方式选哪种取决于你的部署场景。stdio是最简单的方式。Server 作为一个子进程启动Client 通过标准输入输出跟它通信。这种方式适合本地工具比如文件系统操作、本地数据库查询、命令行工具封装。优点是零网络配置进程生命周期由 Client 管理安全性好。缺点是只能本机用没法跨机器共享。SSEServer-Sent Events是网络传输方式。Server 跑在一个 HTTP 服务上Client 通过 SSE 长连接接收 Server 推送的消息通过普通 HTTP POST 发送请求。这种方式适合远程服务、多客户端共享的场景。比如你把一个内部知识库封装成 MCP Server 部署在服务器上团队里所有人的 AI 助手都能连上去用。提示如果你只是自己本地用优先选 stdio配置简单、调试方便。需要团队共享或跨网络访问时再上 SSE。2.3 协议消息格式JSON-RPC 2.0MCP 的底层消息格式用的是JSON-RPC 2.0。这是一个很成熟的远程调用协议请求和响应都是 JSON 对象。一个典型的请求长这样{ jsonrpc: 2.0, id: 1, method: tools/call, params: { name: query_database, arguments: { sql: SELECT * FROM users LIMIT 10 } } }响应则是{ jsonrpc: 2.0, id: 1, result: { content: [ { type: text, text: id | name | email\n1 | Alice | aliceexample.com } ] } }协议定义了几组核心方法initialize用于握手协商能力tools/list列出可用工具tools/call调用工具resources/list和resources/read用于资源读取prompts/list和prompts/get用于提示模板。整个协议不算大核心方法就十来个花半小时翻一遍文档就能上手。2.4 能力协商机制连接建立时Client 和 Server 会通过initialize方法交换各自支持的能力。Server 告诉 Client“我支持 tools、resources、prompts还支持日志和进度通知。”Client 告诉 Server“我支持 roots 和 sampling。”这个协商过程决定了后续哪些功能可用。这个设计的好处是向前兼容。以后协议扩展了新能力老 Client 连新 Server 时双方只启用共同支持的部分不会因为版本不一致直接崩掉。3. 动手写一个 MCP Server3.1 环境准备与依赖安装理论说再多不如跑一遍。我用 Python 来演示因为官方 SDK 对 Python 支持最完善。先建一个干净的项目目录mkdir mcp-demo cd mcp-demo python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install mcp官方 SDK 包名就叫mcp装完之后你会得到mcp.server和mcp.client两个模块。Server 端我们主要用mcp.server.fastmcp这是封装好的高层 API写起来很省事。3.2 用 FastMCP 定义一个查询工具假设我们要做一个查询本地 SQLite 数据库的 Server。先准备一个测试库import sqlite3 conn sqlite3.connect(demo.db) conn.execute(CREATE TABLE IF NOT EXISTS users (id INTEGER, name TEXT, email TEXT)) conn.execute(INSERT OR IGNORE INTO users VALUES (1, Alice, aliceexample.com)) conn.execute(INSERT OR IGNORE INTO users VALUES (2, Bob, bobexample.com)) conn.commit() conn.close()然后写 Server 代码from mcp.server.fastmcp import FastMCP import sqlite3 mcp FastMCP(demo-db-server) mcp.tool() def query_users(limit: int 10) - str: 查询用户列表返回指定条数的用户记录。 conn sqlite3.connect(demo.db) cursor conn.execute(SELECT id, name, email FROM users LIMIT ?, (limit,)) rows cursor.fetchall() conn.close() if not rows: return 没有查询到用户记录。 lines [id | name | email] for row in rows: lines.append(f{row[0]} | {row[1]} | {row[2]}) return \n.join(lines) if __name__ __main__: mcp.run()就这么几行。mcp.tool()装饰器把函数注册成一个工具函数的 docstring 会自动变成工具描述参数类型注解会变成 JSON Schema。模型看到的就是这些信息它根据描述判断什么时候该调这个工具。3.3 注册资源与提示模板除了工具MCP 还支持资源和提示模板。资源适合暴露只读数据比如配置文件、文档内容。提示模板适合预定义一些常用的提示词结构。mcp.resource(config://app) def get_config() - str: 返回应用配置信息。 return {theme: dark, language: zh-CN} mcp.prompt() def analyze_user(user_id: int) - str: 生成分析指定用户的提示词。 return f请分析用户 {user_id} 的行为特征并给出运营建议。资源用 URI 标识Client 可以通过resources/read读取。提示模板则是给用户或上层应用调用的模型本身不会主动触发。3.4 本地调试与验证写完之后怎么验证官方提供了一个mcp dev命令可以启动一个调试界面mcp dev server.py它会打开一个本地网页你可以在里面看到所有注册的工具、资源、提示还能手动调用测试。这个工具在开发阶段非常有用省得你每次都去接一个完整的 Client 来测。如果不想用调试界面也可以直接跑起来然后用官方的 Client 库写个测试脚本import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): params StdioServerParameters(commandpython, args[server.py]) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools() print(可用工具:, [t.name for t in tools.tools]) result await session.call_tool(query_users, {limit: 5}) print(调用结果:, result.content[0].text) asyncio.run(main())跑通这个脚本说明你的 Server 已经能正常工作了。4. 把 MCP 接进实际应用4.1 在 Claude Desktop 中配置 MCP Server最直接的验证方式是把 Server 接到 Claude Desktop 里。找到配置文件macOS 在~/Library/Application Support/Claude/claude_desktop_config.jsonWindows 在%APPDATA%\Claude\claude_desktop_config.json。写入{ mcpServers: { demo-db: { command: python, args: [/absolute/path/to/server.py] } } }重启 Claude Desktop如果配置正确输入框旁边会出现一个工具图标点开能看到query_users。这时候你直接问“帮我查一下前5个用户”模型就会自动调用这个工具。注意路径一定要写绝对路径相对路径在 Desktop 环境下经常找不到文件。另外 Python 最好用虚拟环境里的解释器路径避免依赖缺失。4.2 在代码中集成 MCP Client如果你在开发自己的 AI 应用需要在代码里集成 MCP Client。核心流程是启动 Server 进程、建立会话、获取工具列表、把工具列表转换成你的 LLM 框架能识别的格式、在模型返回工具调用时转发给 MCP Server。以 OpenAI 风格的 Function Calling 为例转换逻辑大概是这样async def build_tool_specs(session): tools await session.list_tools() specs [] for tool in tools.tools: specs.append({ type: function, function: { name: tool.name, description: tool.description, parameters: tool.inputSchema } }) return specs模型返回tool_calls时把name和arguments取出来调session.call_tool(name, arguments)再把结果塞回对话历史。整个链路就通了。4.3 多 Server 并行接入实际项目里往往需要同时接多个 Server。比如一个查数据库、一个操作浏览器、一个读文件系统。MCP Client 支持同时管理多个连接每个连接独立会话。你只需要把各 Server 的工具列表合并注意处理重名问题。sessions {} for name, params in server_configs.items(): read, write await stdio_client(params).__aenter__() session await ClientSession(read, write).__aenter__() await session.initialize() sessions[name] session all_tools [] for name, session in sessions.items(): tools await session.list_tools() for tool in tools.tools: tool_name f{name}__{tool.name} # 加前缀避免重名 all_tools.append(convert_to_spec(tool, tool_name))调用的时候根据前缀路由到对应的 session。这个模式在工具数量多的时候特别有用能有效组织能力。5. 常见问题与排查技巧5.1 连接失败排查表现象可能原因排查方法Client 启动后看不到工具Server 进程启动失败手动在终端跑 Server 脚本看是否报错工具调用返回空参数 schema 不匹配检查函数参数类型注解是否完整中文返回乱码编码未指定在 Server 启动时设置PYTHONIOENCODINGutf-8SSE 连接超时防火墙或端口未开放用 curl 测试 SSE 端点是否可达工具描述不生效docstring 格式问题确保 docstring 是函数的第一条语句5.2 参数 Schema 的坑FastMCP 会根据函数签名自动生成 JSON Schema但有些类型它推断不出来。比如你用了Optional[str]生成的 schema 里type可能是[string, null]某些模型对这个格式支持不好。稳妥的做法是尽量用简单类型复杂结构用str接收 JSON 字符串在函数内部自己解析。另一个坑是默认值。带默认值的参数在 schema 里会标记为非必填但模型有时候还是会漏传。如果你的逻辑依赖某个参数最好在函数内部再做一次兜底判断。5.3 超时与长任务处理MCP 的工具调用默认没有超时限制但实际使用中如果一个工具跑了 30 秒还没返回很多 Client 会主动断开。对于耗时操作正确做法是利用 MCP 的进度通知机制定期发送进度更新让 Client 知道任务还在跑。mcp.tool() async def long_task(ctx, steps: int 10) - str: for i in range(steps): await ctx.report_progress(i 1, steps) await asyncio.sleep(1) return 任务完成ctx是 FastMCP 自动注入的上下文对象用它来发进度、记日志都很方便。5.4 安全性注意事项MCP Server 本质上是在暴露系统能力安全边界必须划清楚。几个原则第一最小权限Server 只暴露必要的工具不要图省事把整个文件系统或数据库都开放出去。第二输入校验模型生成的参数不可信SQL 查询要参数化文件路径要限制在允许的目录内。第三审计日志每次工具调用都记下来出了问题能追溯。提示如果 Server 要暴露给多人使用务必在 SSE 层加认证不要裸奔在公网上。6. 生态现状与扩展方向MCP 的生态在过去一年里长得很快。官方和社区已经贡献了大量现成的 Server覆盖了常见的开发工具和平台。比如Playwright MCP可以让 AI 直接操控浏览器做自动化测试Figma MCP能读取设计稿信息Blender MCP和Unity MCP把 3D 工具接进了 AI 工作流。国内也有团队在做蓝湖 MCP这类设计协作工具的接入。从趋势上看MCP 正在成为 LLM 应用接入外部能力的默认选择。它的价值不在于技术有多复杂而在于把一件重复劳动标准化了。以前每接一个新工具都要写一遍适配代码现在只要有一个 MCP Server所有支持 MCP 的客户端都能直接用。如果你手里有内部系统想接 AI我的建议是先从一个小工具开始用 FastMCP 写个最简单的 Server跑通整个链路。感受一下模型自动调用工具的过程再逐步扩展。不要一上来就搞大而全的平台容易卡在细节里出不来。最后分享一个我在调试时常用的小技巧在 Server 的工具函数里加一行日志把收到的参数原样打印到 stderr。因为 stdio 模式下 stdout 被协议占用stderr 才是你的调试输出通道。Client 那边通常会把 stderr 转发到日志里这样你就能看到模型实际传了什么参数排查问题快很多。