MCP专题学习笔记 · AI Agent
MCP 专题学习笔记
核心定位
MCP 是 Model Context Protocol,可以理解为:
给 Agent 使用外部能力的一套标准协议。它让 Agent 用统一方式发现工具、读取资源、复用提示模板,而不是每个应用单独硬编码一套插件接口。
它不是 REST API 的替代品。更准确的关系是:
代码块收起展开
业务系统继续负责真实业务
MCP Server 负责把业务能力包装成 Agent 能发现、能调用、能审计的能力
MCP Client 负责在 Agent 侧连接并使用这些能力一屏架构
代码块收起展开<rect x="290" y="82" width="180" height="104" rx="10" fill="#dcfce7" stroke="#4ade80"/>
<text x="344" y="112" font-weight="700">Resources</text>
<text x="316" y="140">只读上下文资料</text>
<text x="316" y="164">note / doc / record</text>
<rect x="520" y="82" width="180" height="104" rx="10" fill="#fef3c7" stroke="#f59e0b"/>
<text x="578" y="112" font-weight="700">Prompts</text>
<text x="546" y="140">可复用任务模板</text>
<text x="546" y="164">plan / review / explain</text>| 能力 | 判断标准 |
|---|---|
| Tool | 会执行动作或产生副作用,例如搜索、写文件、跑命令 |
| Resource | 只暴露可读取的上下文资料 |
| Prompt | 给模型复用的任务模板或操作流程 |
Client / Server 生命周期
三个核心对象
| 对象 | 本质 | 例子 | 后端类比 | 设计重点 |
|---|---|---|---|---|
| Tool | 可执行动作 | search_notes、create_task | Controller 暴露的业务操作 | 参数 schema、权限、审计、错误 |
| Resource | 可读资料 | notes://index、project://history | 只读查询接口 | URI 稳定、权限、内容大小 |
| Prompt | 可复用提示模板 | make_week_plan | 业务模板/工作流模板 | 输入变量、适用场景、版本 |
Tool 设计原则
坏工具:
代码块收起展开
run_command(command: string)问题:权限过大、参数无边界、无法预测副作用、审计困难。
好工具:
代码块收起展开
search_notes(keyword: string, limit: integer)
read_note(path: string)
create_study_task(title: string, source_note: string, due_date?: string)好工具必须满足:
| 标准 | 说明 |
|---|---|
| 名称具体 | 模型看名字就知道用途 |
| 参数少 | 参数越多,模型越容易填错 |
| schema 严格 | 类型、必填、枚举、长度都要限制 |
| 错误可读 | 失败后 Agent 能恢复 |
| 权限明确 | 只读/写入/危险操作分开 |
| 返回结构化 | 不要只返回一大段自然语言 |
MCP Server 内部结构
代码块收起展开
final class McpToolDefinition {
String name; // 工具名,必须稳定,改名等于破坏协议
String description; // 给模型看的用途说明,决定模型会不会选它
JsonSchema inputSchema; // 参数结构,服务端必须二次校验
ToolRiskLevel riskLevel; // READ_ONLY / WRITE / DANGEROUS
}
final class McpToolResult {
boolean ok; // 是否执行成功
String message; // 给模型看的简短解释
Object data; // 结构化结果,不要只塞自然语言
List<String> evidencePaths; // 如果读取资料,返回来源路径
}协议握手与能力协商
MCP 的关键不是“能不能调用函数”,而是 client 和 server 先把能力边界说清楚。能力协商至少要回答三件事:server 提供什么、client 支持什么、后续调用按什么 schema 校验。
sequenceDiagram
participant A as Agent
participant C as Client
participant S as Server
A->>C: connect
C->>S: initialize
S-->>C: capabilities
C->>S: list tools
S-->>C: tool schemas
A->>C: choose tool
C->>S: call tool
S-->>C: result
C-->>A: observation
| 阶段 | 输入 | 输出 | 必查点 |
|---|---|---|---|
| initialize | client 信息、协议版本 | server 信息、能力列表 | 版本不兼容要拒绝 |
| list tools | 空或分页参数 | tool name、description、inputSchema | 名称稳定,schema 完整 |
| list resources | URI 范围 | resource uri、mime、description | 只暴露白名单资源 |
| call tool | tool name、arguments | structured result 或 error | 服务端二次校验 |
| read resource | resource uri | content、metadata | 大小限制和权限检查 |
Tool Schema 示例
下面是 search_notes 的最小 schema。重点不是字段多,而是模型填错时服务端能明确拒绝。
代码块收起展开
{
"name": "search_notes", // 稳定工具 ID,改名会破坏调用方
"description": "Search allowed Obsidian notes by keyword.",
"inputSchema": {
"type": "object", // arguments 必须是对象,不能直接传字符串
"required": ["keyword"], // 必填字段越少越好,但必须明确
"properties": { // 每个字段都要限定类型和边界
"keyword": {
"type": "string", // keyword 是检索文本,不允许数组或对象
"minLength": 2, // 太短会产生大量噪声召回
"maxLength": 80, // 太长说明用户可能传了整段 prompt
"description": "Search keyword or phrase."
},
"limit": {
"type": "integer", // limit 必须是整数,便于限制返回量
"minimum": 1, // 至少返回一个候选
"maximum": 20, // 防止一次读取过多上下文
"default": 5
},
"scope": {
"type": "string", // keyword 是检索文本,不允许数组或对象
"enum": ["ai-agent", "all-allowed"],
"default": "ai-agent"
}
},
"additionalProperties": false // 拒绝模型幻觉出来的多余字段
}
}| 字段 | 作用 | 失败时怎么处理 |
|---|---|---|
name | tool 唯一标识 | 未注册则返回 TOOL_NOT_FOUND |
description | 帮模型选择工具 | 描述含糊会导致误调用 |
required | 最小必需参数 | 缺失则拒绝执行 |
enum | 收敛模型选择 | 非法值返回 schema error |
additionalProperties | 禁止多余参数 | 防止幻觉参数进入业务层 |
安全边界
| 风险 | 错误做法 | 正确做法 |
|---|---|---|
| 路径越权 | 模型传什么路径就读什么 | resolve 后校验必须在白名单根目录内 |
| 命令执行 | 暴露 run_command | 只暴露白名单脚本或固定动作 |
| 写入污染 | 直接写正式笔记 | 先写草稿、diff preview、人工确认 |
| 删除数据 | 暴露 delete | 默认不暴露,或软删除 + confirmation |
| 大文件读取 | 一次返回整库 | 限制文件大小、分页、摘要 |
| Prompt 注入 | 笔记内容指挥 Agent 忽略规则 | 资源内容当数据,不当系统指令 |
Java 后端落地方式
推荐把 MCP Server 当适配层,而不是把业务全部塞进 MCP:
flowchart TD
Client[MCP Client] --> Server[MCP Server]
Server --> Guard[Permission / Schema / Audit]
Guard --> Spring[Spring Boot API]
Spring --> Service[Business Service]
Service --> DB[(MySQL / Redis)]
Service --> Notes[Obsidian Files]
| 层 | 职责 | 不该做什么 |
|---|---|---|
| MCP Server | 协议适配、能力声明、参数校验、审计 | 承载复杂业务规则 |
| Spring Boot | 业务逻辑、权限、事务、日志 | 依赖某一个 Agent 客户端 |
| Notes/RAG | 资料读取和索引 | 绕过权限直接暴露全盘 |
| Agent Runtime | 决定何时调用工具 | 直接修改真实数据 |
适合你的 MCP 项目
Obsidian Notes MCP
| Tool | 参数 | 作用 | 风险等级 |
|---|---|---|---|
list_note_roots | 无 | 列出允许访问的笔记根目录 | 只读 |
search_notes | keyword, limit | 搜索标题/正文 | 只读 |
read_note | path, max_chars | 读取指定 Markdown | 只读 |
recent_notes | days, limit | 最近修改笔记 | 只读 |
get_ai_agent_map | 无 | 返回本目录学习地图 | 只读 |
Project MCP
| Tool | 参数 | 作用 |
|---|---|---|
read_project_history | project_path | 读取项目 history |
list_tasks | project_path | 查看任务状态 |
append_learning_log | content | 追加学习日志,需确认 |
run_safe_check | script_name | 运行白名单检查脚本 |
MCP 与 RAG 的关系
flowchart TD
Agent["Agent"] --> MCP["search_notes"]
MCP --> Retriever["RAG Retriever"]
Retriever --> Vector[("Vector DB")]
Retriever --> Markdown["Markdown Source"]
Markdown --> Evidence["引用片段"]
Evidence --> Agent
| 问题 | MCP 负责 | RAG 负责 |
|---|---|---|
| Agent 怎么调用能力 | 工具协议、参数、返回 | 不负责 |
| 知识怎么检索 | 可把检索暴露成 tool | chunk、embedding、rerank |
| 权限怎么管 | server 侧白名单和审计 | 数据过滤和来源控制 |
| 结果怎么复盘 | tool call trace | retrieved chunks |
最小实现顺序
flowchart TD
A[只读 search_notes] --> B[read_note]
B --> C[recent_notes]
C --> D[get_ai_agent_map]
D --> E[接 RAG Retriever]
E --> F[增加写入草稿工具]
F --> G[人工确认后写入]
不要第一天就写 delete_note、edit_note、run_command。MCP 的第一课是边界,不是炫工具数量。
错误分类与恢复策略
MCP tool 的错误要让 Agent 能恢复,而不是只抛异常字符串。错误至少分成参数错误、权限错误、资源错误、执行错误、协议错误。
代码块收起展开
final class McpError {
String code; // 稳定错误码,例如 ARGUMENT_INVALID
String message; // 给模型看的简短原因
boolean retryable; // 是否允许 Agent 换参数或稍后重试
String field; // 哪个参数出错,没有则为空
String traceId; // 对应服务端审计日志
}| 错误码 | 典型原因 | Agent 应该怎么做 |
|---|---|---|
ARGUMENT_INVALID | 参数缺失、类型错、超长 | 修正参数后重试一次 |
PERMISSION_DENIED | 路径越权、危险操作 | 停止调用,解释权限边界 |
RESOURCE_NOT_FOUND | 文件或 URI 不存在 | 换检索条件或追问用户 |
TOOL_TIMEOUT | 后端慢、外部服务卡住 | 降级回答或稍后重试 |
TOOL_FAILED | 业务异常 | 记录 trace,给出可读失败原因 |
PROTOCOL_ERROR | client/server 消息不兼容 | 断开并提示版本问题 |
Notes MCP 验收用例
| 用例 | 输入 | 期望结果 | 证明什么 |
|---|---|---|---|
| 正常搜索 | keyword=RAG, limit=5 | 返回标题、路径、摘要、score | 检索链路可用 |
| 参数缺失 | 没有 keyword | ARGUMENT_INVALID | schema 生效 |
| 越权读取 | path=F:\secret.md | PERMISSION_DENIED | 白名单生效 |
| 大文件读取 | max_chars=999999 | 被限制或分页 | 上下文预算受控 |
| 不存在文件 | 合法目录下的假路径 | RESOURCE_NOT_FOUND | 错误可恢复 |
| prompt 注入 | 笔记中写“忽略规则” | 只当资料引用 | 资源不是系统指令 |
| trace 检查 | 任意 tool call | 有参数、耗时、结果、错误 | 可审计 |
MCP 生产契约
学习 MCP 时最容易漏掉的是:协议能通不等于能力可用。一个可放进作品集的 MCP Server 至少要有稳定契约,让 Agent 知道“能调用什么、不能调用什么、失败后怎么恢复”。
| 契约项 | 必须明确 | 反例 |
|---|---|---|
| 能力发现 | tools/list 返回稳定 name、description、inputSchema、readOnlyHint | 每次启动工具名变化 |
| 输入边界 | 字段类型、长度、枚举、默认值、路径白名单 | path 接收任意绝对路径 |
| 输出形态 | 成功和失败都返回结构化 content,不只是一段字符串 | 报错直接抛异常,Agent 只能猜 |
| 副作用声明 | read-only / destructive / requires-confirmation | 写文件工具伪装成普通查询 |
| 超时策略 | 每个 tool 的 timeout、retryable、fallback | 卡住后 Agent 无限等待 |
| 审计字段 | traceId、tool、argsHash、latency、status、errorCode | 只在控制台打印日志 |
| 版本兼容 | protocolVersion、serverVersion、schemaVersion | client 升级后无提示地坏掉 |
推荐的 tool response 语义:
| 字段 | 作用 | 例子 |
|---|---|---|
isError | 区分工具失败和正常结果 | true |
code | 稳定错误码,便于 Agent 决策 | PERMISSION_DENIED |
message | 给模型看的短说明 | path is outside notes root |
retryable | 是否值得换参数重试 | false |
evidence | 成功结果的来源或摘要 | sourcePath, score, titlePath |
traceId | 后端审计入口 | tool-20260706-... |
判断 MCP Server 是否成熟,看这 4 个问题:
| 问题 | 合格答案 |
|---|---|
| 模型传错参数怎么办 | schema 拦截,返回 ARGUMENT_INVALID,不执行工具 |
| 模型试图读白名单外文件怎么办 | 返回 PERMISSION_DENIED,trace 记录被拒绝路径 |
| 工具执行一半超时怎么办 | 返回 TOOL_TIMEOUT,标明是否可重试 |
| 返回内容太大怎么办 | 分页、截断或摘要,并在结果里写明 truncated=true |
验收清单
- 能解释 MCP client / server / tool / resource / prompt。
- 能写出一个只读 MCP tool 的 schema。
- 能限制工具只能访问
F:\2. ObsidianNotes白名单目录。 - 能处理参数错误、文件不存在、权限拒绝。
- 能记录每次 tool call 的参数、结果、耗时、错误。
- 能区分 MCP 和 RAG:MCP 是调用协议,RAG 是知识检索能力。
- 能把已有 Spring Boot API 包装成 MCP tool,而不是重写业务系统。