第5章 能力接入——Tools、MCP 与 Skill
第4章明确了何时可以推进。接下来要回答推进时究竟调用什么,以及怎样解释结果。接入本地函数、外部服务和方法资料,不应形成三套彼此绕开的通道。它们都要承接当前目标与授权,绑定请求依据,留下可以核对的结果。调用方式可以不同,运行责任不能因换了协议而消失。
本章先建立工具契约,再说明 MCP 怎样适配到它,最后用 Skill 组织已有工具与方法资源。沿用第3章的摘录任务,不另建大型示范系统。第3章源码只有两个固定本地工具。本章另用 capability_lab.py 在同一 Harness 上适配真实的本地 stdio MCP 服务,并加载一份版本化方法包。模型仍用明确标注的脚本响应,创建和验收仍走原来的代码。这项实践验证接入关系,不声称已经完成远端服务或真实模型联调。
5.1工具契约与参数结构
Tool 是程序向模型暴露的可调用能力。模型根据名称、说明与参数提出请求,harness 决定是否执行,并返回关联结果。Tool 不是模型内部掌握的函数,不是名称写进提示词就存在的功能,也不是对任意代码的许可。最小形态包含调用名、输入语义、结果语义、可信实现和实际效果范围。
能力契约还要回答执行身份、前置条件、资源上界与完成条件。一个“读取”接口是否访问固定快照,还是每次读取当前远端版本,决定结果能支持哪种判断。一个“创建”接口是新建还是覆盖,决定动作需要核对什么。名称相似不足以证明它们能够互换。
第3章的 read_source 输入只有固定 source_id,返回对应启动快照。create_card 只接收来源与原文,路径和版式由程序固定。工具定义见 book/v2/harness.py 的 TOOLS,执行语义见 Harness.tool。粒度足够让模型选择原文,又没有把路径、覆盖模式和执行代码交给模型。这是按任务需要确定粒度,不是所有工具都应只有一个参数。
工具过粗,会让调用者看不见内部效果与部分失败。把“搜集材料并创建报告”合成一项调用,仍需返回来源、版本、提交对象和部分效果,不能只回“完成”。工具过细,则把本来由可信程序保证的细节交给模型反复决定,例如打开、写入、刷新与关闭的顺序。合理粒度应让一项请求表达可授权的业务动作,同时由实现保证其内部条件。
先写契约再选接入方式。契约可以是一份小型工具记录,不要求自建框架。至少明确参数含义、哪些效果可能发生、结果怎样证明、错误怎样分类。否则统一包装只能统一字段名,无法统一行为。
契约要能被检查,第一层是参数形状。JSON Schema 是描述 JSON 数据允许形状与约束的一种语言。它不是执行授权,也不是证明参数符合业务目的的判据。最小形态可以只包含对象类型、属性定义、必填字段、额外字段规则及必要的长度约束。程序仍需执行对应校验。
required 表示属性必须存在,不表示值非空。允许 null 要在该字段的类型或其他合法约束中明确表达;允许空字符串,也要由长度条件决定。对象的 properties 只是定义可出现属性,不会自动使每个属性必填。嵌套对象的必填条件属于那个对象,不能只在顶层列一次。
default 是注解,不要求通用校验器自动填值。字段缺失时是否补默认值,是应用契约的决定。若程序确实填入默认值,要说明何时填、填什么,以及怎样记录原输入与实际参数。不能把某个库的默认填充行为写成 JSON Schema 本身的执行规则。
第3章把 schema 给模型,同时在固定工具中手工检查。字段集合必须精确匹配,来源必须属于固定集合,摘录必须满足长度与原文条件。见 book/v2/harness.py 的 Harness.tool。Python 标准库不会因为收到 schema 就自动完成全部 JSON Schema 校验。当前手工检查适用于这两个窄接口,不是通用验证器。
建设更广的接口时,先确定实际支持的 schema 方言和验证范围,再决定使用已有校验组件或继续保持窄契约。不应宣称支持完整 schema,却只检查顶层类型。模型侧的结构约束也不能替代本地检查:它不负责当前授权、来源版本或实际对象是否仍然符合条件。
5.2参数取值与请求绑定
参数缺失表示这项属性没有提交。null 是明确提交的空值。空字符串、空数组和空对象是各自类型中的实际值。默认值是程序按约定选择的替代值。这四类不是“没有内容”的不同写法。最小语义规则要逐字段规定它们的合法性与效果。
更新接口最容易在这里丢失含义。若约定缺失 note 表示保留原值,note: null 表示清除,note: "" 表示设置为空字符串,那么 args.get("note") 无法区分前两者。可以在适配层使用缺失标记。下面仅展示判别原理,不是本工程已有更新工具:
MISSING = object()
value = args.get("note", MISSING)
if value is MISSING:
operation = "keep"
elif value is None:
operation = "clear"
else:
operation = "set"
标记只用于程序内部,不能当作 JSON 值发送。后续执行还要核对字段是否允许清除、设置值是否合法,以及目标版本是否可更新。当前摘录文件只创建全新对象,没有清除或覆盖接口;不能为了说明更新语义就暗中扩大现有工具效果。
某些模型接口要求声明的属性全部必填,并用可空类型表达“可选”。这是该接口的格式限制,不是统一业务语义。把原本可缺失的更新字段机械改成“必填且可空”,再把 null 转为缺失,会消除清除动作;反向把缺失补为 null,则可能把保留变成清除。
应保留不可变的原始工具契约,另生成模型可见的接口表示。必要时显式表达 keep、clear、set 操作,把“是否更新”与“更新为何值”分开。适配层把新表示转换回原业务语义,并验证转换结果。转换规则本身也需要版本,不能在请求返回后换成另一种解释。
默认值同样要固定。不能让运行时全局默认值变化,导致旧请求执行不同动作。记录原参数、转换版本与实际参数,才能解释为何程序最终调用了某个实现。若找不到保语义的表示,就不要向该模型接口暴露这项能力;拒绝接入比静默改变效果更可靠。
形状和取值说清之后,还要把这次请求绑到具体实现。工具映射是模型可见名称到实际执行能力的绑定。它不是按名称查找最新函数的便利字典。最小绑定需要包括实际 schema、语义转换、实现版本及调用目标。模型提出的参数必须在这次请求绑定的契约下解释。
第3章初始化时复制 TOOLS 与 MAPPING,请求意图记录 schema、schema hash 和映射标记。见 book/v2/harness.py 的 Harness.__init__ 与 Harness.loop。源码没有热更新,因此运行级固定副本足以维持当前绑定。MAPPING 中的简短版本标记也不是对全部环境依赖的完整证明。
若增加适配器,应扩展绑定对象:原始业务 schema、提交给模型的实际 schema、参数与结果转换版本、实现版本、服务实例标识,以及可获得的依赖信息。schema hash 只能证明某份定义未变化,不能证明远端实现没变。无法取得远端构建版本时,要写明缺口,不能用本地适配器版本替代它。
响应回到当前名称时,不应重新读取全局目录并执行最新同名工具。旧请求已经依据旧语义作出提议。契约变化后,要保留旧绑定直至合法结束,或停止旧请求并重新选择。不能靠模型“不太可能碰到变更”维持正确性。
发现描述与调用 schema 也要分开。发现描述帮助选择候选,至少说明用途、输入大意、效果与限制;完整 schema 才约束具体调用。简介不是可执行契约,但也不能隐藏影响授权的效果。候选规模、按需激活及常驻策略由第7章处理,这里先保证选择后拿到的是请求实际绑定的契约。
5.3结果、受理与错误
工具结果是执行端按契约返回的观察材料,不是必然成立的完成证明。最小结果应区分结果状态、业务数据、对象版本、动作关联与证据范围。读取返回的文本是数据,创建返回的提交回执是动作观察;两者不能只因都装进 JSON 就具有相同证明能力。
服务错误至少要分清协议、认证、业务和通信等待。协议错误表示请求或响应无法按所选规则解释;认证错误表示当前凭证没有通过身份检查;业务错误表示业务条件不满足或约定动作未成功;超时表示本次等待没有取得所需结果。错误类别帮助决定下一步,却不能单独证明外部效果未发生。
例如业务服务可能先完成部分工作再报告失败,通信也可能在已受理之后中断。要取得“没有执行”的结论,需要可靠的边界回执,不是某个 HTTP 状态码或一段错误文本。认证失败通常发生在效果前,但适配器也应按目标服务实际契约解释,不能把所有封装层错误都归到未提交。
后台服务需要区分受理和结束。受理回执应关联动作或提交尝试,并提供可查询的 handle。这里的 handle 是后续操作引用的执行对象标识,不是任意任务的权限凭证。查询要定义返回状态、最终性、版本和未知;取消要定义是否只是请求停止,是否能影响已提交工作,以及怎样核对结束。
这些是接入前应取得的契约,不要求本章实现通用后台任务系统。没有可靠查询方法时,等待超时后保留未知并停止自动重提。第11章再依据动作性质、受理与查询规则讨论重试资格。第3章的同步工具并没有后台 handle,也没有因本节定义而获得查询或取消能力。
5.4MCP 的协议范围与执行权限
MCP,即 Model Context Protocol,是客户端与服务端交换能力描述、调用及相关资源的公开协议。它不是一种新权限,不是本地沙箱,也不是调用后自动安全的标记。最小接入需要一个承接 harness 调用的客户端、一个提供能力的服务端,以及双方明确支持的协议与传输契约。
本节字段依据2026-11-01检索到的官方工具规范。为避免将不同版本混写,结构化结果的说明锚定官方2025-06-18版本,不宣称它是最新版本。随书固定 stdio 客户端在 initialize 中实际核对这一版本,但只支持约定的工具子集,不等于完整实现该版本的全部能力。接入其他服务时仍须记录所选版本并按对应规范核对,不照搬某家 SDK 或平台包装成通用协议。
tools/list 用于发现工具。工具定义以 name 标识工具,以 inputSchema 描述输入,并可包含功能 description 与 outputSchema。列表通过 cursor 与 nextCursor 衔接分页,调用方不能只读第一页就宣称取得了完整集合。发现得到一个工具不等于当前任务已经允许使用它,服务端发布的说明也不能修改宿主授权。
tools/call 用于调用工具,其参数包含 name 与 arguments。协议请求身份用于匹配协议响应;它与模型的 tool_call_id、业务 action_id 不是同一个对象。适配器必须保存关联关系,不能用某个协议编号代替全部业务身份。
返回中的 content 是内容项集合,不保证只是一个字符串。structuredContent 是服务端返回的结构化 JSON 对象,与模型的结构化生成不是同一概念。outputSchema 若存在,官方该版本要求服务端提供符合它的结构化结果,并建议客户端校验。宿主可以把结果校验定为自己的执行要求,但应区分宿主要求与协议的 MUST、SHOULD。
isError: true 表示工具执行错误,与 JSON-RPC 的协议错误对象分开。isError 未设为真也不能直接变成“业务产物已完成”,还要检查具体能力的完成回执。错误内容可以交给模型纠正提议,但不能授予重试副作用的资格。若结构化结果与文字说明冲突,应按契约停止核对,不自行选择有利的一份。
官方规范来源:https://modelcontextprotocol.io/specification/2025-06-18/server/tools。本节不是完整协议消息模板;初始化、能力声明及其他版本要求要由实际客户端按所选规范完成。
5.5传输方式影响的范围
本地 stdio 接入通过标准输入输出交换协议消息,服务端通常是宿主启动的本地进程。它不是“没有网络所以受限”的证明。本地服务可能继承文件权限、环境凭证和访问其他服务的能力。最小配置要明确启动对象、工作目录、可继承环境、通信通道和退出后的关联工作。
模型 API Key 不应顺手进入工具进程环境。第3章没有启动第三方工具;新增服务时要构造专用环境,只交付该连接需要的凭据。协议输出也应与诊断输出分开,避免日志破坏消息解析。关闭父进程不证明服务创建的全部后代和远端工作都已结束。
HTTP 接入把执行转交给远端服务。要核对固定目标、传输身份、认证方式、重定向、响应边界与发送范围。本地路径限制不能保护远端服务的全部资源,断开连接也不证明远端动作撤回。相关执行生命周期由第10章处理,未知与可靠接续由第11章进一步处理。
凭据归属与执行身份应进入绑定接口。调用属于哪个账户,使用哪份已绑定的连接,允许读取和发送什么,不能由模型参数自由选择。认证成功不代表所有资源已获准。这里保留责任入口,不提前建立组织平台的权限全集;第13章再按本书的 provider、connector 用法解释不同归属。
传输适配只应改变通信步骤,不应绕开原有参数检查、当前修订、写前记录和验收。若服务能力上界无法满足任务需求,就不能仅因提供 MCP 接口而接入。
5.6远端接入与方法包
可以用远端 read_source 说明进一步适配的关系,但不能把随书本地 MCP 实践冒充远端联调。原本地工具和新增固定 stdio 服务都只返回本次启动绑定的两个 fixtures。若改接任意远端来源,需要新增来源登记与快照规则,随后重新验证发送和摘录条件。
模型侧仍可只提交 source_id。宿主把该标识映射到获准连接与远端来源对象,取得实际 inputSchema,按绑定的转换规则生成 tools/call 参数。模型不因此获得任意服务地址、凭据或来源路径的选择权。不同服务同名工具也必须有明确的本地别名与连接绑定。
结果回来后,先完成协议匹配与结果校验,再提取来源内容、稳定标识和版本。若远端不提供可靠版本,宿主可以计算取得内容的 hash,说明它只绑定本次观察字节,不证明远端未来不变。需要跨请求一致时,应固定取得的快照,而不是稍后按相同来源名重新读取并假定相同。
随后把已校验内容按授权进入模型请求,再允许模型选择摘录。仅在服务端完成读取,不足以满足“创建请求已包含材料”。适配后的创建检查仍要从实际载荷核对已发送的内容,并把产物绑定到同份来源内容。现有代码只认识固定 fixtures,因此这条路径需要显式扩展,不能只替换传输函数。
MCP 还有 resources 与 prompts。Resource 是可读取的资源及其标识、内容等描述,不是调用工具的另一个名称;最小用法是定向取得一份获准材料。Prompt 是服务提供的可参数化提示模板,不是更高优先级授权;最小用法是取回模板后按宿主规则组成输入。本书不要求工具接入同时实现全部资源和模板能力。
资源与提示模板之外,方法包是另一类可加载内容。本书用 Skill 指可加载的方法与资源包。它不是所有平台共用的统一标准,也不是新的工具权限,更不是必然独立运行的子 agent。最小方法包可以包含方法说明、所需资料、版本与依赖;脚本是可选项,不是定义成立的前提。
方法说明回答在什么条件下采用这条过程,先取得哪些材料,怎样调用已有工具,以及怎样检查结果。资料提供细节依据,例如格式规范、来源约定与错误处理。依赖回答需要哪些工具能力和版本。包版本绑定本次采用的方法,不能在运行中悄悄切换说明而沿用旧判据。
摘录方法可以规定:读取指定来源,等来源结果进入下一次请求,再选择原文,按固定顺序提交创建,最后核对产物。它没有增加第三个执行系统,而是组织同一 read_source、create_card 调用路径。方法包不能因要求“先读再创建”就替代程序对实际发送与原文的检查。
加载前先根据目标、适用范围、工具依赖和效果要求决定是否采用。加载后只取当前阶段需要的说明与资料,并记录版本和来源。包中文本仍是所声明来源的材料,不因文件名叫 Skill 就成为用户授权。它要求新增工具或外发材料时,必须重新核对任务许可。
可选脚本也沿同一能力契约接入。固定脚本可以被包装成窄工具;允许任意脚本或第三方依赖则改变执行上界。包签名、版本或良好说明不能替代第10章的强制边界。没有合适执行条件时,可以只采用方法与资料,不执行附带代码。
5.7方法生效与步骤执行
方法被采用,指方法说明或资料进入了实际模型请求。步骤执行是相关工具动作及结果经过检查。两者不是同一事实。最小验证应分别记录采用了哪份方法、请求包含哪些必需材料,以及动作前的程序条件是否成立。
第3章已经提供一个具体边界:两份材料的读取结果必须出现在产生创建提议的实际请求中。程序检查 disclosed_sources,而不是相信模型说“读过”。同响应先执行读取再创建,也因模型尚未收到材料而被拒绝。这个检查证明材料已进入请求,不证明模型逐句理解。
扩展到方法包时,可以记录方法版本、必需资料版本和请求中实际发送的集合。对必须由程序保障的步骤,再设置明确的检查条件。例如原文与绑定快照逐字匹配、来源顺序固定、创建目标全新、当前修订有效。
程序判据应在采用方法时说明,不依赖模型对自己行为的承诺。每项检查要有对象、条件、证据与不通过时的处理。方法说明还要写清这些检查不能证明什么,不能在失败之后悄悄改变完成标准。
并非每个自然语言方法要求都能直接自动验证。“选得最有代表性”没有在当前任务中建立判据,不能因模型读过方法就宣布满足。应把它排除在自动完成范围之外,或另定义可用证据与人工判断。方法约束的价值在于使过程和出口更明确,不在于把所有文字要求包装成布尔值。
验证失败后,返回真实缺口,不补造一个已执行步骤,也不伪造工具调用消息。若方法要求的工具没有绑定,停止或换用满足当前许可的方法。工具与方法始终同线;加载方法包不会创造独立消息历史、后台调度者或额外行动权。
5.8本章判据
统一接入完成的标志,是调用形式变化以后,原有语义与运行责任仍然可核对。
- ☐工具是否明确输入、结果、效果范围、身份与完成条件,粒度是否适合授权和核对?
- ☐实际校验是否覆盖声明范围,
required、可空与空值是否分开,default是否没有被当作自动执行规则? - ☐缺失保留、null 清除与空值设置是否保留语义,模型接口转换是否不改变原始契约?
- ☐请求是否绑定实际 schema、语义转换与实现版本,远端版本缺口是否如实记录?
- ☐受理与完成、协议与执行错误、认证与业务条件、超时与未知是否分开?
- ☐MCP 是否按明确版本适配,结构化结果是否校验,协议错误或
isError是否没有被简化为业务效果结论? - ☐本地与远端接入是否保留执行及发送上界,凭据是否不混用、不交给模型选择?
- ☐Skill 是否只组织方法、资料和可选代码,必需步骤是否由明确检查验证而不是由模型自述证明?
本章没有回答什么
本章没有实现完整 MCP 客户端、后台执行器、方法包管理系统或组织权限模型。能力如何大规模发现与按需加载由第7章处理;本地进程及远端转交的实际边界由第10章处理;查询、取消、未知与重试的可靠接续由第11章处理。统一契约不保证所有服务语义相同,也不要求每种能力都接成 MCP 或每个任务都加载 Skill。
实践落点
随书 practices.py 的 CapabilityBinding.bind 固定实际 schema、版本和转换方式;convert 分别保留缺失、null 与字符串,不把默认值当作自动动作。
capability_lab.py 则把同一个摘录系统分成普通函数和 MCP 两种读取路径。FixedStdioClient 启动固定服务,完成 initialize、initialized 通知、tools/list 和 tools/call。它只支持约定的工具子集;分页、额外工具或另一协议版本都会拒绝,不能拿这份练习客户端连接任意服务。
每条协议消息单独检查编号和字节边界。validate_read_result 检查 structuredContent 的窄 schema,并将原文字节、来源标识和 hash 与宿主启动快照比较。不能只把服务返回的 hash 自己重算一次就当成来源绑定。随后沿原 Harness.loop 返回配对工具结果,创建仍要求材料已进入产生创建请求的输入。
MethodPackage.bind 核对工具依赖和资料版本,load 只加载读取、创建或完成阶段需要的资料。方法说明与参考资源保存在这份小型方法包中,不是通用 Skill 格式,也不是 MCP resources。方法文本没有增加权限;同批次读取与创建仍会被 sources_not_disclosed 拒绝。
从随书项目根目录运行:
python3 -I -B book/v2/capability_lab.py --demo
python3 -I -B book/v2/test_capability_lab.py
python3 -I -B book/v2/test_practices.py
本次本地接入测试18项通过。正常路径经过真实子进程 stdio 通信,使用3次脚本模型请求、3次工具调用创建并验收真实产物;反向路径验证方法已进入请求,也不能跳过“材料必须已进入请求”这一条件。通信不是模拟的,模型选择仍是脚本给出的。客户端固定启动对象、净化环境并关闭无关 FD,服务本身可信;这不是第10章的任意程序强制边界。
用量测试只核算合成数据。没有付费模型调用或真实模型 usage,不报告模型成功率、缓存收益或实际账单。