ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Claude에 도구 등록하는 방법 — input_schema 설계부터 defer_loading까지
    IT 2026. 6. 16. 22:00
    Claude에 도구 등록하는 방법 — input_schema 설계부터 defer_loading까지

    지난 글에서 deferred loading으로 도구 100개를 컨텍스트 밖으로 밀어낼 수 있다고 했다. 이 글은 그 전 단계, 도구를 어떻게 정의하고 등록하는가를 다룬다. 도구 등록의 본질은 JSON 스키마다 — 세 가지 필수 필드가 각각 무엇을 결정하는지, input_schema를 어떻게 설계해야 LLM이 파라미터를 혼동하지 않는지, description이 왜 단순한 메모가 아니라 BM25 검색 인덱스이기도 한지를 다이어그램으로 해부한다.

    도구 호출은 어떻게 작동하는가

    LLM이 도구를 "직접 실행"한다는 인상이 있지만, 실제로는 다르다. LLM은 도구를 어떤 파라미터로 불러야 하는지를 JSON 형태로 출력할 뿐이다. 그 JSON을 받아 실제로 실행하는 것은 LLM 밖의 코드다. 결과가 다시 LLM에게 전달되면, LLM이 그 결과를 보고 최종 자연어 응답을 만든다.

    diagram

    다이어그램 설명. LLM은 두 번의 API 호출로 응답을 완성한다. 첫 번째 호출에서 tool_use 블록을 출력하고, 두 번째 호출에서 실행 결과(tool_result)를 받아 자연어 응답을 생성한다. LLM이 직접 코드를 실행하는 게 아니라 항상 외부 실행기를 경유한다 — 이 구조 덕분에 도구 실행을 완전히 통제할 수 있다.

    도구 정의 객체 구조 — 세 가지 필수 필드

    도구 하나는 JSON 객체 하나다. 세 가지 필수 필드가 있다.

    diagram

    다이어그램 설명. name은 식별자이므로 영문 소문자와 밑줄로 구성한다. description은 LLM이 "이 도구가 지금 필요한가"를 판단할 때 읽는 문장이다 — 단순한 주석이 아니다. input_schema는 LLM이 호출 시 파라미터를 어떻게 채울지 알려주는 계약서다.

    MCP 서버가 Claude Code의 tools/list 요청에 응답하는 JSON-RPC 메시지는 이렇다.

    {
      "jsonrpc": "2.0",
      "id": 1,
      "result": {
        "tools": [
          {
            "name": "sdk_build_app",
            "description": "SDK로 앱을 빌드한다. 디바이스 또는 에뮬레이터 배포용 패키지를 생성할 때 사용. arch(arm64/x86)와 configuration(Debug/Release)을 받는다.",
            "inputSchema": {
              "type": "object",
              "properties": {
                "project_dir": {
                  "type": "string",
                  "description": "빌드할 프로젝트 루트 경로 (절대경로)"
                },
                "arch": {
                  "type": "string",
                  "enum": ["arm64", "x86"],
                  "description": "타겟 아키텍처"
                },
                "configuration": {
                  "type": "string",
                  "enum": ["Debug", "Release"],
                  "description": "빌드 설정. 릴리스 배포라면 Release"
                }
              },
              "required": ["project_dir"]
            }
          }
        ]
      }
    }
    

    JSON 설명. jsonrpc: "2.0"·id·result는 JSON-RPC 2.0(클라이언트-서버 간 요청·응답을 JSON으로 주고받는 표준 프로토콜) 봉투(envelope)다. 도구 정의 자체는 result.tools 배열 안에 들어간다. inputSchema는 camelCase — MCP 프로토콜 표기법이다(직접 Anthropic API의 input_schema snake_case와 다르니 주의). enum으로 허용 값을 명시하면 LLM이 임의 변형을 쓰지 않고, required에 없는 파라미터는 LLM이 생략해도 도구가 호출된다.

    input_schema 설계 원칙

    input_schema는 JSON Schema(데이터 구조의 형식과 제약을 JSON으로 기술하는 국제 표준) 형식을 따른다. 다섯 가지 원칙이 있다.

    diagram

    다이어그램 설명. 가장 효과가 큰 것은 ③번 enum이다. LLM은 arch를 물어보면 "arm64인가 aarch64인가 ARM64인가"를 고민한다. enum: ["arm64", "x86"]을 보는 순간 고민이 사라진다. ④번도 중요하다 — "경로"라고만 쓰면 LLM이 상대경로를 쓸지 절대경로를 쓸지 모른다. "절대경로, 예: /home/user/project"라고 쓰면 틀릴 여지가 없다.

    파라미터 타입별로 자주 쓰는 패턴을 비교해 보면 이렇다.

    diagramdiagram

    다이어그램 설명. 가장 흔한 실수는 boolean 파라미터에 설명을 안 쓰는 것이다. "force": {"type": "boolean"}만 있으면 LLM은 True가 무엇을 강제하는지 모른다. "True면 캐시를 무시하고 재빌드, False면 변경된 파일만 빌드"처럼 각 값의 효과를 명시해야 LLM이 상황에 맞게 선택한다.

    description 작성법 — LLM 선택 정확도를 결정하는 숨은 변수

    description은 두 가지 독자를 동시에 위한 글이다. 하나는 사람(코드 리뷰어, 미래의 나), 다른 하나는 LLM이다. LLM은 description을 읽고 "지금 이 요청에 이 도구가 맞는가"를 판단한다. 그리고 도구가 deferred loading 상태라면, BM25 검색 엔진(키워드 기반 검색 알고리즘)이 이 텍스트에서 키워드를 뽑아 인덱싱한다. description이 곧 검색 인덱스다.

    나쁜 예와 좋은 예를 비교해 보자.

    diagram

    ▲ 나쁜 예 — 동사 하나짜리 description

    diagram

    ▲ 좋은 예 — 서비스 접두사 + 동작 + 사용 맥락

    다이어그램 설명. 좋은 description의 구성은 세 층이다. ① 무엇을 하나 ("앱을 빌드한다"), ② 언제 쓰나 ("디바이스 배포용 패키지 생성", "디바이스 없이 테스트할 때"), ③ 어떤 입력을 받나 ("arch·configuration 지정 가능"). ②번이 가장 중요하다 — LLM이 "지금 이게 맞는 상황인가"를 ②번 문장으로 판단하기 때문이다. 서비스 접두사(sdk_)는 여러 MCP 서버가 붙을 때 이름 충돌을 막고 BM25 검색 범위를 좁혀 주는 역할도 한다.

    MCP 서버를 통한 등록 — 도구를 외부 프로세스로 분리

    도구를 API 호출 코드 안에 하드코딩하는 방식은 도구가 10개 이하일 때는 괜찮다. 그 이상이 되거나, 도구 로직이 별도 서비스·언어·환경에 있다면 MCP(Model Context Protocol, AI 모델과 외부 도구를 잇기 위한 표준 규약) 서버가 맞다. MCP 서버는 독립 프로세스로 실행되면서 Claude Code나 API가 JSON-RPC로 질의하면 도구 목록과 실행 결과를 반환한다.

    Claude Code에서 MCP 서버를 등록하는 방법은 ~/.claude/settings.json에 한 블록을 추가하는 것이다. Claude Code는 이 설정을 읽어 MCP 서버에 연결하고, JSON-RPC로 도구 목록(tools/list)을 받아온다.

    deferred loading — JSON-RPC 어디에도 없는 이유

    tools/list 응답 JSON을 아무리 봐도 defer_loading이 없다. 이유는 단순하다 — MCP JSON-RPC 스펙에 이 필드가 없기 때문이다. deferred loading은 MCP 서버가 아니라 클라이언트(Claude Code)가 결정한다. MCP 서버는 도구를 나열하기만 하고, Claude Code가 settings.jsonENABLE_TOOL_SEARCH를 보고 deferred loading 여부를 자동으로 적용한다.

    diagram

    다이어그램 설명. MCP 서버는 도구를 그냥 나열하기만 한다. Claude Code가 ENABLE_TOOL_SEARCH: "auto"를 보고 도구 정의 합계가 컨텍스트 창의 10%를 넘으면 자동으로 deferred loading으로 전환하고, Tool Search Tool을 컨텍스트에 추가한다. alwaysLoad: true로 지정된 도구만 항상 컨텍스트에 올라가고, 나머지는 Tool Search Tool이 JIT 로드한다.

    deferred loading 전략은 settings.json 한 곳에서 모두 제어한다.

    {
      "mcpServers": {
        "my-dev-tools": {
          "command": "node",
          "args": ["~/mcp-server/index.js"],
          "env": {
            "ENABLE_TOOL_SEARCH": "auto"
          }
        }
      },
      "tools": [
        { "name": "read_file",  "alwaysLoad": true },
        { "name": "list_files", "alwaysLoad": true }
      ]
    }
    

    ENABLE_TOOL_SEARCH는 세 가지 값을 받는다. "auto"는 도구 정의 합계가 컨텍스트 창의 10%를 넘을 때 자동으로 deferred loading으로 전환한다(권장). "always"는 도구 수와 무관하게 항상 적용하고, "never"는 디버깅용으로 항상 전부 로드한다. alwaysLoad의 판단 기준은 하나다 — 거의 모든 요청에서 쓰이는 도구인가. 파일 읽기처럼 매 요청마다 필요한 것은 true로, 특정 작업에서만 쓰는 것은 deferred로 두면 된다. Tool Search Tool은 ENABLE_TOOL_SEARCH 설정 시 Claude Code가 자동으로 추가한다.

    전체 등록 플로우 한눈에 보기

    diagram

    다이어그램 설명. ①~③은 도구 자체의 설계와 MCP 서버 등록, ④~⑤는 settings.json에서 deferred loading 전략 설정이다. MCP 서버에서는 defer_loading을 쓰지 않고, Claude Code가 settings.json을 읽어 자동으로 처리한다. 실전에서는 ⑥에서 LLM이 엉뚱한 도구를 선택하면 ①로 돌아가 descriptionname을 다듬는다.

    도구 설계가 정확도를 결정한다

    Anthropic이 공개한 수치에서 Claude Opus 4 기준 도구 선택 정확도가 Flat Loading(도구 전부 노출)에서 49%였다가 Deferred Loading으로 74%로 올랐다. 이 수치는 "deferred loading을 켜면 자동으로" 달성되는 게 아니다. BM25 검색이 맞는 도구를 찾아 올리려면, 그 도구의 이름과 description에 LLM이 검색할 만한 키워드가 실제로 있어야 한다. 도구 정의가 곧 검색 가능성이고, 검색 가능성이 곧 정확도다.

    도구를 등록하는 것은 API 호출 하나가 아니라, LLM이 도구를 찾고·선택하고·올바르게 호출하는 전체 경로를 설계하는 일이다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.