11 분 소요

Summary

2편까지 하면 도구가 동작합니다. 그런데 붙여놓고 며칠 쓰다 보면 이런 일이 생겨요.

에이전트가 그 도구를 안 부릅니다. 있는 줄 알면서도 그냥 추측으로 답해요. 부르긴 부르는데 인자를 틀립니다. 지역 코드 자리에 지역 이름을 넣죠. 잘 부르는데 한 번 부르고 대화가 끝납니다. 응답이 너무 커서 컨텍스트를 다 먹었거든요. 실패하면 거기서 멈춥니다. 오류 메시지에 다음에 뭘 해야 하는지가 없어서요.

넷 다 도구 코드의 버그가 아닙니다. 설계 문제예요. 그리고 넷 다 고칠 수 있습니다.

이번 편의 관점은 한 줄로 이렇습니다.

MCP 도구는 API 가 아니라 프롬프트다.

이름도, 설명도, 스키마도, 오류 메시지도 전부 모델이 읽는 텍스트입니다. API 문서를 쓴다고 생각하면 실패하고, 프롬프트를 쓴다고 생각하면 잘 됩니다.

💡 이 글에서 다루는 것

  • 좋은 도구의 여섯 축 채점표
  • 축① 개수 — 도구가 많을수록 정확도가 떨어지는 이유
  • 축② 이름과 설명 — 여기가 진짜 라우터다
  • 축③ 입력 스키마 — 틀리게 부를 수 없게 만들기, 그리고 룩업 도구
  • 축④ 출력 — 토큰 예산을 계산으로 잡기 (수식은 쉽게 풀어 씁니다)
  • 축⑤ 오류 — 에이전트가 스스로 고치게 만드는 메시지
  • 축⑥ 부작용 — 멱등성과 사람 승인
  • 같은 기능, 못 만든 버전 vs 잘 만든 버전 나란히 채점
  • 도구 설계 체크리스트



1. 왜 “API 가 아니라 프롬프트” 인가

MCP 스펙은 도구를 이렇게 정의합니다 — 서버가 제공하는 Tools 는 “모델이 실행하는 함수” 예요. 그런데 그 함수를 부를지 말지, 어떤 인자로 부를지는 모델이 텍스트만 보고 판단합니다.

모델이 보는 건 딱 이것뿐이에요.

모델이 보는 것 어디서 오나 (n8n 기준)
도구 이름 도구 노드 이름
도구 설명 Description 파라미터
입력 스키마 $fromAI() 의 키·타입·설명
이전 호출의 결과·오류 도구가 돌려준 값

여러분이 워크플로 안에 아무리 정교한 로직을 넣어도, 모델은 그걸 못 봅니다. 위 네 줄이 전부예요.

그래서 스펙도 안전 관점에서 같은 말을 뒤집어 합니다 — “도구의 동작 설명(annotations 등)은 신뢰할 수 있는 서버에서 온 게 아니라면 신뢰할 수 없는 것으로 취급해야 한다”. 설명이 곧 모델의 행동을 바꾼다는 걸 스펙이 인정하고 있는 거죠. 공격 관점에서 위험한 만큼, 설계 관점에서는 지렛대입니다.


2. 채점표 — 여섯 축

이 글에서 쓸 기준입니다. 각 축 2점, 총 12점으로 봅니다.

묻는 것
① 개수 도구가 필요한 만큼만 있나
② 이름·설명 언제 쓰는지가 한 줄로 서나
③ 입력 스키마 틀리게 부를 수 있는 여지를 줄였나
④ 출력 토큰 예산 안에 드나
⑤ 오류 다음 수를 알려주나
⑥ 부작용 되돌릴 수 있나, 승인이 필요한가

하나씩 봅니다.


3. 축① 개수 — 적을수록 정확해진다

도구를 20개 붙이면 에이전트가 20개를 다 잘 쓸까요. 아닙니다. 도구 목록 전체가 매 턴 컨텍스트에 들어가고, 모델은 그 안에서 하나를 골라야 합니다. 비슷비슷한 도구가 많을수록 고르기가 어려워지고, 카탈로그 자체가 토큰을 먹습니다.

n8n 이 이걸 다루는 장치가 1편에서 본 MCP Client Tool 의 Tools to Include 예요.

설정 의미
All 서버가 주는 도구 전부
Selected 고른 것만
All Except 뺄 것만 지정

기본값을 그대로 두지 마세요. 1편에서 본 대로 n8n 인스턴스 레벨 MCP 서버는 도구가 60개가 넘습니다. 그걸 All 로 에이전트에 물리면 카탈로그만으로 컨텍스트를 크게 잡아먹고, 선택 정확도도 떨어져요. 워크플로를 만드는 게 목적이면 빌더 도구 몇 개만 고르는 게 낫습니다.

우리가 서버를 만들 때의 규칙도 같습니다.

하지 말 것 대신
CRUD 를 도구 4개로 쪼개기 조회·변경 2개로
파라미터만 다른 도구 여러 개 도구 1개 + 열거형 인자
“혹시 몰라” 도구 남기기 안 쓰면 뺀다

경험칙으로 한 서버에 도구 5~10개가 편안한 범위입니다. 그보다 많아지면 서버를 도메인별로 나누세요 — 2편에서 Path 를 의미 있게 지으라고 한 이유가 여기에도 있습니다.


4. 축② 이름과 설명 — 진짜 라우터는 여기다

에이전트가 도구를 고르는 과정은 사실상 이름과 설명으로 하는 검색입니다. 그러니 이름과 설명은 “이 도구가 무엇인지” 가 아니라 “언제 이걸 써야 하는지” 를 적어야 해요.

n8n 의 Call n8n Workflow Tool 문서도 Description 파라미터를 “에이전트가 언제 이 도구를 써야 하는지를 지정하는 곳” 이라고 설명합니다. 무엇을 하는 도구인지가 아니라요.

나란히 놓고 보면 차이가 확실합니다.

항목 ❌ 못 만든 버전 ✅ 잘 만든 버전
이름 getData get_apartment_trades

설명은 길어서 따로 놓습니다.

아파트 데이터를 가져옵니다.

특정 시군구·연월의 아파트 매매 실거래를 조회합니다. 지역은 반드시 5자리 법정동 코드여야 하며, 코드를 모르면 먼저 search_region_code 를 부르세요. 전월세는 다루지 않습니다.

✅ 쪽 설명 안에 들어간 것들을 뜯어보면 이래요.

문장 하는 일
“특정 시군구·연월의 아파트 매매 실거래” 범위를 좁혀 오인 호출을 막음
“반드시 5자리 법정동 코드” 입력 형식을 못 박음
“모르면 먼저 search_region_code 도구 간 순서를 알려줌
“전월세는 다루지 않습니다” 경계를 알려줌 — 없는 기능을 기대하지 않게

마지막 두 줄이 특히 효과가 큽니다. 에이전트가 헤매는 상황의 상당수는 “다음에 뭘 불러야 할지 모를 때” 와 “이 도구가 안 되는 걸 되는 줄 알 때” 거든요. 설명에 그 두 개를 적으면 대부분 사라집니다.

작성 공식: <무엇을> <어떤 범위에서> 한다. 입력은 <형식>. <선행 도구> 를 먼저 부를 것. <하지 않는 것> 은 다루지 않는다.


5. 축③ 입력 스키마 — 틀리게 부를 수 없게

2편에서 본 대로 도구의 입력 스키마는 $fromAI() 로부터 만들어집니다. 그러니 스키마 설계 = $fromAI() 설계예요.

5-1. 네 가지 손잡이

손잡이 쓰는 법 효과
키 이름 codelawd_cd 무엇을 넣어야 하는지가 이름에서 보임
description 예시를 넣는다 형식 오류가 급감
type number boolean json 명시 문자열 "5" 같은 게 안 옴
defaultValue 흔한 값을 기본으로 인자 누락 실패를 흡수

description실제 값 예시를 넣는 게 비용 대비 효과가 제일 큽니다.

{{ $fromAI("deal_ymd", "Target month, YYYYMM. Example: 202608", "string") }}

YYYYMM 형식 이라고만 쓰면 모델이 2026-08 을 넣기도 해요. Example: 202608 한 줄이 그걸 막습니다.

5-2. 열거형은 설명으로 표현한다

$fromAI() 에는 enum 인자가 없습니다. 그러니 설명에 허용값을 나열하세요.

{{ $fromAI("deal_type", "One of: trade, jeonse, monthly. Default trade.", "string", "trade") }}

이렇게 하면 자유 문자열보다 훨씬 안정적으로 들어옵니다.

5-3. 룩업 도구를 따로 두는 이유

에이전트가 가장 자주 틀리는 자리가 코드 값이에요. 법정동 코드, 계정 ID, 상품 코드 같은 것들. 모델은 이런 걸 환각합니다 — 그럴듯한 5자리 숫자를 만들어내죠.

해결은 간단합니다. 코드를 찾아주는 도구를 따로 만들고, 본 도구의 설명에서 그걸 먼저 부르라고 시키는 것.

도구 입력 출력
search_region_code 지역명 (예: 강남구) 후보 코드 목록
get_apartment_trades 코드 + 연월 실거래 요약

이 구조의 진짜 이점은 틀렸을 때 회복이 된다는 겁니다. 코드를 환각하면 그냥 빈 결과가 오지만, 룩업 도구가 있으면 에이전트가 “아 코드를 먼저 찾아야겠구나” 로 돌아갈 수 있어요.

이건 2편에서 “도구가 3개인데 왜 굳이 룩업을 하나 쓰냐” 고 물었을 자리에 대한 답이기도 합니다. 도구 하나를 정확도를 위해 쓰는 겁니다.

5-4. 열지 말아야 할 인자

2편에서 짚은 권한 경계를 다시 강조합니다. 모델이 정하면 안 되는 값은 $fromAI() 로 열지 마세요.

인자 어떻게
API 키·시크릿 자격증명으로. 절대 인자 아님
테넌트·조직 ID 고정값 또는 표현식
조회 상한(limit) 고정값. 모델에게 맡기면 커짐
대상 파일 경로 화이트리스트 열거형

$fromAI() 로 여는 순간 그건 모델이 통제하는 값이 되고, 모델은 사용자 입력의 영향을 받습니다. 즉 프롬프트 인젝션이 곧바로 그 인자에 도달해요. 5편에서 다시 다룹니다.


6. 축④ 출력 — 토큰 예산을 계산으로 잡기

여기가 제일 자주 무너지는 곳입니다. 공공 API 응답을 그대로 돌려주면 한 번 호출에 수만 토큰이 들어가고, 두세 번 부르면 대화가 끝나요.

6-1. 한 턴에 드는 비용

도구를 붙였을 때 컨텍스트에 실제로 쌓이는 양을 식으로 쓰면 이렇습니다.

\[C_{\text{turn}} \;=\; \sum_{i=1}^{N}\left(n_i + d_i + s_i\right) \;+\; \sum_{k=1}^{K}\left(a_k + r_k\right)\]

수식이 낯설면 이렇게 읽으세요.

  • $\sum$ 은 ‘시그마’. “전부 다 더해라” 는 뜻입니다. $\sum_{i=1}^{N}$ 이면 $i$ 가 1부터 $N$ 까지 가면서 뒤에 오는 걸 전부 더하라는 거예요.
  • $N$ 은 도구 개수, $n_i$ 는 $i$ 번째 도구의 이름, $d_i$ 는 그 설명, $s_i$ 는 그 스키마가 차지하는 토큰 수예요.
  • $K$ 는 이번 대화에서 도구를 부른 횟수, $a_k$ 는 $k$ 번째 호출의 인자, $r_k$ 는 그 응답의 토큰 수입니다.
  • 그러니 앞 덩어리는 “도구 카탈로그를 들고 있는 고정비”, 뒤 덩어리는 “실제로 부르면서 드는 변동비” 예요.

핵심은 앞 덩어리가 도구를 한 번도 안 불러도 매 턴 든다는 겁니다. 축① 에서 “도구를 줄여라” 고 한 게 이 항이에요.

6-2. 응답 하나의 상한

거꾸로 풀면 응답 크기의 상한이 나옵니다.

\[r_{\max} \;\approx\; \frac{B - S - H}{K}\]

쉽게 읽으면 — 분수는 한국어로 분모부터 읽어요. “$K$ 분의 ($B$ 빼기 $S$ 빼기 $H$)”.

  • $B$ 는 쓸 수 있는 컨텍스트 예산 전체.
  • $S$ 는 도구 카탈로그 등 고정비($\sum (n_i+d_i+s_i)$).
  • $H$ 는 이미 쌓인 대화 기록.
  • $K$ 는 예상 호출 횟수.
  • 즉 “남은 자리를 호출 횟수로 나눈 것” 이 응답 하나가 써도 되는 최대치입니다.

숫자를 넣어볼게요. 예산 $B$ 를 여유 있게 20만 토큰으로 잡고, 카탈로그 $S$ 가 3천, 대화 $H$ 가 2만, 한 작업에 도구를 $K = 8$ 번 부른다고 하면

\[r_{\max} \approx \frac{200{,}000 - 3{,}000 - 20{,}000}{8} \approx 22{,}000\]

여유가 있어 보이죠. 그런데 이건 한 작업의 이야기입니다. 실제 세션은 작업을 여러 개 이어서 하고, 그때마다 $H$ 가 불어납니다. $H$ 가 15만이 되면 같은 식이 $r_{\max} \approx 3{,}400$ 으로 떨어져요.

실무 결론: 도구 응답 하나를 2천~4천 토큰 안으로 설계하세요. 대략 한글 2~4천 자 정도입니다. 이 범위면 대화가 길어져도 안 무너집니다.

6-3. 그 안에 넣는 방법

기법 하는 일
필드 솎기 응답에서 필요한 열만 남긴다
집계해서 주기 원본 100건 대신 통계 + 상위 5건
페이지네이션 limit offset 을 두고 총 개수를 함께 반환
잘랐다고 말하기 “총 842건 중 20건 표시” 를 응답에 명시

마지막 줄이 중요합니다. 잘라놓고 말을 안 하면 모델은 그게 전부인 줄 압니다. 그리고 “총 842건 중 20건” 이라고 적어주면, 더 필요할 때 모델이 알아서 다음 페이지를 부릅니다.

n8n 에서 이걸 하는 자리는 도구 워크플로의 마지막 노드예요. HTTP Request 로 받은 원본을 Code 나 Set 노드로 줄여서 내보내면 됩니다. 2편에서 도구를 서브워크플로로 만들라고 한 이유가 바로 이겁니다 — HTTP Request Tool 을 그냥 붙이면 이 요약 단계를 넣을 자리가 없어요.


7. 축⑤ 오류 — 다음 수를 알려주는 메시지

도구는 실패합니다. 문제는 실패했을 때 에이전트가 거기서 멈추느냐, 고쳐서 다시 부르느냐 예요. 그건 오류 메시지가 정합니다.

❌ 멈추게 하는 오류 ✅ 고치게 하는 오류
Error: request failed 잘못된 lawd_cd: ‘강남구’.
5자리 숫자가 필요합니다.
search_region_code
코드를 먼저 조회하세요.
500 Internal Server Error 상위 API 일시 오류.
10초 뒤 같은 인자로
재시도하세요. 3회 실패하면
사용자에게 보고하세요.
No data 202608 에 해당 지역 거래 0건.
인자는 유효합니다.
다른 달을 조회하거나
없다고 답하세요.

패턴이 보이시죠. 좋은 오류 메시지에는 세 가지가 들어갑니다.

요소
무엇이 틀렸나 lawd_cd 가 잘못됨
무엇이 맞나 5자리 숫자
다음에 뭘 하나 search_region_code 를 먼저

특히 세 번째 줄의 “결과가 없는 것”과 “호출이 틀린 것”을 구분해 주는 것이 결정적입니다. 이 둘을 똑같이 No data 로 돌려주면 에이전트는 인자를 이리저리 바꿔가며 헛발질하거나, 반대로 데이터가 없는데 인자 탓을 하며 포기해요.

n8n 에서는 도구 워크플로 안에서 IF 노드로 케이스를 가르고 각각 다른 메시지를 반환하면 됩니다. 워크플로가 에러로 죽는 것에러 메시지를 정상 반환하는 것은 다르다는 점도 기억하세요 — 후자가 에이전트에게 훨씬 쓸모 있습니다.


8. 축⑥ 부작용 — 되돌릴 수 있나, 승인이 필요한가

읽기 도구는 마음 편합니다. 쓰기 도구는 다릅니다.

등급 설계
읽기 조회·검색 그냥 열어도 됨
되돌릴 수 있는 쓰기 초안 저장, 라벨 부여 열되 로그를 남김
되돌리기 어려운 쓰기 발송, 결제, 삭제 사람 승인

8-1. 멱등성

같은 인자로 두 번 불려도 결과가 같아야 합니다. 에이전트는 재시도를 자주 해요 — 응답이 늦거나, 앞의 판단을 번복하거나, 컨텍스트가 잘려서 이미 부른 걸 잊어버립니다.

n8n 에서 쓸 수 있는 장치는 이래요.

방법 어떻게
요청 키 $fromAI()request_id 를 받아 중복 차단
조회 후 쓰기 도구 안에서 존재 확인 후 분기
upsert 생성 대신 갱신

8-2. 승인은 어디서 하나 — 중요한 구분

n8n 에는 AI Agent 노드용 사람 검토(Human review) 기능이 있습니다. Tools Panel 의 Human review 섹션에서 승인 채널(Slack, Discord, Telegram, Microsoft Teams, Gmail, WhatsApp Business Cloud, Google Chat, Microsoft Outlook, n8n 내장 Chat)을 고르면, 검토자에게 “AI 가 어떤 도구를 어떤 인자로 쓰려 하는지” 가 전달되고 승인/거부를 고르게 됩니다.

🚨 그런데 이건 n8n 안의 AI Agent 이야기입니다. 우리가 만든 MCP 서버를 Claude Code 같은 외부 클라이언트가 부를 때는, 승인은 클라이언트 쪽에서 일어나요. MCP 스펙도 이걸 호스트의 책임으로 둡니다 — “호스트는 어떤 도구든 호출하기 전에 사용자의 명시적 동의를 받아야 한다”.

서버가 승인을 강제할 수 없습니다. 클라이언트가 자동 승인 모드로 돌면 그대로 실행돼요. 그러니 되돌리기 어려운 동작은 도구 자체에 방어를 넣으세요 — 금액 상한, 대상 화이트리스트, dry-run 인자, 감사 로그. “승인 받았겠지” 를 전제하면 안 됩니다.


9. 채점 — 같은 기능, 두 버전

예제 도구 get_apartment_trades 를 두 버전으로 만들어 채점표에 넣어봅니다.

❌ A안 ✅ B안
① 개수 도구 7개(월별·연도별·타입별 분리) 도구 3개(인자로 통합)
② 이름·설명 getData / “데이터 조회” get_apartment_trades / 범위·형식·선행도구·경계 명시
③ 스키마 $fromAI("code") 하나 lawd_cd+deal_ymd+예시+타입, 룩업 도구 별도
④ 출력 API 원본 JSON 전량 통계 + 상위 5건 + “총 N건 중” 표기
⑤ 오류 request failed 원인·정정·다음 수 3종
⑥ 부작용 읽기 전용(문제 없음) 읽기 전용 + limit 고정
A안 B안
① 개수 0 2
② 이름·설명 0 2
③ 스키마 0 2
④ 출력 0 2
⑤ 오류 0 2
⑥ 부작용 2 2
합계 2 / 12 12 / 12

A안도 동작은 합니다. 워크플로를 열어보면 아무 문제 없어요. 그런데 에이전트에게 물려보면 절반은 안 부르고, 부르면 코드를 환각하고, 성공하면 컨텍스트가 날아갑니다.

여기서 얻는 교훈: MCP 도구는 워크플로 에디터에서 테스트해서는 품질을 알 수 없습니다. 실제 에이전트에게 물려서, 실제 질문을 시켜봐야 알아요. 그 방법이 4편입니다.


10. 도구 설계 체크리스트

항목 기준
도구 개수 서버당 5~10개, 넘으면 서버 분리
이름 동사_명사 소문자 스네이크
설명 범위 + 입력 형식 + 선행 도구 + 안 하는 것
$fromAI 의미 있는 이름, 1~64자 규칙 준수
$fromAI 설명 실제 값 예시 포함
열거형 설명에 허용값 나열 + defaultValue
코드성 인자 룩업 도구를 짝으로 제공
열지 말 것 키·테넌트·limit·경로는 고정
응답 크기 2천~4천 토큰. 자르면 총 개수 명시
오류 원인 + 정정 + 다음 수
빈 결과 인자 오류와 구분해서 반환
쓰기 도구 멱등성 + 도구 자체 방어

4편에서는 이 도구들을 실제 클라이언트에 붙이고, 안 될 때 원인을 가르는 방법을 봅니다.



참고 문서