- Published on
Claude Tool Use 400 invalid_tool_schema 해결 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버/에이전트에 Claude Tool Use를 붙이다 보면 가장 흔히 맞닥뜨리는 에러 중 하나가 400 invalid_tool_schema입니다. 메시지는 짧지만 원인은 다양합니다. 대부분 도구(tool) 정의의 JSON Schema가 Claude가 기대하는 형태와 미묘하게 어긋나서 발생하며, 특히 타입/required/추가 속성 허용 여부 같은 “스키마 엄격성”에서 많이 터집니다.
이 글에서는 (1) 어떤 경우에 invalid_tool_schema가 나는지, (2) Claude Tool 스키마를 올바르게 작성하는 패턴, (3) 실제로 빠르게 원인을 좁히는 디버깅 절차를 코드와 함께 정리합니다.
> 참고: 네트워크/프록시 환경에서 요청이 변형되거나 헤더가 꼬여 진단이 어려운 경우도 있습니다. OAuth나 리다이렉트, 프록시 계층을 함께 운영 중이라면 Proxy 뒤 Nginx에서 OAuth 리다이렉트 URI 불일치 해결처럼 “중간 계층이 요청을 어떻게 바꾸는지” 점검하는 습관이 도움이 됩니다.
invalid_tool_schema란 무엇인가
invalid_tool_schema는 말 그대로 tool 정의의 schema가 유효하지 않다는 뜻입니다. 여기서 schema는 일반적으로 다음을 포함합니다.
name: 도구 이름(식별자)description: 모델이 도구를 언제 써야 하는지 이해하기 위한 설명input_schema: 도구 입력을 기술하는 JSON Schema
문제는 JSON Schema 자체가 “문법적으로 JSON이 맞다”와 “Claude가 지원하는 JSON Schema subset 규칙을 만족한다”가 다를 수 있다는 점입니다. 즉, 다른 JSON Schema validator에서는 통과하지만 Claude에서는 거절될 수 있습니다.
가장 흔한 원인 TOP 8
아래 항목이 실제로 가장 많이 원인입니다. 하나씩 체크하면 대부분 해결됩니다.
1) input_schema 최상위 type이 object가 아님
Tool 입력은 대부분 type: "object"를 기대합니다.
- 잘못된 예:
type: "string"또는 배열을 최상위로 둠 - 해결: 최상위는 object로 두고 그 안에 properties로 구성
2) properties는 있는데 required가 없거나 불일치
required에 선언한 필드가properties에 없음- 혹은 반대로 필수로 받아야 하는데 required를 빼서 모델이 빈 입력을 만들게 됨
특히 required에 오타가 있으면 즉시 invalid_tool_schema로 떨어지는 경우가 많습니다.
3) JSON Schema 키 오타/잘못된 위치
대표적으로:
inputSchema(camelCase)로 작성parameters같은 OpenAI 스타일 키를 섞어 씀required를properties내부에 넣음
Claude는 “기대하는 구조”가 꽤 엄격하므로, 키 이름/위치가 다르면 바로 실패합니다.
4) type/enum/items 조합이 모순
예:
type: "string"인데items를 둠(배열 전용)type: "array"인데items가 없음enum이 문자열 목록인데 type을 number로 둠
5) additionalProperties 처리 문제
엄격 모드로 운영하려고 additionalProperties: false를 켜는 경우가 많은데, 이때 properties에 정의되지 않은 키가 들어올 여지가 있으면 모델이 도구 호출을 구성하다가 실패하거나, 스키마 자체가 특정 조건에서 거절되는 경우가 있습니다.
권장 패턴은:
- 초기에 디버깅 단계:
additionalProperties: true또는 생략 - 안정화 후:
additionalProperties: false+ 모든 입력 키를 명시
6) nullable 표현 방식 혼용
JSON Schema에서 nullable은 OpenAPI 스타일이고, 표준 JSON Schema에서는 보통 type: ["string", "null"]처럼 표현합니다. 일부 조합은 Claude에서 거절될 수 있습니다.
권장:
- null 허용이 꼭 필요하면
type: ["string", "null"]패턴을 사용 - 아니면 아예 null을 금지하고 빈 문자열/옵션 필드로 처리
7) description/metadata에 너무 복잡한 구조를 넣음
description에는 텍스트만 두는 것이 안전합니다. 객체/배열을 넣거나, 스키마 외부에 커스텀 키를 마구 추가하면 거절될 수 있습니다.
8) 도구 이름 규칙 위반(문자/길이)
도구 이름에 공백/특수문자/너무 긴 문자열 등을 넣으면 플랫폼에 따라 거부될 수 있습니다.
권장:
snake_case또는kebab-case대신 보통snake_case/영문+숫자+언더스코어 중심- 예:
search_docs,get_user_profile
올바른 Tool 스키마 예시(정석 템플릿)
아래는 “최소한으로 안전한” 입력 스키마 템플릿입니다. 문제 재현/디버깅 시 이 형태로 먼저 단순화한 뒤, 점진적으로 확장하는 방식이 가장 빠릅니다.
{
"name": "search_docs",
"description": "문서 저장소에서 키워드로 검색합니다.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "검색 키워드"
},
"top_k": {
"type": "integer",
"description": "반환할 최대 결과 수",
"minimum": 1,
"maximum": 20
}
},
"required": ["query"],
"additionalProperties": false
}
}
핵심 포인트는 다음입니다.
- 최상위
type: object properties에 모든 필드 정의required는 실제 존재하는 키만additionalProperties: false를 켰다면 정의되지 않은 키는 절대 못 들어오게 설계
Node.js(Typescript)에서 invalid_tool_schema 빠르게 재현/검증하기
실제 장애 상황에서는 “내가 보낸 payload가 무엇인지”부터 확정해야 합니다. 프록시/미들웨어/로깅 필터가 JSON을 바꾸는 경우도 흔합니다.
아래는 요청 직전 payload를 그대로 덤프하고, 스키마를 최소 validator로 한 번 더 확인하는 예시입니다.
import Ajv from "ajv";
const ajv = new Ajv({ allErrors: true });
// Claude tool input_schema 자체를 검증하는 게 아니라,
// 우리가 의도한 형태(최상위 object, required 일치 등)를 사전 점검하는 용도
function precheckToolSchema(tool: any) {
if (!tool?.name || typeof tool.name !== "string") throw new Error("tool.name missing");
if (!tool?.input_schema) throw new Error("tool.input_schema missing");
if (tool.input_schema.type !== "object") throw new Error("input_schema.type must be object");
const props = tool.input_schema.properties ?? {};
const req: string[] = tool.input_schema.required ?? [];
for (const k of req) {
if (!props[k]) throw new Error(`required key not in properties: ${k}`);
}
// 추가로: input_schema가 JSON Schema로서 크게 깨지지 않았는지 확인(부분적)
const validate = ajv.compile(tool.input_schema);
// 샘플 입력으로 한 번 돌려보기(필수값만)
const sample: any = {};
for (const k of req) sample[k] = "__sample__";
validate(sample);
if (validate.errors) {
throw new Error("AJV errors: " + JSON.stringify(validate.errors));
}
}
const tool = {
name: "search_docs",
description: "문서 저장소에서 키워드로 검색합니다.",
input_schema: {
type: "object",
properties: {
query: { type: "string" },
top_k: { type: "integer", minimum: 1, maximum: 20 }
},
required: ["query"],
additionalProperties: false
}
};
precheckToolSchema(tool);
console.log("tool schema ok");
이 사전 점검만으로도 “required 오타”, “최상위 타입 오류”, “properties 누락” 같은 1차 사고는 대부분 잡힙니다.
Python에서 흔한 실수: dict를 JSON으로 직렬화하는 과정
Python에서는 None이 JSON으로 직렬화되면 null이 됩니다. 의도치 않게 null이 들어가면서 스키마와 충돌하는 경우가 있습니다. 또한 로깅/저장 과정에서 인코딩 문제가 섞이면 payload 자체가 깨져 보이기도 합니다.
인코딩 이슈가 의심되면 관련해서는 Python UnicodeDecodeError 해결 - 인코딩 탐지·일괄변환처럼 “입출력 경로”를 먼저 정리해두면 디버깅 비용이 줄어듭니다.
아래는 tool 정의를 JSON으로 안전하게 출력(검증)하는 예시입니다.
import json
tool = {
"name": "search_docs",
"description": "문서 저장소에서 키워드로 검색합니다.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"top_k": {"type": "integer", "minimum": 1, "maximum": 20}
},
"required": ["query"],
"additionalProperties": False
}
}
# ensure_ascii=False로 실제 한글이 깨지지 않게 확인
payload = json.dumps(tool, ensure_ascii=False)
print(payload)
# 역직렬화로 JSON round-trip이 되는지 확인
json.loads(payload)
여기서 additionalProperties는 JSON에서는 boolean이므로 False가 맞습니다. 다만 일부 코드에서 문자열로 들어가면("false") 스키마가 깨질 수 있으니 주의하세요.
실전 디버깅 체크리스트(장애 대응 순서)
invalid_tool_schema는 “스키마가 틀렸다”가 핵심이므로, 아래 순서로 좁히는 게 가장 빠릅니다.
1) 요청 직전의 tools payload를 그대로 로그로 남기기
- 애플리케이션 내부 객체가 아니라 실제 전송되는 JSON 문자열을 덤프
- 프록시/게이트웨이/미들웨어가 있다면 그 구간에서 body 변형 여부 확인
2) tool을 1개만 남기고 최소 스키마로 축소
- 문제가 나는 tool이 여러 개면 하나씩 분리
input_schema를 위의 “정석 템플릿” 수준으로 단순화
3) required/properties 불일치부터 확인
- required 오타가 가장 치명적이고 흔함
- 필수 필드를 줄이고 하나씩 늘리며 확인
4) additionalProperties를 잠시 완화
- 우선
additionalProperties를 제거하거나 true로 두고 통과 여부 확인 - 통과하면 이후 false로 되돌리며 누락된 properties를 채움
5) enum/type/items 조합 검증
- 배열이면 items 필수
- enum 값의 타입과 type 일치
6) null 허용 여부 재설계
- null이 들어올 가능성이 있으면 스키마에 반영
- 아니면 애초에 null을 만들지 않도록 입력 전처리
장애를 줄이는 운영 팁
- 도구 스키마를 코드에서 생성하지 말고(동적 생성 최소화), 정적 JSON/타입 정의로 관리한 뒤 PR 리뷰 대상으로 삼는 것이 안전합니다.
- 스키마 변경 시 CI에서 “required/properties 일치 검사” 같은 린트 단계를 추가하면 재발이 크게 줄어듭니다.
- 에이전트가 커지면 도구도 많아져서, 배포 후에야 invalid_tool_schema가 터지면 영향이 큽니다. 쿠버네티스/배포 파이프라인에서 문제가 생겼을 때처럼(예: K8s CrashLoopBackOff - Readiness·Liveness 5분 진단) “최소 재현 → 원인 축소 → 점진적 확장” 루틴을 그대로 적용하는 게 효과적입니다.
마무리
400 invalid_tool_schema는 대부분 “JSON Schema를 조금이라도 엄격하게 쓰려다 생기는 사소한 불일치”에서 시작합니다. 최상위 object, required/properties 정합성, additionalProperties, null 처리, 배열(items) 규칙만 체계적으로 점검하면 빠르게 해결됩니다.
다음 단계로는, 스키마를 통과시킨 뒤에도 모델이 엉뚱한 인자를 넣거나 호출을 망설이는 문제가 생길 수 있으니 description을 더 명확히 쓰고, 입력 예시를 시스템 프롬프트/가이드에 포함하는 방식으로 안정성을 높여보는 것을 권합니다.