MCP 서버에 도구를 여러 개 붙여두면, 그중 무엇을 언제 부를지는 LLM이 스스로 고른다. 이때 LLM이 보는 것은 구현 코드가 아니다. 도구의 이름·설명(description)·입력 스키마, 이 세 가지 텍스트뿐이다. 즉 description은 사람용 주석이 아니라 모델이 읽는 실행 인터페이스다. 여기가 부실하면 도구가 아무리 잘 작동해도 엉뚱한 때에 불리거나 아예 안 불린다.
나쁜 설명과 좋은 설명
똑같은 도구라도 설명 한 줄이 선택률을 가른다.
// 약함: 무엇을 하는지만 있고, 언제 쓰는지가 없다
{
name: "search_posts",
description: "글을 검색한다",
}
// 나음: 용도·시점·경계를 담았다
{
name: "search_posts",
description:
"블로그에 이미 발행된 글을 제목·태그로 검색한다. " +
"새 글을 쓰기 전 중복 주제를 피하려 할 때 먼저 부른다. " +
"글을 생성하지는 않는다 — 발행은 publish_post를 쓴다.",
}
두 번째가 담은 것은 세 가지다. 무엇을(발행된 글을 검색), 언제(새 글 쓰기 전 중복 확인), 그리고 경계(생성은 이 도구가 아님). 특히 경계 문장이 중요하다. 비슷한 도구가 여럿일수록 LLM은 “이건 저 도구 말고 이 도구”라는 구분선을 설명에서 찾는다. 이름만으로는 search_posts와 publish_post가 어떻게 나뉘는지 모델이 확신하기 어렵다.
파라미터에도 설명을 단다
입력 스키마의 각 필드도 모델이 채워 보내는 값이다. 필드 이름만으로 뜻이 모호하면 설명을 붙인다. zod를 쓴다면 .describe()로 단다.
import { z } from "zod";
const schema = z.object({
query: z.string().describe("검색어. 제목·태그에서 부분 일치로 찾는다"),
section: z
.enum(["mcp", "security"])
.describe("특정 섹션으로 한정할 때만. 생략하면 전체 검색"),
limit: z.number().int().default(10).describe("최대 결과 개수 (기본 10)"),
});
기본값·단위·“생략하면 어떻게 되는지”를 적어두면 모델이 불필요한 인자를 지어내는 일이 준다. 반대로 section: "특정 섹션"처럼 뭉뚱그리면, 모델은 없는 섹션 이름을 만들어 넣기도 한다.
실무 체크리스트
- 첫 문장에 무엇을 하는지 한 줄로 적는다.
- 언제 쓰는지, 그리고 가능하면 언제 안 쓰는지를 적는다.
- 비슷한 도구와의 경계를 문장으로 긋는다.
- 부작용·전제(파일을 만든다, 되돌릴 수 없다 등)를 숨기지 않는다.
- 모든 파라미터에 설명을 달고, 기본값·단위·허용 값을 밝힌다.
도구 설명은 결국 모델에게 건네는 사용 설명서다. 사람이 처음 보는 API 문서를 읽고 손을 뻗듯, 모델도 그 문장만 읽고 도구를 고른다. 구현 코드를 다듬는 만큼 설명문도 다듬을 값어치가 있다 — 여기서는 잘 쓴 한 문장이 곧 잘 동작하는 도구다.
이 글에 대한 의견은 아래 댓글로 남겨주세요 (GitHub 계정 필요). 로그인 없이 남기고 싶다면