Claude Code 并非只有一个简单的 main() 入口。作为一个需要在多种场景下运行的 AI Agent 运行时,它设计了多入口架构(Multi-Entrypoint Architecture),让同一套核心能力可以灵活地服务于终端交互用户、外部工具链调用者,以及程序化集成的开发者。本文将从源码层面解析这一架构的设计思路与实现细节。
一、为什么需要多个入口
一个成熟的 AI Agent 平台通常面临三类使用场景:
- 终端交互:开发者在终端中直接与 Agent 对话,需要完整的 TUI(Terminal User Interface)、会话管理、历史记录和交互式权限确认。
- 工具链集成:IDE、编辑器或其他程序通过标准化协议(如 MCP)调用 Agent 的工具能力,不需要交互式 UI,但需要严格的输入输出契约。
- 程序化调用:其他程序或脚本将 Agent 作为库/SDK 使用,需要类型安全的 API 接口和细粒度的生命周期控制。
如果强行用单一入口满足这三类需求,代码会变得异常臃肿——终端 UI 的渲染逻辑会侵入到纯工具调用路径,而 SDK 的类型定义又会污染 CLI 的启动性能。Claude Code 的解决思路是:共享核心逻辑,分离入口适配层。
二、多入口架构概览
Claude Code 的入口体系由三个核心文件构成:
| 入口 | 文件 | 模式 | 适用场景 |
|---|---|---|---|
| CLI | entrypoints/cli.tsx | 交互式终端 | 日常开发、对话式编码 |
| MCP | entrypoints/mcp.ts | MCP Server | IDE 集成、工具链调用 |
| SDK | entrypoints/agentSdkTypes.ts | 类型定义/库 API | 程序化集成、自动化流水线 |
这三个入口并非孤立运行,它们共享相同的工具注册表(getTools())、状态管理(AppStateStore)、权限系统(permissions)和模型配置(model/model.js)。入口层只负责协议适配和生命周期初始化,真正的业务逻辑沉淀在共享模块中。
flowchart TD
subgraph Entrypoints["入口层"]
CLI["entrypoints/cli.tsx
终端交互入口"]
MCP["entrypoints/mcp.ts
MCP Server 入口"]
SDK["entrypoints/agentSdkTypes.ts
SDK 类型入口"]
end
subgraph Shared["共享核心"]
Tools["tools.ts
工具注册表"]
State["AppStateStore
状态管理"]
Perm["permissions
权限系统"]
Model["model/model.js
模型配置"]
Config["config.js
配置系统"]
end
subgraph Adapters["入口特定逻辑"]
Ink["Ink 渲染器
(CLI 专属)"]
Stdio["StdioServerTransport
(MCP 专属)"]
Types["类型导出 + 函数签名
(SDK 专属)"]
end
CLI --> Ink
CLI --> Shared
MCP --> Stdio
MCP --> Shared
SDK --> Types
SDK -.-> Shared这种分层设计带来两个显著优势:
- 启动性能优化:CLI 入口通过动态导入(
await import())实现"快速路径",例如--version检查无需加载任何其他模块即可完成; - 协议无关性:新增一种入口(如 HTTP API 或 WebSocket)时,只需在入口层做协议适配,无需改动核心业务逻辑。
三、CLI 入口:终端交互的 bootstrap
entrypoints/cli.tsx 是整个 CLI 的最外层包装器,职责是在加载完整应用之前,快速处理特殊标志和子命令。它的设计哲学是:能提前返回的,绝不加载多余模块。
3.1 顶层环境预处理
文件开头是一段纯副作用的环境变量设置(第 4-28 行):
// Bugfix for corepack auto-pinning
process.env.COREPACK_ENABLE_AUTO_PIN = '0';
// Set max heap size for child processes in CCR environments
if (process.env.CLAUDE_CODE_REMOTE === 'true') {
const existing = process.env.NODE_OPTIONS || '';
process.env.NODE_OPTIONS = existing
? `${existing} --max-old-space-size=8192`
: '--max-old-space-size=8192';
}
// Harness-science L0 ablation baseline
if (feature('ABLATION_BASELINE') && process.env.CLAUDE_CODE_ABLATION_BASELINE) {
for (const k of ['CLAUDE_CODE_SIMPLE', 'CLAUDE_CODE_DISABLE_THINKING', ...]) {
process.env[k] ??= '1';
}
}来源:
src/entrypoints/cli.tsx,第 1-28 行
这段代码位于所有导入之前,确保后续加载的模块(尤其是子进程相关的 BashTool、AgentTool)能够读取到正确的环境状态。特别值得注意的是 feature() 函数的使用——它是编译期特性开关,允许通过死代码消除(DCE)将特定逻辑从外部构建中移除。
3.2 快速路径(Fast Paths)
cli.tsx 的核心是一个 main() 函数,它按优先级检查各种命令行标志,并为每个标志走独立的快速返回路径:
| 标志/子命令 | 处理逻辑 | 动态导入的模块 |
|---|---|---|
--version / -v | 直接输出内联版本号,零模块加载 | 无 |
--dump-system-prompt | 加载配置和模型,渲染系统提示后退出 | config.js, prompts.js |
--claude-in-chrome-mcp | 启动 Chrome 集成 MCP Server | mcpServer.js |
--daemon-worker=<kind> | 启动守护进程工作线程 | workerRegistry.js |
remote-control | 启动远程控制桥接模式 | bridgeMain.js |
daemon | 启动长期运行的守护进程 | daemon/main.js |
ps / logs / attach / kill | 后台会话管理 | cli/bg.js |
--tmux --worktree | Tmux worktree 快速切换 | worktree.js |
async function main(): Promise<void> {
const args = process.argv.slice(2);
// Fast-path for --version/-v: zero module loading needed
if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
console.log(`${MACRO.VERSION} (Claude Code)`);
return;
}
// ... 其他快速路径
// No special flags detected, load and run the full CLI
const { main: cliMain } = await import('../main.js');
await cliMain();
}来源:
src/entrypoints/cli.tsx,第 30-250 行
这种分层架构意味着:运行 claude --version 时,整个应用只需解析这一个文件;而只有当没有匹配任何快速路径时,才会最终动态导入 main.js,进入完整的 CLI 初始化流程。
3.3 与 main.tsx 的关系
cli.tsx 和 main.tsx 的关系可以类比为外壳(Shell)与内核(Kernel):
cli.tsx处理"命令行标志级别的路由"main.tsx处理"应用级别的初始化"——包括 Commander.js 参数解析、配置系统加载、信任对话框、会话恢复、Ink React 渲染器启动等
当 cli.tsx 调用 cliMain() 时,控制权移交到 main.tsx,后者通过 renderAndRun() 启动 Ink 的 React 组件树,进入交互式 REPL 循环。
四、MCP 入口:作为 Server 对外暴露工具
Model Context Protocol(MCP)是 Anthropic 推出的开放协议,用于标准化 AI 模型与外部工具之间的通信。Claude Code 的 entrypoints/mcp.ts 实现了 MCP Server 角色,允许其他客户端(如 Claude Desktop、Cursor、或自定义脚本)通过 stdio 传输层调用 Claude Code 的工具集。
4.1 Server 初始化与能力声明
MCP Server 的初始化非常简洁(第 40-52 行):
const server = new Server(
{
name: 'claude/tengu',
version: MACRO.VERSION,
},
{
capabilities: {
tools: {},
},
},
);来源:
src/entrypoints/mcp.ts,第 40-52 行
这里声明了 Server 的基本身份(name 为 claude/tengu,tengu 是 Claude Code 的内部代号)和核心能力——当前只暴露 tools 能力。传输层采用 StdioServerTransport,意味着 MCP Client 通过 stdin/stdout 与 Server 通信:
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
}来源:
src/entrypoints/mcp.ts,第 148-151 行
4.2 工具暴露:ListTools 与 CallTool
MCP Server 需要响应两类核心请求:
ListTools:返回所有可用工具的元数据(名称、描述、输入/输出 JSON Schema)。Claude Code 的工具原本使用 Zod 定义 Schema,因此这里通过 zodToJsonSchema 进行转换:
server.setRequestHandler(
ListToolsRequestSchema,
async (): Promise<ListToolsResult> => {
const toolPermissionContext = getEmptyToolPermissionContext();
const tools = getTools(toolPermissionContext);
return {
tools: await Promise.all(
tools.map(async tool => {
let outputSchema: ToolOutput | undefined;
if (tool.outputSchema) {
const convertedSchema = zodToJsonSchema(tool.outputSchema);
// MCP SDK 要求 outputSchema 根节点必须有 type: "object"
if (
typeof convertedSchema === 'object' &&
convertedSchema !== null &&
'type' in convertedSchema &&
convertedSchema.type === 'object'
) {
outputSchema = convertedSchema as ToolOutput;
}
}
return {
...tool,
description: await tool.prompt({ ... }),
inputSchema: zodToJsonSchema(tool.inputSchema) as ToolInput,
outputSchema,
};
}),
),
};
},
);来源:
src/entrypoints/mcp.ts,第 54-90 行
这里有个细节值得注意:MCP SDK 对 outputSchema 的格式有严格要求——根节点必须是 type: "object"。对于 z.union、z.discriminatedUnion 等生成的 anyOf/oneOf 根节点 Schema,MCP 入口会直接跳过 outputSchema 字段,避免协议层面的兼容性问题。
CallTool:执行具体的工具调用:
server.setRequestHandler(
CallToolRequestSchema,
async ({ params: { name, arguments: args } }): Promise<CallToolResult> => {
const toolPermissionContext = getEmptyToolPermissionContext();
const tools = getTools(toolPermissionContext);
const tool = findToolByName(tools, name);
if (!tool) {
throw new Error(`Tool ${name} not found`);
}
// 构造 ToolUseContext(非交互式会话)
const toolUseContext: ToolUseContext = {
abortController: createAbortController(),
options: {
commands: MCP_COMMANDS,
tools,
mainLoopModel: getMainLoopModel(),
thinkingConfig: { type: 'disabled' },
mcpClients: [],
mcpResources: {},
isNonInteractiveSession: true,
debug,
verbose,
agentDefinitions: { activeAgents: [], allAgents: [] },
},
getAppState: () => getDefaultAppState(),
setAppState: () => {},
messages: [],
readFileState: readFileStateCache,
// ... 其他回调
};
const finalResult = await tool.call(
(args ?? {}) as never,
toolUseContext,
hasPermissionsToUseTool,
createAssistantMessage({ content: [] }),
);
return {
content: [{
type: 'text' as const,
text: typeof finalResult === 'string'
? finalResult
: jsonStringify(finalResult.data),
}],
};
},
);来源:
src/entrypoints/mcp.ts,第 92-147 行
在 MCP 模式下,thinkingConfig 被强制设为 disabled,isNonInteractiveSession 设为 true,这体现了入口层的职责——根据运行模式调整核心行为,而不需要在每个工具内部判断"当前是否在 MCP 模式下"。
4.3 MCP 入口的适用场景
- IDE 集成:VS Code / Cursor 通过 MCP 调用 Claude Code 的
Bash、Edit、Read等工具; - 自动化脚本:在 CI/CD 流水线中通过标准化协议调用代码审查(
review)工具; - Claude Desktop:通过本地 MCP Server 扩展 Claude Desktop 的文件操作能力。
五、SDK 类型:程序化调用的类型契约
entrypoints/agentSdkTypes.ts 是 Claude Code Agent SDK 的公共 API 接口层。与 CLI 和 MCP 不同,SDK 入口不直接启动任何服务,而是暴露类型定义和函数签名,供外部 TypeScript/JavaScript 项目以库的形式使用。
5.1 类型分层架构
文件顶部的注释清晰地说明了类型分层:
/**
* Main entrypoint for Claude Code Agent SDK types.
*
* This file re-exports the public SDK API from:
* - sdk/coreTypes.ts - Common serializable types (messages, configs)
* - sdk/runtimeTypes.ts - Non-serializable types (callbacks, interfaces)
*
* SDK builders who need control protocol types should import from
* sdk/controlTypes.ts directly.
*/来源:
src/entrypoints/agentSdkTypes.ts,第 1-11 行
这种分层设计将类型按使用场景分为三类:
| 层级 | 文件 | 内容 |
|---|---|---|
| Core Types | sdk/coreTypes.ts | 可序列化的消息、配置类型 |
| Runtime Types | sdk/runtimeTypes.ts | 含方法/回调的非序列化接口 |
| Control Types | sdk/controlTypes.ts | 控制协议类型(bridge 子路径消费者使用) |
5.2 核心函数签名
SDK 暴露了几个核心操作原语,当前实现为 stub(抛出 not implemented),但接口已经稳定:
export function tool<Schema extends AnyZodRawShape>(
_name: string,
_description: string,
_inputSchema: Schema,
_handler: (args: InferShape<Schema>, extra: unknown) => Promise<CallToolResult>,
_extras?: {
annotations?: ToolAnnotations
searchHint?: string
alwaysLoad?: boolean
},
): SdkMcpToolDefinition<Schema> {
throw new Error('not implemented');
}
export function createSdkMcpServer(
_options: { name: string; version?: string; tools?: Array<SdkMcpToolDefinition<any>> },
): McpSdkServerConfigWithInstance {
throw new Error('not implemented');
}
export function query(_params: {
prompt: string | AsyncIterable<SDKUserMessage>;
options?: Options;
}): Query {
throw new Error('query is not implemented in the SDK');
}来源:
src/entrypoints/agentSdkTypes.ts,第 44-95 行
这些函数的设计意图非常明确:
tool():允许 SDK 用户定义自定义 MCP 工具,与 Claude Code 内置工具在同一进程中运行;createSdkMcpServer():创建一个 MCP Server 实例,可以通过 SDK transport 使用;query():单次查询的便利函数,支持字符串提示词或异步迭代器输入。
5.3 V2 Session API(不稳定)
SDK 还定义了多轮对话的 Session API,目前标记为 @alpha:
/**
* V2 API - UNSTABLE
* Create a persistent session for multi-turn conversations.
*/
export function unstable_v2_createSession(_options: SDKSessionOptions): SDKSession {
throw new Error('unstable_v2_createSession is not implemented in the SDK');
}
/**
* V2 API - UNSTABLE
* One-shot convenience function for single prompts.
*/
export async function unstable_v2_prompt(
_message: string,
_options: SDKSessionOptions,
): Promise<SDKResultMessage> {
throw new Error('unstable_v2_prompt is not implemented in the SDK');
}来源:
src/entrypoints/agentSdkTypes.ts,第 97-136 行
此外还有会话管理函数:getSessionMessages()、listSessions()、renameSession()、tagSession()、forkSession() 等,完整覆盖了会话生命周期的读写需求。
六、沙箱类型:SDK 的安全边界
entrypoints/sandboxTypes.ts 定义了 Agent SDK 的沙箱配置类型,是整个沙箱系统的单一可信来源(single source of truth)。它使用 Zod Schema 定义了网络、文件系统和整体沙箱行为的验证规则:
export const SandboxNetworkConfigSchema = lazySchema(() =>
z.object({
allowedDomains: z.array(z.string()).optional(),
allowManagedDomainsOnly: z.boolean().optional(),
allowUnixSockets: z.array(z.string()).optional(),
allowAllUnixSockets: z.boolean().optional(),
allowLocalBinding: z.boolean().optional(),
httpProxyPort: z.number().optional(),
socksProxyPort: z.number().optional(),
}).optional(),
);
export const SandboxFilesystemConfigSchema = lazySchema(() =>
z.object({
allowWrite: z.array(z.string()).optional(),
denyWrite: z.array(z.string()).optional(),
denyRead: z.array(z.string()).optional(),
allowRead: z.array(z.string()).optional(),
allowManagedReadPathsOnly: z.boolean().optional(),
}).optional(),
);来源:
src/entrypoints/sandboxTypes.ts,第 12-65 行
这些 Schema 通过 .describe() 提供了丰富的文档说明,例如 enableWeakerNetworkIsolation 字段明确标注了安全风险:
"macOS only: Allow access to com.apple.trustd.agent in the sandbox. Needed for Go-based CLI tools (gh, gcloud, terraform, etc.) to verify TLS certificates… Reduces security — opens a potential data exfiltration vector…"
沙箱类型与 SDK 的集成方式是声明式的:SDK 消费者通过配置文件指定安全边界,运行时层(SandboxManager)负责 enforcement,而入口层只需传递验证后的配置对象。
七、入口选择逻辑:main.tsx 如何决定走哪条路
入口的最终选择发生在 main.tsx 的 initializeEntrypoint() 函数中(第 517-539 行):
function initializeEntrypoint(isNonInteractive: boolean): void {
// Skip if already set (e.g., by SDK or other entrypoints)
if (process.env.CLAUDE_CODE_ENTRYPOINT) {
return;
}
const cliArgs = process.argv.slice(2);
// Check for MCP serve command
const mcpIndex = cliArgs.indexOf('mcp');
if (mcpIndex !== -1 && cliArgs[mcpIndex + 1] === 'serve') {
process.env.CLAUDE_CODE_ENTRYPOINT = 'mcp';
return;
}
if (isEnvTruthy(process.env.CLAUDE_CODE_ACTION)) {
process.env.CLAUDE_CODE_ENTRYPOINT = 'claude-code-github-action';
return;
}
// Set based on interactive status
process.env.CLAUDE_CODE_ENTRYPOINT = isNonInteractive ? 'sdk-cli' : 'cli';
}来源:
src/main.tsx,第 517-539 行
判断 isNonInteractive 的依据包括:是否存在 -p/--print 标志、是否存在 --init-only 标志、是否存在 --sdk-url 参数、或者 stdout.isTTY 是否为假:
const isNonInteractive = hasPrintFlag || hasInitOnlyFlag || hasSdkUrl || !process.stdout.isTTY;来源:
src/main.tsx,第 804-807 行
基于 CLAUDE_CODE_ENTRYPOINT 的值,main.tsx 进一步映射为 clientType,用于遥测分析和行为差异化:
| CLAUDE_CODE_ENTRYPOINT | clientType | 说明 |
|---|---|---|
mcp | 从 CLI 流程分叉 | claude mcp serve 子命令 |
sdk-ts | sdk-typescript | TypeScript SDK |
sdk-py | sdk-python | Python SDK |
sdk-cli | sdk-cli | 非交互式 CLI(管道模式) |
claude-vscode | claude-vscode | VS Code 扩展 |
claude-desktop | claude-desktop | Claude Desktop |
local-agent | local-agent | 本地 Agent 模式 |
remote | remote | 远程会话模式 |
| (未设置) | cli | 默认交互式 CLI |
flowchart TD
Start([启动 Claude Code]) --> CheckEnv{CLAUDE_CODE_ENTRYPOINT
已设置?}
CheckEnv -->|是| UseExisting[直接使用已有值]
CheckEnv -->|否| ParseArgs[解析命令行参数]
ParseArgs --> CheckMCP{包含
mcp serve?}
CheckMCP -->|是| SetMCP["ENTRYPOINT = 'mcp'"]
CheckMCP -->|否| CheckAction{CLAUDE_CODE_ACTION
为真?}
CheckAction -->|是| SetAction["ENTRYPOINT = 'claude-code-github-action'"]
CheckAction -->|否| CheckTTY{stdout.isTTY?}
CheckTTY -->|否 / --print / --sdk-url| SetSdkCli["ENTRYPOINT = 'sdk-cli'"]
CheckTTY -->|是| SetCli["ENTRYPOINT = 'cli'"]
SetMCP --> MapClient[映射为 clientType]
SetAction --> MapClient
SetSdkCli --> MapClient
SetCli --> MapClient
UseExisting --> MapClient
MapClient --> InitCore[初始化共享核心模块]
InitCore --> BranchMode{clientType}
BranchMode -->|cli| RunInk[启动 Ink TUI]
BranchMode -->|mcp| RunMcp[启动 MCP Server]
BranchMode -->|sdk-*| RunSdk[进入 SDK 模式]
BranchMode -->|remote| RunRemote[建立远程连接]值得注意的是,--bare 标志的处理发生在 cli.tsx 中(第 210 行),它会提前设置 CLAUDE_CODE_SIMPLE=1,影响后续模块的加载行为;而 --mcp-config 的处理则在 main.tsx 的 Commander action 中完成(第 1415 行),用于配置外部 MCP Server 的连接。
八、各入口的适用场景总结
| 场景 | 推荐入口 | 原因 |
|---|---|---|
| 日常终端对话 | CLI (claude) | 完整的交互式体验,支持会话恢复、历史记录、文件预览 |
| CI/CD 自动化 | SDK CLI (claude -p) | 非交互式,管道友好,输出可结构化(--output-format json) |
| IDE 集成 | MCP (claude mcp serve) | 标准化协议,IDE 无需了解 Claude Code 内部实现 |
| 自定义 Agent 工具 | SDK (tool() + createSdkMcpServer()) | 在同一进程中定义工具,低延迟,高可控 |
| GitHub Actions | GitHub Action (CLAUDE_CODE_ACTION) | 专用遥测标识,适配 Actions 环境 |
| 远程开发环境 | Remote (claude remote-control) | 桥接本地与远程环境,支持多设备协同 |
九、小结
Claude Code 的多入口架构展现了成熟工程化项目的典型设计模式:
- 入口层做减法:
cli.tsx通过快速路径避免不必要的模块加载;mcp.ts只做协议适配;agentSdkTypes.ts只暴露类型契约。 - 核心层做加法:工具注册表、权限系统、状态管理、模型配置等复杂逻辑沉淀在共享模块,被所有入口复用。
- 环境变量做路由:
CLAUDE_CODE_ENTRYPOINT作为单一来源的入口标识,让遥测、行为差异化、权限控制都有了统一的判断依据。
理解这一架构,不仅有助于阅读 Claude Code 的源码,也为设计自己的多模式 AI Agent 系统提供了可借鉴的范式。