
MCP Python SDK 2.0에서 직접 만든 서버가 망가진 2가지 원인 (타입 힌트가 *args로 뭉개지는 문제)
요약
MCP Python SDK 2.0을 사용하여 서버를 구축할 때 발생하는 두 가지 주요 오류와 해결 방법을 다룹니다. SDK 2.0의 변경된 API 사용법과 데코레이터 사용 시 타입 힌트가 유실되어 스키마가 망가지는 문제를 설명합니다.
핵심 포인트
- SDK 2.0에서는 Server 대신 MCPServer와 @server.tool()을 사용해야 함
- SDK 2.0은 함수의 타입 힌트를 기반으로 입력 스키마를 자동 생성함
- 데코레이터 사용 시 functools.wraps를 반드시 사용하여 시그니처를 보존해야 함
- wraps를 누락하면 도구의 인수가 *args, **kwargs로 인식되어 스키마 오류 발생
서론
MCP (Model Context Protocol) 서버를 Python으로 직접 만들었을 때, 에러 메시지만으로는 원인을 파악할 수 없는 문제 2가지를 겪었습니다. 두 가지 모두 직접 구현하고 검증까지 마친 후 작성합니다.
- SDK 2.0에서 API가 변경되어, 자주 보이는 작성 방식이 작동하지 않음
- 도구(Tool) 함수를 데코레이터(Decorator)로 감싸면, 모든 도구의 인수가 통째로 망가짐
2번이 특히 까다로웠습니다. 서버는 정상적으로 기동되고, 도구 목록도 반환하며, 연결도 가능합니다. 망가진 것은 인수의 스키마(Schema)뿐이라서, 실제로 호출되기 전까지는 알아차릴 수 없습니다.
Server + @server.list_tools()가 작동하지 않음
1. 증상
인터넷상의 샘플이나 조금 전의 기사에 나와 있는 방식으로 서버를 구성하면 작동하지 않습니다.
# 작동하지 않음
from mcp.server import Server
server = Server("my-server")
...
올바른 작성법
SDK 2.0에서는 MCPServer와 @server.tool()을 사용합니다. 도구를 하나씩 선언하는 형태로, list_tools / call_tool을 직접 구현할 필요가 없어졌습니다.
from mcp.server import MCPServer
server = MCPServer("my-server")
@server.tool()
...
입력 스키마(Input Schema)는 타입 힌트(Type Hint)로부터 자동 생성됩니다. 이 부분이 다음 이야기로 이어집니다.
functools.wraps를 붙이지 않으면 모든 도구의 인수가 망가진다
2. 데코레이터 (이것이 본론입니다)
무슨 일이 일어났는가
예외 처리(Exception Handling)를 공통화하고 싶어서 다음과 같은 데코레이터를 작성했습니다. 흔히 볼 수 있는 형태라고 생각합니다.
def handle_errors(func):
async def wrapper(*args, **kwargs):
try:
...
서버는 기동됩니다. 도구 목록에도 get_sales가 나타납니다. 연결도 가능합니다.
하지만, 모든 도구의 인수가 *args와 **kwargs가 됩니다.
{
"name": "get_sales",
"inputSchema": {
...
}
}
호출하는 쪽에서는 start_date도 end_date도 존재하지 않는 도구로 보이게 됩니다.
원인
앞서 언급했듯이, SDK 2.0은 입력 스키마를 함수의 타입 힌트로부터 생성합니다.
데코레이터로 감싸면, @server.tool()이 받는 것은 원래 함수가 아니라 wrapper입니다. wrapper의 시그니처(Signature)는 (*args, **kwargs)이며, 타입 힌트는 비어 있습니다. SDK는 이를 충실하게 스키마화합니다.
즉, SDK는 올바르게 작동하고 있으며, 잘못된 정보를 전달하고 있는 것은 이쪽(사용자)입니다. 그래서 에러도 경고도 발생하지 않습니다.
대처
functools.wraps를 붙입니다. 이렇게 하면 __name__, __doc__, __wrapped__ 등이 계승되어, inspect.signature()가 원래 함수의 시그니처를 반환하게 됩니다.
import functools
def handle_errors(func):
@functools.wraps(func) # ← 이것
...
functools.wraps는 "디버깅 시 함수명이 바뀌지 않도록 하는, 있으면 친절한 관례" 정도로 취급되기 쉽지만, 타입 힌트로부터 스키마를 생성하는 메커니즘 위에서는 필수적입니다. 붙이지 않으면 기능 자체가 망가집니다.
알아차리기 위한 체크
자기 진단으로 스키마를 직접 확인하는 것이 확실합니다. 도구를 늘릴 때마다 유용합니다.
import inspect
for name, fn in tools.items():
sig = inspect.signature(fn)
...
자작 서버의 검증 항목에 이것을 넣은 이후로는 동일한 종류의 사고는 일어나지 않고 있습니다.
덤: 금액 집계는 LLM에게 시키지 말 것
MCP 서버를 작성하는 목적이 집계나 회계 관련인 경우, 수치 계산은 서버 측(Python)에서 확정 짓고 결과만 반환하는 설계로 해두는 것이 안전합니다.
Raw Data(생데이터)를 반환하고 "합계해 줘"라고 요청하는 방식을 취하면, 검산할 방법이 없는 숫자가 나오게 됩니다. 도구의 반환값(return value)을 {"transactions": [...]}가 아니라 {"total": 123456, "count": 42, "transactions": [...]}로 설정해 두면, 호출 측(caller)은 집계된 값을 그대로 사용할 수 있습니다.
이는 MCP에 국한된 문제가 아니라, LLM과 인접한 코드를 작성할 때 지켜야 할 일반적인 경계라고 생각합니다.
요약
| 증상 | 원인 | 대처 |
|---|---|---|
| 샘플대로 작성했는데 작동하지 않음 | SDK 2.0에서 Server → MCPServer, @server.list_tools() → @server.tool()로 변경됨 | 새로운 API로 다시 작성 |
도구는 보이지만 인자(argument)가 args / kwargs가 됨 | 데코레이터(decorator)로 감싸는 과정에서 타입 힌트(type hint)가 유실되었고, 스키마(schema)가 거기서부터 생성됨 | 데코레이터에 functools.wraps를 추가 |
두 번째 문제는 에러가 발생하지 않고, 기동과 접속도 성공하지만, 스키마만 망가진 상태로 표면화됩니다. MCP 서버를 직접 만들다가 "도구는 인식되는데 인자가 전달되지 않는" 증상을 겪는다면, 먼저 데코레이터를 의심해 보시기 바랍니다.
Discussion

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