당신이 한 마디도 입력하기 전에 MCP 서버가 토큰을 낭비하고 있습니다
요약
MCP(Model Context Protocol) 서버가 모든 도구의 상세 스키마를 사전에 로드함으로써 발생하는 과도한 토큰 낭비 문제를 지적합니다. 이를 해결하기 위해 도구의 전체 스키마를 즉시 로드하는 대신, 필요할 때만 가져오는 '지연 로딩(on-demand)' 패턴을 제안합니다.
핵심 포인트
- MCP 서버의 모든 도구 스키마가 시스템 프롬프트에 포함되어 토큰 오버헤드 발생
- 도구 정의만으로 컨텍스트 예산의 15~20%를 차지할 수 있음
- 해결책으로 도구 이름과 요약만 먼저 로드하고 상세 스키마는 필요 시 호출하는 방식 제안
- 지연 로딩 패턴을 통해 컨텍스트 비용을 획기적으로 절감 가능
지난주에 직접 세어보았습니다. 제 에이전트 세션 중 하나에 47개의 MCP 도구(tools)가 연결되어 있었습니다. 호출된 것이 아니라, 그저 _로드(loaded)_되어 있었을 뿐입니다. 제가 단 한 글자도 입력하기 전에, 모든 도구의 전체 JSON 스키마(schema)가 시스템 프롬프트(system prompt)에 자리 잡고 있었습니다.
계산을 해보았습니다. 각 도구의 스키마(이름, 설명, 파라미터 형태, 예시 등)는 설명이 얼마나 상세하냐에 따라 평균 150~400 토큰(tokens)을 차지합니다. 47개의 도구는 약 11,000 토큰의 순수 오버헤드(overhead)를 발생시켰습니다. 이는 제가 비용을 지불한 컨텍스트(context)이며, 제가 그 도구들의 절반도 사용하지 않더라도 모델이 매 턴마다 읽어야 하는 내용입니다.
이것은 아무도 예산에 포함하지 않는 조용한 비용입니다. 모두가 50KB 크기의 로그 파일을 컨텍스트에 쏟아붓는 도구 호출(tool call)에 대해서는 걱정합니다. 하지만 누군가 주문을 하기도 전에 도구의 메뉴(menu) 자체가 이미 비싸져 있다는 사실은 알아채는 사람이 적습니다.
왜 이런 일이 발생하는가
MCP 서버는 설계상 도구의 전체 목록을 사전에 광고합니다. 프로토콜의 목적은 모델이 무엇을 사용할 수 있는지 알게 하는 것이기 때문입니다. 하지만 "사용 가능하다"는 것과 "상세한 내용까지 모두 로드되었다"는 것이 반드시 같을 필요는 없습니다. send_slack_message 도구와 query_datadog_metrics 도구는 사용자가 오직 git_status만 사용하는 세션에서도 두 도구의 완전한 파라미터 스키마(parameter schemas)가 주입됩니다.
GitHub, Slack, 데이터베이스, 디자인 도구, 그리고 자체 내부 도구들까지 몇 개의 서버를 쌓아 올리면, 이제 10개의 도구가 아니라 60100개의 도구를 보게 됩니다. 저는 도구 정의(tool definitions)만으로 전체 컨텍스트 예산의 1520%를 차지하는 세션들을 보았습니다. 이는 실제 대화가 시작되기도 전의 일입니다.
해결책: 도구가 아닌 스키마를 지연시키세요
저에게 실제로 효과가 있었던 패턴은 제가 사용하는 하네스(harness)에서 가져온 방식입니다. 도구를 이름과 한 줄 설명으로만 나열하고, 전체 스키마는 필요할 때(on demand) 가져오는 것입니다.
구체적으로, 세션 시작 시 모든 도구에 대해 다음과 같은 내용이 컨텍스트에 들어가는 대신:
{
"name": "mcp__github__create_pull_request",
"description": "Create a pull request on GitHub with title, body, base/head branches...",
...
다음과 같이 단 한 줄만 받게 됩니다:
mcp__github__create_pull_request
그리고 경량화된 검색 도구가 지연 목록(deferred list)과 함께 배치됩니다:
def tool_search(query: str, max_results: int = 5) -> list[dict]:
"""지연된 도구 이름/설명과 쿼리를 매칭합니다.
매칭된 항목에 대해서만 전체 JSON 스키마 (JSON schemas)를 반환하며, 전체 레지스트리를 반환하지는 않습니다."""
...
모델은 tool_search("select:mcp__github__create_pull_request")를 정확히 한 번 호출하여 전체 스키마를 돌려받으며, 오직 그 도구의 정의만이 컨텍스트 (context)에 들어갑니다. 이는 해당 턴뿐만 아니라 세션(session) 내내 유지됩니다. 그 외의 모든 것은 목록상의 이름으로만 남습니다.
실제로 이 방식은 약 80개의 지연된 도구 이름(각각 약 3~4 토큰, 전체 인덱스는 약 300 토큰으로 가정)을 사용하여, 즉시 로드(eagerly-loaded)되는 스키마의 예상치인 18,000 토큰에서 실제로 사용된 도구에 대해 1,000 토큰 미만으로 줄였습니다. 이것은 미미한 이득이 아닙니다. "내 컨텍스트 창의 절반이 도구 정의로 채워져 있다"와 "도구 정의는 반올림 오차 수준이다" 사이의 차이입니다.
이것이 해결하지 못하는 것
만약 세션 중에 어차피 모든 도구를 사용할 계획이라면 지연 로딩 (Deferred loading)은 도움이 되지 않습니다. 선불로 지불하느냐 턴(turn)에 걸쳐 나누어 지불하느냐의 차이일 뿐, 총비용은 동일합니다. 이 방식이 승리하는 이유는 대부분의 세션이 기술적으로 "사용 가능한" 도구 중 아주 작은 부분만을 건드리기 때문입니다. 만약 당신의 워크플로가 한 번의 대화에서 47개의 도구 중 40개를 진정으로 필요로 한다면, 즉시 로딩 (eager loading)과 지연 로딩 (deferred loading)은 수렴하게 됩니다.
또한 이것은 수다스러운 도구 출력 (outputs) 문제는 해결하지 못합니다. 그것은 별개의 문제(샌드박스 실행 (sandboxed execution), 출력 절단 (output truncation), 반환 전 요약 (summarize-before-return))이며 별도의 해결책이 필요합니다. 스키마 비대화 (Schema bloat)와 출력 비대화 (output bloat)는 두 가지 서로 다른 토큰 소모 요인입니다. 하나를 해결한다고 해서 다른 하나가 해결되지는 않습니다.
실제 시사점
만약 여러분이 MCP 설정을 구축하거나 구성하고 있다면, 흥미로운 질문을 던지기 전에 지루한 질문부터 먼저 던져보세요. 즉, "모델이 호출해야 할 도구(tools)가 무엇인가"가 아니라, "모델이 기본적으로 무엇이 존재한다는 것을 알고 있어야(know exist) 하는가, 반대로 무엇을 찾아볼 수(look up) 있게 할 것인가"를 물어야 합니다. 대부분의 도구 카탈로그(tool catalogs)는 서버 작성자에게 가장 저항이 적은 경로라는 이유로 "모든 것을, 항상"이라는 답변으로 그 질문에 답합니다. 하지만 이는 아무도 읽지 않는 메뉴를 위해 여러분의 컨텍스트 예산(context budget)을 낭비하는 가장 쉬운 방법이기도 합니다.
해결책은 영리하지 않습니다. 모델과 전체 스키마(schema) 사이에 이름, 설명, 그리고 검색 기능(search function)을 두는 것입니다. 하지만 이것은 80개의 도구에 대한 비용을 지불하는 것과, 실제로 호출한 3개의 도구에 대한 비용을 지불하는 것 사이의 차이를 만들어냅니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기