MCP 学习笔记:从核心概念到安全边界,一次搞懂 Model Context Protocol 架构
1. 先搞清楚 MCP 到底解决什么问题MCP 全称 Model Context Protocol是一个开放协议用来把大语言模型应用和外部工具、数据源、上下文系统连起来。它不替代大模型也不替代普通 API而是给 AI 应用提供一套标准化方式让模型应用能一致地发现、访问、调用外部能力。适合谁适合刚接触 MCP、想搞懂 Host/Client/Server 三层架构、并且想亲手跑通一次工具调用的开发者。在没有 MCP 的时候一个智能助手要访问文件系统、数据库、搜索服务开发者往往得分别写三套封装。工具少还能忍工具一多就出问题不同 AI 应用重复封装相同工具复用性差工具描述、参数格式、返回格式、错误格式没有统一标准权限控制分散难以统一审计模型上下文里要塞大量工具定义上下文成本高本地工具、远程服务、企业内部系统之间缺少统一连接方式。MCP 的思路是把这层连接抽象出来。AI 应用不再直接面向每个外部系统写死集成逻辑而是通过 MCP Client 连接不同的 MCP ServerServer 再以统一协议暴露工具、资源和提示模板。这样工具接入就变得可发现、可复用、可授权、可审计。我试过把一个本地文件查询工具从“直接写死在应用里”改成 MCP Server 暴露最大的感受是模型侧看到的工具 schema 变干净了权限声明也集中到了一处排查问题时不用在应用代码和工具代码之间来回跳。这一篇会按“概念—架构—最小配置—调用验证—安全边界”的顺序走一遍重点放在能复制、能跑通、能排错。你跟着做完至少能得到一个本地 MCP Server 的最小可运行配置以及一次真实的工具调用结果。2. TaoToken 前置准备把模型侧入口先配好MCP 本身是协议层它不提供模型推理能力。你要验证一次完整的工具调用链路除了 MCP Server还需要一个能发起对话、能识别工具调用的模型入口。这里我用 TaoToken 来做模型侧接入原因是它的接口形态和常见 OpenAI 兼容接口一致配置成本低适合拿来跑通 MCP 的工具调用验证。先把三个关键信息准备好Base URL、API Key、Model ID。Base URL 用https://taotoken.net/apiAPI Key 在控制台的 API Keys 页面创建Model ID 按你实际要验证的模型填。这三个东西后面在 MCP Client 的配置里会反复出现建议先记在一个临时文件里。创建 Key 的入口在这里https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_learning_notes如果你只是想先看看模型对话效果不急着接 MCP可以先用模型对话页面确认 Key 能用https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_learning_notes接入文档在这里遇到参数不确定时对照看https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_learning_notes这里要强调一点MCP 和 tool calling 不是同一个层级的概念。Tool calling 关注的是模型如何以结构化方式请求调用某个工具MCP 关注的是工具、资源、提示模板如何以标准化协议暴露给不同 AI 应用。Function calling 解决“模型如何调用工具”MCP 解决“工具如何被发现、连接、复用和管理”。两者是互补关系不是替代关系。在实际系统里MCP 暴露出来的 tools 最终仍会被转换成模型可用的 tool schema。模型生成 tool call 后Host 再通过 MCP Client 调用对应 MCP Server。所以你在配置时模型侧只需要一个能识别 tool schema 的接口MCP 侧负责工具发现和转发。如果你打算长期做编码类或 Agent 类任务可以考虑 Coding Plan它在多轮工具调用场景下更省心https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_learning_notes3. 可复制配置本地 MCP Server 最小实现这一节给你一份可以直接复制运行的本地 MCP Server 最小配置。我用 Python 的 FastMCP 来写因为它把 tool、resource、prompt 的声明都封装成了装饰器适合用来理解 MCP Server 的核心职责声明并提供能力。先装依赖pip install mcp然后新建一个文件example_server.pyfrom mcp.server.fastmcp import FastMCP import sys mcp FastMCP(example-server) mcp.tool() def get_weather(city: str) - str: Get weather information for a city. Args: city: City name. return fWeather information for {city}. mcp.resource(config://application) def get_application_config() - str: Return application configuration as a resource. return application configuration content mcp.prompt() def summarize_prompt(topic: str) - str: Return a reusable prompt template. return fPlease summarize the following topic: {topic} if __name__ __main__: print(server started, filesys.stderr) mcp.run(transportstdio)这段代码里mcp.tool()声明可调用工具mcp.resource()声明可读取资源mcp.prompt()声明可复用提示模板mcp.run(transportstdio)表示通过 STDIO 方式运行本地 Server。这里有个容易踩的坑STDIO transport 下标准输入输出通常用于承载协议消息。如果你往 stdout 随意打印普通日志可能破坏协议通信。所以日志要写到 stderr 或日志文件上面代码里print(server started, filesys.stderr)就是正确做法。接下来是 MCP Client 侧的配置。不同 Host 的配置文件路径不一样但核心字段就三个Base URL、Key、Model ID。以常见的 JSON 配置为例{ mcpServers: { example-server: { command: python, args: [/absolute/path/to/example_server.py], env: { TAOTOKEN_BASE_URL: https://taotoken.net/api, TAOTOKEN_API_KEY: sk-你的Key, TAOTOKEN_MODEL_ID: 你的ModelID } } } }注意args里要用绝对路径相对路径在不同 Host 的工作目录下容易找不到文件。env里的三个变量是给模型侧接入用的MCP Server 本身不直接消费它们但 Host 在把工具结果回填给模型时会用到。如果你用的是 TOML 格式的配置等价写法是[mcp_servers.example-server] command python args [/absolute/path/to/example_server.py] [mcp_servers.example-server.env] TAOTOKEN_BASE_URL https://taotoken.net/api TAOTOKEN_API_KEY sk-你的Key TAOTOKEN_MODEL_ID 你的ModelID配置写完后先别急着接模型单独启动一次 Server 看能不能正常跑起来python /absolute/path/to/example_server.py如果终端没有报错并且 stderr 输出了server started说明 Server 进程本身没问题。接下来才是让 Host 通过 MCP Client 去连接它。4. 验证请求跑通一次工具调用配置就绪后下一步是验证整条链路Host 创建 MCP ClientClient 连接 Server完成初始化和能力协商获取 tools/resources/prompts然后发起一次工具调用。一个典型的 MCP 交互流程是这样的Host 构造上下文MCP Client 连接 MCP ServerServer 返回可用能力列表Host 把相关工具暴露给模型模型生成工具调用请求MCP Client 调用 ServerServer 执行并返回结果Host 把结果放回模型上下文模型生成最终回答。在 Client 侧逻辑顺序可以概括为初始化、发现能力、调用工具、处理结果。概念性伪代码长这样async def run_client() - None: async with create_mcp_session(example-server) as session: await session.initialize() tools await session.list_tools() print(Available tools:, tools) result await session.call_tool( nameget_weather, arguments{city: London} ) print(Tool result:, result)这段伪代码省略了具体 SDK 的 transport 创建细节重点展示调用顺序。实际跑的时候你要确认三件事Server 能正常启动、Client 能完成初始化、tools 能被正确发现。验证时我建议先单独调list_tools确认返回的工具列表里有get_weather并且它的inputSchema里city是必填的 string。确认无误后再调call_tool参数传{city: London}。如果返回结果里包含Weather information for London.说明工具调用链路是通的。如果你用的是支持远程 MCP 的平台也可以把远程 MCP Server 作为工具源直接交给模型平台由平台负责工具发现、调用和结果回填。这种模式下应用侧集成成本低但具体能力、鉴权方式、审批策略和支持范围取决于对应平台。还有一种更通用的方式应用自己实现 MCP Client先从 MCP Server 获取工具定义再转换成模型可识别的 function/tool schema。转换函数大概是这样def mcp_tool_to_function_tool(mcp_tool: dict) - dict: return { type: function, function: { name: mcp_tool[name], description: mcp_tool.get(description, ), parameters: mcp_tool.get(inputSchema, {type: object}) } }这种方式更灵活适合需要精细控制权限、日志、审计和工具执行策略的系统。转换完成后模型生成 tool call应用再通过 MCP Client 调用对应 Server把结果回填到模型上下文。验证成功后你会看到模型在回答里引用了工具返回的内容而不是凭空编造。这一步是整个 MCP 学习里最有成就感的环节因为它把前面所有概念都串起来了。5. 常见报错排查401、local proxy failed、reading choices、OAuth跑 MCP 工具调用时报错往往集中在几个固定位置。下面按真实报错逐条对照。401 Unauthorized这个最常见基本是 Key 或 Base URL 配错了。先检查TAOTOKEN_API_KEY是不是完整复制有没有多余空格再确认TAOTOKEN_BASE_URL是https://taotoken.net/api不要漏掉/api。如果 Key 是在别的环境创建的确认它没有过期或被禁用。排查顺序是先单独用模型对话页面验证 Key 能用再回到 MCP 配置里检查。local proxy failed这个报错通常出现在 Host 尝试连接本地 MCP Server 时。原因可能是command写错了比如python不在 PATH 里或者args里的脚本路径不是绝对路径。先手动在终端跑一遍python /absolute/path/to/example_server.py确认能启动如果手动能跑但 Host 报这个错检查 Host 的工作目录和权限确保它能访问到脚本文件。reading choices 相关报错这类报错一般出现在模型返回结果解析阶段说明返回结构不符合预期。常见原因是模型侧接口返回的不是标准 tool call 格式或者 MCP Client 在转换工具 schema 时字段名对不上。检查mcp_tool_to_function_tool里inputSchema的键名是否和模型侧期望的一致有些接口要求parameters而不是inputSchema。OAuth 相关报错远程 MCP Server 如果访问用户数据或受保护系统会涉及认证授权。报错通常表现为 token 无效、scope 不足或回调地址不匹配。排查时先确认授权范围是否覆盖了你要调用的工具再检查 token 有没有被放进模型上下文这是错误做法token 不应暴露给模型。授权应该由确定性的安全机制控制而不是依赖模型判断。除了这些还有一类隐蔽问题STDIO 日志污染协议输出。表现是 Server 能启动但 Client 初始化失败或工具列表为空。检查 Server 代码里有没有往 stdout 打印普通日志有的话改成 stderr。排错时建议按链路顺序走先确认 Server 能独立启动再确认 Client 能初始化再确认 tools 能被发现最后确认 call_tool 能返回结果。每一步都单独验证比一次性跑全链路更容易定位问题。如果你在接入文档里找不到对应参数可以对照 API Keys 页面重新生成一个 Key 试试https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_learning_notes6. 安全边界检查清单与后续接入MCP 让 AI 应用能访问更多外部能力也放大了安全边界问题。只要工具能读取敏感数据、调用外部 API 或执行有副作用的动作就必须建立明确的权限边界。下面这份检查清单可以直接拿去对照。工具权限分级低风险工具只读、无副作用、可重复执行可以自动调用但要记录日志中风险工具可能写入非关键数据或影响用户体验需要参数校验必要时请求确认高风险工具涉及删除、付款、权限变更、外部发送或生产环境操作必须人工确认并保留审计和回滚机制。认证与授权只授予完成任务所需的最小权限区分只读权限和写入权限敏感操作需要用户确认或管理员审批访问令牌不应暴露给模型上下文所有高风险操作应可审计。Prompt Injection 风险MCP 工具可能返回网页、文档、邮件、评论、数据库字段等外部内容这些内容可能包含恶意指令。正确做法是把工具返回内容视为不可信数据而不是系统指令。错误做法是把外部文档中的指令当作最高优先级指令执行正确做法是把外部文档内容作为 observation只用于回答任务问题。数据外发风险使用远程 MCP Server 时工具参数、用户输入、上下文片段或资源内容可能被发送到外部服务。系统应明确哪些数据会发送给远程 Server、远程 Server 的维护方是否可信、是否需要脱敏或最小化传输数据、是否记录外发请求日志、是否允许用户撤销授权。调试与测试 MCP Server 时关注点包括Server 是否能正常启动、Client 是否能完成初始化、tools/resources/prompts 是否能被正确发现、工具 schema 是否清晰稳定可解析、工具调用参数是否被正确校验、错误返回是否结构化、STDIO 日志是否污染协议输出、远程连接是否正确处理认证和授权。测试不应只验证“工具能运行”还要验证“工具如何暴露给模型应用”因为工具名称、描述、输入 schema 和错误返回都会影响模型是否能正确使用工具。把这份清单过一遍后你就可以把本地 MCP Server 接到真实工作流里了。模型侧入口用 TaoToken 的 API 配置工具侧按最小权限原则声明日志和审计单独留一份。后续如果要接更多工具优先复用已有的 MCP Server 声明方式而不是每个应用单独封装。这样工具生态才能保持可发现、可复用、可授权、可审计。