Python으로 MCP 서버 구축하기: 도구 설계(Tool Design)를 통해 배운 점
요약
MCP(Model Context Protocol) 서버를 구축할 때 모델의 추론 성능을 높이기 위한 도구 설계(Tool Design) 전략을 다룹니다. 모델은 구현 코드가 아닌 도구 이름, 설명, 인자 문서 등을 바탕으로 행동하므로, 명확한 산문 형태의 설명과 제약 조건 설정이 중요함을 강조합니다.
핵심 포인트
- 모델은 구현 코드가 아닌 도구의 메타데이터(이름, 설명, 인자)를 보고 판단함
- 모델이 데이터 구조를 추측하지 않도록 명확한 스키마와 설명을 제공해야 함
- 타입 힌트와 Literal을 사용하여 모델의 행동 범위를 제약하고 유효성 검사를 강화함
- 에러 메시지는 모델이 오류를 수정하고 재시도할 수 있게 돕는 프롬프트 역할을 함
제 MCP 서버의 첫 번째 버전은 작동은 했지만 여전히 쓸모가 없었습니다. 인스펙터(Inspector)에서 수동으로 호출했을 때는 모든 도구가 정확한 데이터를 반환했지만, Claude를 연결하자 모델이 약 3분의 1의 확률로 잘못된 행동을 하는 것을 목격했습니다. 모델은 이미 정확한 컬럼 이름을 알고 있는 값에 대해 검색을 시도하거나, 행을 한 번에 하나씩 페이지를 넘기며 탐색하거나, 컬럼 이름이 Student Name인 시트에 Name이라는 컬럼을 임의로 만들어내곤 했습니다.
이 중 그 어느 것도 제 코드의 버그가 아니었습니다. 핸들러(Handlers)는 괜찮았습니다. 잘못된 것은 핸들러에 대해 제가 작성한 모든 것들, 즉 도구 이름(tool names), 설명(descriptions), 인자 문서(argument docs), 그리고 에러 문자열(error strings)이었습니다. 모델은 오직 그것들만을 봅니다. 모델은 여러분의 구현(implementation)을 결코 볼 수 없으므로, 모델이 추론하는 대상은 여러분의 구현이 아닙니다.
MCP, 즉 모델 컨텍스트 프로토콜(Model Context Protocol)은 Claude나 Cursor와 같은 AI 클라이언트가 외부 데이터와 통신하는 표준 방식입니다. MCP 서버는 고정된 도구 목록을 광고하며, 클라이언트는 그 목록에 포함된 것만 호출할 수 있습니다. 저는 Google Sheet를 읽기 전용 MCP 서버로 게시하는 PasteSheet를 만들었는데, 제 전체 인터페이스는 스프레드시트 위의 세 가지 도구로 구성되어 있습니다. 이것이 많은 교훈을 주는 방식으로 틀어지기에 충분하다는 것이 밝혀졌습니다.
코드를 보기 전에 한 가지 밝힐 점이 있습니다. 제 서버는 Python이 아니라 PHP로 작성되었습니다. 아래의 교훈들은 언어 수준이라기보다는 프로토콜 수준의 것이며, Python으로 보여드리는 이유는 대부분의 사람들이 이를 구축할 때 공식 SDK를 사용하기 때문입니다. 여기에 있는 모든 내용은 제가 실제로 적용하여 배포한 결정들을 번역한 것입니다.
설정 (Setup)
SDK 설치는 한 번이면 충분하며, 도구 인터페이스는 단순한 타입 힌트(type-hinted) 함수들로 구성됩니다.
uv add "mcp[cli]" # 또는: pip install "mcp[cli]"
from typing import Annotated, Literal
from mcp.server import MCPServer
...
여러분은 JSON Schema를 작성할 필요가 없습니다. 타입 힌트(type hints)가 곧 스키마(schema)가 되는데, 이는 모델이 여러분의 타입이 아니라 여러분이 쓴 산문(prose)을 읽고 있다는 사실을 깨닫기 전까지는 매우 즐거운 경험입니다.
모델에게 데이터를 학습할 수 있는 저렴한 방법을 제공하세요
저의 첫 번째 실수는 모델이 시트의 형태(shape)를 알고 있다고 가정했던 것이었습니다. 모델은 이를 알지 못하므로 추측을 하게 되며, 추측한 컬럼 이름은 빈 결과를 초래하고, 모델은 이를
그 안에서는 두 가지 작은 작업이 수행되고 있습니다. Literal["asc", "desc"]는 스키마(schema) 내에서 정렬 방향을 산문(prose)으로 설명하는 대신 제약(constrain)을 걸어주며, SDK는 함수가 실행되기 전에 그 외의 모든 것을 거부하여 모델에게 스스로 수정할 수 있는 유효성 검사 오류(validation error)를 전달합니다. 그리고 독스트링(docstring)은 반환 형태인 {data, total, limit, offset}을 명시하므로, 모델은 total이 존재한다는 것을 알고 우연히 페이지네이션(pagination)을 발견하는 대신 두 번째 페이지 호출을 계획할 수 있습니다.
에러 메시지는 프롬프트입니다
에이전트(agent)는 당신의 에러를 읽고 즉시 다시 시도합니다. 이는 모든 에러 문자열을 다음 턴에 정답을 얻을 수 있는 기회로 만들기도 하고, 로그를 보고 있는 사람을 위해 작성한다면 막다른 길(dead end)로 만들기도 합니다.
제 서버에서 발생한 실제 에러를 거의 그대로 가져오면 다음과 같습니다:
if search and not plan_allows_search():
raise ValueError(
"Partial-match and full-text search require a Pro plan. To look up rows "
...
"Pro 플랜이 필요합니다"라는 문구 자체는 대화를 종료시켜 버립니다. 여전히 작동하는 인자(argument)의 이름을 명시하고, 이 시트에 실제로 존재하는 컬럼으로 채워진 예시를 보여주면 거절이 재지시(redirect)로 바뀝니다. 모델은 filters를 사용하여 재시도하고 사용자는 원하는 답을 얻게 됩니다. 이 한 가지 변화가 제가 겪었던 나머지 대부분의 막다른 길을 제거했습니다.
동일한 논리가 당신이 제공하지 않는 도구(tool)에도 적용됩니다. 제 도구들은 의도적으로 읽기 전용(read-only)이며, 왜 읽기 전용이 더 안전한 기본값인지에 대해서는 별도로 작성했지만, 여기서의 설계 핵심은 명확합니다. 존재하지 않는 도구는 호출될 수 없으며, 경계(boundary)를 설명하는 에러는 그 어떤 설명보다 모델에게 당신의 서버 형태를 더 빠르게 가르쳐줍니다.
호출자가 사용할 수 없는 인자를 광고하지 마세요
위 규칙의 가장 깔끔한 버전은 인자를 아예 보여주지 않는 것입니다. 제 서버의 경우, 부분 일치(partial matching)와 집계(aggregation)는 유료 기능이므로, 스키마(schema)는 요청마다 생성되며 권한이 없는 호출자에게는 해당 인자들이 단순히 누락됩니다. 모델은 본 적 없는 인자를 오용할 수 없으며, 누락된 인자가 많을수록 결정 공간(decision space)이 줄어듭니다.
Python은 이 과정을 제가 원하는 만큼 편리하게(ergonomic) 만들어주지는 않습니다. 타입 힌트(Type hints)는 정적이기 때문에, 호출자별로 변동을 주려면 서버를 특정 배포 환경에 맞춰 구축할 때 서로 다른 도구 세트를 등록하거나, 더 낮은 수준의 list-tools 핸들러(handler)를 사용해야 합니다. 만약 이 두 가지 방식이 귀하의 사례에 그만한 가치가 없다면, 앞서 언급한 교정 에러(corrective error)가 정직한 대안입니다. 이는 비용이 전혀 들지 않는 대신 한 번의 턴(turn)을 낭비하게 할 뿐입니다.
직접 구현하는 것이 가치가 없어지는 지점
이 부분은 시작하기 전에 꼭 읽어보셨으면 하는 내용입니다. 이 글에서 다루는 MCP 부분은 진정으로 쉬우며, 만약 본인의 데이터를 기반으로 서버를 구축하는 것이라면 그냥 바로 시작하시면 됩니다. SDK는 훌륭하며 몇 개의 도구를 만드는 것은 많은 코드가 필요하지 않습니다.
비용이 많이 드는 부분은 당신이 래핑(wrapping)하려는 대상이 무엇이냐에 달려 있습니다. 특히 Google Sheets의 경우, Google Cloud 프로젝트, Sheets API 활성화, 서비스 계정(service account) 및 해당 JSON 키, 그리고 할당량(quota)이 필요합니다. Google은 429 에러를 반환하기 전까지 프로젝트당 분당 300개의 읽기 요청, 사용자당 분당 60개의 요청을 허용하지만, 에이전트(agent)는 인간의 트래픽과는 다른 방식으로 말을 많이(chatty) 합니다. 사용자의 질문 하나가 스키마 읽기 한 번과 서너 개의 쿼리로 이어집니다. 단순한(naive) 서버는 이 한계치에 빠르게 도달하며, 결국 캐시(cache)를 작성하게 되고, 이제 당신은 원래 만들고자 했던 것이 아니라 인프라를 유지 관리하게 됩니다. 저는 Google Sheets API rate limits에 관한 글에서 이 내용을 더 자세히 다룬 적이 있습니다.
그것은 단순한 홍보가 아니라 계산된 결과입니다. 커스텀 도구(custom tools)나 쓰기 권한(write access)이 필요할 때는 직접 구축하십시오. 그렇게 하면 그 작업이 당신에게 실질적인 가치를 가져다줄 것입니다. 반면, Google Sheets MCP 서버가 제공하는 세 가지 읽기 도구(read tools) 정도만 있으면 되는 상황이라면 호스팅된 서비스를 이용하십시오. 그런 경우에는 오후 시간의 수고를 들여 원치 않는 캐시(cache)까지 관리하게 되는 상황을 피할 수 있기 때문입니다.
저는 PasteSheet를 만들었습니다. Google Sheets URL을 붙여넣기만 하면 캐싱된 JSON API와 AI 에이전트가 쿼리할 수 있는 읽기 전용 MCP 서버를 얻을 수 있습니다. 무료 티어를 제공하며, 신용카드 등록이나 Google Cloud 프로젝트가 필요하지 않습니다. 만약 MCP 서버를 배포해 보셨다면, 모델이 어떤 도구 설명(tool descriptions)을 계속 무시했는지 알려주세요. 모든 사람에게 그런 경험이 있을 것이라고 생각합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기