claude-code/docs/ko/level-2-systems/tool-system.md
JuYoung Jeong 3358348196 docs: add comprehensive Korean source code analysis (19 documents, 6,926 lines)
Add complete Korean-language technical documentation analyzing Claude Code CLI
internals, organized in a leveled guide structure (Level 1-3 + Appendix).

Level 1 (입문): architecture overview, request lifecycle, key concepts
Level 2 (시스템): QueryEngine, Tool system, Command system, Permission system,
  Agent coordinator, UI/Ink components
Level 3 (심화): MCP/LSP integration, IDE bridge, Plugin/Skill system,
  Context compression, OAuth/Auth, Telemetry
Appendix: Korean-English glossary, file map, design patterns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:39:03 +09:00

26 KiB

Tool 시스템: 도구 레지스트리 & 실행 파이프라인

1. 개요

Tool 시스템은 Claude Code의 핵심 확장 메커니즘이다. LLM(Large Language Model, 대규모 언어 모델)이 텍스트 생성 외에 실제 작업을 수행할 수 있도록 40개 이상의 도구를 등록, 검증, 실행하는 파이프라인 전체를 관할한다.

역할 요약:

  • 등록: 빌트인(built-in, 내장) 도구와 MCP(Model Context Protocol) 도구를 단일 풀(pool)로 조합
  • 검증: Zod v4 스키마 기반 입력 유효성 검사 및 권한 확인
  • 실행: ToolUseContext를 의존성 주입(dependency injection) 컨테이너로 활용하여 각 도구에 실행 환경 전달
  • 결과 처리: 결과 포맷팅, 대용량 결과의 디스크 저장, UI 렌더링

파일 구조:

파일 역할
src/Tool.ts 핵심 타입 및 인터페이스 정의
src/tools.ts 도구 레지스트리 및 조합 로직
src/tools/*/ 개별 도구 구현체 (40개 이상)

2. 아키텍처 다이어그램

graph TD
    A[LLM tool_use 블록 반환] --> B[toolMatchesName으로 도구 탐색]
    B --> C{도구 발견?}
    C -- 아니오 --> D[에러 반환]
    C -- 예 --> E[Zod 스키마 입력 검증]
    E --> F{validateInput 통과?}
    F -- 실패 --> G[ValidationResult 에러 반환]
    F -- 통과 --> H[checkPermissions 호출]
    H --> I{권한 결과}
    I -- deny --> J[거부 메시지 반환]
    I -- ask --> K[사용자에게 확인 요청]
    I -- allow --> L[tool.call 실행]
    K -- 승인 --> L
    K -- 거부 --> J
    L --> M[ToolResult 반환]
    M --> N{결과 크기 > maxResultSizeChars?}
    N -- 예 --> O[디스크에 저장, 프리뷰 반환]
    N -- 아니오 --> P[mapToolResultToToolResultBlockParam]
    O --> P
    P --> Q[렌더링 및 메시지 히스토리 추가]

    subgraph "도구 레지스트리 (tools.ts)"
        R[getAllBaseTools] --> S[getTools]
        S --> T[filterToolsByDenyRules]
        T --> U[assembleToolPool]
        U --> V[MCP 도구 병합]
    end

    subgraph "개별 도구 구현체 (src/tools/*/)"
        W[BashTool]
        X[FileReadTool]
        Y[AgentTool]
        Z[기타 40+ 도구]
    end

3. 핵심 타입 및 인터페이스

src/Tool.ts는 792줄 규모의 타입 전용 파일로, 런타임 코드를 최소화하고 타입 시스템을 통해 도구 계약(contract)을 강제한다.

3.1 ToolInputJSONSchema

export type ToolInputJSONSchema = {
  [x: string]: unknown
  type: 'object'
  properties?: {
    [x: string]: unknown
  }
}

MCP 도구처럼 Zod 스키마 대신 JSON Schema를 직접 사용하는 도구를 위한 타입이다. 빌트인 도구는 inputSchema: Input(Zod 기반)을 사용하고, MCP 도구는 inputJSONSchema를 사용하는 두 경로가 공존한다.

3.2 ToolPermissionContext

export type ToolPermissionContext = DeepImmutable<{
  mode: PermissionMode
  additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
  alwaysAllowRules: ToolPermissionRulesBySource
  alwaysDenyRules: ToolPermissionRulesBySource
  alwaysAskRules: ToolPermissionRulesBySource
  isBypassPermissionsModeAvailable: boolean
  isAutoModeAvailable?: boolean
  strippedDangerousRules?: ToolPermissionRulesBySource
  shouldAvoidPermissionPrompts?: boolean
  awaitAutomatedChecksBeforeDialog?: boolean
  prePlanMode?: PermissionMode
}>

DeepImmutable<T>로 감싸져 있어 런타임 중 권한 컨텍스트가 변경되지 않음을 타입 수준에서 보장한다. alwaysAllowRules, alwaysDenyRules, alwaysAskRules는 각각 소스(source)별로 분류된 규칙 맵으로, .claude/settings.json의 훅(hook) 설정이 여기에 반영된다.

3.3 ToolUseContext

전체 시스템에서 가장 복잡한 타입이다. 모든 tool.call()에 전달되는 의존성 주입 컨테이너 역할을 한다.

주요 필드 분류:

카테고리 필드 설명
설정 options.tools, options.commands, options.mainLoopModel 런타임 설정
상태 관리 getAppState(), setAppState() React 상태 접근자
세션 인프라 setAppStateForTasks 서브에이전트에서도 루트 스토어에 접근 가능
UI 콜백 setToolJSX, addNotification, appendSystemMessage UI 업데이트 함수
중단 제어 abortController 도구 실행 취소 신호
파일 캐시 readFileState: FileStateCache LRU 캐시 기반 파일 상태
컨텍스트 추적 queryTracking, toolDecisions 체인 추적 및 결정 기록
에이전트 식별 agentId, agentType 서브에이전트 구분
권한 관련 localDenialTracking, requireCanUseTool 비동기 서브에이전트용 거부 추적
프롬프트 캐시 renderedSystemPrompt, contentReplacementState 캐시 공유 최적화

setAppStateForTasks는 일반 setAppState와 다르게 비동기 서브에이전트에서도 루트 AppState에 접근할 수 있도록 설계된 점이 특징이다. 일반 setAppState는 비동기 에이전트에서 no-op(무작동)으로 작동하여 격리를 유지하지만, 세션 스코프 인프라(예: 백그라운드 태스크 등록)는 루트까지 전파되어야 하기 때문이다.

3.4 ValidationResult

export type ValidationResult =
  | { result: true }
  | { result: false; message: string; errorCode: number }

도구 실행 전 사전 검증(pre-flight validation) 결과 타입이다. 실패 시 message가 LLM에게 반환되어 모델이 입력을 수정할 수 있도록 안내한다.

3.5 Tool 인터페이스

export type Tool<
  Input extends AnyObject = AnyObject,
  Output = unknown,
  P extends ToolProgressData = ToolProgressData,
> = {
  name: string
  aliases?: string[]
  inputSchema: Input
  inputJSONSchema?: ToolInputJSONSchema
  maxResultSizeChars: number
  call(args, context, canUseTool, parentMessage, onProgress?): Promise<ToolResult<Output>>
  description(input, options): Promise<string>
  checkPermissions(input, context): Promise<PermissionResult>
  validateInput?(input, context): Promise<ValidationResult>
  isConcurrencySafe(input): boolean
  isReadOnly(input): boolean
  isDestructive?(input): boolean
  // ... UI 렌더링 메서드 다수
}

제네릭 파라미터 3개가 입력 스키마 타입(Input), 출력 타입(Output), 진행 상태 타입(P)을 각각 고정한다. UI 렌더링 관련 메서드(renderToolResultMessage, renderToolUseMessage 등)가 도구 인터페이스에 직접 포함된 점이 이 설계의 특징으로, 도구 로직과 렌더링 로직이 같은 모듈 내에 공존한다.

3.6 buildTool 팩토리 함수

export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return {
    ...TOOL_DEFAULTS,
    userFacingName: () => def.name,
    ...def,
  } as BuiltTool<D>
}

모든 도구는 buildTool을 통해 생성되며, 다음 기본값이 주입된다:

메서드 기본값 설계 원칙
isEnabled () => true 명시적 비활성화 없이는 활성
isConcurrencySafe () => false 실패 안전(fail-closed): 기본적으로 동시 실행 불가
isReadOnly () => false 기본적으로 쓰기 작업으로 간주
isDestructive () => false 기본적으로 비파괴적
checkPermissions allow 반환 일반 권한 시스템에 위임
toAutoClassifierInput () => '' 보안 분류기에서 제외
userFacingName () => def.name 도구명을 그대로 표시

BuiltTool<D> 타입은 타입 수준 스프레드(spread)를 통해 런타임 {...TOOL_DEFAULTS, ...def} 의미론을 정확히 반영한다.

3.7 Tools 타입

export type Tools = readonly Tool[]

Tool[] 대신 이 타입을 사용함으로써 코드베이스 전체에서 도구 집합이 조합, 전달, 필터링되는 지점을 타입 시스템으로 추적할 수 있다.


4. Tool 레지스트리 (tools.ts)

src/tools.ts는 도구 등록 방식을 세 가지 패턴으로 분류한다.

4.1 정적 임포트 (Static Import)

핵심 도구들은 ES 모듈 정적 임포트로 등록된다:

import { AgentTool } from './tools/AgentTool/AgentTool.js'
import { BashTool } from './tools/BashTool/BashTool.js'
import { FileEditTool } from './tools/FileEditTool/FileEditTool.js'
// ...

번들러(bundler)가 빌드 시점에 포함 여부를 결정할 수 있어 트리 셰이킹(tree shaking)이 적용된다.

4.2 조건부 require() — 기능 플래그 기반

bun:bundlefeature() 함수를 활용한 dead code elimination(데드 코드 제거) 패턴이다:

import { feature } from 'bun:bundle'

const SleepTool = feature('PROACTIVE') || feature('KAIROS')
  ? require('./tools/SleepTool/SleepTool.js').SleepTool
  : null

const cronTools = feature('AGENT_TRIGGERS')
  ? [
      require('./tools/ScheduleCronTool/CronCreateTool.js').CronCreateTool,
      // ...
    ]
  : []

feature() 플래그가 false인 경우, 번들러는 해당 require() 브랜치 전체를 빌드 산출물에서 제거한다. 이는 프로덕션 번들 크기를 최소화하는 동시에 기능별 점진적 배포를 가능하게 한다.

4.3 조건부 require() — 환경 변수 기반

USER_TYPE 환경 변수로 Ant(내부 Anthropic) 전용 도구를 분리한다:

const REPLTool =
  process.env.USER_TYPE === 'ant'
    ? require('./tools/REPLTool/REPLTool.js').REPLTool
    : null

이 패턴을 사용하는 도구: REPLTool, SuggestBackgroundPRTool, ConfigTool, TungstenTool.

4.4 지연 require() — 순환 의존성 해결

TeamCreateTool, TeamDeleteTool, SendMessageTool은 순환 의존성(circular dependency)을 해결하기 위해 지연 로딩(lazy loading)된다:

// Lazy require to break circular dependency: tools.ts -> TeamCreateTool/TeamDeleteTool -> ... -> tools.ts
const getTeamCreateTool = () =>
  require('./tools/TeamCreateTool/TeamCreateTool.js')
    .TeamCreateTool as typeof import('./tools/TeamCreateTool/TeamCreateTool.js').TeamCreateTool

이 팀 도구들은 tools.ts를 임포트하는 모듈 체인에 속하므로, 정적 임포트로 처리하면 모듈 초기화 시점에 순환이 발생한다. 지연 함수로 감싸면 실제 호출 시점까지 로딩을 미룰 수 있다.

4.5 getAllBaseTools() 함수

전체 도구 목록의 단일 진실 공급원(single source of truth)이다:

export function getAllBaseTools(): Tools {
  return [
    AgentTool,
    TaskOutputTool,
    BashTool,
    ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
    // ...
    ...(isTodoV2Enabled()
      ? [TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool]
      : []),
    // ...
    ...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
  ]
}

주석에 따르면 이 목록은 Statsig 시스템 프롬프트 캐싱 설정(claude_code_global_system_caching)과 동기화를 유지해야 한다. 도구 순서가 바뀌면 프롬프트 캐시 키가 무효화될 수 있기 때문이다.

4.6 getTools() 함수

권한 컨텍스트를 적용하여 실제 사용 가능한 도구 목록을 반환한다:

export const getTools = (permissionContext: ToolPermissionContext): Tools => {
  // 1. CLAUDE_CODE_SIMPLE 환경 변수: Bash/Read/Edit만 반환
  if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) { ... }

  // 2. 특수 도구 제외 (ListMcpResources 등 별도 경로로 추가)
  const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name))

  // 3. Deny 규칙으로 도구 필터링
  let allowedTools = filterToolsByDenyRules(tools, permissionContext)

  // 4. REPL 모드: 원시 도구 숨김 (REPL 내부 VM에서만 접근 가능)
  if (isReplModeEnabled()) { ... }

  // 5. isEnabled() 검사
  return allowedTools.filter((_, i) => isEnabled[i])
}

4.7 assembleToolPool() 함수

빌트인 도구와 MCP 도구를 하나의 풀로 결합하는 최종 조합 함수다:

export function assembleToolPool(
  permissionContext: ToolPermissionContext,
  mcpTools: Tools,
): Tools {
  const builtInTools = getTools(permissionContext)
  const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)

  // 빌트인 도구를 앞쪽 연속 블록으로 유지하여 프롬프트 캐시 안정성 확보
  const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
  return uniqBy(
    [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
    'name',
  )
}

정렬 시 빌트인 도구와 MCP 도구를 별도 파티션으로 유지하는 이유는 프롬프트 캐시 안정성(prompt cache stability) 때문이다. 서버 측 캐시 정책이 빌트인 도구 블록 끝에 캐시 브레이크포인트(cache breakpoint)를 두므로, 두 파티션을 평탄하게 섞으면 MCP 도구가 빌트인 목록 사이에 끼어들어 캐시 키가 무효화된다.


5. Tool 실행 파이프라인

LLM이 tool_use 블록을 반환한 시점부터 결과가 대화 히스토리에 기록되기까지의 전체 흐름이다.

단계 1: 도구 탐색

export function toolMatchesName(
  tool: { name: string; aliases?: string[] },
  name: string,
): boolean {
  return tool.name === name || (tool.aliases?.includes(name) ?? false)
}

export function findToolByName(tools: Tools, name: string): Tool | undefined {
  return tools.find(t => toolMatchesName(t, name))
}

aliases 필드는 도구명이 변경될 때 하위 호환성(backward compatibility)을 유지하기 위한 장치다. 구 도구명으로 호출된 LLM 요청도 새 도구로 라우팅된다.

단계 2: 입력 검증

Zod v4 스키마로 입력을 파싱한다. 파싱 실패 시 구조화된 에러 메시지가 LLM에 반환되어 모델이 입력을 수정하도록 유도한다.

옵셔널 메서드 validateInput이 구현된 경우, Zod 파싱 통과 후 추가적인 도구별 검증이 실행된다:

validateInput?(
  input: z.infer<Input>,
  context: ToolUseContext,
): Promise<ValidationResult>

단계 3: 권한 확인

checkPermissions(
  input: z.infer<Input>,
  context: ToolUseContext,
): Promise<PermissionResult>

PermissionResultbehavior 필드에 따라 실행 경로가 분기된다:

behavior 동작
allow 즉시 실행
deny 거부 메시지 반환, LLM에 전달
ask 사용자에게 확인 UI 표시

buildTool의 기본 구현은 allow를 반환하여 일반 권한 시스템(permissions.ts)에 위임한다. 도구별 특수 로직이 필요한 경우에만 이 메서드를 오버라이드한다.

단계 4: 실행

call(
  args: z.infer<Input>,
  context: ToolUseContext,
  canUseTool: CanUseToolFn,
  parentMessage: AssistantMessage,
  onProgress?: ToolCallProgress<P>,
): Promise<ToolResult<Output>>

ToolUseContext는 도구가 필요로 하는 모든 의존성(앱 상태, 파일 캐시, 중단 컨트롤러, UI 콜백 등)을 담은 컨테이너로 전달된다. onProgress 콜백을 통해 실행 중 진행 상태를 스트리밍할 수 있다.

단계 5: 결과 처리

export type ToolResult<T> = {
  data: T
  newMessages?: (UserMessage | AssistantMessage | AttachmentMessage | SystemMessage)[]
  contextModifier?: (context: ToolUseContext) => ToolUseContext
  mcpMeta?: { _meta?: Record<string, unknown>; structuredContent?: Record<string, unknown> }
}

contextModifier는 동시 실행에 안전하지 않은 도구(isConcurrencySafe = false)에서만 사용 가능하며, 도구 결과에 따라 ToolUseContext를 변경할 수 있다. 예를 들어 EnterPlanModeTool은 이를 통해 권한 모드를 전환한다.

단계 6: 결과 직렬화 및 저장

maxResultSizeChars: number
mapToolResultToToolResultBlockParam(
  content: Output,
  toolUseID: string,
): ToolResultBlockParam

결과 크기가 maxResultSizeChars를 초과하면 디스크에 저장하고 LLM에는 파일 경로와 프리뷰만 전달한다. FileReadTool의 경우 이 값이 Infinity로 설정되어 있는데, 파일 내용을 디스크에 다시 저장하면 Read → 파일 → Read 순환이 발생하고 이미 자체적으로 크기 제한을 적용하기 때문이다.

단계 7: 에러 처리

도구는 에러 UI를 커스터마이즈할 수 있다:

renderToolUseErrorMessage?(
  result: ToolResultBlockParam['content'],
  options: { ... },
): React.ReactNode

이 메서드가 없으면 <FallbackToolUseErrorMessage />가 사용된다. 파일 탐색 도구처럼 "파일을 찾을 수 없습니다" 같은 도구별 에러 메시지가 필요한 경우에만 구현한다.


6. Tool 카테고리 분석

src/tools/ 디렉토리의 모든 도구를 카테고리별로 분류한다.

파일 조작

도구 설명 읽기 전용
FileReadTool 파일 읽기, 이미지/PDF/노트북 지원 O
FileEditTool 파일 일부 편집 (문자열 대체) X
FileWriteTool 파일 생성 및 전체 덮어쓰기 X
GlobTool 파일 패턴 탐색 O
GrepTool 파일 내용 패턴 검색 (ripgrep 래핑) O
NotebookEditTool Jupyter 노트북 셀 편집 X

GlobToolGrepToolhasEmbeddedSearchTools()true인 경우(Ant 내부 빌드) 제외된다. Ant 빌드에는 bfs/ugrep이 Bun 바이너리에 내장되어 있고, Bash 내에서 find/grep 명령이 이 빠른 도구로 앨리어스(alias)되기 때문이다.

실행

도구 설명
BashTool 셸 명령 실행 (샌드박스 지원)
REPLTool 격리된 VM 환경에서 코드 실행 (Ant 전용)
PowerShellTool Windows PowerShell 명령 실행

BashTool은 파이프라인 구성 명령을 분석하여 검색/읽기/목록 조회 명령인지 판단하고 UI에서 접을 수 있게(collapsible) 표시한다. 이를 위해 BASH_SEARCH_COMMANDS, BASH_READ_COMMANDS, BASH_LIST_COMMANDS, BASH_SEMANTIC_NEUTRAL_COMMANDS 집합을 사용한다.

에이전트

도구 설명
AgentTool 서브에이전트 생성 및 실행
SendMessageTool 팀 내 에이전트 간 메시지 전송
TeamCreateTool 에이전트 팀 생성
TeamDeleteTool 에이전트 팀 해체

AgentTool은 시스템에서 가장 복잡한 도구로, 로컬/원격 에이전트 실행, 워크트리(worktree) 생성, 포크(fork) 서브에이전트, 코디네이터 모드 등 다수의 실행 경로를 포함한다. AgentTool의 임포트 목록만 80줄에 달한다.

태스크 관리

도구 설명
TaskCreateTool 새 태스크 생성
TaskGetTool 태스크 상태 조회
TaskListTool 태스크 목록 조회
TaskUpdateTool 태스크 업데이트
TaskOutputTool 태스크 출력 스트리밍
TaskStopTool 태스크 중단

태스크 관련 도구 6개는 isTodoV2Enabled() 플래그로 일괄 활성화/비활성화된다.

MCP / LSP

도구 설명
MCPTool MCP 서버 도구 래퍼 (동적)
LSPTool Language Server Protocol 통합
McpAuthTool MCP 인증 처리
ListMcpResourcesTool MCP 서버 리소스 목록
ReadMcpResourceTool MCP 리소스 내용 읽기

ListMcpResourcesToolReadMcpResourceToolgetTools()에서 specialTools 집합으로 분리되어 별도 경로로 추가된다. LSPToolENABLE_LSP_TOOL 환경 변수로 명시적으로 활성화해야 한다.

워크플로우 제어

도구 설명
EnterPlanModeTool Plan 모드 진입 (쓰기 작업 차단)
ExitPlanModeV2Tool Plan 모드 종료
EnterWorktreeTool Git 워크트리 모드 진입
ExitWorktreeTool Git 워크트리 모드 종료
SkillTool 스킬 파일 실행
ToolSearchTool 지연 로드된 도구 검색

EnterWorktreeToolExitWorktreeToolisWorktreeModeEnabled() 조건으로 등록된다.

사용자 상호작용

도구 설명
AskUserQuestionTool 사용자에게 질문 및 응답 수집
BriefTool 간략한 상태 업데이트 표시
ConfigTool 설정 읽기/쓰기 (Ant 전용)
TodoWriteTool 할 일 목록 업데이트
SleepTool 실행 일시 중지 (PROACTIVE/KAIROS)

TodoWriteToolrenderToolResultMessage를 구현하지 않는 도구의 예시다. 이 도구는 결과를 투명 패널(todo panel)로 업데이트하므로 대화 트랜스크립트(transcript)에 별도 결과를 출력하지 않는다.

웹 접근

도구 설명
WebFetchTool URL에서 HTML/텍스트 가져오기
WebSearchTool 웹 검색 실행

기타 (실험적/내부)

도구 설명 조건
SyntheticOutputTool 합성 출력 생성 항상 특수 처리
RemoteTriggerTool 원격 트리거 실행 AGENT_TRIGGERS_REMOTE 플래그
ScheduleCronTool 계열 크론 작업 관리 AGENT_TRIGGERS 플래그
SleepTool 에이전트 슬립 PROACTIVE 또는 KAIROS 플래그
TungstenTool 내부 도구 Ant 전용
TestingPermissionTool 권한 테스트 NODE_ENV === 'test'

7. 주요 설계 결정

7.1 Zod v4 기반 스키마 검증

빌드 시점과 런타임 모두에서 타입 안전성을 보장하기 위해 Zod v4를 채택했다. z.infer<Input>으로 입력 타입을 자동 추론하므로 별도의 타입 정의가 불필요하다. 또한 Zod 스키마는 Anthropic API가 요구하는 JSON Schema로 직렬화되어 LLM에 도구 명세로 전달된다.

lazySchema 패턴이 다수의 도구에서 사용된다:

// FileReadTool.tsx에서
import { lazySchema } from '../../utils/lazySchema.js'

이는 스키마 생성 비용이 높거나 순환 의존성이 있는 경우, 실제 사용 시점까지 스키마 평가를 지연시키는 패턴이다.

7.2 조건부 도구 로딩과 번들 크기 최적화

bun:bundlefeature() 함수는 Bun의 번들러가 이해하는 특수 API다. feature('FLAG_NAME')false로 평가되는 빌드에서는 해당 require() 브랜치가 번들에서 완전히 제거된다. 이를 통해:

  1. 프로덕션 빌드 크기 최소화
  2. 미완성 기능의 코드가 배포되지 않음 보장
  3. A/B 테스트 및 단계적 기능 롤아웃 지원

런타임 기능 플래그(isEnvTruthy, process.env.USER_TYPE)와 빌드 타임 플래그(feature())를 구분하여 사용하는 점이 특징이다.

7.3 ToolUseContext의 의존성 주입 패턴

모든 도구가 단일 context: ToolUseContext 인수를 통해 필요한 의존성에 접근한다. 이 패턴의 장점:

  • 테스트 용이성: 컨텍스트 객체를 모킹(mocking)하여 도구를 격리 테스트 가능
  • 확장성: 새 의존성을 추가할 때 함수 시그니처를 변경하지 않고 컨텍스트에 필드만 추가
  • 서브에이전트 격리: setAppState가 서브에이전트에서 no-op으로 작동하도록 컨텍스트를 교체하여 격리 달성

단점으로는 컨텍스트 타입이 매우 커진다는 점이 있다 — ToolUseContext는 약 50개의 필드를 포함한다.

7.4 프롬프트 캐시 안정성 설계

assembleToolPool이 빌트인/MCP 도구를 별도 파티션으로 정렬하는 이유는 Anthropic API의 프롬프트 캐싱 메커니즘과 직접 연관된다. 서버 측 캐시 정책은 빌트인 도구 목록 끝에 캐시 브레이크포인트를 배치하는데, 도구 순서가 변경되면 이 브레이크포인트 이후의 모든 토큰이 캐시 미스(cache miss)가 된다. 이 설계를 통해 MCP 서버 연결/해제가 빈번해도 빌트인 도구 캐시는 유지된다.

7.5 shouldDefer / alwaysLoad — 지연 도구 로딩

도구 수가 많아지면 LLM에 전달되는 프롬프트 크기가 크게 증가한다. shouldDefer: true인 도구는 초기 프롬프트에서 스키마가 생략되고(defer_loading: true 플래그로 API에 전달), ToolSearchTool을 통해 필요 시 온디맨드(on-demand)로 스키마를 가져올 수 있다:

readonly shouldDefer?: boolean
readonly alwaysLoad?: boolean  // 지연 도구 중에서도 항상 초기 로드

이를 통해 40개 이상의 도구를 보유하면서도 초기 컨텍스트 토큰 소비를 최소화한다.


8. 실제 도구 구현 패턴

BashTool 구현 패턴

// BashTool.tsx (단순화)
export const BashTool = buildTool({
  name: BASH_TOOL_NAME,
  // 1. Zod 스키마 정의
  inputSchema: lazySchema(() => z.object({
    command: z.string(),
    timeout: z.number().optional(),
  })),
  // 2. 핵심 실행 로직
  async call(args, context, canUseTool, parentMessage, onProgress) {
    // ... 실행 로직
  },
  // 3. 검색/읽기 명령 판별
  isSearchOrReadCommand(input) {
    return isSearchOrReadBashCommand(input.command)
  },
  // 4. 읽기 전용 여부 (기본값 false 사용)
  isReadOnly: () => false,
})

FileReadTool 구현 패턴

FileReadTool.tsx는 빌드 시점에 수십 개의 유틸리티를 임포트하는 복잡한 도구다. PDF, 이미지, Jupyter 노트북, 일반 텍스트 등 다양한 파일 형식을 처리하며, maxResultSizeChars: Infinity를 통해 결과가 디스크에 저장되지 않도록 보장한다.


내비게이션