에러 처리 (Handling errors)
에러 처리 (Handling errors)
도구는 세 가지로 실패할 수 있고, SDK는 각각을 다르게 처리해요.
ToolError를 raise 하면 모델이 여러분의 메시지를 봐요. MCPError를 raise 하면 프로토콜이 봐요. 다른 무엇이든 raise 하면 크래시예요: 모델은 호출이 실패했다는 것만 알고, 여러분의 로그는 트레이스백을 받아요.
이 페이지는 선택에 관한 것이에요.
모델이 고칠 수 있는 에러
뭔가를 조회하는 도구가 조회를 놓치게 해봐요:
# docs_src/handling_errors/tutorial001.py
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
mcp = MCPServer("Bookshop")
CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}
@mcp.tool()
def get_author(title: str) -> str:
"""Look up the author of a book in the catalog."""
if title not in CATALOG:
raise ToolError(f"No book titled {title!r} in the catalog.")
return CATALOG[title]
mcp.server.mcpserver.exceptions의 ToolError는 도구가 모델에게 뭔가 잘못됐다고 알리는 방법이에요.
카탈로그에 없는 제목으로 호출하고 결과를 보세요:
result.is_error # True
result.content # [TextContent(text="Error executing tool get_author: No book titled 'Nothing' in the catalog.")]
result.structured_content # None
- 요청은 성공했어요. 결과가 있고, 호출자에서 raise 된 건 없어요.
is_error는True이고, 여러분의 메시지(도구 이름이 접두됨)가 모델이 읽는 바로 그 위치content에 있어요.structured_content는None이에요. 실패한 호출은 구조화할 반환값이 없어요.
이건 도구 에러고, 거의 항상 여러분이 원하는 것이에요.
도구를 호출하는 건 모델이에요. 모델이 인자를 골랐죠. 그래서 도구 에러는 대화의 한 턴이에요: 모델이 *"No book titled 'Nothing' in the catalog."*를 읽고, 제목을 잘못 추측했음을 깨닫고, 더 나은 걸로 다시 호출해요. raise 한 줄로 자가-교정 에이전트를 얻은 거예요.
서버에서 ToolError는 로그의 INFO 한 줄이고 트레이스백이 없어요. 예상했던 일이니 조사할 게 없어요.
!!! tip
도구에서 에러 메시지를 절대 return하지 마세요. 반환된 문자열은 is_error=False라서 모델에게(그리고 모든 클라이언트 UI에게) 도구가 성공했고 그 문자열이 답인 것처럼 보여요. raise 하세요. 그 플래그가 신호예요.
모델이 고칠 수 없는 에러
이제 ToolError를 MCPError로 바꿔요.
# docs_src/handling_errors/tutorial002.py
from mcp import MCPError
from mcp.server import MCPServer
from mcp.types import INVALID_PARAMS
mcp = MCPServer("Bookshop")
CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}
@mcp.tool()
def get_author(title: str) -> str:
"""Look up the author of a book in the catalog."""
if title not in CATALOG:
raise MCPError(code=INVALID_PARAMS, message=f"No book titled {title!r} in the catalog.")
return CATALOG[title]
MCPError는 SDK의 프로토콜 에러예요. 도구 래퍼가 잡지 않는 유일한 예외예요: 전파되고, 전체 tools/call 요청이 결과 대신 JSON-RPC 에러로 실패해요.
{
"code": -32602,
"message": "No book titled 'Nothing' in the catalog."
}
- 결과가 없어요.
content도is_error도 없어요: 모델이 읽을 게 없어요. - 대신 호스트 애플리케이션이 에러를 받아요. 도구가 아예 존재하지 않을 때 받을 것과 같은 방식이에요.
code,message,data가 그대로 도착해요.INVALID_PARAMS는-32602이고,mcp.types가 그것과 다른 JSON-RPC 에러 코드(INVALID_REQUEST,INTERNAL_ERROR, ...)를 상수로 export 해서 매직 넘버를 타이핑하지 않게 해줘요.
!!! check 같은 조회, 같은 실패, 하지만 이제 호출은 반환 대신 클라이언트 쪽에서 raise 해요:
```text
mcp.shared.exceptions.MCPError: No book titled 'Nothing' in the catalog.
```
첫 버전은 모델에게 반응할 문장을 건넸어요. 이건 아무것도 안 건네요. `get_author`에선 엄격히 나빠요. 그게 다음 섹션의 요점이에요.
어느 것을 raise 할까
두 경로는 서로 다른 두 질문에 답해요.
ToolError를 raise — 실행 실패에 대해: 도구가 하려던 일이 안 됐어요. 호출을 고른 건 모델이니 모델이 결과를 보고 복구 기회를 얻어야 해요. 철자 오류 제목, 타임아웃된 업스트림 API, 없는 행: 모두 도구 에러예요.MCPError를 raise — 요청 자체가 거부돼야 할 때: 클라이언트에 도구가 의존하는 기능이 없거나, 서버가 누구에게도 서빙할 상태가 아니거나, 호출자가 필수 단계를 건너뛰었거나. 모델의 재시도는 그 어느 것도 고치지 못하니 메시지를 건넬 이득이 없어요.
하나의 질문이 결정해요: 더 똑똑한 모델이 이것을 피할 수 있었나? 예 → ToolError. 아니오 → MCPError.
그 테스트로 보면 get_author의 두 번째 버전은 잘못된 선택을 했어요: 더 나은 제목이 고치니까 모델이 메시지를 볼 자격이 있었어요. 그건 메커니즘을 보여주는 게지, 추천이 아니에요.
!!! info
MCPError는 from mcp import MCPError에 있고 code, message, 선택적 data 페이로드를 받아요. 거기 넣는 것이 클라이언트가 받는 것이에요: SDK는 raise 된 MCPError를 정제하지 않고 그대로 전달해요.
그 밖의 예외
이제 검사를 빼고 딕셔너리 조회가 스스로 실패하게 해요:
# docs_src/handling_errors/tutorial004.py
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}
@mcp.tool()
def get_author(title: str) -> str:
"""Look up the author of a book in the catalog."""
return CATALOG[title]
CATALOG[title]이 KeyError를 raise 해요. 계획하지 않았으니 SDK는 크래시로 취급해요:
result.is_error # True
result.content # [TextContent(text="Error executing tool get_author")]
호출은 여전히 is_error=True를 반환하니 모델은 실패했음을 알고 넘어갈 수 있어요. 모델이 받지 못하는 것은 예외 텍스트예요: 여러분 코드의 KeyError, 세 라이브러리 아래 드라이버의 SQL 스택은 서버 내부를 설명할 수 있으니 절대 서버를 떠나지 않아요.
대신 여러분이 받아요. 서버는 완전한 트레이스백을 ERROR로 로그하고, Tool 'get_author' raised an unexpected exception로 기록해요. 그래서 프로덕션 WARNING 로그는 모든 ToolError 동안 조용하고, 실제로 뭔가 고장난 순간에만 말을 해요.
존재하지 않는 리소스
리소스도 같은 선을 긋고, 흔한 경우에 하나의 이름 붙은 예외를 제공해요.
# docs_src/handling_errors/tutorial003.py
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ResourceNotFoundError
mcp = MCPServer("Bookshop")
CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}
@mcp.resource("books://{title}")
def book(title: str) -> str:
"""The catalog entry for one book."""
if title not in CATALOG:
raise ResourceNotFoundError(f"No book titled {title!r} in the catalog.")
return f"{title} by {CATALOG[title]}"
books://{title}은 템플릿이에요. 어떤 제목이든 매칭하니, "URI가 잘 만들어졌다"와 "책이 존재한다"는 서로 다른 두 질문이고, 둘째는 함수만 답할 수 있어요.
답할 수 없을 때 ResourceNotFoundError를 raise 해요. SDK는 그것을 스펙이 없는 리소스에 부여한 프로토콜 에러로 바꿔요: data에 요청된 URI가 있는 -32602. 클라이언트는 어느 읽기가 실패했는지 알아요.
{
"code": -32602,
"message": "No book titled 'Nothing' in the catalog.",
"data": {"uri": "books://Nothing"}
}
여기 is_error=True 반쪽 결과가 없다는 걸 주목하세요. 리소스 읽기는 콘텐츠를 반환하거나 실패하거나 둘 중 하나예요: 리소스는 프로토콜 경로만 있어요. ResourceError는 "not found"가 아닌 실패(-32603, 여러분의 메시지)에 대한 같은 것이고, 둘 다 로그의 INFO 한 줄이에요. MCPError를 제외한 다른 예외는 크래시예요: 클라이언트는 URI만 이름 짓는 -32603을 받고 트레이스백은 ERROR로 로그에 가요. 템플릿과 리소스의 나머지 전부는 **리소스(Resources)**에 있어요.
raise 하지 않는 에러
잘못된 인자는 함수에 도달하지 않아요.
get_author에 문자열이 아닌 title을 보내면 SDK가 여러분을 호출하기 전에 입력 스키마에 대해 거부하고, 모델이 읽고 고칠 수 있는 것과 같은 종류의 is_error=True 도구 에러로 처리해요. **Tools**가 Field(le=50) 제약으로 같은 거부를 보여줘요.
그건 여러분이 쓰지 않아도 되는 raise 문의 한 부류를 의미해요: 자신의 타입 힌트를 재검증하지 마세요.
!!! info
이 페이지에서 클라이언트가 보는 모든 것을, 테스트에 쓸 인메모리 Client도 봐요. 심지어 raise_exceptions=True도 실패한 도구의 예외를 호출자에게 돌려주지 않아요: 그 플래그가 작동할 수 있는 시점이 되면, 여러분의 예외는 이미 is_error=True 결과예요. 결과에 대해 단언하세요. 크래시의 트레이스백이 필요하면 서버 로그에 있고, pytest의 caplog가 잡아요. **Testing**이 그 패턴을 다뤄요.
요약
- 도구에서 **
ToolError**를 raise → 호출이content에 여러분의 메시지를 담은is_error=True로 반환돼요. 모델이 읽고 재시도할 수 있어요. - **
MCPError**를 raise → 호출 자체가 JSON-RPC 에러로 실패해요. 모델은 아무것도 못 보고 호스트가 처리해요.code,message,data가 온전히 살아남아요. - 결정 질문: 더 똑똑한 모델이 이것을 피할 수 있었나? 예 →
ToolError. 아니오 →MCPError. - 그 밖의 예외는 크래시 → 모델에게는
Error executing tool <name>만 있는is_error=True, 여러분에게는 트레이스백이 있는ERROR레코드. - 리소스 핸들러에서
ResourceNotFoundError→ 프로토콜의-32602, URI는data에. - 잘못된 인자는 함수가 실행되기 전에 스키마에 대해 거부돼요. 그런 건
raise하지 않아요. - import:
from mcp import MCPError,from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError, 에러 코드 상수는mcp.types에서.
에러 처리 끝. 그건 서버가 노출하는 모든 것이에요. 모든 핸들러가 읽을 수 있고 실행 중에 클라이언트에게 돌려줄 수 있는 것은 다음 섹션 **핸들러 내부(Inside your handler)**예요.
가장 자주 만날 SDK 에러의 정확한 텍스트, 각각의 의미, 각각에 대한 한-동작 수정은 **Troubleshooting**에 있어요.