mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 22:35:51 +00:00
docs: 文档更新
This commit is contained in:
@@ -1,73 +1,239 @@
|
||||
---
|
||||
title: "Hooks 生命周期钩子 - 自定义行为注入"
|
||||
description: "解析 Claude Code Hooks 系统:在 AI 工具调用的关键节点(PreToolUse、PostToolUse、Notification 等)插入自定义 shell 命令,实现行为定制。"
|
||||
keywords: ["Hooks", "生命周期钩子", "自定义 Hook", "行为注入", "PreToolUse"]
|
||||
title: "Hooks 生命周期钩子 - 执行引擎与拦截协议"
|
||||
description: "从源码角度解析 Claude Code Hooks 系统:22 种 Hook 事件、6 种 Hook 类型、同步/异步执行协议、JSON 输出 schema、if 条件匹配、以及 Hook 如何注入上下文和拦截工具调用。"
|
||||
keywords: ["Hooks", "生命周期钩子", "拦截器", "PreToolUse", "Hook 协议"]
|
||||
---
|
||||
|
||||
{/* 本章目标:解释 Hooks 系统的设计和应用场景 */}
|
||||
{/* 本章目标:从源码角度揭示 Hook 的执行引擎、匹配机制、返回值协议和生命周期管理 */}
|
||||
|
||||
## 什么是 Hooks
|
||||
## 22 种 Hook 事件
|
||||
|
||||
Hooks 是用户定义的 shell 命令,在 Claude Code 生命周期的特定时刻自动执行。
|
||||
Claude Code 定义了 22 种 Hook 事件(`coreTypes.ts:25-53`),覆盖完整的 Agent 生命周期:
|
||||
|
||||
类比:React 的 `useEffect` 让你在组件渲染后执行自定义逻辑。Claude Code 的 Hooks 让你在 AI 的关键行为前后执行自定义脚本。
|
||||
| 阶段 | 事件 | 触发时机 | 匹配字段 |
|
||||
|------|------|---------|---------|
|
||||
| **会话** | `SessionStart` | 会话启动 | `source` |
|
||||
| | `SessionEnd` | 会话结束 | `reason` |
|
||||
| | `Setup` | 初始化完成 | `trigger` |
|
||||
| **用户交互** | `UserPromptSubmit` | 用户提交消息 | — |
|
||||
| | `Stop` | Agent 停止响应 | — |
|
||||
| | `StopFailure` | Agent 停止失败 | `error` |
|
||||
| **工具执行** | `PreToolUse` | 工具调用前 | `tool_name` |
|
||||
| | `PostToolUse` | 工具调用后(成功) | `tool_name` |
|
||||
| | `PostToolUseFailure` | 工具调用后(失败) | `tool_name` |
|
||||
| **权限** | `PermissionRequest` | 权限请求 | `tool_name` |
|
||||
| | `PermissionDenied` | 权限被拒 | `tool_name` |
|
||||
| **子 Agent** | `SubagentStart` | 子 Agent 启动 | `agent_type` |
|
||||
| | `SubagentStop` | 子 Agent 停止 | `agent_type` |
|
||||
| **压缩** | `PreCompact` | 上下文压缩前 | `trigger` |
|
||||
| | `PostCompact` | 上下文压缩后 | `trigger` |
|
||||
| **协作** | `TeammateIdle` | Teammate 空闲 | — |
|
||||
| | `TaskCreated` | 任务创建 | — |
|
||||
| | `TaskCompleted` | 任务完成 | — |
|
||||
| **MCP** | `Elicitation` | MCP 服务器请求用户输入 | `mcp_server_name` |
|
||||
| | `ElicitationResult` | Elicitation 结果返回 | `mcp_server_name` |
|
||||
| **环境** | `ConfigChange` | 配置变更 | `source` |
|
||||
| | `CwdChanged` | 工作目录变更 | — |
|
||||
| | `FileChanged` | 文件变更 | `file_path` |
|
||||
| | `InstructionsLoaded` | 指令加载 | `load_reason` |
|
||||
| | `WorktreeCreate` / `WorktreeRemove` | Worktree 操作 | — |
|
||||
|
||||
## 可用的 Hook 事件
|
||||
## 6 种 Hook 类型
|
||||
|
||||
| 事件 | 触发时机 | 典型用途 |
|
||||
Hooks 配置支持 6 种执行方式(`src/types/hooks.ts`):
|
||||
|
||||
| 类型 | 执行方式 | 适用场景 |
|
||||
|------|---------|---------|
|
||||
| **PreToolUse** | 工具调用前 | 拦截危险操作、自定义审批逻辑 |
|
||||
| **PostToolUse** | 工具调用后 | 记录日志、触发通知、自动格式化 |
|
||||
| **PreCompact** | 上下文压缩前 | 标记不可丢失的信息 |
|
||||
| **PostCompact** | 上下文压缩后 | 验证关键信息是否保留 |
|
||||
| **Notification** | AI 发出通知时 | 自定义通知渠道(Slack、邮件等) |
|
||||
| **StopFailure** | AI 循环异常停止时 | 自定义错误处理 |
|
||||
| `command` | Shell 命令(bash/PowerShell) | 通用脚本、CI 检查 |
|
||||
| `prompt` | 注入到 AI 上下文 | 代码规范提醒 |
|
||||
| `agent` | 启动子 Agent 执行 | 复杂分析任务 |
|
||||
| `http` | HTTP 请求 | 远程服务、Webhook |
|
||||
| `callback` | 内部 JS 函数 | 系统内置 Hook |
|
||||
| `function` | 运行时注册的函数 Hook | Agent/Skill 内部使用 |
|
||||
|
||||
## Hook 的能力
|
||||
## 执行引擎:execCommandHook
|
||||
|
||||
Hook 脚本不仅能"观察",还能"干预":
|
||||
`execCommandHook()`(`src/utils/hooks.ts:829-1417`)是命令型 Hook 的执行核心:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="拦截操作" icon="hand">
|
||||
返回特定信号可以阻止工具调用执行
|
||||
</Card>
|
||||
<Card title="修改行为" icon="pen">
|
||||
返回结构化的 JSON 输出,影响 Claude Code 的后续行为
|
||||
</Card>
|
||||
<Card title="注入上下文" icon="syringe">
|
||||
向 AI 的对话中注入额外信息
|
||||
</Card>
|
||||
<Card title="触发外部流程" icon="bolt">
|
||||
调用 CI/CD、发送通知、更新 Issue tracker
|
||||
</Card>
|
||||
</CardGroup>
|
||||
```
|
||||
execCommandHook(hook, hookEvent, hookName, jsonInput, signal)
|
||||
├── Shell 选择: hook.shell ?? DEFAULT_HOOK_SHELL
|
||||
│ ├── bash: spawn(cmd, [], { shell: gitBashPath | true })
|
||||
│ └── powershell: spawn(pwsh, ['-NoProfile', '-NonInteractive', '-Command', cmd])
|
||||
├── 变量替换
|
||||
│ ├── ${CLAUDE_PLUGIN_ROOT} → pluginRoot 路径
|
||||
│ ├── ${CLAUDE_PLUGIN_DATA} → plugin 数据目录
|
||||
│ └── ${user_config.X} → 用户配置值
|
||||
├── 环境变量注入
|
||||
│ ├── CLAUDE_PROJECT_DIR
|
||||
│ ├── CLAUDE_ENV_FILE(SessionStart/Setup/CwdChanged/FileChanged)
|
||||
│ └── CLAUDE_PLUGIN_OPTION_*(plugin options)
|
||||
├── stdin 写入: jsonInput + '\n'
|
||||
├── 超时: hook.timeout * 1000 ?? 600000ms(10分钟)
|
||||
└── 异步检测: 检查 stdout 首行是否为 {"async":true}
|
||||
```
|
||||
|
||||
## 配置方式
|
||||
### 异步 Hook 的检测协议
|
||||
|
||||
Hooks 在 `settings.json` 中配置:
|
||||
Hook 进程的 stdout 第一行如果是 `{"async":true}`,系统将其转为后台任务(`hooks.ts:1199-1246`):
|
||||
|
||||
```typescript
|
||||
const firstLine = firstLineOf(stdout).trim()
|
||||
if (isAsyncHookJSONOutput(parsed)) {
|
||||
executeInBackground({
|
||||
processId: `async_hook_${child.pid}`,
|
||||
asyncResponse: parsed,
|
||||
...
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
后台 Hook 通过 `registerPendingAsyncHook()` 注册到 `AsyncHookRegistry`,完成后通过 `enqueuePendingNotification()` 通知主线程。
|
||||
|
||||
### asyncRewake:Hook 唤醒模型
|
||||
|
||||
`asyncRewake` 模式的 Hook 绕过 `AsyncHookRegistry`。当 Hook 退出码为 2 时,通过 `enqueuePendingNotification()` 以 `task-notification` 模式注入消息,唤醒空闲的模型(通过 `useQueueProcessor`)或在忙碌时注入 `queued_command` 附件。
|
||||
|
||||
## Hook 输出的 JSON Schema
|
||||
|
||||
同步 Hook 的输出遵循严格的 Zod schema(`src/types/hooks.ts:49-567`):
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": { "tool_name": "Write" },
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "npx prettier --write $CLAUDE_FILE_PATH"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"continue": false, // 是否继续执行
|
||||
"suppressOutput": true, // 隐藏 stdout
|
||||
"stopReason": "安全检查失败", // continue=false 时的原因
|
||||
"decision": "approve" | "block", // 全局决策
|
||||
"reason": "原因说明", // 决策原因
|
||||
"systemMessage": "警告内容", // 注入到上下文的系统消息
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow" | "deny" | "ask",
|
||||
"permissionDecisionReason": "匹配了安全规则",
|
||||
"updatedInput": { ... }, // 修改后的工具输入
|
||||
"additionalContext": "额外上下文" // 注入到对话
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个例子:每当 AI 写入一个文件后,自动用 Prettier 格式化。
|
||||
### 各事件的 hookSpecificOutput
|
||||
|
||||
## 安全控制
|
||||
| 事件 | 专有字段 | 作用 |
|
||||
|------|---------|------|
|
||||
| `PreToolUse` | `permissionDecision`, `updatedInput`, `additionalContext` | 拦截/修改工具输入 |
|
||||
| `UserPromptSubmit` | `additionalContext` | 注入额外上下文 |
|
||||
| `PostToolUse` | `additionalContext`, `updatedMCPToolOutput` | 修改 MCP 工具输出 |
|
||||
| `SessionStart` | `initialUserMessage`, `watchPaths` | 设置初始消息和文件监控 |
|
||||
| `PermissionDenied` | `retry` | 指示是否重试 |
|
||||
| `Elicitation` | `action`, `content` | 控制用户输入对话框 |
|
||||
|
||||
- 托管设置(企业管理员)的 Hooks 优先级最高,用户不能覆盖
|
||||
- Hook 执行有超时限制
|
||||
- Hook 的输出会被解析和验证,防止注入攻击
|
||||
## Hook 匹配机制:getMatchingHooks
|
||||
|
||||
`getMatchingHooks()`(`hooks.ts:1685-1956`)负责从所有来源中查找匹配的 Hook:
|
||||
|
||||
### 多来源合并
|
||||
|
||||
```
|
||||
getHooksConfig()
|
||||
├── getHooksConfigFromSnapshot() ← settings.json 中的 Hook(user/project/local)
|
||||
├── getRegisteredHooks() ← SDK 注册的 callback Hook
|
||||
├── getSessionHooks() ← Agent/Skill 前置注册的 session Hook
|
||||
└── getSessionFunctionHooks() ← 运行时 function Hook
|
||||
```
|
||||
|
||||
### 匹配规则
|
||||
|
||||
`matcher` 字段支持三种模式(`matchesPattern()`, `hooks.ts:1428-1463`):
|
||||
|
||||
```
|
||||
"Write" → 精确匹配
|
||||
"Write|Edit" → 管道分隔的多值匹配
|
||||
"^Bash(git.*)" → 正则匹配
|
||||
"*" 或 "" → 通配(匹配所有)
|
||||
```
|
||||
|
||||
### if 条件过滤
|
||||
|
||||
Hook 可以指定 `if` 条件,只在特定输入时触发。`prepareIfConditionMatcher()`(`hooks.ts:1472-1503`)预编译匹配器:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": [{
|
||||
"command": "check-git-branch.sh",
|
||||
"if": "Bash(git push*)"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
`if` 条件使用 `permissionRuleValueFromString` 解析,支持与权限规则相同的语法(工具名 + 参数模式)。Bash 工具还会使用 tree-sitter 进行 AST 级别的命令解析。
|
||||
|
||||
### Hook 去重
|
||||
|
||||
同一个 Hook 命令在不同配置层级(user/project/local)可能重复。系统按 `pluginRoot\0command` 做 Map 去重,保留**最后合并的层级**。
|
||||
|
||||
## 工作区信任检查
|
||||
|
||||
**所有 Hook 都要求工作区信任**(`shouldSkipHookDueToTrust()`, `hooks.ts:286-296`)。这是纵深防御措施——防止恶意仓库的 `.claude/settings.json` 在未信任的情况下执行任意命令。
|
||||
|
||||
```typescript
|
||||
// 交互模式下,所有 Hook 要求信任
|
||||
const hasTrust = checkHasTrustDialogAccepted()
|
||||
return !hasTrust
|
||||
```
|
||||
|
||||
SDK 非交互模式下信任是隐式的(`getIsNonInteractiveSession()` 为 true 时跳过检查)。
|
||||
|
||||
## 四种 Hook 能力的源码映射
|
||||
|
||||
### 1. 拦截操作(PreToolUse)
|
||||
|
||||
```json
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`processHookJSONOutput()` 将 `permissionDecision` 映射为 `result.permissionBehavior = 'deny'`,并设置 `blockingError`,阻止工具执行。
|
||||
|
||||
### 2. 修改行为(updatedInput / updatedMCPToolOutput)
|
||||
|
||||
```json
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"updatedInput": { "command": "npm test -- --bail" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`updatedInput` 替换原始工具输入;`updatedMCPToolOutput`(PostToolUse 事件)替换 MCP 工具的返回值——可用于过滤敏感数据。
|
||||
|
||||
### 3. 注入上下文(additionalContext / systemMessage)
|
||||
|
||||
- `additionalContext` → 通过 `createAttachmentMessage({ type: 'hook_additional_context' })` 注入为用户消息
|
||||
- `systemMessage` → 注入为系统警告,直接显示给用户
|
||||
|
||||
### 4. 控制流程(continue / stopReason)
|
||||
|
||||
```json
|
||||
{ "continue": false, "stopReason": "构建失败,停止执行" }
|
||||
```
|
||||
|
||||
`continue: false` 设置 `preventContinuation = true`,阻止 Agent 继续执行后续操作。
|
||||
|
||||
## Session Hook 的生命周期
|
||||
|
||||
Agent 和 Skill 的前置 Hook 通过 `registerFrontmatterHooks()` 注册(`runAgent.ts:567-575`),绑定到 agent 的 session ID。Agent 结束时通过 `clearSessionHooks()` 清理。
|
||||
|
||||
```typescript
|
||||
// runAgent.ts:567 — 注册 agent 的前置 Hook
|
||||
registerFrontmatterHooks(rootSetAppState, agentId, agentDefinition.hooks, ...)
|
||||
|
||||
// runAgent.ts:820 — finally 块清理
|
||||
clearSessionHooks(rootSetAppState, agentId)
|
||||
```
|
||||
|
||||
这确保 Agent A 的 Hook 不会泄漏到 Agent B 的执行中。
|
||||
|
||||
@@ -1,68 +1,175 @@
|
||||
---
|
||||
title: "MCP 协议 - 开放的工具生态扩展"
|
||||
description: "深入解析 Claude Code 的 MCP(Model Context Protocol)集成:通过标准协议对接数据库、API 和自定义服务,突破内置工具的能力边界。"
|
||||
keywords: ["MCP", "Model Context Protocol", "工具扩展", "外部集成", "API 对接"]
|
||||
title: "MCP 协议 - 连接管理、工具发现与执行链路"
|
||||
description: "从源码角度解析 Claude Code 的 MCP 集成:7 种传输层实现、connectToServer 的 memoize 缓存、工具发现的 LRU 策略、认证状态机、以及 MCP 工具如何进入权限检查链路。"
|
||||
keywords: ["MCP", "Model Context Protocol", "工具扩展", "MCP 客户端", "工具发现"]
|
||||
---
|
||||
|
||||
{/* 本章目标:解释 MCP 协议如何扩展 AI 的能力边界 */}
|
||||
{/* 本章目标:从源码角度揭示 MCP 客户端的连接管理、工具发现协议和执行链路 */}
|
||||
|
||||
## 内置工具的局限
|
||||
## 架构总览:从配置到可用工具
|
||||
|
||||
Claude Code 内置了 50+ 工具,覆盖了通用的软件开发需求。但每个团队都有特殊需求:
|
||||
```
|
||||
settings.json: { mcpServers: { "my-db": { command: "npx", args: [...] } } }
|
||||
↓
|
||||
getAllMcpConfigs() ← 合并 user/project/local 三级配置
|
||||
↓
|
||||
useManageMCPConnections() ← React Hook 管理连接生命周期
|
||||
↓
|
||||
connectToServer(name, config) ← memoize 缓存(lodash memoize)
|
||||
├── 创建 Transport(stdio/sse/http/...)
|
||||
├── new Client() ← @modelcontextprotocol/sdk
|
||||
├── client.connect(transport) ← 超时控制(MCP_TIMEOUT, 默认 30s)
|
||||
└── 返回 MCPServerConnection ← { connected | failed | needs-auth | pending }
|
||||
↓
|
||||
fetchToolsForClient(client) ← LRU(20) 缓存
|
||||
├── client.request({ method: 'tools/list' })
|
||||
└── 每个工具包装为 MCPTool ← 统一 Tool 接口
|
||||
↓
|
||||
assembleToolPool() ← 合并内置工具 + MCP 工具
|
||||
↓
|
||||
工具名格式: mcp__<serverName>__<toolName> ← buildMcpToolName()
|
||||
```
|
||||
|
||||
- 连接内部数据库查询数据
|
||||
- 调用公司内部 API
|
||||
- 操作特定的 DevOps 工具
|
||||
- 访问私有的知识库
|
||||
## 7 种传输层实现
|
||||
|
||||
不可能把所有人的需求都内置进去。
|
||||
`connectToServer()`(`client.ts:596-1643`)根据 `config.type` 分发到不同的 Transport 实现:
|
||||
|
||||
## MCP:一个标准的"插头"
|
||||
| 传输类型 | Transport 类 | 适用场景 | 认证方式 |
|
||||
|----------|-------------|---------|---------|
|
||||
| `stdio`(默认) | `StdioClientTransport` | 本地子进程 | 无 |
|
||||
| `sse` | `SSEClientTransport` | 远程 SSE 服务 | `ClaudeAuthProvider` + OAuth |
|
||||
| `http` | `StreamableHTTPClientTransport` | HTTP 流 | `ClaudeAuthProvider` + OAuth |
|
||||
| `sse-ide` | `SSEClientTransport` | IDE 集成 | lockfile token |
|
||||
| `ws-ide` | `WebSocketTransport` | IDE WebSocket | `X-Claude-Code-Ide-Authorization` |
|
||||
| `ws` | `WebSocketTransport` | WebSocket 服务 | session ingress token |
|
||||
| `claudeai-proxy` | `StreamableHTTPClientTransport` | claude.ai 代理 | OAuth bearer + 401 重试 |
|
||||
|
||||
**Model Context Protocol**(模型上下文协议)是 Anthropic 提出的开放标准,定义了 AI 与外部工具之间的通信方式。
|
||||
### stdio 传输的进程管理
|
||||
|
||||
<Frame caption="MCP 连接架构——AI 的 USB 接口">
|
||||
<img src="/docs/images/mcp-architecture.png" alt="MCP 连接架构图" />
|
||||
</Frame>
|
||||
stdio 类型的 MCP 服务器作为子进程运行,cleanup 时采用 **信号升级策略**(`client.ts:1431-1564`):
|
||||
|
||||
类比:USB 是电脑连接外设的标准接口。MCP 是 AI 连接外部能力的标准接口。
|
||||
```
|
||||
SIGINT (100ms) → SIGTERM (400ms) → SIGKILL
|
||||
```
|
||||
|
||||
## 工作原理
|
||||
总清理时间上限 600ms,防止 MCP 服务器关闭阻塞 CLI 退出。
|
||||
|
||||
<Steps>
|
||||
<Step title="启动 MCP Server">
|
||||
开发者编写一个 MCP Server,暴露自定义工具(比如"查询数据库"、"发送 Slack 消息")
|
||||
</Step>
|
||||
<Step title="Claude Code 连接">
|
||||
在配置文件中声明要连接的 MCP Server
|
||||
</Step>
|
||||
<Step title="工具自动发现">
|
||||
连接后,MCP Server 提供的工具自动出现在 AI 的可用工具列表中
|
||||
</Step>
|
||||
<Step title="透明调用">
|
||||
AI 像使用内置工具一样使用 MCP 工具——无需知道底层实现
|
||||
</Step>
|
||||
</Steps>
|
||||
### 远程传输的认证状态机
|
||||
|
||||
## 三种连接方式
|
||||
SSE/HTTP 类型使用 `ClaudeAuthProvider` 实现 OAuth 认证流程。认证失败时进入 `needs-auth` 状态,并写入 15 分钟 TTL 的缓存文件(`mcp-needs-auth-cache.json`),避免重复弹出认证提示。
|
||||
|
||||
| 方式 | 适用场景 |
|
||||
|------|---------|
|
||||
| **stdio** | MCP Server 作为子进程运行,通过标准输入/输出通信。最简单 |
|
||||
| **SSE** | 通过 HTTP Server-Sent Events 通信。适合远程服务 |
|
||||
| **StreamableHTTP** | 基于 HTTP 流的双向通信。适合复杂的交互场景 |
|
||||
```
|
||||
连接尝试 → 401 Unauthorized
|
||||
↓
|
||||
handleRemoteAuthFailure()
|
||||
├── logEvent('tengu_mcp_server_needs_auth')
|
||||
├── setMcpAuthCacheEntry(name) ← 写入 15min TTL 缓存
|
||||
└── return { type: 'needs-auth' } ← UI 显示认证提示
|
||||
```
|
||||
|
||||
## 权限一视同仁
|
||||
## 连接缓存与重连机制
|
||||
|
||||
MCP 提供的工具和内置工具一样受权限系统管控:
|
||||
`connectToServer` 使用 lodash `memoize` 缓存连接对象,缓存 key 为 `${name}-${JSON.stringify(config)}`。
|
||||
|
||||
- 需要用户确认才能调用
|
||||
- 可以设置 allow/deny 规则
|
||||
- 支持沙箱限制
|
||||
### 缓存失效触发
|
||||
|
||||
这确保了第三方工具不会绕过安全边界。
|
||||
当连接关闭时(`client.onclose`),清除所有相关缓存(`client.ts:1376-1404`):
|
||||
|
||||
## 实际例子
|
||||
```typescript
|
||||
client.onclose = () => {
|
||||
const key = getServerCacheKey(name, serverRef)
|
||||
fetchToolsForClient.cache.delete(name) // 工具缓存
|
||||
fetchResourcesForClient.cache.delete(name) // 资源缓存
|
||||
fetchCommandsForClient.cache.delete(name) // 命令缓存
|
||||
connectToServer.cache.delete(key) // 连接缓存
|
||||
}
|
||||
```
|
||||
|
||||
### 连接降级检测
|
||||
|
||||
远程传输有 **连续错误计数器**(`client.ts:1229`):
|
||||
|
||||
```typescript
|
||||
let consecutiveConnectionErrors = 0
|
||||
const MAX_ERRORS_BEFORE_RECONNECT = 3
|
||||
```
|
||||
|
||||
遇到终端错误(ECONNRESET、ETIMEDOUT、EPIPE 等)连续 3 次后,主动关闭 transport 触发重连。对于 HTTP 传输,还检测 session 过期(404 + JSON-RPC code -32001)。
|
||||
|
||||
### 请求级超时保护
|
||||
|
||||
每个 HTTP 请求使用独立的 `setTimeout` 超时(`wrapFetchWithTimeout`,`client.ts:493`),而非共享 `AbortSignal.timeout()`。原因是 Bun 对 AbortSignal.timeout 的 GC 是惰性的——每个请求约 2.4KB 原生内存,即使请求毫秒级完成也要等 60s 才回收。
|
||||
|
||||
```typescript
|
||||
const controller = new AbortController()
|
||||
const timer = setTimeout(c => c.abort(...), MCP_REQUEST_TIMEOUT_MS, controller)
|
||||
timer.unref?.() // 不阻止进程退出
|
||||
```
|
||||
|
||||
## 工具发现:从 MCP 到 Tool 接口
|
||||
|
||||
`fetchToolsForClient()`(`client.ts:1745-2000`)使用 `memoizeWithLRU` 缓存(上限 20),将 MCP 工具转换为 Claude Code 的统一 Tool 接口:
|
||||
|
||||
```typescript
|
||||
const fullyQualifiedName = buildMcpToolName(client.name, tool.name)
|
||||
// 结果: "mcp__my-db__query"
|
||||
```
|
||||
|
||||
### 工具描述截断
|
||||
|
||||
MCP 工具描述上限 2048 字符(`MAX_MCP_DESCRIPTION_LENGTH`)。OpenAPI 生成的 MCP 服务器曾观察到 15-60KB 的描述文档。
|
||||
|
||||
### 工具能力标注
|
||||
|
||||
每个 MCP 工具根据 `tool.annotations` 自动标注:
|
||||
|
||||
| 注解 | 映射到 | 含义 |
|
||||
|------|--------|------|
|
||||
| `readOnlyHint` | `isReadOnly()` + `isConcurrencySafe()` | 只读,可并行 |
|
||||
| `destructiveHint` | `isDestructive()` | 破坏性操作 |
|
||||
| `openWorldHint` | `isOpenWorld()` | 开放世界(不可枚举) |
|
||||
| `title` | `userFacingName()` | 显示名称 |
|
||||
|
||||
### MCP 工具的权限检查
|
||||
|
||||
MCP 工具默认返回 `{ behavior: 'passthrough' }`(`client.ts:1816-1834`),意味着它们始终进入权限确认流程。工具名使用 `mcp__` 前缀精确匹配权限规则。
|
||||
|
||||
## MCP 工具的执行链路
|
||||
|
||||
```
|
||||
AI 生成 tool_use: { name: "mcp__my-db__query", input: { sql: "..." } }
|
||||
↓
|
||||
MCPTool.call() ← client.ts:1835
|
||||
├── ensureConnectedClient() ← 确保连接有效(重连)
|
||||
├── callMCPToolWithUrlElicitationRetry() ← 带 Elicitation 重试
|
||||
│ ├── client.request({ method: 'tools/call' })
|
||||
│ ├── 处理图片结果(resize + persist)
|
||||
│ └── 内容截断(mcpContentNeedsTruncation)
|
||||
├── McpSessionExpiredError → 重试一次
|
||||
└── 返回 { data: content, mcpMeta }
|
||||
```
|
||||
|
||||
### Session 过期自动重试
|
||||
|
||||
HTTP 传输的 MCP session 可能过期。检测到 `McpSessionExpiredError` 后自动重试一次(`client.ts:1862`),因为 `ensureConnectedClient()` 已经清除了缓存并建立了新连接。
|
||||
|
||||
### 内容截断与持久化
|
||||
|
||||
大型 MCP 工具输出通过 `truncateMcpContentIfNeeded` 截断,二进制内容(图片)通过 `persistBinaryContent` 写入文件并返回文件路径。图片自动 resize(`maybeResizeAndDownsampleImageBuffer`)。
|
||||
|
||||
## MCP 连接的并发控制
|
||||
|
||||
```typescript
|
||||
// 本地服务器并发连接数
|
||||
getMcpServerConnectionBatchSize() // 默认 3
|
||||
|
||||
// 远程服务器并发连接数
|
||||
getRemoteMcpServerConnectionBatchSize() // 默认 20
|
||||
```
|
||||
|
||||
本地 MCP 服务器(stdio)是重量级的子进程,默认限制 3 个并发连接。远程服务器是轻量级 HTTP 请求,允许 20 个并发。
|
||||
|
||||
## 实际配置示例
|
||||
|
||||
```json
|
||||
// settings.json 中的 MCP 配置
|
||||
@@ -72,9 +179,13 @@ MCP 提供的工具和内置工具一样受权限系统管控:
|
||||
"command": "npx",
|
||||
"args": ["@my-org/db-mcp-server"],
|
||||
"env": { "DB_URL": "postgres://..." }
|
||||
},
|
||||
"remote-api": {
|
||||
"type": "http",
|
||||
"url": "https://api.example.com/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
配置后,AI 就多了"查询数据库"这个能力——用自然语言描述需求,AI 自动生成查询并执行。
|
||||
配置后,AI 的工具列表中会出现 `mcp__my-database__query` 和 `mcp__remote-api__*` 工具——与内置工具使用相同的权限检查链路和 UI 渲染。
|
||||
|
||||
Reference in New Issue
Block a user