docs: refresh translations for recent English changes (#3379)
This commit is contained in:
parent
d8b638304e
commit
c5d7d0b6d0
6 changed files with 3149 additions and 1047 deletions
|
|
@ -1,25 +1,25 @@
|
|||
---
|
||||
translation:
|
||||
sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601]
|
||||
sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5]
|
||||
tool: 1
|
||||
---
|
||||
# 오류 처리 {#handling-errors}
|
||||
|
||||
도구가 실패하는 방식은 두 가지이며, SDK는 이 둘을 매우 다르게 다룹니다.
|
||||
도구가 실패하는 방식은 세 가지이며, SDK는 각각을 다르게 다룹니다.
|
||||
|
||||
일반적인 예외를 발생시키면 **모델**이 보게 됩니다. `MCPError`를 발생시키면 **프로토콜**이 보게 됩니다.
|
||||
`ToolError`를 발생시키면 **모델**이 메시지를 보게 됩니다. `MCPError`를 발생시키면 **프로토콜**이 보게 됩니다. 그 밖의 것을 발생시키면 크래시로 취급됩니다. 모델은 호출이 실패했다는 사실만 알게 되고, 트레이스백은 로그에 남습니다.
|
||||
|
||||
이 페이지는 둘 중 무엇을 선택할지에 관한 내용입니다.
|
||||
이 페이지는 무엇을 선택할지에 관한 내용입니다.
|
||||
|
||||
## 모델이 고칠 수 있는 오류 {#an-error-the-model-can-fix}
|
||||
|
||||
무언가를 조회하는 도구를 하나 두고, 조회가 실패하게 해 봅시다.
|
||||
|
||||
```python title="server.py" hl_lines="11-12"
|
||||
```python title="server.py" hl_lines="2 12-13"
|
||||
--8<-- "docs_src/handling_errors/tutorial001.py"
|
||||
```
|
||||
|
||||
이 두 줄에는 MCP와 관련된 것이 전혀 없습니다. `get_author`는 여느 Python 함수가 그러듯 평범한 `ValueError`를 발생시킵니다.
|
||||
`mcp.server.mcpserver.exceptions`에 있는 `ToolError`는 도구가 무언가 잘못되었다고 모델에게 알리는 수단입니다.
|
||||
|
||||
카탈로그에 없는 제목으로 호출하고 결과를 살펴보세요.
|
||||
|
||||
|
|
@ -30,13 +30,15 @@ result.structured_content # None
|
|||
```
|
||||
|
||||
* 요청은 **성공했습니다**. 결과가 있고, 호출한 쪽에서는 아무 예외도 발생하지 않았습니다.
|
||||
* `is_error`가 `True`이고, 예외 메시지(앞에 도구 이름이 붙음)가 `content`에, 즉 모델이 읽는 바로 그 자리에 들어 있습니다.
|
||||
* `is_error`가 `True`이고, 메시지(앞에 도구 이름이 붙음)가 `content`에, 즉 모델이 읽는 바로 그 자리에 들어 있습니다.
|
||||
* `structured_content`는 `None`입니다. 실패한 호출에는 구조화할 반환 값이 없습니다.
|
||||
|
||||
이것이 **도구 오류**이며, 도구가 발생시키는 **모든** 예외의 기본 동작입니다. 그리고 거의 언제나 원하는 동작이기도 합니다.
|
||||
이것이 **도구 오류**이며, 거의 언제나 원하는 동작입니다.
|
||||
|
||||
도구를 호출하는 쪽은 모델입니다. 인자를 고른 것도 모델입니다. 그래서 도구 오류는 대화의 한 차례가 됩니다. 모델은 *"No book titled 'Nothing' in the catalog."*를 읽고, 제목을 잘못 추측했다는 것을 깨닫고, 더 나은 제목으로 다시 호출합니다. `raise` 한 줄을 썼을 뿐인데 스스로 교정하는 에이전트를 얻은 셈입니다.
|
||||
|
||||
서버에서 `ToolError`는 로그의 `INFO` 한 줄이며, 트레이스백은 없습니다. 예상한 실패이므로 조사할 것이 없습니다.
|
||||
|
||||
!!! tip
|
||||
도구에서 오류 메시지를 `return`으로 돌려주지 마세요. 반환된 문자열은 `is_error=False`이므로,
|
||||
모델에게(그리고 모든 클라이언트 UI에게) 도구가 제대로 작동했고 그 문자열이 답인 것처럼 보입니다.
|
||||
|
|
@ -44,7 +46,7 @@ result.structured_content # None
|
|||
|
||||
## 모델이 고칠 수 없는 오류 {#an-error-the-model-cannot-fix}
|
||||
|
||||
이제 `ValueError`를 `MCPError`로 바꿔 봅시다.
|
||||
이제 `ToolError`를 `MCPError`로 바꿔 봅시다.
|
||||
|
||||
```python title="server.py" hl_lines="1 3 14"
|
||||
--8<-- "docs_src/handling_errors/tutorial002.py"
|
||||
|
|
@ -77,10 +79,10 @@ result.structured_content # None
|
|||
|
||||
두 경로는 서로 다른 두 질문에 답합니다.
|
||||
|
||||
* **실행**이 실패했을 때는 **아무 예외나 발생시키세요**. 도구가 하려던 일이 되지 않은 경우입니다. 호출을 선택한 것은 모델이므로, 모델이 그 결과를 보고 회복할 기회를 얻어야 합니다. 철자가 틀린 제목, 시간 초과된 상위 API, 존재하지 않는 행은 모두 도구 오류입니다.
|
||||
* **실행**이 실패했을 때는 **`ToolError`를 발생시키세요**. 도구가 하려던 일이 되지 않은 경우입니다. 호출을 선택한 것은 모델이므로, 모델이 그 결과를 보고 회복할 기회를 얻어야 합니다. 철자가 틀린 제목, 시간 초과된 상위 API, 존재하지 않는 행은 모두 도구 오류입니다.
|
||||
* **요청 자체**를 거부해야 할 때는 **`MCPError`를 발생시키세요**. 도구가 의존하는 기능이 클라이언트에 없거나, 서버가 누구에게도 응답할 수 있는 상태가 아니거나, 호출한 쪽이 필수 단계를 건너뛴 경우입니다. 모델이 재시도해도 이 중 어느 것도 해결되지 않으므로, 메시지를 모델에게 건네서 얻을 것이 없습니다.
|
||||
|
||||
판단 기준은 질문 하나입니다. **더 똑똑한 모델이었다면 이 상황을 피할 수 있었을까?** 예 -> 일반 예외. 아니요 -> `MCPError`.
|
||||
판단 기준은 질문 하나입니다. **더 똑똑한 모델이었다면 이 상황을 피할 수 있었을까?** 예 -> `ToolError`. 아니요 -> `MCPError`.
|
||||
|
||||
이 기준으로 보면 `get_author`의 두 번째 버전은 잘못된 선택을 했습니다. 더 나은 제목이면 해결되므로, 모델이 메시지를 볼 자격이 있었습니다. 그 버전은 메커니즘을 보여 주기 위한 것이지, 권장하기 위한 것이 아닙니다.
|
||||
|
||||
|
|
@ -89,6 +91,25 @@ result.structured_content # None
|
|||
`data` 페이로드를 받습니다. 여기에 넣은 내용이 그대로 클라이언트가 받는 내용입니다. SDK는 발생한
|
||||
`MCPError`를 정제하지 않고 그대로 전달합니다.
|
||||
|
||||
## 그 밖의 모든 예외 {#any-other-exception}
|
||||
|
||||
이제 검사를 빼고 딕셔너리 조회가 스스로 실패하게 둬 봅시다.
|
||||
|
||||
```python title="server.py" hl_lines="11"
|
||||
--8<-- "docs_src/handling_errors/tutorial004.py"
|
||||
```
|
||||
|
||||
`CATALOG[title]` 조회는 `KeyError`를 발생시킵니다. 대비하지 않은 예외이므로 SDK는 이를 크래시로 취급합니다.
|
||||
|
||||
```python
|
||||
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`에는 조용하다가, 실제로 무언가 고장 난 순간에 알려 줍니다.
|
||||
|
||||
## 존재하지 않는 리소스 {#a-resource-that-doesnt-exist}
|
||||
|
||||
리소스도 같은 선을 긋고, 흔한 경우를 위해 이름 붙은 예외를 하나 제공합니다.
|
||||
|
|
@ -109,7 +130,7 @@ result.structured_content # None
|
|||
}
|
||||
```
|
||||
|
||||
여기에는 `is_error=True`인 절반짜리 결과가 없다는 점에 주목하세요. 리소스 읽기는 내용을 반환하거나 실패하거나 둘 중 하나입니다. 리소스에는 프로토콜 경로만 있습니다. 템플릿을 비롯해 리소스에 관한 나머지 모든 내용은 **[리소스](resources.md)**에서 다룹니다.
|
||||
여기에는 `is_error=True`인 절반짜리 결과가 없다는 점에 주목하세요. 리소스 읽기는 내용을 반환하거나 실패하거나 둘 중 하나입니다. 리소스에는 프로토콜 경로만 있습니다. `ResourceError`는 "찾을 수 없음"이 아닌 실패를 위한 같은 종류의 예외이며(`-32603`, 작성한 메시지), 둘 다 로그에 `INFO` 한 줄로 남습니다. `MCPError`를 제외한 그 밖의 모든 예외는 크래시입니다. 클라이언트는 URI만 밝히는 `-32603`을 받고, 트레이스백은 `ERROR` 수준으로 로그에 남습니다. 템플릿을 비롯해 리소스에 관한 나머지 모든 내용은 **[리소스](resources.md)**에서 다룹니다.
|
||||
|
||||
## 직접 발생시킬 일이 없는 오류 {#errors-you-never-raise}
|
||||
|
||||
|
|
@ -120,19 +141,21 @@ result.structured_content # None
|
|||
이는 작성하지 않아도 되는 `raise` 문이 한 부류 통째로 있다는 뜻입니다. 타입 힌트를 직접 다시 검증하지 마세요.
|
||||
|
||||
!!! info
|
||||
이 페이지의 모든 내용은 **클라이언트**가 보는 것이며, 테스트를 작성할 때 쓸 인메모리 `Client`도
|
||||
정확히 같은 것을 봅니다. `raise_exceptions=True`조차 도구 오류를 트레이스백으로 되돌리지 않습니다.
|
||||
이 페이지에서 **클라이언트**가 보는 모든 것은, 테스트를 작성할 때 쓸 인메모리 `Client`도
|
||||
똑같이 봅니다. `raise_exceptions=True`조차 실패한 도구의 예외를 호출한 쪽으로 되돌려 주지 않습니다.
|
||||
그 플래그가 동작할 수 있는 시점에는 예외가 이미 `is_error=True` 결과가 되어 있기 때문입니다.
|
||||
결과에 대해 단언하세요. 이 패턴은 **[테스트](../get-started/testing.md)**에서 다룹니다.
|
||||
검증은 결과를 대상으로 하세요. 크래시의 트레이스백이 필요하다면 서버 로그에 있으며, pytest의
|
||||
`caplog`로 캡처할 수 있습니다. 이 패턴은 **[테스트](../get-started/testing.md)**에서 다룹니다.
|
||||
|
||||
## 요약 {#recap}
|
||||
|
||||
* 도구에서 **아무 예외**나 발생시키면 -> 호출은 `is_error=True`와 함께 메시지를 `content`에 담아 반환합니다. 모델이 읽고 재시도할 수 있습니다. 이것이 기본 동작입니다.
|
||||
* 도구에서 **`ToolError`**를 발생시키면 -> 호출은 `is_error=True`와 함께 메시지를 `content`에 담아 반환합니다. 모델이 읽고 재시도할 수 있습니다.
|
||||
* **`MCPError`**를 발생시키면 -> 호출 자체가 JSON-RPC 오류로 실패합니다. 모델은 아무것도 보지 못하고, 호스트가 처리합니다. `code`, `message`, `data`는 그대로 유지됩니다.
|
||||
* 판단 기준이 되는 질문은 **더 똑똑한 모델이었다면 이 상황을 피할 수 있었을까?**입니다. 예 -> 예외. 아니요 -> `MCPError`.
|
||||
* 판단 기준이 되는 질문은 **더 똑똑한 모델이었다면 이 상황을 피할 수 있었을까?**입니다. 예 -> `ToolError`. 아니요 -> `MCPError`.
|
||||
* 그 밖의 **모든 예외**는 크래시입니다 -> 모델에게는 `Error executing tool <name>`만 담긴 `is_error=True`가 가고, 로그에는 트레이스백이 담긴 `ERROR` 레코드가 남습니다.
|
||||
* 리소스 핸들러에서 `ResourceNotFoundError`를 발생시키면 -> 프로토콜의 `-32602`가 되며, URI가 `data`에 담깁니다.
|
||||
* 잘못된 인자는 함수가 실행되기 전에 스키마와 대조해 거부되므로, 이를 위해 `raise`를 쓰지 않습니다.
|
||||
* `from mcp import MCPError`를 쓰고, 오류 코드 상수는 `mcp.types`에서 가져옵니다.
|
||||
* 임포트: `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`, 그리고 오류 코드 상수는 `mcp.types`에서 가져옵니다.
|
||||
|
||||
오류 처리까지 마쳤습니다. 이것으로 서버가 **노출하는** 모든 것을 다뤘습니다. 모든 핸들러가 실행 중에 무엇을 읽을 수 있고 클라이언트에게 무엇을 되돌려 할 수 있는지는 다음 절인 **[핸들러 내부](../handlers/index.md)**에서 다룹니다.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a]
|
||||
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3]
|
||||
tool: 1
|
||||
---
|
||||
# 미디어 {#media}
|
||||
|
|
@ -86,6 +86,24 @@ result.structured_content # None
|
|||
`Audio`를 그렇게 만들면 클라이언트는 `mime_type="audio/wav"`라고 전달받고, 그대로 믿고
|
||||
디코딩에 실패합니다. `data=`를 전달할 때는 `format=`도 전달하세요.
|
||||
|
||||
## 리소스 임베드하기 {#embedding-a-resource}
|
||||
|
||||
도구는 문서도 반환할 수 있습니다. 텍스트나 바이트를 그 문서가 위치한 URI, MIME 타입과 함께 묶은 것입니다. 이것이 또 다른 종류의 콘텐츠 블록인 **`EmbeddedResource`**입니다. 평범한 `str`과 달리 콘텐츠가 무엇인지 클라이언트에게 알려 주므로, 클라이언트는 이를 첨부 파일로 보여 주거나 이미 알고 있는 리소스임을 알아볼 수 있습니다.
|
||||
|
||||
```python title="server.py" hl_lines="7 14 16-18"
|
||||
--8<-- "docs_src/media/tutorial005.py"
|
||||
```
|
||||
|
||||
* `brand://guidelines`는 평범한 리소스입니다(**[리소스](resources.md)**에서 다룹니다). 도구는 요청이 있을 때 같은 문서를 모델에게 건네며, `guidelines()`를 직접 호출하므로 단일 정보 출처가 유지됩니다.
|
||||
* `EmbeddedResource`와 `TextResourceContents`는 `mcp.types`에서 가져옵니다. 이미지처럼 헬퍼가 있는 것은 아닙니다. 만든 블록은 그대로 결과에 들어가고, `structured_content`는 없습니다.
|
||||
* 리소스가 등록된 URI를 쓰세요. 그래야 클라이언트가 첨부 파일과 `brand://guidelines`가 같은 문서임을 알 수 있습니다. 등록 여부와 상관없이 어떤 URI든 허용됩니다.
|
||||
|
||||
```python
|
||||
result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))]
|
||||
```
|
||||
|
||||
바이너리 콘텐츠에는 `TextResourceContents` 대신 `BlobResourceContents(uri=..., mime_type=..., blob=...)`를 쓰고, 바이트를 base64로 인코딩해 `blob`에 넣으세요. 클라이언트가 나중에 `resources/read`로 읽을 수 있는 포인터만 보내려면 대신 `ResourceLink(name=..., uri=...)`를 반환하세요. 이것도 콘텐츠 블록입니다.
|
||||
|
||||
## 아이콘 {#icons}
|
||||
|
||||
`Icon`은 콘텐츠가 아니라 메타데이터입니다. 이미지를 담지 않고 URI로 이미지를 가리키며, 클라이언트는 이를 가져와 서버 이름, 도구, 리소스, 프롬프트 옆에 표시할 수 있습니다.
|
||||
|
|
@ -115,6 +133,7 @@ client.server_info.icons # [Icon(src="https://example.com/brand-kit.png", mime_
|
|||
|
||||
* 도구에서 `Image`나 `Audio`를 반환하면 클라이언트는 `ImageContent` / `AudioContent` 블록을 받습니다. 바이트는 base64로 인코딩되고 MIME 타입이 함께 갑니다.
|
||||
* `path=`로 만들어 확장자가 MIME 타입을 정하게 하거나, 메모리의 `data=`와 명시적인 `format=`으로 만드세요.
|
||||
* `EmbeddedResource`를 반환하면 문서(텍스트 또는 base64 blob, URI와 MIME 타입 포함)를 결과에 넣을 수 있고, `ResourceLink`를 반환하면 포인터만 보냅니다.
|
||||
* 미디어 결과에는 `structured_content`도 출력 스키마도 없습니다.
|
||||
* `Icon`은 포인터입니다. `src` URI에 선택적인 `mime_type`, `sizes`, `theme`이 더해집니다.
|
||||
* `icons=[...]`는 서버, 도구, 리소스, 프롬프트에서 동작하며, 클라이언트는 대응하는 객체에서 아이콘을 찾습니다.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094]
|
||||
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd]
|
||||
tool: 1
|
||||
---
|
||||
# 프롬프트 {#prompts}
|
||||
|
|
@ -139,10 +139,55 @@ uv run mcp dev server.py
|
|||
```
|
||||
|
||||
!!! info
|
||||
**[도구](tools.md)**를 읽었다면 이 페이지의 내용은 이미 모두 알고 있는 셈입니다. 같은 데코레이터,
|
||||
**[도구](tools.md)**를 읽었다면 여기까지의 내용은 이미 모두 알고 있는 셈입니다. 같은 데코레이터,
|
||||
설명이 되는 같은 docstring, 같은 `Annotated`/`Field`입니다. 달라지는 것은 누가 실행하는지(사용자)와
|
||||
결과가 어디로 가는지(대화 속으로)뿐입니다.
|
||||
|
||||
## 텍스트 그 이상 {#more-than-text}
|
||||
|
||||
`UserMessage`와 `AssistantMessage`는 `str`을 받는 자리라면 어디든 콘텐츠 블록이나 `Image` / `Audio` 헬퍼도 받습니다. 프롬프트에서 자주 나오는 경우는 두 가지입니다. 문서를 첨부하는 경우와 그림을 첨부하는 경우입니다.
|
||||
|
||||
### 파일 임베딩 {#embedding-a-file}
|
||||
|
||||
```python title="server.py" hl_lines="5 12 21 23"
|
||||
--8<-- "docs_src/prompts/tutorial004.py"
|
||||
```
|
||||
|
||||
* 스타일 가이드는 `style://python`에 있는 리소스이며(**[리소스](resources.md)**에서 다룹니다), `server.py` 옆의 `style-guide.md`에서 읽어 옵니다. 아무 Markdown 파일이나 그 자리에 두세요.
|
||||
* `EmbeddedResource(resource=TextResourceContents(...))`(둘 다 `mcp.types`에 있습니다)는 URI와 MIME 타입과 함께 파일을 첫 번째 메시지로 담고, 이 파일을 참조하는 요청이 일반 텍스트로 뒤따릅니다.
|
||||
* 가이드를 f-string에 붙여 넣는 대신 임베딩하면 클라이언트가 첨부 파일로 보여 주고 나중에 `style://python`을 다시 열 수 있으며, 모델은 파일을 원문 그대로 받습니다. 바이너리 파일에는 base64 `blob`을 담은 `BlobResourceContents`를 사용하세요.
|
||||
|
||||
렌더링하면 첫 번째 메시지의 `content`는 `resource` 블록입니다.
|
||||
|
||||
```json
|
||||
{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}}
|
||||
```
|
||||
|
||||
### 이미지 첨부 {#attaching-an-image}
|
||||
|
||||
```python title="server.py" hl_lines="4 15"
|
||||
--8<-- "docs_src/prompts/tutorial005.py"
|
||||
```
|
||||
|
||||
* `Image`는 **[이미지, 오디오, 아이콘](media.md)**의 헬퍼입니다. 프롬프트가 렌더링될 때 `UserMessage`가 이를 `ImageContent` 블록(파일은 base64로 인코딩되고, MIME 타입은 `.png`에서 추측)으로 변환합니다. `Audio`도 같은 방식으로 `AudioContent`가 됩니다.
|
||||
* `architecture.png`라는 이름의 PNG를 아무거나 `server.py` 옆에 두세요. 프롬프트 인수는 문자열이므로 그림은 항상 서버에서 나옵니다. `component`는 문구만 제공합니다.
|
||||
|
||||
```json
|
||||
{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"}
|
||||
```
|
||||
|
||||
## 런타임에 목록 바꾸기 {#changing-the-list-at-runtime}
|
||||
|
||||
클라이언트가 연결된 상태에서도 프롬프트를 추가할 수 있습니다. 예를 들어 사용자가 지시 사항을 자신만의 메뉴 항목으로 저장하게 할 수 있습니다. 프롬프트를 등록한 다음 알림을 보내세요.
|
||||
|
||||
```python title="server.py" hl_lines="5 23-27"
|
||||
--8<-- "docs_src/prompts/tutorial006.py"
|
||||
```
|
||||
|
||||
* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))`는 `@mcp.prompt()`와 똑같이 함수를 등록하고, `mcp.remove_prompt(name)`은 그 반대입니다. `add_prompt`는 같은 이름의 기존 항목을 덮어쓰지 않고 유지하므로, 저장이 교체가 되도록 이 도구는 먼저 이전 항목을 제거합니다. `prompts/list`에는 변경 사항이 즉시 반영됩니다.
|
||||
* `await ctx.notify_prompts_changed()`는 `subscriptions/listen` 스트림을 듣고 있는 모든 `2026-07-28` 클라이언트에게 `notifications/prompts/list_changed`를 보냅니다(**[구독](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()`는 호출한 클라이언트가 2026 이전 버전일 때 그 클라이언트에게 보냅니다(**[레거시 클라이언트 지원](../run/legacy-clients.md)**). 둘 다 호출하세요. 알릴 대상이 없으면 각각 아무 일도 하지 않습니다.
|
||||
* 알림을 받은 클라이언트는 `prompts/list`를 다시 호출합니다. Python `Client`에서는 `async with client.listen(prompts_list_changed=True) as sub:`이며, `PromptsListChanged` 이벤트를 내놓습니다.
|
||||
|
||||
## 요약 {#recap}
|
||||
|
||||
* 함수에 `@mcp.prompt()`를 붙이면 프롬프트가 됩니다. 이름은 함수에서, 설명은 docstring에서 옵니다.
|
||||
|
|
@ -151,5 +196,7 @@ uv run mcp dev server.py
|
|||
* `str`을 반환하면 사용자 메시지 하나가 됩니다. `UserMessage` / `AssistantMessage`의 목록을 반환하면 여러 턴의 대화 시작점을 마련할 수 있습니다.
|
||||
* `title=`과 `Field(description=...)`은 클라이언트가 UI에 표시하는 내용입니다.
|
||||
* 필수 인수가 빠지면 요청 전체가 실패합니다. 프롬프트별 오류 결과는 없습니다.
|
||||
* `EmbeddedResource`나 `Image`를 `UserMessage`로 감싸면 문서나 그림을 첨부할 수 있습니다.
|
||||
* 런타임에 프롬프트를 추가하거나 제거하려면 `mcp.add_prompt(...)` / `mcp.remove_prompt(...)`를 쓰고, 이어서 `await ctx.notify_prompts_changed()`와 `await ctx.session.send_prompt_list_changed()`를 호출하세요.
|
||||
|
||||
프롬프트(또는 리소스 템플릿) 인수의 서버 측 자동 완성은 **[자동 완성](completions.md)**에서 다룹니다.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, e866c192e11d1c14, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c6f7d2a148aa49f4, c851964bb3301907, d715db6f8dccc9cc, ef86634aa70498a7]
|
||||
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, 91be9b73602abcf1, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c7eff2a5698225fa, c851964bb3301907, 8f296f1f09e4c400, d715db6f8dccc9cc, a0c344a48450dbe4]
|
||||
tool: 1
|
||||
---
|
||||
# 구조화된 출력 {#structured-output}
|
||||
|
|
@ -103,7 +103,7 @@ result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions
|
|||
--8<-- "docs_src/structured_output/tutorial003.py"
|
||||
```
|
||||
|
||||
`TypedDict`는 런타임에 평범한 `dict`이므로, 바로 그 형태로 만들어서 반환하면 됩니다. 스키마와 검증, `structured_content`는 `BaseModel` 버전과 똑같습니다(설명은 빠지는데, `TypedDict`에는 설명을 둘 자리가 없기 때문입니다).
|
||||
`TypedDict`는 런타임에 평범한 `dict`이므로, 바로 그 형태로 만들어서 반환하면 됩니다. 스키마와 검증, `structured_content`는 `BaseModel` 버전과 같은 규칙을 따릅니다. 클래스 독스트링이나 `Annotated[..., Field(description=...)]` 표기를 추가하면 그 내용이 설명이 되고, 딕셔너리에서 빼 둔 `NotRequired` 키는 `structured_content`에서도 빠집니다.
|
||||
|
||||
## 데이터클래스 {#a-dataclass}
|
||||
|
||||
|
|
@ -185,16 +185,16 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
|
|||
어노테이션은 `WeatherData`를 약속합니다. 그런데 업스트림 응답이 더 이상 `humidity`를 보내지 않습니다.
|
||||
|
||||
!!! check
|
||||
`get_weather`를 호출해도 반쯤 빈 객체를 클라이언트에 슬그머니 넘기지 않습니다. 호출은 실패하고, 오류의 첫 몇 줄이 문제의 필드를 지목합니다.
|
||||
`get_weather`를 호출해도 반쯤 빈 객체를 클라이언트에 슬그머니 넘기지 않습니다. 호출은 실패합니다. 클라이언트는 `is_error=True`와 함께 `Error executing tool get_weather`를 받으므로, 모델은 있지도 않은 날씨를 자신 있게 읽어 내는 대신 호출이 실패했다는 사실을 알게 됩니다. 필드 이름은 개발자를 위한 것으로, 서버 로그에 `ERROR` 수준으로 남습니다.
|
||||
|
||||
```text
|
||||
Error executing tool get_weather: 1 validation error for WeatherData
|
||||
Tool 'get_weather' raised an unexpected exception
|
||||
...
|
||||
pydantic_core._pydantic_core.ValidationError: 1 validation error for WeatherData
|
||||
humidity
|
||||
Field required [type=missing, input_value={'temperature': 16.2, 'conditions': 'Overcast'}, input_type=dict]
|
||||
```
|
||||
|
||||
이 텍스트는 `is_error=True` 상태의 도구 결과로 돌아오므로, 모델은 있지도 않은 날씨를 자신 있게 읽어 내는 대신 호출이 실패했다는 사실을 알게 됩니다.
|
||||
|
||||
참고로 `-> WeatherData` 도구에서 평범한 `dict`를 반환해도 괜찮습니다. 위 예제에서 `json.loads`가 만들어 낸 결과가 바로 평범한 딕셔너리였습니다. 검증 대상은 Python 타입이 아니라 값입니다.
|
||||
|
||||
## 구조화된 출력 끄기 {#opting-out}
|
||||
|
|
@ -209,6 +209,10 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
|
|||
|
||||
반대로 `structured_output=True` 옵션은 자동 감지를 필수 요건으로 바꿉니다. 반환 타입으로 스키마를 만들 수 없는 도구는 텍스트로 물러나는 대신 임포트 시점에 예외를 일으킵니다.
|
||||
|
||||
## 콘텐츠 블록과 미디어 {#content-blocks-and-media}
|
||||
|
||||
콘텐츠 블록과 미디어(`TextContent`, `EmbeddedResource`, `Image`, `Audio` 등이 단독으로 쓰이거나, `list`, `tuple`, `Sequence`의 항목으로 쓰이거나, 유니언의 갈래로 쓰이는 경우)는 구조화된 출력이 자동으로 꺼집니다. 모델이 읽기 위한 것이므로 자동 감지가 여기서는 스키마를 만들지 않습니다(`Image`와 `Audio`는 **[이미지, 오디오, 아이콘](media.md)**에서 다룹니다). 다만 `structured_output=True` 옵션을 주면 콘텐츠 블록 클래스에 대해서는 여전히 스키마를 강제로 만듭니다.
|
||||
|
||||
## 타입 힌트가 없는 클래스 {#a-class-without-type-hints}
|
||||
|
||||
요청하지 않았는데도 구조화되지 않은 결과로 끝나는 길이 하나 있습니다. **본문에 어노테이션이 전혀 없는** 클래스를 반환하는 경우입니다.
|
||||
|
|
@ -237,6 +241,6 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
|
|||
* 스칼라, 리스트, 튜플, 유니언은 `{"result": ...}` 형태로 감싸집니다. 모델, `TypedDict`, 데이터클래스, 어노테이션이 달린 클래스, 그리고 `dict[str, ...]` 타입은 이미 객체이므로 그대로 유지됩니다.
|
||||
* 모든 결과에는 `content`(모델을 위한 텍스트)와 `structured_content`(애플리케이션을 위한 데이터)가 **함께** 담깁니다.
|
||||
* 반환한 값은 스키마에 맞춰 검증됩니다. 어긋나면 손상된 결과가 아니라 도구 오류가 됩니다.
|
||||
* `structured_output=False` 옵션으로 도구의 구조화된 출력을 끌 수 있습니다. 타입 힌트가 없는 클래스는 아무 경고 없이 꺼지므로 주의하세요.
|
||||
* `structured_output=False` 옵션으로 도구의 구조화된 출력을 끌 수 있습니다. 콘텐츠 블록, `Image`, `Audio`는 기본적으로 꺼집니다. 타입 힌트가 없는 클래스는 아무 경고 없이 꺼지므로 주의하세요.
|
||||
|
||||
이제 도구가 돌려줄 수 있는 모든 것을 손에 넣었습니다. 다음은 두 번째 프리미티브인 **[리소스](resources.md)**입니다.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [e4cc390d56573409, 8566e2b68594e9ad, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
|
||||
sections: [e4cc390d56573409, f30cf8103a6e918c, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
|
||||
tool: 1
|
||||
---
|
||||
# 도구 {#tools}
|
||||
|
|
@ -39,6 +39,8 @@ SDK는 이 타입 힌트로부터 JSON Schema를 생성해 `tools/list` 과정
|
|||
|
||||
두 인자 모두 기본값이 없으므로 `required`에 들어 있습니다. 이 부분은 곧 고칩니다. (`title` 키는 Pydantic이 만들어 내는 부산물입니다. 계약에 해당하는 것은 속성과 그 타입, 그리고 `required`입니다.)
|
||||
|
||||
`$schema` 키도 없습니다. MCP는 이 키가 없는 스키마를 **JSON Schema 2020-12**로 취급하는데, Pydantic이 생성하는 것이 바로 이 형식이므로 **[저수준 Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)**에서 스키마를 직접 손으로 작성하기 전까지는 따로 고를 것이 없습니다.
|
||||
|
||||
!!! tip
|
||||
여기서 타입 힌트는 문서가 아닙니다. 타입 힌트가 바로 **계약**입니다. 클라이언트가 `"limit": "ten"`을 보내면
|
||||
함수가 실행되기도 전에 SDK가 거부합니다.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd]
|
||||
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd]
|
||||
tool: 1
|
||||
---
|
||||
# URI 템플릿과 경로 안전성 {#uri-templates-and-path-safety}
|
||||
|
|
@ -98,7 +98,7 @@ translation:
|
|||
|
||||
내장 검사는 흔한 경우를 막아 주지만 샌드박스 경계까지는 알 수 없습니다. 파일시스템에 접근할 때는 `safe_join`으로 경로를 해석하고 기준 디렉터리 안에 머무르는지 확인하세요.
|
||||
|
||||
```python title="server.py" hl_lines="4 14"
|
||||
```python title="server.py" hl_lines="5 15"
|
||||
--8<-- "docs_src/uri_templates/tutorial002.py"
|
||||
```
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ translation:
|
|||
이 검사는 휴리스틱 사전 필터입니다. 파일시스템 접근에서는 `safe_join`이 여전히 격리 경계입니다.
|
||||
|
||||
!!! tip
|
||||
핸들러가 요청을 처리할 수 없다면(파일이 없거나, id를 알 수 없는 경우) 예외를 발생시키세요. SDK가 이를 오류 응답으로 바꿉니다. 프로토콜 오류와 도구 오류의 차이는 **[오류 처리](handling-errors.md)**에서 확인하세요.
|
||||
핸들러가 요청을 처리할 수 없다면(파일이 없거나, id를 알 수 없는 경우) 위의 `read_manual`처럼 `ResourceNotFoundError`를 발생시키세요. 클라이언트는 작성한 메시지와 URI가 담긴 `-32602` 오류를 받습니다. 예상치 못한 예외는 대신 일반적인 `-32603` 오류가 됩니다. **[오류 처리](handling-errors.md#a-resource-that-doesnt-exist)**를 참고하세요.
|
||||
|
||||
## 저수준 Server의 리소스 {#resources-on-the-low-level-server}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue