/Part 2: 도구 시스템/도구 구현 패턴
🔧
Part 2중급⏱ 약 60분 · 3개 섹션

도구 구현 패턴

실전 도구 해부

도구 구현 패턴 대표 이미지
이 챕터에서 배울 것
  • 각 도구는 일관된 디렉토리 구조를 따름 (Tool.ts, prompt.ts, UI.tsx, constants.ts)
  • 읽기 전용 도구는 isConcurrencySafe: true로 병렬 실행 가능
  • UI.tsx는 도구 호출/결과/진행률/오류의 렌더링을 분리
전체 아키텍처에서 현재 위치
⚙️코어 엔진
🔧도구 시스템
🛡️보안
🧠컨텍스트
🤝멀티 에이전트
🌐생태계
인프라
1

파일 도구 (Read, Edit, Write)

파일 시스템 조작 도구의 구현과 권한 체크

파일 읽기문자열 치환권한 체크결과 포맷팅|tools/FileReadTool/FileReadTool.tstools/FileEditTool/FileEditTool.tstools/FileWriteTool/FileWriteTool.ts

파일 조작 도구는 Claude Code의 가장 기본적인 능력입니다. FileReadTool, FileEditTool, FileWriteTool는 각각 읽기/편집/쓰기를 담당합니다.

src/tools/FileEditTool/FileEditTool.ts (핵심 구조)
1// src/tools/FileEditTool/FileEditTool.ts — 파일 편집 도구
2import { buildTool } from '../../Tool.js'
3import { checkWritePermissionForTool, matchingRuleForInput }
4 from '../../utils/permissions/filesystem.js'
5import { activateConditionalSkillsForPaths, addSkillDirectories }
6 from '../../skills/loadSkillsDir.js'
7import { getLspServerManager } from '../../services/lsp/manager.js'
8import { notifyVscodeFileUpdated } from '../../services/mcp/vscodeSdkMcp.js'
9
10// FileEditTool의 핵심 동작:
11// 1. 파일 읽기 + 기존 내용 확인
12// 2. old_string → new_string 문자열 치환
13// 3. LSP 서버에 변경 알림 (didChange)
14// 4. VSCode MCP에 변경 알림
15// 5. 조건부 스킬 활성화 (파일 경로 기반)
16// 6. 파일 히스토리 기록
17
18export const FileEditTool = buildTool({
19 name: FILE_EDIT_TOOL_NAME,
20 inputSchema: lazySchema(() => z.strictObject({
21 file_path: z.string().describe('수정할 파일의 절대 경로'),
22 old_string: z.string().describe('교체할 기존 텍스트'),
23 new_string: z.string().describe('새 텍스트'),
24 replace_all: z.boolean().optional().describe('모든 매칭 교체 여부'),
25 })),
26
27 // 쓰기 도구이므로 권한 확인 필수
28 async checkPermissions(input, context): Promise<PermissionResult> {💡
29 return checkWritePermissionForTool(input.file_path, context)
30 },💡
31 getPath(input) { return input.file_path },
32
33 // 권한 패턴 매칭: "FileEdit(src/**/*.ts)" 형태
34 async preparePermissionMatcher(input) {
35 return (pattern: string) => matchWildcardPattern(pattern, input.file_path)
36 },
37
38 async call(input, context) {
39 // 파일 읽기 → old_string 검색 → 치환 → 저장
40 const content = await readFile(input.file_path, 'utf-8')
41 const newContent = content.replace(input.old_string, input.new_string)
42 await writeTextContent(input.file_path, newContent)
43 💡
44 // LSP 서버에 변경 알림
45 const lspManager = getLspServerManager()
46 await lspManager?.changeFile(input.file_path, newContent)
47 💡
48 // VSCode에 변경 알림 (MCP 연동)
49 notifyVscodeFileUpdated(input.file_path)
50 💡
51 // 조건부 스킬 활성화
52 activateConditionalSkillsForPaths([input.file_path])
53
54 return { output: { success: true, linesChanged } }
55 },
56})
2

검색 도구 (Grep, Glob)

ripgrep 래퍼와 글로브 패턴 검색 구현

정규식 검색글로브 패턴읽기 전용 도구결과 잘라내기|tools/GrepTool/GrepTool.tstools/GlobTool/GlobTool.ts

GrepTool은 ripgrep을 래핑한 검색 도구입니다. 읽기 전용이므로 isConcurrencySafe: true, isReadOnly: true로 선언됩니다.

src/tools/GrepTool/GrepTool.ts
1// src/tools/GrepTool/GrepTool.ts — 검색 도구 구현
2
3const inputSchema = lazySchema(() =>💡
4 z.strictObject({
5 pattern: z.string()
6 .describe('정규식 패턴 (ripgrep 문법)'),
7 path: z.string().optional()
8 .describe('검색 경로 (기본: 현재 디렉토리)'),
9 glob: z.string().optional()
10 .describe('파일 필터 글로브 (예: "*.ts", "*.{ts,tsx}")'),
11 output_mode: z.enum(['content', 'files_with_matches', 'count'])💡
12 .optional()
13 .describe('출력 모드'),
14 '-B': semanticNumber(z.number().optional())
15 .describe('매칭 전 N줄 표시'),
16 '-A': semanticNumber(z.number().optional())
17 .describe('매칭 후 N줄 표시'),
18 '-C': semanticNumber(z.number().optional())
19 .describe('매칭 전후 N줄 표시'),
20 head_limit: semanticNumber(z.number().optional())
21 .describe('결과 N개 제한 (기본: 250)'),💡
22 '-i': semanticBoolean(z.boolean().optional())
23 .describe('대소문자 무시'),
24 multiline: semanticBoolean(z.boolean().optional())
25 .describe('여러 줄 매칭'),
26 })
27)
28
29export const GrepTool = buildTool({
30 name: GREP_TOOL_NAME,
31 searchHint: 'search file contents regex ripgrep',💡
32 inputSchema,💡
33 isConcurrencySafe: () => true, // 읽기 전용 → 병렬 안전
34 isReadOnly: () => true,
35
36 async checkPermissions(input, context) {
37 return checkReadPermissionForTool(resolvedPath, context)
38 },
39
40 async call(input, context) {
41 const results = await ripGrep(/* ... */)
42 return { output: results }
43 },
44})
3

도구 UI 렌더링 패턴

renderToolUseMessage, renderToolResultMessage 등 렌더링 프로토콜

호출 렌더링결과 렌더링진행률 스트리밍오류 표시|tools/BashTool/UI.tsxtools/FileEditTool/UI.tsx

각 도구의 UI.tsx는 도구 호출/결과/진행률/오류의 렌더링을 담당합니다. 이 분리 패턴으로 도구 로직과 표현이 독립적으로 변경됩니다.

도구 UI 렌더링 프로토콜 (6개 함수)
1// src/tools/BashTool/UI.tsx — BashTool 렌더링 프로토콜
2
3// 1. 도구 호출 표시 (명령어 하이라이팅)
4export function renderToolUseMessage(input, options): ReactNode {
5 return <BashCommandDisplay command={input.command} timeout={input.timeout} />
6}
7
8// 2. 도구 결과 표시 (stdout/stderr 분리)
9export function renderToolResultMessage(result, options): ReactNode {
10 return <BashOutputDisplay stdout={result.stdout} stderr={result.stderr} />
11}
12
13// 3. 진행률 표시 (스트리밍 출력)
14export function renderToolUseProgressMessage(progress, options): ReactNode {
15 return <BashStreamingOutput lines={progress.lines} />
16}
17
18// 4. 오류 표시
19export function renderToolUseErrorMessage(error, options): ReactNode {
20 return <BashErrorDisplay error={error} />
21}
22
23// 5. 요약 텍스트 (컴팩트 뷰에서 사용)
24export function getToolUseSummary(input): string {
25 return truncate(input.command, TOOL_SUMMARY_MAX_LENGTH)
26}
27
28// 6. 사용자 표시 이름
29export function userFacingName(input): string {
30 return 'Bash'
31}

학습 체크리스트

이해한 항목을 체크하고 학습 진도를 확인하세요

0/3 완료0%

📋 Chapter 5 요약: 도구 구현 패턴

이 챕터에서 배운 핵심 개념을 정리합니다

1
일관된 디렉토리 구조
Tool.ts, prompt.ts, UI.tsx, constants.ts로 모든 도구가 동일한 패턴을 따릅니다.
2
읽기 전용 도구
isReadOnly: true + isConcurrencySafe: true → 안전하게 병렬 실행 가능.
3
UI 렌더링 분리
도구 로직(call)과 표현(UI.tsx)이 분리되어 독립적으로 변경 가능.

다음 챕터 미리보기: 다음 챕터에서는 가장 복잡한 도구인 BashTool(18파일, 131KB 파서)을 심층 분석합니다.