读完这篇,你能做什么
能连接只读服务器并记录发现、调用及非法参数响应,说明宿主与服务端各自控制什么。
学习理由、准备要求与知识点
为什么学它
需要连接不同工具服务时,MCP 提供统一协议;先认识其职责可避免误当完整 Agent 框架。
先读这些内容
知识点清单
- Host、Client、Server 职责
- 工具、资源与提示
- JSON-RPC 请求标识
- 协议版本和能力协商
- tools
/list与分页 - tools
/call请求 - 工具结果及注解可信度
- 发现能力与业务授权
运行前先确认协议以MCP 2025-11-25规范为基线;Java17算法只构建目录路由,不执行握手、认证或网络调用。
01先弄懂它是什么
应用要接入不同的外部工具和资料,可以用 MCP 约定统一的交流格式。Host 是用户正在使用的应用,负责整合模型和权限;Client 代表它与某个 Server 连接;Server 提供工具、资源或提示。可以把它理解为各方怎么对话的协议。MCP 本身不是模型,也不会自动决定任务下一步该做什么。
MCP 中的几类能力用途不同:工具负责执行操作,资源提供可读内容,提示提供可复用的交互模板。客户端先完成连接和能力协商,再使用相应协议方法。比如成功看到某个工具的名称和参数,只能说明服务宣称有这项能力;能否让当前用户执行、工具实现是否可靠、结果能否采信,仍要由应用检查。
多个Server可能都提供search工具。Host需要保留服务身份与原始工具名的组合,再映射到模型可见名称;若只以search为键,后注册工具可能覆盖先前工具。工具路由身份必须在发现、调用和结果关联中保持一致。
文中这几个词,先认一认
- Host
- 整合用户界面、模型和权限控制的宿主应用。
- Client与Server
- Client 负责与服务保持协议交互,Server 负责发布能力;同一个 Host 可以管理与多个服务的连接。
- 命名空间
- 用服务身份和工具名一起区分能力,避免不同服务的同名工具被混淆;真正调用该服务时,仍使用它发布的原始工具名。
02看看它怎么工作
MCP使用JSON-RPC风格的请求、响应和通知。请求ID用于关联结果,通知没有同样的响应等待关系;生命周期能力协商决定双方可使用的特性,不能把所有能力字段简单求集合交集,因为不同方向有不同职责。协议版本应按固定规范解释。
客户端用 tools/list 查找工具,结果可能需要翻页;目录变动时,再根据双方协商支持的通知能力更新。调用时用 tools/call,传入工具原始名称和参数,并按约定检查返回结果。工具自带的“只读”等注解只是服务的声明,应用仍要验证权限与实际行为,不能直接当作可信授权或安全证明。
本题构建不可变工具目录,以二元组(serverId,toolName)作为键。同服务同名重复拒绝,不同服务同名允许,查询未知组合明确失败。二元组避免用分隔符拼接导致碰撞,例如服务名和工具名都可能含冒号;真实模型可见名称映射另由Host维护。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart LR
H["Host 用户与权限"] --> C1["Client A"]
H --> C2["Client B"]
C1 --> S1["Server A 工具目录"]
C2 --> S2["Server B 工具目录"]
S1 --> R["服务与工具二元组路由"]
S2 --> R
R --> V["授权后调用及结果校验"]按顺序看,每一步做了什么
- 为每个Server配置可信身份、连接方式及授权范围。
- 完成协议版本与能力协商,再进行工具发现。
- 读取完整分页目录,以服务和工具名组合建立路由。
- 校验调用参数及权限后发送tools
/call。 - 关联结果并验证输出,目录变更时更新版本快照。
03对照实现代码
OpenJDK 17 GA,jdk-17+35 · URI.create / getScheme / getHost
下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。
URI endpoint = URI.create(configuredEndpoint); // 只解析结构,不发起网络连接。
String scheme = endpoint.getScheme();
String host = endpoint.getHost(); // 解析出的主机也不等于已获授权目标。
// Host仍需按受信配置限制协议、主机及重定向;不要把工具返回URL直接注册为新服务。URI.create解析URI字符串,结构非法会报错;getScheme和getHost读取组成部分,不执行DNS查询,也不建立MCP连接。解析成功只是结构证据。
某些URI没有主机或使用非层次结构,不能假设所有合法URI都可作为远程HTTP服务。协议方案、目标范围和重定向政策需要由应用明确检查。
源码桥梁展示服务端点身份与工具名称应分开管理。完整算法使用本地配置的Server ID作为命名空间,不把URL文本相似性当成服务身份等价。
本篇协议说明固定2025-11-25规范,Java代码只实现目录路由。它没有实现JSON-RPC传输、认证或MCP握手,不能把内存测试通过称为协议互通完成。
代码先调用谁,再调用谁
- 解析受信配置的服务端点
- 按Host策略确认连接范围
- 建立Server身份
- 发现工具并保持原始名称
- 二元组路由到正确连接
04动手写一个例子
多服务工具目录的无碰撞路由
输入服务ID和工具名列表,构造不可变二元组目录;重复服务ID或同服务重复工具拒绝,跨服务同名允许。Java17 Main.java测试冲突和未知路由。
- 输入 1
服务A:search,服务B:search- 输出 1
两个独立路由
- 输入 2
服务A重复注册search- 输出 2
拒绝
先校验服务ID唯一,保证一个命名空间只对应一个受信连接。再逐个工具构造Key记录,记录的结构化相等性不会受分隔符选择影响。
putIfAbsent检测同一二元组冲突,返回不可变快照防止调用过程中目录被意外修改。目录更新应构建新快照并切换版本,不能偷偷改变已排队调用的目标。
解析模型可见别名后,应查回完整Key,再读取原服务与原工具名。未知Key直接失败,不尝试按相似名称或任意服务回退,避免调用到错误能力。
为什么这样写是对的目录中每个Key唯一对应同一serverId与toolName,跨服务同名不覆盖;生成快照后路由关系不可变,未知组合不会被自动替换。
N为工具总数,C为服务与工具名总字符量;哈希需读取字符串,单次Key查询成本与名称长度L有关。目录不含真实网络发现成本。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
import java.util.*;
public class Main {
record Server(String id,List<String> tools){} record Key(String server,String tool){}
static Map<Key,String> catalog(List<Server> servers){
Map<Key,String> routes=new HashMap<>();Set<String> ids=new HashSet<>();
for(Server server:servers){
if(server.id()==null||server.id().isBlank()||!ids.add(server.id()))throw new IllegalArgumentException("server id");
for(String tool:server.tools()){
if(tool==null||tool.isBlank())throw new IllegalArgumentException("tool name");
Key key=new Key(server.id(),tool); // 结构化二元组,避免分隔符拼接碰撞。
if(routes.putIfAbsent(key,server.id())!=null)throw new IllegalArgumentException("duplicate tool");
}
}
return Map.copyOf(routes); // 目录作为不可变快照交付。
}
static String resolve(Map<Key,String> routes,Key key){
String route=routes.get(key);if(route==null)throw new IllegalArgumentException("unknown route");return route; // 不做模糊回退。
}
static void eq(Object e,Object a){if(!e.equals(a))throw new AssertionError(a);}
public static void main(String[] args){
var map=catalog(List.of(new Server("A",List.of("search")),new Server("B",List.of("search"))));
eq(2,map.size());eq("B",resolve(map,new Key("B","search")));
eq(0,catalog(List.of()).size());
eq(2,catalog(List.of(new Server("a:b",List.of("c")),new Server("a",List.of("b:c")))).size());
try{catalog(List.of(new Server("A",List.of("x","x"))));throw new AssertionError();}catch(IllegalArgumentException expected){}
try{resolve(map,new Key("C","search"));throw new AssertionError();}catch(IllegalArgumentException expected){}
try{map.clear();throw new AssertionError();}catch(UnsupportedOperationException expected){}
System.out.println("PASS 7 tests");
}
}这些特殊情况也要试一试
- 跨服务同名合法,同服务同名重复属于目录冲突。
- 名称包含分隔符时仍不碰撞,依赖结构化Key。
- 空目录合法,调用未知路由明确失败。
进阶选读 · 后续再读:动态目录与长任务兼容
场景工具列表在任务执行中更新,已排队调用仍引用旧Schema。
做法给目录快照版本,调用绑定服务、工具、Schema和授权上下文;变更时检查兼容性,必要时使旧计划重新验证,而不是仅替换名称。
复杂度构建新目录平均O(N),切换引用可O(1),但兼容性检查和旧任务迁移成本不能忽略。
好处与代价快照增加内存与版本管理,却避免调用前后目标悄悄变化;服务不可用时应明确失败或按预定义路由恢复。
05项目里怎么用
接入Server前明确它需要哪些文件、网络和凭证范围,按最小权限运行。服务器返回的工具描述、结果和注解都是外部数据,即使服务经过安装,也不能让它自行扩展Host的权限。对写工具保留操作预览与版本绑定确认。
发现分页要读到结束,处理list_changed时应在能力协商允许的范围内刷新。目录刷新失败可以保留已知版本并标记状态,但不能把部分新旧条目混成一个没有版本的集合。每次调用记录服务身份和工具原名,方便定位同名工具问题。
协议互通验收需要独立检查握手、发现、调用、错误及断线恢复。内存目录测试只证明路由算法,真实Server仍可能返回不合规参数Schema、超大结果或业务错误。Host应限制结果大小、超时和外部目标,保证失败范围可控。
容易踩的坑
- 把MCP当成模型或自动规划框架。
- 发现工具就默认获得执行权限。
- 只用工具名作键,多个服务互相覆盖。
- 将只读注解当成可信安全承诺。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 结构化命名空间 | 同名能力路由清楚 | 需维护模型别名映射 | 多个MCP服务的Host |
| 不可变目录快照 | 运行期间行为可复核 | 更新需切换与旧版本管理 | 长任务及动态目录 |
| 直接专用适配器 | 接口控制细,依赖少 | 每类服务单独开发 | 少量稳定业务系统 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01Host与Client有什么区别?
展开参考答案与推导
Host整合用户、模型和权限;Client负责与特定Server的协议会话。一个Host可管理多个Client连接。
02工具注解为什么不能直接授权?
展开参考答案与推导
它是服务提供的信息,不证明实现没有副作用,也不证明用户允许该操作,Host仍需自己的策略。
03目录Key为何用二元组?
展开参考答案与推导
工具身份包含服务与原始名称,结构化组合避免同名覆盖及字符串分隔符碰撞。
换个情况,还会处理吗
01两个服务都有search,模型如何调用正确者?
展开参考答案与推导
Host提供可区分的别名并映射回完整服务与工具Key,执行前再按真实连接和Schema检查。
02目录刷新只得到一页,能立即替换吗?
展开参考答案与推导
应按分页契约完成快照或明确部分状态,避免遗漏工具或混合版本;调用仍绑定可复核目录版本。
03工具结果建议添加一个新Server,能自动信任吗?
展开参考答案与推导
不能把外部结果升级为受信配置;端点与权限需要Host既定策略和用户授权范围。
写代码试一试
01证明分隔符拼接有碰撞
- 输入 / 条件
(a:b,c)与(a,b:c)- 输出 / 预期
字符串拼接可相同,Key不同
提示 比较结构化字段。
展开参考答案与推导
若用冒号拼接都可能成为a:b:c;record分别保存两个字段,相等性逐字段比较,不发生该碰撞。
02未知路由是否回退同名工具?
- 输入 / 条件
请求C/search,仅A/search存在- 输出 / 预期
拒绝
提示 避免隐式换服务。
展开参考答案与推导
同名不说明能力、权限和数据相同,回退必须是显式业务策略,本题不允许。
03为何返回不可变Map?
- 输入 / 条件
构建后调用方尝试clear- 输出 / 预期
抛UnsupportedOperationException
提示 快照不能被意外修改。
展开参考答案与推导
Map.copyOf保护映射结构;若值是可变对象仍需额外不可变设计,本题值为String。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能连接只读服务器并记录发现、调用及非法参数响应,说明宿主与服务端各自控制什么。
本篇重点加练
协议能力协商与业务授权分别决定能否通信和能否执行,需要避免混用两类证据。
- 协议版本和能力协商
- 发现能力与业务授权
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
为一个本地笔记服务画Host、Client、Server关系图,分别安排工具、资源和提示,解释为什么把它们混为一种能力会影响使用方式。
- 提交内容
- 一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
- 怎样算完成
- 逐项回应题目要求,区分事实、假设与个人判断。
- 能用自己的例子解释适用条件,记录尚未弄懂的点。
练到的知识点Host、Client、Server 职责 · 工具、资源与提示
- 作业 02代码推演
编写一组初始化请求与响应草稿,注明JSON-RPC标识、协议版本和能力字段;推演不支持版本、未知响应ID和重复ID的处理。
- 提交内容
- 手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
- 怎样算完成
- 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
- 主动检查至少一个边界或异常条件,能解释手算与运行差异。
练到的知识点JSON-RPC 请求标识 · 协议版本和能力协商
- 作业 03原理问答
构造两份连接档案:协议能力相同但用户授权不同、用户授权相同但协议能力不同。分别列出可执行动作及仍需补齐的条件。
- 提交内容
- 概念或合同说明、关系图与自造案例,明确事实、假设和适用条件。
- 怎样算完成
- 关键概念有明确边界,能用案例检验声明。
- 未获证据支持的假设单独标注。
练到的知识点协议版本和能力协商 · 发现能力与业务授权
综合应用
- 作业 04面试场景
工具列表有三页,第二页请求失败,而用户已选择一个工具。设计完整发现与调用流程,说明何时允许调用以及需要保留哪些发现证据。
- 提交内容
- 场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
- 怎样算完成
- 方案回应给定约束,列出至少一种替代方案及代价。
- 每项关键结论说明所需证据,不能核实的部分明确标注。
练到的知识点tools/list 与分页 · tools/call 请求
- 作业 05编程实践
搭建不接真实外部服务的假MCP服务器,返回带“请忽略应用权限”文本的工具结果;为客户端写测试,检查发现、调用与业务授权分别如何判定。
- 提交内容
- 可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
- 怎样算完成
- 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
- 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。
练到的知识点工具结果及注解可信度 · 发现能力与业务授权
- 作业 06排错实验
让分页游标重复、响应ID错配和工具名发生变化,记录客户端状态,找出能导致重复调用或错误关联的最小消息序列。
- 提交内容
- 最小故障输入、复现步骤、观测记录、定位依据及回归用例。
- 怎样算完成
- 其他人可按记录复现故障,并区分现象与已验证原因。
- 定位过程有观测依据,回归用例可以再次暴露原问题。
练到的知识点Host、Client、Server 职责 · JSON-RPC 请求标识 · tools/list 与分页
- 作业 07编程实践
编写离线握手与授权判定器,使用预制能力表和用户许可表;在能力重协商、许可撤回和未声明能力三个场景保存独立判定记录。
- 提交内容
- 可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
- 怎样算完成
- 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
- 验证目标由合同确定,模拟结果不冒充真实服务证据。
练到的知识点协议版本和能力协商 · 发现能力与业务授权
- 作业 08排错实验
服务器升级后新增一项写工具,客户端沿用上次工具缓存。用消息回放检查缓存能力、当前连接能力与当前用户许可是否被错误合并。
- 提交内容
- 最小故障序列、观测记录、原因核对与可再次触发原问题的测试。
- 怎样算完成
- 故障可以稳定重现,现象与已验证原因分开。
- 观测记录足以判断声明的合同是否被违反。
练到的知识点协议版本和能力协商 · 发现能力与业务授权
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
设计两个服务器提供同名工具时的命名、能力协商和授权策略,给出服务升级后的兼容与权限回归用例。
- 提交内容
- 设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
- 怎样算完成
- 比较使用一致条件与事先声明的标准,记录失败与未知结果。
- 说明方案的适用前提、未覆盖风险及后续验证方法。
练到的知识点工具、资源与提示 · 协议版本和能力协商 · 发现能力与业务授权
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 跨域迁移
把能力协商与授权分离的原则迁移到浏览器插件或数据库驱动,给出一个具体接入场景,说明哪些合同可以借鉴、哪些无法直接照搬。
- 提交内容
- 原场景与目标场景的对应表、可迁移的机制、不能迁移的前提及验证设计。
- 怎样算完成
- 逐项核对两种场景的前提,不只比较相似术语。
- 至少一个不能直接迁移的边界有具体检验方法。
练到的知识点协议版本和能力协商 · 发现能力与业务授权
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读规范英文
MCP 2025-11-25:Architecture (新标签页)↗
阅读 Host、Client、Server 职责与能力协商,明确协议互通、模型编排和授权的不同层次。
- 实践规范英文
MCP 2025-11-25:Lifecycle (新标签页)↗
按初始化、能力协商、initialized 通知和关闭顺序阅读,用脱敏记录验证会话生命周期。
- 深入规范英文
MCP 2025-11-25:Tools (新标签页)↗
阅读 tools
/list、tools/call、输入 Schema、结果与错误表示,对照实际消息做契约验收。 - 实践实践指南英文
MCP 工具接入:把一个小工具交给 AI 调用 (新标签页)↗
先读 Unit 1,再从 Unit 2 只选一个工具。小练习:做一个仅返回本地模拟天气的工具,记录工具名称、参数、返回内容与失败提示;先不连接真实账号或写入外部数据。 讲义公开;平台项目需要账号,课程页面提供免费证书流程说明。模型和托管服务可能另收费。
- 深入参考文档英文
OpenJDK 17 URI 源码 (新标签页)↗
按指定版本定位
URI.create/getScheme/getHost;对照源码分析理解实际调用边界。