入口体系:CLI / MCP / SDK

📑 目录

Claude Code 并非只有一个简单的 main() 入口。作为一个需要在多种场景下运行的 AI Agent 运行时,它设计了多入口架构(Multi-Entrypoint Architecture),让同一套核心能力可以灵活地服务于终端交互用户、外部工具链调用者,以及程序化集成的开发者。本文将从源码层面解析这一架构的设计思路与实现细节。


一、为什么需要多个入口

一个成熟的 AI Agent 平台通常面临三类使用场景:

  1. 终端交互:开发者在终端中直接与 Agent 对话,需要完整的 TUI(Terminal User Interface)、会话管理、历史记录和交互式权限确认。
  2. 工具链集成:IDE、编辑器或其他程序通过标准化协议(如 MCP)调用 Agent 的工具能力,不需要交互式 UI,但需要严格的输入输出契约。
  3. 程序化调用:其他程序或脚本将 Agent 作为库/SDK 使用,需要类型安全的 API 接口和细粒度的生命周期控制。

如果强行用单一入口满足这三类需求,代码会变得异常臃肿——终端 UI 的渲染逻辑会侵入到纯工具调用路径,而 SDK 的类型定义又会污染 CLI 的启动性能。Claude Code 的解决思路是:共享核心逻辑,分离入口适配层


二、多入口架构概览

Claude Code 的入口体系由三个核心文件构成:

入口文件模式适用场景
CLIentrypoints/cli.tsx交互式终端日常开发、对话式编码
MCPentrypoints/mcp.tsMCP ServerIDE 集成、工具链调用
SDKentrypoints/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 行

这段代码位于所有导入之前,确保后续加载的模块(尤其是子进程相关的 BashToolAgentTool)能够读取到正确的环境状态。特别值得注意的是 feature() 函数的使用——它是编译期特性开关,允许通过死代码消除(DCE)将特定逻辑从外部构建中移除。

3.2 快速路径(Fast Paths)

cli.tsx 的核心是一个 main() 函数,它按优先级检查各种命令行标志,并为每个标志走独立的快速返回路径:

标志/子命令处理逻辑动态导入的模块
--version / -v直接输出内联版本号,零模块加载
--dump-system-prompt加载配置和模型,渲染系统提示后退出config.js, prompts.js
--claude-in-chrome-mcp启动 Chrome 集成 MCP ServermcpServer.js
--daemon-worker=<kind>启动守护进程工作线程workerRegistry.js
remote-control启动远程控制桥接模式bridgeMain.js
daemon启动长期运行的守护进程daemon/main.js
ps / logs / attach / kill后台会话管理cli/bg.js
--tmux --worktreeTmux 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.tsxmain.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.unionz.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 被强制设为 disabledisNonInteractiveSession 设为 true,这体现了入口层的职责——根据运行模式调整核心行为,而不需要在每个工具内部判断"当前是否在 MCP 模式下"。

4.3 MCP 入口的适用场景

  • IDE 集成:VS Code / Cursor 通过 MCP 调用 Claude Code 的 BashEditRead 等工具;
  • 自动化脚本:在 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 Typessdk/coreTypes.ts可序列化的消息、配置类型
Runtime Typessdk/runtimeTypes.ts含方法/回调的非序列化接口
Control Typessdk/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.tsxinitializeEntrypoint() 函数中(第 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_ENTRYPOINTclientType说明
mcp从 CLI 流程分叉claude mcp serve 子命令
sdk-tssdk-typescriptTypeScript SDK
sdk-pysdk-pythonPython SDK
sdk-clisdk-cli非交互式 CLI(管道模式)
claude-vscodeclaude-vscodeVS Code 扩展
claude-desktopclaude-desktopClaude Desktop
local-agentlocal-agent本地 Agent 模式
remoteremote远程会话模式
(未设置)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 ActionsGitHub Action (CLAUDE_CODE_ACTION)专用遥测标识,适配 Actions 环境
远程开发环境Remote (claude remote-control)桥接本地与远程环境,支持多设备协同

九、小结

Claude Code 的多入口架构展现了成熟工程化项目的典型设计模式:

  1. 入口层做减法cli.tsx 通过快速路径避免不必要的模块加载;mcp.ts 只做协议适配;agentSdkTypes.ts 只暴露类型契约。
  2. 核心层做加法:工具注册表、权限系统、状态管理、模型配置等复杂逻辑沉淀在共享模块,被所有入口复用。
  3. 环境变量做路由CLAUDE_CODE_ENTRYPOINT 作为单一来源的入口标识,让遥测、行为差异化、权限控制都有了统一的判断依据。

理解这一架构,不仅有助于阅读 Claude Code 的源码,也为设计自己的多模式 AI Agent 系统提供了可借鉴的范式。