ClOr

ClOr

백엔드 실무 트러블슈팅과 AI 에이전트 구조 분석을 기록합니다.

Claude Code 해부학 (완결)

51만 줄 소스코드를 19편에 걸쳐 분석한 완결 시리즈

전체 시리즈 보기 →

백엔드 트러블슈팅

실무에서 겪은 장애와 해결 과정 기록

전체 시리즈 보기 →

최신 글

article thumbnail

시리즈 이전 편: [Part 5] 동적 프롬프트 분석

AI 모델은 기본적으로 대화만 할 수 있습니다. 텍스트를 받아서 텍스트를 돌려주는 것, 그게 전부입니다. 그런데 Claude Code는 파일을 읽고, 코드를 실행하고, 디렉토리를 탐색합니다. 어떻게 가능할까요?

답은 도구(Tool) 시스템입니다. 모델이 "이 파일을 읽어야겠다"고 판단하면 tool_use 블록을 출력하고, 호스트 프로그램이 해당 도구를 실행해서 결과를 다시 모델에게 돌려줍니다. 이번 편에서는 Claude Code가 40개 도구를 어떤 구조로 정의하고 관리하는지, 소스 코드를 직접 따라가 보겠습니다.

목차

  • Tool 인터페이스 설계 — 모든 도구의 뼈대
  • 40개 도구 카테고리별 정리
  • 도구 하나의 구조 — FileReadTool 분석
  • 도구 권한 체크 흐름
  • 자신의 AI 앱에 도구 시스템을 추가하려면
  • 정리

Tool 인터페이스 설계 — 모든 도구의 뼈대

도구의 타입 정의는 src/Tool.ts에 있습니다. 핵심 필드만 추리면 이렇습니다.

// src/Tool.ts (362행~)
export type Tool<Input, Output, P> = {
  readonly name: string
  inputSchema: Input              // Zod 스키마
  description(input, options): Promise<string>
  prompt(options): Promise<string>
  call(args, context, canUseTool, parentMessage, onProgress?): Promise<ToolResult<Output>>
  checkPermissions(input, context): Promise<PermissionResult>
  isReadOnly(input): boolean
  isConcurrencySafe(input): boolean
  isEnabled(): boolean
  maxResultSizeChars: number
}

name은 모델이 호출할 때 사용하는 식별자이고, inputSchema는 Zod로 정의한 파라미터 스키마입니다. description은 모델에게 보여줄 도구 설명, prompt는 시스템 프롬프트에 삽입될 상세 지침입니다. call이 실제 실행 로직이고, checkPermissions가 실행 전 권한 검사를 담당합니다.

모든 도구는 직접 이 타입을 구현하지 않고, buildTool() 함수를 통해 생성됩니다.

// src/Tool.ts (783행~)
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return {
    ...TOOL_DEFAULTS,
    userFacingName: () => def.name,
    ...def,
  } as BuiltTool<D>
}

TOOL_DEFAULTSisEnabled: () => true, isReadOnly: () => false 같은 안전한 기본값을 채워 넣습니다. 개별 도구는 필요한 부분만 오버라이드하면 됩니다. 전형적인 빌더 패턴입니다.

40개 도구 카테고리별 정리

src/tools/ 디렉토리 아래 도구별 폴더가 40개 존재합니다. 카테고리별로 나누면 다음과 같습니다.

카테고리 도구 역할
파일 I/O FileReadTool, FileWriteTool, FileEditTool, NotebookEditTool 파일 읽기/쓰기/편집, 주피터 노트북
검색 GlobTool, GrepTool, ToolSearchTool, LSPTool 파일명 패턴, 내용 검색, 도구 검색, LSP
실행 BashTool, PowerShellTool, REPLTool 셸 명령, PowerShell, REPL 실행
에이전트/협업 AgentTool, SendMessageTool, SkillTool, TeamCreateTool, TeamDeleteTool 서브에이전트 생성, 메시지, 스킬, 팀
태스크 관리 TaskCreateTool, TaskGetTool, TaskListTool, TaskUpdateTool, TaskStopTool, TaskOutputTool 백그라운드 태스크 CRUD
상태/제어 ConfigTool, TodoWriteTool, SleepTool, BriefTool, EnterPlanModeTool, ExitPlanModeTool 설정, 할일, 대기, 모드 전환
외부 연동 WebFetchTool, WebSearchTool, MCPTool, McpAuthTool, ListMcpResourcesTool, ReadMcpResourceTool 웹 요청, MCP 서버 연동
워크트리/인프라 EnterWorktreeTool, ExitWorktreeTool, RemoteTriggerTool, ScheduleCronTool, SyntheticOutputTool, AskUserQuestionTool Git 워크트리, 원격 트리거, 크론

파일 I/O, 검색, 실행이 코딩 에이전트의 핵심 기능이고, 나머지는 협업과 상태 관리를 위한 도구들입니다.

도구 하나의 구조 — FileReadTool 분석

실제 도구가 어떻게 만들어지는지 FileReadTool을 예시로 보겠습니다. 경로는 src/tools/FileReadTool/FileReadTool.ts입니다.

// src/tools/FileReadTool/FileReadTool.ts (227행~)
const inputSchema = lazySchema(() =>
  z.strictObject({
    file_path: z.string().describe('The absolute path to the file to read'),
    offset: semanticNumber(z.number().int().nonnegative().optional())
      .describe('The line number to start reading from'),
    limit: semanticNumber(z.number().int().positive().optional())
      .describe('The number of lines to read'),
    pages: z.string().optional()
      .describe('Page range for PDF files'),
  }),
)

입력 스키마를 lazySchema로 감싸서 지연 초기화합니다. Zod의 strictObject를 사용해 정의되지 않은 필드가 들어오면 거부합니다.

도구 본체는 buildTool로 생성합니다.

// src/tools/FileReadTool/FileReadTool.ts (337행~)
export const FileReadTool = buildTool({
  name: FILE_READ_TOOL_NAME,
  searchHint: 'read files, images, PDFs, notebooks',
  maxResultSizeChars: Infinity,
  strict: true,
  async description() { return DESCRIPTION },
  get inputSchema() { return inputSchema() },
  isConcurrencySafe() { return true },
  isReadOnly() { return true },
  // ...
})

isReadOnly: true는 파일을 수정하지 않는다는 선언이고, isConcurrencySafe: true는 동시에 여러 번 호출해도 안전하다는 뜻입니다. 이 두 플래그가 병렬 실행 결정에 사용됩니다.

도구 권한 체크 흐름

Claude Code의 도구 실행은 무조건 권한 검사를 먼저 거칩니다. Tool.ts 주석을 보면 흐름이 명확합니다.

  1. validateInput — 입력값 자체가 유효한지 검사 (경로 존재 여부 등)
  2. checkPermissions — 도구별 권한 로직 실행 (파일시스템 접근 범위 등)
  3. 일반 권한 시스템alwaysAllow, alwaysDeny, alwaysAsk 규칙 매칭
  4. call — 모든 검사를 통과한 경우에만 실행

FileReadTool의 checkPermissionscheckReadPermissionForTool을 호출해서, 읽으려는 파일이 허용된 경로 안에 있는지 확인합니다.

// src/tools/FileReadTool/FileReadTool.ts (398행~)
async checkPermissions(input, context): Promise<PermissionDecision> {
  const appState = context.getAppState()
  return checkReadPermissionForTool(
    FileReadTool, input, appState.toolPermissionContext,
  )
},

ToolPermissionContext에는 PermissionMode, alwaysAllowRules, alwaysDenyRules 같은 규칙이 담겨 있습니다. 사용자가 "이 도구는 항상 허용"으로 설정했는지, "이 경로는 항상 거부"인지를 여기서 판단합니다.

buildTool의 기본값은 checkPermissions: () => ({ behavior: 'allow', updatedInput: input })입니다. 즉, 별도로 구현하지 않으면 무조건 허용됩니다. 보안이 중요한 도구(Bash, FileWrite 등)만 명시적으로 오버라이드하는 구조입니다.

자신의 AI 앱에 도구 시스템을 추가하려면

Claude Code의 도구 설계에서 가져갈 수 있는 패턴을 정리하면 이렇습니다.

첫째, 스키마를 선언적으로 정의하세요. Zod 같은 라이브러리로 입력/출력 스키마를 정의하면, 모델에게 보낼 JSON Schema 변환과 런타임 검증을 한 번에 해결할 수 있습니다.

둘째, 권한 검사를 도구 실행과 분리하세요. checkPermissionscall을 분리한 덕분에, 권한 정책을 도구 구현과 독립적으로 변경할 수 있습니다. 도구가 늘어나도 보안 정책은 한 곳에서 관리됩니다.

셋째, 메타데이터를 풍부하게 두세요. isReadOnly, isConcurrencySafe, isDestructive 같은 플래그가 병렬 실행, UI 표시, 안전성 판단에 두루 쓰입니다. 도구 수가 40개를 넘어가면 이런 메타데이터 없이는 시스템을 관리할 수 없습니다.

넷째, 빌더 패턴으로 기본값을 통일하세요. buildTool이 안전한 기본값(비활성 동시성, 쓰기 가능 가정)을 채워 넣기 때문에, 개발자가 깜빡 잊어도 fail-closed 방향으로 동작합니다.

정리

Claude Code의 도구 시스템은 결국 "AI 모델에게 현실 세계와 상호작용할 수 있는 인터페이스를 제공하는 것"입니다. 40개 도구 각각이 이름, 스키마, 설명, 권한, 실행 로직을 가진 하나의 독립된 모듈이고, buildTool이라는 팩토리 함수가 이 모듈들의 일관성을 보장합니다.

다음 편에서는 이 도구들의 프롬프트를 깊이 들여다봅니다. BashTool의 프롬프트가 왜 500줄이나 되는지, 모델에게 도구 사용법을 어떻게 가르치는지 분석합니다.


다음 편 예고: [Claude Code 해부학 Part 7] 클로드 코드(Claude Code) 도구 프롬프트 분석 — BashTool은 왜 500줄인가

profile

ClOr

@ClOr

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

ClOr · 백엔드 트러블슈팅과 AI 에이전트 구조 분석을 기록합니다.