12 · MCP、Hook bridges、Mods、浏览器与电脑使用¶
这一章研究外部能力如何接入同一个ToolRuntime,以及接入后仍有哪些功能没有实现。读“bridge支持Claude/Codex/MCP”时必须分开命令hook、JS mods、external subagent、模型provider和浏览器tool;这些不是一条万能兼容层。源码固定main da00f7f,实验固定rc.2;真正第三方server/GUI行为需要额外实测,本课程离线实验不冒充这些平台验收。
12.1 MCP client是一实例一server的effect-owned连接¶
mcp-client没有给loop加专用分支。每个插件实例声明serverName与transport,连接发现tools后注册到ctx.tools;多个实例多server,caller-visible scope控制隔离。serverName限制 [A-Za-z0-9_-]{1,32},相同scope重复被拒绝,另一Agent scope可以复用同namespace。常见tools也来自同一registry pipeline,MCP不绕过guards/approval/PTC。
| transport | 配置 | 实际执行所有者 |
|---|---|---|
stdio |
command、args、env、cwd | MCP SDK StdioClientTransport启动child |
streamable-http |
url、headers | MCP SDK StreamableHTTPClientTransport |
共同配置有toolCallTimeoutMs(默认60000)、failOnStartupError(默认false)、maxInstructionBytes(32768)、reconnect。stdio MCP spawn不走ctx.subprocess.spawn,只是复用scrubbedParentEnv再加explicitenv;managedprocess range、localfile sandbox不会因你有这些DSH services就自动施加到MCPserver。server executable与endpoint是部署trustedconfig,不是随意接受model参数。源码:index.ts · export interface StdioConfig、transport.ts · export function createTransport。
# 示例组成:需要你实际安装并审核这个server。此处命令是占位,
# 不是未经下载就已可运行的DSH内置工具。
- insert:
- id: my-notes-mcp
name: '@deepseek-ai/dsh-mcp-client'
config:
transport: stdio
serverName: my-notes
command: /absolute/path/to/your-server
args: []
env: {}
failOnStartupError: true
toolCallTimeoutMs: 10000
reconnect:
enabled: true
initialDelayMs: 500
maxDelayMs: 10000
maxAttempts: 3
12.2 discovery、命名与完整代交换¶
publicToolName以tuple(serverName,rawName)为identity,cleancase mcp__server__raw;DeepSeek function names最多64char且限定alphabet,replacement/truncation时加12hex SHA-256identity摘要。wire始终用rawName,不能从normalizedpublicname反向猜rawName。有限摘要并非数学上绝无碰撞,registryduplicate检查仍重要。源码:tools.ts · export function publicToolName。
syncTools()是两阶段:先fetch整份catalog并builddefinitions,期间失败留下previousgeneration;随后disposeprevious并registerallnew。swap阶段遇foreignnamespace conflict回滚partialnew,结果这个server为zero tools并report,strict initial模式可throw;不是恢复previous所有工具的事务。重复raw/publicname也拒绝,不保留半份catalog。notifications/reconnectsync通过syncChain serial,防两次dispose/register交错泄露代。源码:tools.ts · export async function syncTools、connection.ts · function enqueueSync。
MCP 工具宣告的 outputSchema 若属于 Harness 支持的 JSON Schema 子集,会保留为结构化输出约束;若包含不支持的词汇,则降级成通用 JSON 值,不会因此拒绝整个工具。声明 taskSupport: required 的工具仍可以出现在 catalog,但执行时明确拒绝,因为这个 bridge 不实现 MCP task-based execution。接入 MCP 需要逐项核对这些能力边界。源码:supportedOutputSchema。
12.3 原始canonical值与model content是两条结果线¶
createMcpToolDefinition()用MCP spec CallToolResult验证wire result;isError=true在image storage之前throw,ToolRuntime于是记录failed/isError;普通successfultyped value只含content与可选structuredContent,不把所有transportmetadata塞进模型history。outputschema若支持structuredschema,则structuredContent也成为required并runtime验证;不能把plainJSON成功当作structuredschema已合规。源码:tools.ts · export function createMcpToolDefinition、tools.ts · function createExecutor。
native model projection:text合并、resource_link变名称/URItext;audio和embeddedresource暂只生成unsupportedplaceholder,raw数据仍供programmaticcaller使用,不自动等价于模型可理解音频/文件。未知block也明确提示。
image结果需符合MIME、canonicalbase64、所有imagebatch的preflight、当前exact route正向image-capability proof、attachmentsdurable save。任意imagebatch拒绝时model projection全部fallbacktext;rawimagecanonicalvalue仍可给PTCcaller。attachmentsstore和modelimagecapability缺失不意味着整个MCP业务调用必须失败,可能是imagecontext不能接纳。成功admission保持图像原position,tool-output后置policy改了value/content会使preparedprojection失配而不偷偷覆盖policy。源码:tools.ts · async function resolveImageAdmission、tools.ts · async function prepareImageProjection。
12.4 reconnect恢复连接,不自动重做有副作用的call¶
reconnectdefaults enabled true、initial500、maxDelay30000、maxAttempts10。一次outage共享连续failedbudget,brieflyconnect又crash不会无限重置;uptime超过stabilitywindow(maxDelay)才视作outage结束。firstready供activation等待,failOnStartupError决定是否rejectplugin;默认false会log并继续supervisor,不等于server已ready。
generation必须仍current且pluginlive才能同步tools或操作state。旧transportclose确认有5秒barrier,不能确认会停reconnect,避免多个stdiochild重叠。budgetexhaust移除server工具,需reload/restart再启;reconnectdisabled时既有失效工具可能留下并失败,不能宣称只要断开就即时从prompt消失。源码:connection.ts · export function startConnection、connection.ts · function settleFailedGeneration。
MCP connection restart与tool invocationretry分开。HTTPtimeout、streamloss或userabort可能发生在server已完成写操作之后;Host不能安全判定是否未执行。你自己的server应接受幂等key或提供operationstatus/reconcile工具;恢复后先读取真实状态,避免自动重送write/delete/send。
12.5 Resources不是伪装成独立工具的每个URI¶
ctx.mcpResources是scope-awareproviderregistry。mcp-client连接注册一server的资源request能力,resource consumer提供三个shared tools:list_mcp_resources、list_mcp_resource_templates、read_mcp_resource。firstserver注册时拥有本scope的tools,lastserver卸载时移除;一server卸载不能误删兄弟server的sharedtools。sourceprompt列出caller可见servernames,request按agent scope解析server,不接受不可见服务。源码:index.ts · register(server:。
cursor/URI由server拥有;resource内容不自然获得systeminstructionpriority。instructions由servercontext有归属且受UTF-8bytes上限,插件extension把model-visibletext送prompt/history让log能重建。资源返回JSON、attachmentimageadmission、tooltypedvalue、contextinjection是不同过程;不要把read resource等同于自动开图片识别。
12.6 命令Hooks桥接复用DSH扩展点,兼容范围是明确子集¶
hook-protocol共享matcher、codec、merge、runHook、detachedtracking与durablehookevents;claude-code/codex桥自己生成stdinpayload、env/substitution并映射decision。runHook经ctx.shell.resolve/execute,payload由bridgebuild,带operation signal/timeout;default600000ms、perhook timeoutSec要换为ms。配置configPath在processload读一次,relative相对processcwd,不自动按每个session发现项目hooks.json。源码:runner.ts · export async function runHook。
| 行为 | Claude Code command bridge | Codex command bridge |
|---|---|---|
| 常见events | SessionStart、UserPromptSubmit、PreToolUse、PostToolUse、Stop、SubagentStart/Stop | 前五种,不含CC subagent events |
| stdin framing | JSON末尾newline | 无newline |
| command variables | CLAUDE_PLUGIN_ROOT/PROJECT_DIR替换与project env | 无额外env/substitution |
| matcher | CC dialect规则 | regex-only |
| PreToolUse | deny、ask,其他委托 | 只honor blocking deny,不当成allow/ask |
| updatedInput | warning+ignored | 无rewrite路径 |
| transcript_path | 空string | null |
| systemMessage | warning+ignored | warning+ignored |
| continue:false | 记录neutralstop,目前缺runhalt机制 | 同样不能当全run已停止 |
源码:index.ts · export function apply、index.ts · export function apply。
workspace取sessioncwd,CLAUDE_PROJECT_DIR默认sessionworkspace,可有部署explicitoverride。CC subagent_type目前general-purpose,配置匹配code-reviewer等specifickind不会神奇工作。Codex payload 保留 model、turn_id,tool_name 使用真实工具名;tool_input 只抽 command,不声称传所有Harness args;last_assistant_message目前null。
runHook基础设施无法执行会转nonblockingerroroutcome(undefinedexit+stderr),turn继续;configread/parse失败会log并不注册任何hooks。命令hook桥不是默认fail-closed安全policy。如果你的安全需求是任何校验程序出错都禁止执行,应写native typedguard明确deny,不要把桥接的容错规则当审计保障。
12.7 Pre、Post和Stop三类决定不应混淆¶
PreTool deny发生业务体之前;ask转审批;普通没有决定应next(),让后续policies仍运行。PostTool block发生执行之后,给modelfeedback或context,不撤销sideeffect。只添context的bridge先delegate再spreaddownstreamdecision,保留后续block与其他metadata,不能凭第一hookallow覆盖更严格下一hook。
Stop映射到agent/turn-stopping,在边界steer一条typedcontext使loop再请求;source stop_hook_active现在总false,无条件blockinghook可能永远续step。自己Agent应加budget/明确stopcondition,而不是写“hook说deny后立刻结束整个task”。SessionStart/subagent生命周期有detachedruns,dispose abort+drain;open-turn有hook/invoked/result对,detachedlifecycle不强造turn记录。
桥接解决“复用一部分已有命令hooks”,本地插件以typedextensionpoints实现新政策更容易清楚表达错误、幂等性和日志。别为了复用旧payload,把本来一个tools/pre-execute listener能解决的逻辑再包成shell子进程。
12.8 Experimental Claude Mods与“Codex Mods”的核对结论¶
当前仓库存在 experimental/claude-code-mods、client-ui-claude-code-mods,没有名为experimental/codex-mods的实现包。Codex相关已实现的是hooks-codex与外部subagentdriver等,不能按请求措辞发明另一JS mods插件。
Claude mods支持JS register(on,options)、orderedhooks/matchers、next chaining、catchhandler、hostprocess/HTTP等API和UIband。SERVEDEVENTS明确只有session.start/end、prompt.submit、turn.start/complete、tool.call、command.run、ui.render;已知但unserved event可以注册却会被报告,不会在DSH自动产生。可重写directhumanprompttext且保留nontextblocks,追加context;tool-inputrewrite仍deferred,不同于promptrewrite。源码:index.ts · export const SERVED_EVENTS、index.ts · function rewritePromptText。
HookRegistry按modload与registrationorder选择chain;mod自己发起的API事件只被更早加载mod观察,避免递归interception。超时/catchdeadline限制业务hook等待,但它是受信Host扩展,不应称为执行任意陌生JSmod的OS沙箱。prototype-safevalue、surfaceadapter和typedresultvalidation仍只保护指定边界。源码:module.ts · select(event:。
这是opt-in experimental publishedprototype,默认Web不应自动加载所有experimental。UIbandclient、hostengine与moddistribution需要一起按manifest部署;有包目录不等于默认preset已有工具。真正creatorAgent改插件时,先看profile/currentroster,再操作explicitbundle。
12.9 browser-use/computer-use registry只保留一个provider席位¶
服务定义browserUse与computerUse均为exclusive namedregistration:一个provider占位,重复同name也拒绝;provider必须先stoptools、awaitownedwork再release,以防旧browser/desktop仍工作时下一provider重入。registry没有内置一个万能browser.click方法,provider自己注册工具,并负责session资源。源码:index.ts · register(name:、index.ts · register(name:。
| experimental provider | 真实实现 | 生命周期/权限要点 |
|---|---|---|
| browser-use-playwright-mcp | pinned Playwright MCP,Nodecli,Chromium | launch perliveSession或exclusiveattach;清理上游PLAYWRIGHT_MCP env避免绕开配置 |
| browser-use-chrome-devtools-mcp | pinned Chrome DevTools MCP | launch/attach,禁用usage-statistics;CDPendpoint部署指定 |
| browser-use-stagehand-native | native Stagehand + connectionWorker + ownedChromium | modelcredentials独立于sessionmodel,某些actions实际另有模型费用 |
| computer-use-cua-driver-mcp | 已安装cua-driver mcp命令 | startupdiscovery严格,exclusiveprovider,shutdown后release |
| computer-use-cua-driver-native | @trycua/cua-driver same-process SDK | platform权限与nativehandle,tools + screenshotprojection,shutdown/uniffiDestroy |
源码:index.ts · export function apply、index.ts · export function apply、index.ts · export function apply、index.ts · export async function apply、index.ts · export async function apply。
12.10 浏览器资源属于一次live Agent activation,不是durable Sessionid¶
SessionResources Map的key是exact Agentobject;available检查activeRegistry仍持有该Agent。resume同sessionid不会接回旧浏览器资源;launchsession浏览器state不从Sessionlogrestore。attach一次只能给一个liveowner,busyactivation可被maskbrowsertools让其他conversation继续。每Session操作串行,其他ownedSessions可独立推进;caller取消等待不能随意取消Session-ownedstartup。
MCPbrowsermount在agent/created等待scope-ownedclient,strictstartup且reconnect false;它在scope检查tool不得被另Agentborrow,blockedscope有restrictions防继承泄露。资源框架的 unload 会先 await 每个资源的 close,再释放 provider reservation;只有 close Promise 真正 reject 时,SessionResources 才保留 entry/exclusive ownership 并拒绝 disposal。MCP 路径的 close 是 scope.dispose,而其中 ConnectionHandle.dispose 在无法确认 transport 已关闭时只是 log error 后继续 resolve。因此不能把资源框架的通用规则升级成“MCP child 未确认退出就一定保留席位”的保证;attach 场景需要关注该错误并核验旧 server/浏览器实际状态。源码:index.ts · export class SessionResources、mcp.ts · export function mountSessionMcp。
Stagehand的act/observe/extract可能调用独立配置model,cancel后等activeworkdrain,推理/browserinput可在drain期间继续;completedclick无法回滚,失败cleanup阻reuse。attachedbrowser还可能由human或其他app改变。所以自己的Agent应先freshsnapshot再action,action后freshstate验收;recovery不依赖旧dom token。源码:index.ts · const GUIDANCE。
Cua native在Hostdesktop运行,element_token从freshwindowsnapshot取得;新snapshot使旧token失效,target和legacy pid/window_id不要混用。backgroundinput优先,但refusal不授权foregroundretry;macOS cursoroverlayfacility可能unavailable,即使screenshots/input可工作。OSpermissions、可见desktop、nativeSDK安装是部署条件,静态课程网站没有开放这样的控制入口。
12.11 自己的Agent应该怎样接这些能力¶
外部notes/ticket/search优先用受控MCPserver或native tool;需要复杂policy用DSH typedguards,在开call之前明确failclosed。已有CC/Codexcommandhooks可复用但逐项核对兼容子集;creator扩展用profile-ownedbundle并独立审查hostJS。浏览器/电脑能力放单独受限profile/机器,明确provider席位、owned/attached资源、freshsnapshot与真实model费用。
所有集成都要分别验收catalogdiscovery、callfailuretaxonomy、sideeffectstatus、contextprojection与quiescentcleanup。await成功只代表对应Promise已settle;不保证server动作成功、图像已被模型接纳、session资源能恢复或所有trace记录完整。