应用系统体系架构-02:MCP 协议
模型应用经常需要读取项目文件、查询数据库或调用业务 API。如果每个宿主都分别为每个系统编写接口,工具定义、参数格式、错误处理和连接管理会产生大量重复工作。MCP(Model Context Protocol) 为这些能力提供统一的发现和访问方式,使宿主与业务实现能够分别演进。
MCP 负责通信契约,实际业务仍由 Server 实现。统一协议可以减少适配工作,但不能自动保证数据正确、操作获授权或服务持续可用。
集成问题
设有 个模型宿主和 个数据或工具系统。逐对定制连接时,可能需要维护 条接口;采用公共协议后,适配工作可以趋向宿主侧 份和系统侧 份。
这个比较反映接口复用,而不是完整成本公式。不同系统的认证、部署、数据含义和业务错误仍需适配。例如库存数据库与工单系统都能通过 MCP 接入,但“库存不存在”和“工单不存在”需要各自的业务解释。
模型不能仅凭预训练知识知道当前库存、私有代码或最新部署状态。MCP 让应用能够访问这些真实信息,并把结果送回模型上下文。
私有资料与实时状态需要分别考虑。促销和退款条款可以检索后组织进上下文,当前库存、天气和订单状态则通常需要查询对应服务。MCP 可以提供读取或检索工具,RAG 也可以增量更新;两者能组合,不能仅用“动态还是静态”把全部场景严格分开。
架构角色
MCP 系统包括 Host、Client 和 Server。
| 角色 | 职责 | 典型实现 |
|---|---|---|
| Host | 用户交互、任务管理、权限和上下文组织 | IDE、聊天应用、业务助手 |
| Client | 维护与某个 Server 的协议连接 | Host 内部的 MCP 客户端 |
| Server | 暴露资源、工具和 Prompt 模板 | 文件服务、数据库适配器、业务服务 |

一个 Host 可以连接多个 Server,通常为每个连接创建对应 Client。图中的 1:1 表示某个 Client 管理对应 Server 的会话,不表示整个应用只能访问一个系统。
Server 可以是本地子进程,也可以是远程网络服务。把数据库、文件与协作工具分别部署,有助于隔离权限和故障;Host 聚合这些能力时,需要处理名称冲突、连接状态和任务路由。
模型提出工具调用,Host 决定是否执行,Server 完成具体业务。协议连接本身不授予数据库修改、文件删除或对外发送权限。
书店示例
用户问“列出书店里的全部图书”时,模型首先需要知道这里指哪个书店、有哪些可用能力。宿主提供工具说明,模型提出调用,真正读取商品数据的是 Server。
| 阶段 | 实际工作 |
|---|---|
| 模型请求 | 用户问题与 get_all_books 等工具定义共同输入 |
| 调用选择 | 返回工具名、参数和调用标识 |
| 宿主执行 | Client 请求 Server,由业务实现查询数据 |
| 结果回传 | 返回包含图书标识、名称等字段的结构化数据 |
| 回答生成 | 模型依据本次结果组织人可读的列表或表格 |
课堂中的数据库版和 JSON 模拟版提供相似工具,但取数位置不同。同一个问题返回不同书目,可能是数据源或演示数据不同,应先检查本轮实际工具、数据来源和版本,不能只归因于模型回答波动。
通常至少需要“选择工具”和“使用工具结果”两次模型交互;多步骤任务可能有更多轮。能直接回答的问题也未必调用工具,而涉及当前书店事实时,不应用模型记忆替代查询。
天气等工具还应返回观测或预报时间、时区与地点。不同城市日期可能不同,不能把使用者本地日期自动当成服务数据的日期。
协议分层
理解 MCP 可以区分三个层次:业务原语定义要访问什么,消息层规定如何表达请求与响应,传输层负责送达消息。OSI 七层模型可以帮助进行类比,但 MCP 不要求逐层对应七层实现。
| 层次 | 内容 |
|---|---|
| 能力层 | Resources、Tools、Prompts 及能力协商 |
| 消息层 | JSON-RPC 请求、响应和通知 |
| 传输层 | 本地 stdio 或远程 HTTP 传输 |
| 运行环境 | 进程、网络、认证、资源回收 |
stdio 适合宿主启动本地服务:宿主向子进程标准输入写入协议消息,Server 从标准输出返回消息。远程服务可以使用 Streamable HTTP;HTTP+SSE 是较早的传输形态,连接流程和配置不能与现代方案混用。
SSE 提供服务器到客户端的事件流,不能仅凭一个 SSE 流就理解为任意方向都通过同一通道传送。具体请求、会话和通知行为由所用协议版本规定。
消息格式与传输方式是两个层次:JSON-RPC 规定方法、参数和响应,stdio/HTTP 决定怎样发送它们。Server 的 HTTP 地址属于连接配置,不是每条 JSON-RPC 方法名;例如 tools/call 是协议方法,具体工具名在 params.name 中。
HTTP 请求响应模型也不要求每次都重新建立 TCP 并立即关闭。连接复用和连接池可以减少开销,SSE 则在 HTTP 响应中持续发送事件。不要把“普通 HTTP 一定短连接”作为判断依据。
标准流分离
stdio 下,stdout 只能承载协议消息,日志应写入 stderr。普通 print()、框架启动横幅或默认控制台日志都可能破坏消息解析。
1 | stdout: |
把日志插到 JSON 前后,会让 Client 无法区分协议数据与调试信息。Python 可以把日志处理器配置到 stderr;Java 的日志框架也需要确认目标流。排错时应分别收集协议消息与错误输出,先判断进程是否成功启动,再判断协议是否正常工作。
消息结构
MCP 使用 JSON-RPC 2.0 表达通信。请求、成功响应、错误响应和通知是不同类型,字段不能混用。
工具调用请求的例子:
1 | { |
成功响应用相同 id 对应请求:
1 | { |
请求包含 method,成功响应包含 result,错误响应包含 error。通知没有请求 ID,也不要求对方返回响应。ID 用于关联异步请求与结果,不能通过消息返回顺序猜测对应关系。
会话生命周期
连接建立后,Client 发出 initialize 请求,双方交换协议版本、身份说明和能力。Server 返回协商结果,Client 再发送 notifications/initialized 通知,随后进入常规操作。
初始化阶段应确认双方支持的版本以及服务能力。例如 Server 未声明资源订阅能力时,Client 不应直接假定订阅可用。能力变化还需要通过相应通知和列表更新机制处理。
MCP 没有对所有传输通用的 shutdown RPC。关闭方式取决于传输:stdio 可以关闭输入、等待进程退出,并在必要时终止进程;HTTP 服务则需要清理连接和会话。关闭时还应释放数据库连接、临时任务和文件句柄,避免只停止通信而留下后台工作。
本地 command 配置让 Host 有能力启动服务进程;独立 HTTP 服务由自己的部署环境管理。stdio Server 可以在一个会话中处理多次调用,不保证每次工具返回后就自动退出。是否延迟启动、复用或关闭进程由宿主和连接生命周期决定。
资源读取
Resource 表示可以放入上下文的资料,通过 URI 标识,并带有内容类型。资源可以是项目说明、日志、数据库结构、配置或二进制文件。
常见操作包括列出资源、读取资源、列出 URI 模板,以及在服务支持时订阅变化。URI 不必是普通网页地址,它可以使用 projects://、grades:// 等业务标识。
1 | from fastmcp import FastMCP |
模板中的参数需要校验。若 URI 参数直接映射到文件路径,还需要路径规范化、目录边界检查和用户授权,不能只检查字符是否为字母或数字。
资源通常用于读取资料。修改库存、发送消息或删除记录应通过有明确副作用的工具完成,使操作范围和结果更容易审查。
工具调用
Tool 暴露可执行能力,定义中包含名称、描述和输入 Schema。模型据此选择工具并生成参数,Server 收到调用后执行具体逻辑。
1 | { |
required 表达必填字段,enum 表达允许值,描述说明单位和业务含义。客户端提供 Schema 不能替代服务端验证;工具收到非法参数、未授权目标或不允许的状态时,必须实际拒绝执行。
工具还应说明副作用。calculate_risk 可以只读取项目预算并返回风险;send_alert 会向外发送消息;delete_record 会修改数据库。这些能力应有不同权限和审批策略。
工具描述是模型选择接口的重要依据,不等于模型会阅读并执行整个方法体。一个描述应明确用途、参数语义、返回内容与限制。例如 search_books(keyword) 要说明关键词匹配的范围,get_all_books() 要说明是否分页、是否包含下架商品。代码实现负责兑现这些说明,不能只让名称看起来合理。
Prompt 模板
Prompt 提供可复用的消息模板,可以接受语言、项目或审查目标等参数。它帮助宿主组织任务,但不拥有高于 Host 系统规则的权限。
1 |
|
Resources 提供资料,Tools 执行操作,Prompts 组织任务。三者可以共同支持一个业务场景:先取项目说明,再使用审查模板,最后调用实际测试工具。
Prompt 原语还可以包装用户的简短要求:用户只说“用 JavaScript 做前端”,模板可以补入项目结构、输入验证和输出要求,再交给模型。取得模板不表示这些规则已经被执行;它是构造后续请求的资料,不是额外的授权来源。
能力协商
Capabilities 声明协议参与者支持什么功能。工具列表变化、资源订阅和日志等能力,应与真实实现一致。
1 | { |
experimental 用于扩展能力,不能假设所有客户端都理解其中自定义字段。Host 应先读取协商结果,再决定是否启用订阅、通知或其他进阶操作。
列表变化通知只提示能力已变化,客户端通常仍需重新读取相应列表,并处理正在进行的任务如何使用旧工具定义。
错误语义
JSON-RPC 标准错误描述消息或协议处理失败:
| 错误码 | 含义 |
|---|---|
| -32700 | JSON 解析失败 |
| -32600 | 请求对象不合法 |
| -32601 | 方法不存在 |
| -32602 | 参数无效 |
| -32603 | 内部处理错误 |
| -32000 至 -32099 | 预留的服务端错误范围 |
1 | { |
协议错误与 工具执行失败 要区分。工具存在且请求可以处理,但业务查询失败时,可以在工具结果中使用 isError,并提供清楚的业务错误信息。库存不足、权限拒绝、网络超时和数据库故障不应全部返回一条“失败”字符串。
错误反馈应足够具体,让调用方决定补参数、有限重试或终止;同时需要脱敏,避免把密码、Token、内部路径和完整敏感堆栈传给模型。
进度与日志
长任务可以通过进度通知报告已完成工作。请求方提供 progressToken,通知使用该 Token 对应任务,并携带当前进度和可选总量。
1 | { |
协议日志使用相应日志消息机制,例如 notifications/message。日志与任务结果有不同用途:日志用于诊断,结果用于下一步业务处理。stdio 的错误流分离仍然需要保持,不能因协议支持日志就把任意日志文本直接写到 stdout。
界面显示的进展可能来自多种事件:模型流式内容、工具执行进度、协议日志和业务任务状态。它们不能全部等同为模型内部思考过程。追踪时要记录事件来源与调用 ID,避免把工具日志误当成模型结论。
服务监控可以记录工具名、请求标识、执行时间、状态和 Trace ID。跨服务调用应传播追踪上下文,避免只有零散日志而无法还原一次完整请求。
Python 服务
uv 可以管理 Python 项目和依赖,使环境与运行命令具有可复现性。
虚拟环境主要隔离 Python 包及相关运行配置。不同项目可能需要不同解释器版本,或使用不同版本的计算库,应在项目中声明并锁定依赖,而不是反复修改全局环境。虚拟环境不会自动限制文件、网络和系统权限,不能替代 Sandbox。
1 | uv init edu-mcp |
FastMCP 使用装饰器登记资源、工具和提示。下面的工具根据预算返回示例风险等级,类型标注和 docstring 用于生成契约说明。
1 |
|
该阈值只是业务规则示例。实际项目需要从配置或数据源取得规则,并限制当前用户可以查询的项目。独立 fastmcp 包与 MCP Python SDK 中的 FastMCP 导入路径可能不同,工程应固定依赖并使用匹配的文档。
复现已有项目时,应一起检查 Python 版本、依赖声明和锁文件。uv sync --locked 可以按现有锁文件同步;若锁文件与声明不一致,应明确处理,而不静默安装另一组依赖。Host 启动命令也应使用这个项目环境。
异步处理
数据库和外部 API 经常需要等待 I/O。异步工具可以在等待网络时释放事件循环,使服务处理其他请求。
1 | import httpx |
示例地址需要替换为实际服务,并配置认证及响应 Schema。async 不会自动把阻塞驱动或 CPU 密集计算变成非阻塞操作;SQLite 读取应使用适当异步驱动或受控线程执行,并确保连接正确释放。
执行到 await client.get(...) 时,是当前协程等待网络结果,事件循环可以处理其他就绪任务。当前函数不会越过 await 直接执行 response.json();响应完成后才继续这一步。协程的必要状态和连接也仍存在,不是所有资源都被释放。
异步等待与 CPU 并行是不同概念。多个独立网络查询可以共同等待,但需要先后结果的步骤仍然保持依赖顺序。
超时后还要判断操作是否可能已经完成。只读查询可以较容易重试,外部写入则需要幂等标识和状态确认,避免重复执行。
文件安全
字符串前缀比较不足以验证目录边界。例如 /safe2/file 也以 /safe 开头;相对路径和符号链接还可能改变实际目标。
1 | from pathlib import Path |
这段代码表达路径规范化与目录检查,仍需要考虑文件大小、用户权限和检查后文件被替换的竞态。执行进程本身也应具有有限文件权限,避免所有防护只依靠应用中的一条判断。
副作用控制
删除记录、更新库存、发送通知等操作需要明确的输入、目标和影响范围。审批应绑定具体操作,而不是让一次确认成为后续任意写入的许可。
1 | 模型提出:删除记录 101。 |
工具的只读、破坏性和幂等提示可以帮助 Host 分类,但这些注解只是声明,不能证明实现无副作用。Server 仍需鉴权、参数检查和审计,Host 也需执行自身的操作策略。
Java 集成
Spring AI 适合把模型能力接入已有 Java 应用。MCP 适配层负责协议和参数转换,Service/DAO 保留业务规则、数据库访问和事务,避免复制既有逻辑。
1 |
|
示例省略了应用相关类型的导入;CourseService 与 CourseInfo 由业务工程定义。具体注解、starter 和自动配置需要与所选 Spring AI 版本匹配。BOM 管理相关依赖版本,stdio 与 Web 服务的 starter 也应按部署模式选择。
依赖注入有助于复用事务、缓存、认证和测试替身,但不能认为所有 Bean 都自动对模型公开。工具应显式登记或扫描,并只暴露所需能力。
@Service、@Controller 等通常通过元注解与 @Component 关联,这不是普通 Java 类继承。IoC 管理对象创建,依赖注入把已有业务 Service 提供给工具适配层;MCP 入口可以直接复用书店查询,不必复制一套业务代码。
提供工具 与 调用模型 也要分开。只提供库存工具的 MCP Server 不一定需要 ChatClient;负责装配 Prompt 并请求模型的应用才配置相应客户端、模型和凭证。创建 Bean、登记 Tool、配置模型连接各有步骤,简短代码片段不能替代完整工程配置。
宿主配置
本地服务通常由宿主根据命令、参数和环境变量启动。绝对路径能减少工作目录差异造成的问题。
1 | { |
不同 Host 的配置文件位置、可用字段和传输支持可能不同,不能直接复制到任何宿主。由 GUI 启动的子进程也可能没有交互式 Shell 的 PATH,因此需要确认 uv、Python 或 Java 可被找到。
接入多个服务时,可以先逐个验证启动、初始化与工具调用,再组合配置。某个 Host 接受的 env 或连接字段不一定被另一个接受;分阶段排查能够区分单服务错误和组合后的命名、权限问题。
真实凭证应通过受控环境或秘密管理提供。不要把真实 API Key 写进公共配置、仓库或模型上下文,也不要将全部宿主环境变量传给工具进程。
调试测试
MCP Inspector 可以检查连接、初始化、能力列表、参数 Schema 和调用结果。调试顺序应从基础连接逐步进入业务:
- 确认可执行文件、参数和工作目录。
- 检查 Server 是否启动,stderr 是否报告错误。
- 检查初始化和能力协商。
- 列出工具、资源和 Prompt 模板。
- 使用完整参数测试正常与失败调用。
例如 calculate_risk 要求 project_id 与 budget,只发送预算应触发缺失参数错误。这个测试能同时检查 Schema 是否正确、服务端是否验证、宿主是否保留错误语义。
测试应分为工具函数单测、协议集成测试和真实 Host 测试。覆盖成功、未知工具、非法参数、资源不存在、超时、权限拒绝和 stdout 污染。仅测试某个函数可以运行,不能证明协议或宿主集成正常。
多服务治理
多个 Server 可能都提供 search 或 get_log,需要通过稳定名称和领域说明消除歧义,例如 python_logs_search 与 java_service_logs_search。命名改变时还要更新注册表、说明和评测,避免旧请求被转到不同能力。
服务侧记录请求、工具、延迟、状态与 Trace,宿主侧记录选择和调用结果。模型选错工具、参数错误、服务故障和权限拒绝应分别统计,便于确定问题属于路由、协议还是业务实现。
数据库真实书目与 JSON 演示书目若使用同名工具和相同描述,宿主未必按期望优先选真实版本,也不保证会同时查询两边。应区分为明确的 live/demo 名称,说明数据来源,或在测试环境只暴露所需实现。
关闭某服务后,原会话仍可能保留此前查询结果或缓存。验证配置变化时,应检查新一轮实际 Tool Call 和返回值,必要时建立新会话,不能只看界面继续出现旧书目就判断服务仍在运行。
协作开发
课程中的 Vibe Coding 用于描述自然语言指导 AI 开发的方式。AI 辅助开发 MCP 服务时,应先描述架构、数据接口、只读或写入边界、异常语义和测试要求,再生成具体实现。提供与依赖版本相符的 API 文档,可以减少模型猜测,但仍需编译和测试验证。
SQL 查询工具不能只检查文本以 SELECT 开头。只读连接、数据库权限、语句解析、参数绑定和结果规模限制需要共同使用。值应采用参数化查询,表名和列名则需要明确白名单。
教务系统练习可以组合课程工具、成绩资源与审查提示:工具查询课程信息,资源返回模拟成绩,Prompt 组织分析任务。接入遗留企业 API 时,则让适配层调用原有 Service,并区分不存在、未授权和系统故障。
MCP 的工程价值来自统一契约与能力复用。完整服务还需要权限、类型校验、资源管理、观测和测试共同支持。

