본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 29. 22:22

토큰 효율적인 MCP 서버 설계하기: 약 100개의 도구를 출시하며 얻은 5가지 패턴

요약

100개 이상의 도구를 제공하는 MCP 서버를 설계하며 얻은 토큰 효율화 패턴 5가지를 소개합니다. LLM의 컨텍스트 윈도우 제한과 비용, 지연 시간 문제를 해결하기 위한 API 설계 전략과 OAuth 2.1 기반의 인증 방식을 다룹니다.

핵심 포인트

  • LLM은 반환된 모든 데이터를 토큰으로 소비하므로 API 응답 최적화가 필수적임
  • 과도한 컨텍스트는 비용 증가, 지연 시간 상승, 모델 추론 품질 저하를 유발함
  • 토큰 효율성을 설계의 최우선 제약 조건으로 설정해야 함
  • API 키 대신 OAuth 2.1과 PKCE를 활용한 보안 인증 패턴 적용

토큰 효율적인 MCP 서버 설계하기: 약 100개의 도구를 출시하며 얻은 5가지 패턴

Model Context Protocol 서버를 작성하기 시작할 때 아무도 말해주지 않는 사실이 하나 있습니다. 어려운 점은 API를 LLM에 노출하는 것이 아닙니다. 어려운 점은 API를 너무 많이 노출하지 않는 것입니다.

우리는 최근 OctoPerf의 MCP 서버를 출시했습니다. 이 서버는 약 100개의 도구를 통해 전체 부하 테스트 (load-testing) 플랫폼을 구동하며, 부하 테스트는 토큰 예산(token budgets)에 대해 매우 가혹한 스트레스 테스트가 된다는 사실이 드러났습니다. 단일 가상 사용자는 깊게 중첩된 액션 트리 (action tree) 구조를 가집니다. 테스트 실행 한 번은 기가바이트 단위의 결과를 생성합니다. 캡처된 단 하나의 HTTP 응답 본문(response body)이 전체 컨텍스트 윈도우 (context window)보다 클 수도 있습니다. 만약 REST API를 MCP 도구로 단순히 미러링(mirroring)한다면, 비용이 많이 들고 느리며 혼란스러워하는 에이전트를 얻게 될 것입니다.

다음은 우리가 이를 피하기 위해 사용한 5가지 패턴과, API 키 없이도 사용할 수 있게 만드는 인증 설정입니다. 이 중 어느 것도 부하 테스트에만 국한된 것이 아닙니다. 여러분이 직접 서버를 구축하고 있다면, 이 내용들을 그대로 가져다 쓸 수 있을 것입니다.

컨텍스트 윈도우가 실제 예산인 이유

REST API와 MCP 서버는 거의 동일해 보입니다. 둘 다 HTTP를 통해 JSON을 전달합니다. 하지만 이들은 매우 다른 소비자에게 데이터를 제공합니다. 웹 UI는 200KB 크기의 엔티티 (entity)를 가져와서 필요한 2%만 렌더링하고 나머지는 비용 없이 버립니다. 하지만 LLM은 그 무엇도 버릴 수 없습니다. 도구가 반환하는 모든 것은 이후의 모든 추론 단계에서 토큰 단위로 다시 읽힙니다.

이는 세 가지 방식으로 영향을 미칩니다:

  • 비용 (Money). 도구의 결과물은 입력 토큰 (input tokens)이며, 에이전트 루프 (agentic loop) 내에서 매 턴마다 다시 읽힙니다. 한 번의 과도하게 큰 응답은 여러 번 과금됩니다.
  • 지연 시간 (Latency). 컨텍스트가 커질수록 응답 속도는 느려지며, 에이전트는 작업당 수십 개의 호출을 체이닝 (chaining)합니다.
  • 품질 (Quality). 이것은 교묘한 문제입니다. 50,000개의 무관한 JSON 토큰을 뒤지는 모델은 중요한 단 하나의 필드를 찾는 능력이 측정 가능한 수준으로 저하됩니다.

그래서 우리는 토큰 비용을 처음부터 **일급 설계 제약 조건 (first-class design constraint)**으로 설정했으며, 이는 전체 API 표면 (API surface)을 형성했습니다.

인증 우선: OAuth 2.1, API 키 없음

패턴을 설명하기에 앞서, 사람들이 가장 많이 질문하는 부분입니다. 어디에도 API 키는 없습니다. 인증은 Streamable HTTP 상에서 PKCE동적 클라이언트 등록 (Dynamic Client Registration)를 사용하는 OAuth 2.1 방식입니다. **DCR (Dynamic Client Registration)**은 사용자가 수동으로 자격 증명을 발행하는 대신 클라이언트가 스스로 등록함을 의미하며, 모든 도구 호출은 연결된 사용자의 권한으로 실행되어 언제든 권한을 취소할 수 있습니다. 우리는 설정 파일에 붙여넣어야 하는 또 다른 장기 생존 비밀키(long-lived secret)를 원하지 않았으며, 쓰기 도구(write tools)가 가득한 MCP 서버는 바로 그런 비밀키를 두어서는 안 되는 곳이기 때문입니다.

패턴 1: 미리 서명된 URL (presigned URLs), 대화에서 바이트(bytes)를 분리하기

[

Ship the file to its destination, not through the messenger
]

부하 테스트(Load testing)는 파일 작업이 많습니다. CSV 데이터셋이 올라가고, JTL 결과, HAR 아카이브, Playwright 트레이스 및 PDF가 내려옵니다. 도구 결과(tool results)를 통해 파일 콘텐츠를 밀어 넣는 것은 터무니없는 일입니다. 고작 2MB의 결과 파일만 해도 대략 50만 개의 토큰(tokens)을 소모하며, 이는 모델이 거의 읽지도 않을 바이트(bytes)에 낭비되는 것입니다.

따라서 파일 도구는 절대로 콘텐츠를 반환하지 않습니다. 대신 콘텐츠를 가져오기 위한 지침을 반환합니다. 즉, 일회용 단기 토큰이 포함된 미리 서명된 URL (presigned URL), HTTP 메서드, 그리고 만료 시간을 반환합니다. 에이전트 호스트의 코드 인터프리터(code interpreter) 또는 일반적인 curl이 우리의 REST API에서 바이트를 직접 가져오며, 모델은 오직 수십 개의 토큰으로 구성된 봉투(envelope) 정보만을 보게 됩니다.

이 방식의 이점은 비용 그 이상입니다. 바이트가 모델을 우회하기 때문에, 파일 크기가 더 이상 LLM의 문제가 되지 않습니다. 에이전트는 50MB 크기의 Playwright 트레이스를 가져와 로컬에서 압축을 풀고, 실패한 단 하나의 셀렉터(selector)를 grep으로 찾아낼 수 있습니다. 이는 어떤 컨텍스트 윈도우(context window)로도 수용할 수 없는 작업입니다.

패턴 2: 엔티티(entity)가 아닌 목록(listings)을 반환하기

파일이 명백한 가해자라면, 엔티티(entities)는 교활한 가해자입니다. 저희의 REST API에서 가상 사용자(virtual user)는 전체 재귀적 children 액션 트리를 포함하고 있는데, 기록된 체크아웃(checkout)의 경우 이 트리는 쉽게 수천 줄의 JSON에 달합니다. ID를 찾기 위해 list_virtual_users를 호출하는 에이전트에게는 이 정보가 전혀 필요하지 않습니다.

따라서 모든 목록(list), 생성(create), 가져오기(import) 도구는 대신 **압축된 투영(compact projection)**을 반환합니다: id, name, description, tags, 타임스탬프(timestamps), 그리고 url입니다. 에이전트가 **추론(reasons about)**하는 것(다음 호출로 체이닝할 ID, 사람이 읽을 수 있는 이름)은 유지하고, 그렇지 않은 것(트리, 이미 알고 있는 ID, 내부 식별자(internal discriminators))은 버립니다. 저희는 엔티티 패밀리당 하나씩, 총 15개의 이러한 도구를 보유하고 있습니다.

마지막 필드는 보기보다 더 중요합니다. 모든 목록에는 **UI의 해당 페이지로 연결되는 딥링크(deep-link)**가 포함되어 있습니다. 이는 약간의 토큰만 소모되지만 두 가지 이점을 제공합니다. 에이전트가 어떤 요약에서도 사용자에게 클릭 가능한 링크를 전달할 수 있다는 점과, 에이전트에게 **우아한 퇴장 방식(graceful exit)**을 제공한다는 점입니다. 질문에 대해 도구 호출을 반복하는 것보다 대화형 차트(interactive chart)로 답하는 것이 더 나을 때, 에이전트는 토큰을 낭비하며 차트를 재현하는 대신 단순히 해당 페이지를 가리킬 수 있습니다.

대략적인 수치로 보면, 전체 엔티티는 수만 개의 토큰에 달하지만, 목록은 약 60개 정도입니다. 규모가 100배(two orders of magnitude) 더 큰 바쁜 프로젝트라면, 첫 번째 호출에서부터 바로 체감됩니다.

패턴 3: 교체(replace)하지 말고 패치(patch)하기

읽기는 쉬운 절반이었습니다. 편집(editing)이야말로 미숙한 설계가 정말로 피를 흘리는(bleeding) 지점입니다. 만약 유일한 쓰기 도구가 update(fullEntity)라면, 500개의 액션이 있는 트리에서 단 하나의 액션 이름만 변경하려 해도 에이전트는 트리 전체를 읽고, 필드 하나만 변경하여 전체를 다시 생성한 뒤, 그 전부를 다시 보내야 합니다. 컨텍스트(context)를 통해 전체 복사본이 두 번 전달될 뿐만 아니라, 모델이 건드리지 말아야 할 필드를 망가뜨릴 실질적인 위험도 따릅니다.

대신 모든 엔티티 패밀리에는 RFC 6902 JSON Patch를 기반으로 구축된 patch_* 도구가 제공됩니다. 에이전트는 오직 작업(operations)만을 전송합니다:

[
  { "op": "replace", "path": "/children/3/name", "value": "Submit payment" },
  { "op": "add", "path": "/children/7/enabled", "value": true }
...

엔티티(entity)의 크기와 상관없이, 정밀한 편집(surgical edit)은 불과 수십 개의 토큰만을 소모합니다. 서버 측에서는 패치(patch)가 적용된 후, 영속화(persisting)하기 전에 Jackson을 통해 다시 역직렬화(re-deserialized)되며, 이 왕복 과정(round-trip)을 통해 구조적으로 유효하지 않은 엔티티를 생성하는 모든 패치는 거부됩니다. 에이전트가 틀릴 수는 있지만, 당신의 스크립트를 손상시킬 수는 없습니다.

한 가지 주의할 점은, 다형성 트리(polymorphic tree)에 대해 올바른 패치를 작성하려면 모델이 모든 노드 유형의 형태(shape)를 알아야 한다는 것입니다. 추측하는 과정은 실패한 시도들에 토큰을 낭비하게 만듭니다. 따라서 우리는 엔티티 스키마(entity schemas)를 MCP 리소스 (MCP resources) (JSON Schema 2020-12, 서브타입당 하나의 oneOf 브랜치)로 게시하며, 리소스를 읽지 못하는 클라이언트를 위해 plain-HTTP 폴백(fallback)을 제공합니다. 에이전트는 시행착오를 통해 필드 이름을 재발견하는 대신, 필요할 때 단 한 번 스키마를 로드하며, 패치에 실패할 경우 관련 스키마를 다시 참조하므로 재시도는 대개 **단 한 번(one shot)**에 성공합니다.

패턴 4: 계층적 읽기 — 상세 정보 전 인덱스, 본문 전 상세 정보

Read the index first, drill into one representative second

위의 패턴들은 일반적입니다. 이 패턴은 **워크플로(workflow)**를 형성하는 것에 관한 것입니다. 스크립트를 검증(validate)할 때, 우리는 스크립트를 재생(replay)하며 모든 동작에 대해 **네 가지 HTTP 엔티티(HTTP entities)**를 캡처합니다: 기록된 요청(request), 재생된 요청, 그리고 두 가지 응답(responses)입니다. 24개의 동작이 포함된 여정의 경우 그 크기가 쉽게 메가바이트(MB) 단위에 달할 수 있습니다. 서버가 절대 하지 말아야 할 한 가지는 이 모든 것을 한꺼번에 넘겨주는 것입니다.

따라서 검증 API는 의도적으로 계층화되어 있습니다:

  1. **인덱스 호출 (An index call)**은 각 액션당 하나의 아주 작은 항목만을 반환합니다: 성공 및 실패 횟수, 타임스탬프, 그리고 본문(body)은 포함하지 않습니다. 보통 수백 개의 토큰 정도이며, 실패 원인(인증, 데이터, 서버 측)별로 그룹화하기에 충분합니다.
  2. **상세 호출 (A detail call)**은 그룹 내 하나의 대표적인 액션에 대한 4개의 HTTP 엔티티를 가져오며, 진단을 확인하거나 반박하는 데 필요한 몇 KB의 데이터를 제공합니다.
  3. **본문 호출 (A body call)**은 한 단계 더 깊이 들어가, 한쪽은 컨텍스트에 들어가지만 양쪽 모두는 들어갈 수 없는 경우를 위해 단일 교환의 단일 본문을 검색합니다.

에이전트는 숙련된 엔지니어가 디버깅하는 방식대로 읽습니다: 전체적인 그림을 보고, 그다음 하나의 대표적인 실패를 확인하며, 필요하다면 하나의 특정 본문을 확인합니다. 검증 오류를 분류(Triage)하는 데 드는 비용은 수십만 개의 토큰 대신 단 몇 천 개의 토큰이면 충분합니다. 모든 것을 반환하는 설계라면 수십만 개의 토큰을 낭비했을 것입니다. 우리는 벤치마크 보고서 (bench reports)에도 동일한 '일반에서 구체로 (general-to-specific)' 형태를 적용했습니다. 의도적으로 get_full_report 도구는 존재하지 않으며, 구조 호출과 위젯 제품군별로 좁은 범위의 값 도구만 존재합니다.

패턴 5: 단순한 도구가 아닌 기술 (skills)

도구는 무엇이 _가능한지_를 정의합니다. 하지만 모델에게 무엇이 _현명한지_를 알려주지는 않습니다. 100개의 도구와 검증 오류에 직면했을 때, 모델은 기술적으로는 올바른 일을 할 수 있지만, 종종 작업 순서를 추측하느라 호출을 낭비하게 됩니다.

그래서 우리는 기술 (skills)을 제공합니다. 이는 에이전트가 필요할 때마다 로드하는 마크다운 플레이북(playbook)이며, 각 기술은 완전한 워크플로우를 인코딩하고 있습니다. 검증 분류 (Validation triage) 기술은 지배적인 실패 그룹을 먼저 수정하고 다시 검증하는 법을 알고 있습니다. 자동 상관관계 (Auto-correlation) 기술은 엔티티를 다시 쓰기 전에 스냅샷을 찍는 법을 알고 있습니다. 시나리오 진단 (Scenario diagnosis) 기술은 개별 액션 테이블을 파고들기 전에 전역 지표를 읽는 법을 알고 있습니다. 도구가 API 접점이라면, **기술은 읽기 순서 (reading order)**이며, 이 순서를 인코딩하는 것이 에이전트를 저렴하고 궤도 이탈 없이 유지하는 비결입니다.

요약

실제 워크플로를 예로 들어보겠습니다: HAR 파일을 가져오고(import), 빨간색 검증 오류를 분류(triage)하며, 자동 상관관계 분석(auto-correlate)을 수행하고, 재검증(re-validate)한 뒤, 500명 사용자의 시나리오를 실행하고, 그 결과를 진단합니다. REST API를 단순히 그대로 복제(naive mirror)한다면 중간 지점에 도달하기도 전에 컨텍스트 윈도우(context window)를 다 써버릴 것이며, 이는 요약을 강제하고 정밀도를 떨어뜨리게 됩니다. 이 다섯 가지 패턴을 결합하면, 동일한 워크플로가 하나의 대화 내에 여유롭게 유지되며, 컨텍스트에는 목록, 인덱스, 확인된 하나의 실패 상세 정보, 패치 작업(patch ops), 통찰력 있는 판결(insight verdicts)과 같은 유의미한 신호(signal) 외에는 거의 남지 않게 됩니다.

만약 여러분만의 MCP 서버를 구축하고 있다면, 가져다 쓸 수 있는 패턴은 간단합니다:

  • 바이트를 대역 외(out of band)로 이동시키세요. 사전 서명된 URL(Presigned URLs)은 수십 개의 토큰이 들지만, 파일은 수백만 개의 토큰이 듭니다.
  • 엔티티(entities)를 투영(Project)하세요. UI가 렌더링하는 것이 아니라, 모델이 추론하는 내용을 반환하세요.
  • 패치(patch) 방식으로 편집하고, 서버 측에서 검증하며, 모델이 처음부터 올바르게 패치할 수 있도록 스키마(schemas)를 공개하세요.
  • 읽기 단계를 계층화(Layer)하세요. 상세 정보(detail) 이전에 인덱스(index)를, 본문(body) 이전에 상세 정보를 제공하세요.
  • 읽기 순서(reading order)를 인코딩하세요. 도구(tools)는 무엇이 가능한지를 정의하고, 기술(skills)은 무엇이 현명한지를 정의합니다.

실제 레코드 유형이 포함된 전체 글은 저희 블로그에 있으며, 직접 테스트해보고 싶다면 서버가 라이브 상태로 운영 중입니다: 문서(docs), 소스 코드 및 Claude Code 플러그인.

만약 MCP 서버를 출시하고 더 나은 패턴을 발견하셨다면, 댓글을 통해 진심으로 의견을 나누고 싶습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0