MCP 서버 설계: 프로덕션 환경에서 배운 3가지 원칙
요약
프로덕션 환경에서 안정적인 MCP(Model Context Protocol) 서버를 구축하기 위한 3가지 핵심 설계 원칙을 다룹니다. LLM의 제한된 컨텍스트와 주의력을 고려하여 도구의 수를 줄이고, 일관된 용어를 사용하며, 엄격한 스키마 검증을 강조합니다.
핵심 포인트
- 워크플로우 중심의 도구 통합으로 모델의 혼란 방지 및 토큰 절약
- 입출력 및 값에 대한 일관된 용어 사용으로 모델의 이해도 향상
- 단순 테스트를 넘어 프로토콜 스키마 기반의 엄격한 검증 수행
MCP를 통해 에이전트에게 도구(tool)를 노출하는 데는 10분이면 충분합니다. 하지만 우리가 제어할 수 없는 모델을 사용하고, 제한된 토큰 예산과 한정된 사고 시간(thinking time) 내에서 작동하며, 모델의 변화에도 견딜 수 있는 MCP 서버를 구축하는 것은 아무도 경고해주지 않는 영역입니다.
우리는 우리가 직접 만든 모델이 아닌, 우리가 선택할 수 없는 모델을 사용하는 제3자 에이전트들에 의해 소비되는 환경을 배포하며 그 차이를 배웠습니다. 그 과정에서 세 가지 원칙이 도출되었으며, 각 원칙은 프로덕션(production) 환경에서 문제가 발생한 후에야 비로소 완전히 믿게 되었습니다.
요약(TL;DR) — 실전 경험에서 얻은 세 가지 MCP 서버 베스트 프랙티스:
- 더 적은 도구, 더 좁은 접점(surface). 기반이 되는 API가 아니라 워크플로우(workflow)를 중심으로 통합하세요.
- 모든 곳에서 일관된 용어 사용. 서버의 모든 입력, 출력, 값에 대해 동일한 개념에는 동일한 이름을 사용하세요.
- 단순히 테스트뿐만 아니라 프로토콜(protocol)에 따라 검증하세요. 스키마(schema)가 계약(contract)이며, 그 외의 모든 것은 힌트일 뿐입니다.
배경
우리는 Trent의 MCP 서버를 반복적으로 개선해 왔습니다. 이는 제품을 위한 하나의 공개적인 접점(surface)이며, 우리가 제어할 수 없는 모델을 사용하는 제3자 에이전트들에 의해 소비됩니다. 각 반복 과정은 우리가 시작할 때 어느 정도 믿고 있었지만, 문제가 발생한 후에야 완전히 내재화하게 된 교훈을 주었습니다. 이 세 가지 원칙은 그 작업 과정에서 결정화되었으며, 빠르게 개발할 때 느껴지는 직관과는 상충합니다. 지나고 보니 이 중 어느 것도 사소한 것이 없었습니다.
1. 더 적은 도구, 더 좁은 접점
작은 구성 단위, 단일 책임 원칙(single responsibility)과 같은 일반적인 소프트웨어 설계의 본능은 MCP에 깔끔하게 적용되지 않습니다. 접점의 소비자는 다른 소프트웨어 조각이 아니라, 유한한 주의력 예산(attention budget)을 가진 LLM(대규모 언어 모델)이기 때문입니다. 적절한 크기의 도구란 기반 API의 가장 작은 원자적 연산(atomic operation)이 아니라, *에이전트가 실제로 수행하고 있는 워크플로우(workflow)*여야 합니다.
우리가 통합(consolidation)을 공격적으로 추진해 온 두 가지 이유는 다음과 같습니다:
- 중복(Overlap)은 도구 선택을 혼란스럽게 합니다. 함정은 보통 완전히 동일해 보이는 도구가 아니라, 이름과 프레이밍(framing)이 달라 겉보기에는 별개처럼 보이지만 실제로는 미세한 차이만 있을 뿐 거의 동일한 데이터를 노출하는 도구들입니다. 모델은 워크플로우(workflow)에 어떤 것이 "올바른" 호출인지 결정해야 하며, 이 결정은 종종 임의적입니다. 더 어려운 작업에서는 디버깅하기 어려운 방식으로 잘못된 선택을 하기도 합니다. 이러한 도구들을 관련 데이터 슬라이스(slice)를 파라미터(parameter)로 노출하는 단일 도구로 통합하면, 모델에게 불필요한 자유도를 제거할 수 있습니다.
- 모든 도구는 컨텍스트(context)를 소비합니다. 약 20개의 도구를 노출하고 있다면, 각 도구의 스키마(schema), 이름, 설명이 (한 번 호출된 이후) 매 턴마다 프롬프트(prompt)에 포함되어 전달됩니다. 이는 에이전트(agent)가 아무것도 수행하기 전에 상당한 양의 컨텍스트를 소모하게 만듭니다. 이러한 토큰(token)들은 긴 루프(loop)를 거치며 누적되어, 에이전트가 실제로 수행하려는 작업과 직접적으로 경쟁하게 됩니다.
통합(consolidation)은 엔지니어인 우리에게도 루프를 더 긴밀하게 만들어 줍니다. 도구의 수가 적다는 것은 테스트해야 할 표면(surface)이 작아지고, 관찰해야 할 실패 모드(failure modes)가 줄어들며, 고객의 문제로부터 원인이 된 도구로 가는 경로가 더 직접적임을 의미합니다. 제품은 사용자에게 더 단순해지고, 워크플로우는 모델에게 더 단순해지며, 코드베이스는 우리에게 더 단순해집니다. 이러한 정렬(alignment)은 드물게 나타나며, 이를 발견할 수 있다면 반드시 잡아야 합니다.
구체적으로 말씀드리면, 우리는 자체 MCP 서버의 도구 수를 17개에서 11개로 줄였고, 그 결과 그동안 문제를 일으켰던 워크플로우 전반에서 도구 사용 능력이 눈에 띄게 향상되었습니다. 모델은 도구 선택에 쓰는 사이클(cycle)을 줄였고, 엄격한 제약 조건 하에서 발생하던 실패 모드들도 대부분 해결되었습니다. 현재 공개된 버전은 PyPI의 trentai-mcp입니다.
이러한 축소를 추진하게 된 계기는 Trent가 제3자의 채팅 인터페이스를 통해 최종 사용자에게 노출되었던 출시 전 통합(integration) 단계에서 비롯되었습니다. 테스트 중에 채팅이 우리의 지침을 안정적으로 따르지 못하는 사례가 계속 발생했는데, 조사 결과 도구의 중복(overlap)이 주요 원인 중 하나로 밝혀졌습니다.
2. 인터페이스 전반의 일관성은 정확성(Correctness)의 속성입니다
서버에 있는 모든 도구(tool)의 입력 스키마(input schema), 출력 스키마(output schema), 그리고 출력 값(output values) 전반에 걸쳐 MCP 도구의 명칭(wording)은 일관되어야 합니다. 만약 어떤 도구는 user_id라는 필드를 호출하고, 다른 도구는 동일한 것을 customer_id라고 부르며, 세 번째 도구는 accountId를 반환한다면, 모델은 매 호출마다 이를 조정(reconcile)해야 합니다. 모델은 대부분 이를 수행해내지만, 이러한 조정 과정은 토큰(token)을 소모하고, 모호함을 유발하며, 예측 불가능한 조건에서 불안정한 도구 호출(flaky tool calls)로 나타납니다.
이 문제는 생각보다 더 중요합니다. 왜냐하면 통신망 너머에 있는 모델을 항상 제어할 수 있는 것은 아니기 때문입니다. MCP 서버가 제3자에 의해 사용될 때, 에이전트(agent)는 토큰 예산이 빠듯하고 사고 시간(thinking time)이 제한된 소형 모델(small model)에서 실행될 수도 있습니다. 최첨단 모델(frontier model)이라면 추론을 통해 극복할 수 있는 일관되지 않은 명칭이라도, 더 작은 모델은 단순히 실패하게 됩니다. 개발 단계에서는 괜찮아 보였던 인터페이스가, 당신이 볼 수 없는 배포 환경에서는 무너져 내릴 수 있습니다.
우리는 앞서 언급한 제3자 출시 전 통합 과정에서 이 문제를 겪었습니다. 우리는 채팅이 Trent 보안 평가에 진행 상황을 기록할 수 있도록 하는 update_tasks 도구를 노출했습니다. 하지만 내부 API는 응답 필드 이름으로 control_id를 사용하고, 입력 필드 이름으로 task_id를 사용하고 있었습니다. 채팅은 이 둘 사이에서 혼란을 겪었고, 도구 호출은 반복적으로 실패했으며, 스스로 디버깅하여 문제를 해결할 수도 없었습니다. 우리 역시 이를 즉시 알아차리지 못했습니다. 계속해서 발생하는 422 에러는 서비스 측의 버그처럼 보였고, 우리는 실패 원인이 API 상류(upstream)인 채팅의 도구 호출에 있다는 것을 깨닫기 전까지 한동안 서비스 측에서 디버깅을 수행했습니다. 입력, 출력, 그리고 값 전반에 걸쳐 명칭을 일관되게 맞춤으로써 이 문제는 해결되었습니다.
제가 정립하기 시작한 프레임워크는 간단합니다. 통신망 반대편에 있는 모델은 당신이 선택할 수 없는 변수라는 점입니다. 따라서 가장 낮은 공통 분모(consumer)를 기준으로 인터페이스를 설계하십시오. 유능한 모델은 일관성 없는 명칭을 넘어서 추론할 수 있지만, 작은 모델들은 거기서 실패합니다. 일관성을 유지하는 데는 배포 전 한 번의 정리 작업 비용이 들지만, 불일치는 모든 소비자(consumer)와 모든 호출마다 영원히 대가를 치르게 됩니다.
3. 작동한다고 해서 구현을 신뢰하지 마세요
이것은 제가 더 빨리 배웠으면 좋았을 원칙입니다.
우리는 에이전트(agent)를 사용하여 MCP 서버를 구축했습니다. 잘 작동했습니다. 에이전트가 구현과 함께 작성한 테스트는 통과했고, 엔지니어 주도의 도그푸딩(dogfooding)도 깔끔하게 실행되었으며, 우리가 중요하게 생각하는 워크플로우에서의 수동 테스트 결과도 모두 녹색(pass)이었습니다. 앞서 다룬 도구 선택 및 명칭 문제 외에도, 우리는 로컬에서는 재현할 수 없는 다른 종류의 실패를 계속해서 마주했습니다. 바로 에이전트가 입력 형태(input shape)를 잘못 파악하거나, 우리가 문서화한 내용과 전혀 일치하지 않는 방식으로 도구를 호출하는 문제였습니다.
내부를 들여다보니, 구현 단계에서 MCP 프로토콜이 명시하는 JSON 속성(properties)에 입력 및 출력 스키마(schema)를 실제로 정의하지 않았음을 발견했습니다. 서버를 작성한 에이전트는 대신 전체 계약(contract), 입력 형태, 출력 형태, 예시 등을 도구의 설명(description) 문자열 안에 긴 주석 같은 덩어리(blob)로 밀어 넣어 두었습니다. 최첨단 모델(Frontier models)은 그것을 읽고 올바른 구조를 추론해냈습니다. 하지만 추론 예산(inference budget)이 적은 작은 모델들은 그러지 못했습니다. 해결책은 구조적인 것입니다. MCP의 inputSchema와 outputSchema는 힌트가 아니라 계약(contract)입니다. 이를 설명(description) 문자열에 밀어 넣는 것은 프로토콜이 제공하는 모든 보장을 포기하는 것과 같습니다.
여기서 얻은 두 가지 교훈은 모두 입 밖으로 내어 말할 가치가 있습니다:
- 프로토콜이 제공하는 구조를 활용하세요. MCP가
inputSchema와outputSchema를 별도의 구조화된 필드로 정의한 데에는 이유가 있습니다. 잘 만들어진 클라이언트(Client)는 이를 사용하여 입력을 검증하고, 에이전트(Agent)의 행동을 제한하며, 오류를 조기에 드러냅니다. 설명(Description)은 힌트일 뿐이지만, 스키마(Schema)는 계약(Contract)입니다. - **에이전트는
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기