docs: refresh translations for recent English changes (#3379)

This commit is contained in:
Max 2026-08-24 19:29:54 +01:00 • committed by GitHub
commit c5d7d0b6d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 3149 additions and 1047 deletions

View file

@ -1,6 +1,6 @@
---
translation:
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, b3530fcf4d11fd56, ebc33704fbd74262, cd0e9c933350390e]
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, cd0e9c933350390e]
tool: 1
---
# 저수준 Server {#the-low-level-server}
@ -116,6 +116,17 @@ asyncio.run(main())
서버는 두 필드를 비교하지 않습니다. 이 SDK의 `Client`는 비교합니다. 선언한 `output_schema`를 만족하지 않는 `structured_content`를 반환하면 `call_tool`이 `RuntimeError`를 일으키는데, 메시지는 `Invalid structured content returned by tool search_books`로 시작해 `jsonschema` 실패 내용을 인용합니다. 스키마를 약속하기는 쉽지만, 지키는 것은 작성자의 몫입니다. 반환 타입과 스키마의 전체 단계는 **[구조화된 출력](../servers/structured-output.md)**에서 확인하세요.
## JSON Schema 2020-12 다이얼렉트 {#the-dialect-is-json-schema-2020-12}
`input_schema`와 `output_schema`는 JSON Schema이며, 다이얼렉트는 [MCP 사양](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage)이 정해 둡니다. `$schema` 키가 없는 스키마는 **JSON Schema 2020-12**입니다. `MCPServer`가 생성하는 스키마는 이 기본값에 기대고(Pydantic은 2020-12를 쓰고 키를 생략합니다), 손으로 작성한 dict도 같은 기준을 따르므로 2020-12 어휘 전체를 사용할 수 있습니다.
```python title="server.py" hl_lines="8 14-15"
--8<-- "docs_src/lowlevel/tutorial007.py"
```
* `input_schema`의 루트는 `"type": "object"`여야 합니다. 그 옆의 `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, 로컬 `$ref`를 쓰는 `$defs`, 그리고 나머지 2020-12 키워드는 쓴 그대로 클라이언트에 도달합니다.
* `$schema` 키는 필요 없습니다. 더 오래된 드래프트를 선택할 때만 추가하세요. 도구의 `output_schema`에 `structured_content`를 대조해 검증하는 이 SDK의 `Client`는 `$schema`를 보고 검증기를 고르며, 키가 없으면 2020-12를 사용합니다.
## `_meta`: 모델이 아닌 애플리케이션을 위한 데이터 {#\_meta-for-the-application-not-the-model}
`content`는 답변 중 모델이 읽는 부분입니다. `structured_content`는 같은 답변을 타입이 있는 데이터로 나타낸 것입니다. `_meta`는 세 번째 채널입니다. 답변의 일부가 전혀 아니면서 결과에 함께 실려 **클라이언트 애플리케이션**으로 가는 데이터입니다.
@ -167,7 +178,7 @@ asyncio.run(main())
--8<-- "docs_src/lowlevel/tutorial006.py"
```
* 첫 번째 인수는 메서드 문자열입니다. 알림에는 짝이 되는 `add_notification_handler`가 있습니다.
* 첫 번째 인수는 메서드 문자열입니다. 알림에는 짝이 되는 `add_notification_handler`가 있습니다. 이 핸들러는 stdio와 핸드셰이크 세대의 HTTP 연결에서 실행됩니다. `2026-07-28` Streamable HTTP 경로에서는 클라이언트가 보낸 알림 POST가 `202`로 수신 확인만 되고 디스패치되지 않는데, 해당 리비전이 HTTP를 통한 클라이언트에서 서버로의 알림을 정의하지 않기 때문입니다.
* `params_type`은 핸들러가 실행되기 **전에** 들어오는 `params`를 검증하는 기준 모델입니다. 따라서 커스텀 메서드는 도구가 받지 못하는 검증을 **받습니다**. `_meta` 필드가 다른 모든 메서드처럼 파싱되도록 `RequestParams`를 상속하세요.
* 핸들러는 `BaseModel`, `dict`, `None` 중 하나를 반환합니다. SDK가 이를 JSON-RPC 결과로 직렬화합니다.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43]
tool: 1
---
# 미들웨어 {#middleware}
@ -53,8 +53,11 @@ tools/call took 0.1 ms
* 연결 설정. `server/discover`이거나, 레거시 세션에서는 `initialize`와
`notifications/initialized`입니다.
* 모든 요청과 모든 알림. 알림의 경우 `ctx.request_id is None`이고,
* 서버에 도달하는 모든 요청과 모든 알림. 알림의 경우 `ctx.request_id is None`이고,
`call_next(ctx)`는 `None`을 반환하며, 무엇을 반환하든 버려집니다.
(`2026-07-28` Streamable HTTP 경로에서는 클라이언트의 알림 POST가 트랜스포트에서 `202`로
확인 응답되고 디스패치되지 않으므로 미들웨어에도 도달하지 않습니다. 해당 리비전은 HTTP를
통한 클라이언트에서 서버로의 알림을 정의하지 않습니다.)
* 서버에 핸들러가 없는 메서드까지도 포함됩니다. `call_next`가
`MCPError(-32601, "Method not found")`를 일으키고, 이 예외는 클라이언트로 가는 길에
미들웨어를 **통과합니다**.
@ -113,8 +116,8 @@ OpenTelemetry 스팬을 내보내는 미들웨어입니다. 직접 추가할 필
* 미들웨어는 `async (ctx, call_next) -> result` 형태이며, `MCPServer(middleware=[...])`로
전달하거나(또는 `mcp.middleware`에 추가하거나) 저수준 `Server`에서는 `server.middleware`에
추가합니다.
* 들어오는 **모든** 메시지(`server/discover`, `initialize`, 요청, 알림, 알 수 없는 메서드)를
감싸며 바깥쪽부터 실행됩니다.
* 서버에 도달하는 들어오는 **모든** 메시지(`server/discover`, `initialize`, 요청, 알림,
알 수 없는 메서드)를 감싸며 바깥쪽부터 실행됩니다.
* `ctx.request_id is None`으로 알림과 요청을 구분합니다.
* `call_next`를 호출하는 대신 예외를 일으키면 메시지 하나를 거부합니다. 연결은 유지됩니다.
* SDK 자체의 OpenTelemetry 트레이싱도 미들웨어이며, 이미 목록에 있습니다.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [ebef1e7a0df854f4, a4c687d3d627d516, 8e79141fc2985342, b345dd05b9c3c7ab, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
sections: [ebef1e7a0df854f4, 8355cfaf1f76c9d5, 8e79141fc2985342, 46bdb07c7537e8a5, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
tool: 1
---
# 클라이언트 {#the-client}
@ -27,9 +27,10 @@ translation:
* `MCPServer`(또는 저수준 `Server`) 인스턴스: **프로세스 내부**에서 연결합니다.
* URL 문자열(`Client("http://localhost:8000/mcp")`): 프로덕션 경로인 Streamable HTTP입니다.
* **트랜스포트**: `async with ... as (read, write)`로 사용할 수 있는 모든 것, 예를 들어 서브프로세스를 감싸는 `stdio_client(...)`입니다.
* `StdioServerParameters`: **서브프로세스**로 실행할 명령이며, 그 stdin과 stdout을 통해 통신합니다.
* **트랜스포트**: `async with ... as (read, write)`로 사용할 수 있는 모든 것, 예를 들어 직접 만든 HTTP 클라이언트를 감싸는 `streamable_http_client(url, http_client=...)`입니다.
이 페이지의 나머지 내용은 세 가지 모두에서 동일합니다. 헤더, 서브프로세스, 타임아웃, `Transport` 프로토콜은 별도의 페이지인 **[클라이언트 트랜스포트](transports.md)**에서 다룹니다.
이 페이지의 나머지 내용은 네 가지 모두에서 동일합니다. 헤더, 서브프로세스, 타임아웃, `Transport` 프로토콜은 별도의 페이지인 **[클라이언트 트랜스포트](transports.md)**에서 다룹니다.
### 연결된 클라이언트에 있는 것 {#whats-on-a-connected-client}
@ -85,7 +86,7 @@ tool.description # 'Search the catalog by title or author.'
`call_tool(name, arguments)`는 도구를 실행하고 `CallToolResult`를 돌려줍니다.
```python title="client.py" hl_lines="26-33"
```python title="client.py" hl_lines="27-34"
--8<-- "docs_src/client/tutorial003.py"
```
@ -117,7 +118,7 @@ result.is_error # False
!!! check
`lookup_book`에 `"Solaris"`(카탈로그에 없는 제목)를 요청하면 함수가
`ValueError`를 발생시킵니다. 그래도 호출은 정상적으로 반환됩니다.
`ToolError`를 발생시킵니다. 그래도 호출은 정상적으로 반환됩니다.
```python
result.is_error # True
@ -125,8 +126,9 @@ result.is_error # False
result.structured_content # None
```
예외 메시지는 **모델**이 읽고 다시 시도할 수 있는 `content`에 담겼습니다. 이는
의도된 것입니다. 도구 오류는 충돌이 아니라 대화의 일부입니다. `structured_content`를
`ToolError`의 메시지는 **모델**이 읽고 다시 시도할 수 있는 `content`에 담겼습니다. 이는
의도된 것입니다. 도구 오류는 충돌이 아니라 대화의 일부입니다. (도구가 다른 예외로
충돌했다면 `content`에는 `Error executing tool lookup_book`만 담겼을 것입니다.) `structured_content`를
믿기 전에 항상 `is_error`를 확인하세요.
!!! warning

View file

@ -1,6 +1,6 @@
---
translation:
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7]
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c]
tool: 1
---
# 클라이언트 트랜스포트 {#client-transports}
@ -87,15 +87,15 @@ TLS 관련 참고 사항이 하나 있습니다. `httpx2`는 번들된 CA 목록
**stdio** 서버는 서브프로세스입니다. 클라이언트가 이를 실행하고, stdin에 JSON-RPC를 쓰고, stdout에서 JSON-RPC를 읽습니다. 데스크톱 호스트가 사용자 컴퓨터에서 서버를 실행하는 방식이 바로 이것입니다. 호스트는 **곧** 이 코드에 UI를 더한 것이며, **[실제 호스트에 연결하기](../get-started/real-host.md)**는 같은 관계를 호스트 쪽에서 설정 파일로 바라본 것입니다.
`StdioServerParameters`로 프로세스를 기술하고, `stdio_client`로 트랜스포트로 바꾼 다음, **그것**을 `Client`에 넘기세요.
`StdioServerParameters`로 프로세스를 기술하고 `Client`에 넘기세요.
```python title="client.py" hl_lines="4-8 12"
```python title="client.py" hl_lines="3-7 11"
--8<-- "docs_src/client_transports/tutorial004.py"
```
`Client`는 매개변수 객체를 단독으로 받지 않습니다. `StdioServerParameters`는 설정이고, `stdio_client(server)`는 그 설정으로 프로세스를 띄우는 방법을 아는 트랜스포트입니다. 항상 감싸서 전달하세요.
블록에 진입하면 프로세스가 생성됩니다. 블록을 벗어나면 서브프로세스도 종료됩니다. stdin을 닫고, 기다리고, 남아 있으면 강제 종료합니다. 직접 정리할 일은 없습니다.
`async with` 블록을 벗어나면 서브프로세스도 함께 종료됩니다. stdin을 닫고, 기다리고, 남아 있으면 강제 종료합니다. 직접 정리할 일은 없습니다.
자식 프로세스의 stderr는 부모 프로세스의 stderr로 나갑니다. 다른 곳으로 보내려면 `mcp`의 `stdio_client`로 트랜스포트를 직접 만들어 `Client(stdio_client(server, errlog=log_file))`처럼 대신 전달하세요.
!!! warning
자식 프로세스는 환경 변수를 상속하지 **않습니다**. 직접 작성하지 않았을 수도 있는 프로세스로
@ -113,16 +113,16 @@ TLS 관련 참고 사항이 하나 있습니다. `httpx2`는 번들된 CA 목록
`Client`에게 위의 모든 것은 같은 것입니다.
**트랜스포트**란 `(read, write)` 메시지 스트림 쌍을 내어주는 비동기 컨텍스트 매니저라면 무엇이든 해당합니다. 정식으로는 `mcp.client`의 `Transport` 프로토콜입니다. `Client`는 인자를 타입으로 구분합니다. 서버 객체는 프로세스 내에서 연결하고, `str`은 `streamable_http_client(url)`이 되며, 그 밖의 것은 트랜스포트로 직접 진입합니다. 마지막 규칙 덕분에 `stdio_client(...)`, `streamable_http_client(...)`, `sse_client(...)`가 모두 같은 자리에 들어가고, 직접 만든 트랜스포트도 쓸 수 있습니다.
**트랜스포트**란 `(read, write)` 메시지 스트림 쌍을 내어주는 비동기 컨텍스트 매니저라면 무엇이든 해당합니다. 정식으로는 `mcp.client`의 `Transport` 프로토콜입니다. `Client`는 인자를 타입으로 구분합니다. 서버 객체는 프로세스 내에서 연결하고, `str`은 `streamable_http_client(url)`이 되며, `StdioServerParameters`는 `stdio_client(params)`가 되고, 그 밖의 것은 트랜스포트로 직접 진입합니다. 마지막 규칙 덕분에 `stdio_client(...)`, `streamable_http_client(...)`, `sse_client(...)`가 모두 같은 자리에 들어가고, 직접 만든 트랜스포트도 쓸 수 있습니다.
## 요약 {#recap}
* `Client(mcp)`(서버 객체)는 인메모리로 연결합니다. 테스트와 임베딩에 사용하세요.
* `Client("http://.../mcp")`(URL)는 프로덕션 트랜스포트인 Streamable HTTP로 연결합니다.
* 헤더, 인증, 프록시, 타임아웃은 `streamable_http_client(url, http_client=...)`에 전달하는 `httpx2.AsyncClient`에 설정합니다. `headers=` 키워드는 없습니다.
* stdio는 `Client(stdio_client(StdioServerParameters(...)))`이며, 매개변수 객체만 단독으로 쓰는 일은 절대 없습니다.
* stdio는 `Client(StdioServerParameters(...))`입니다. 자식 프로세스의 stderr를 다른 곳으로 돌릴 때만 직접 `stdio_client(...)`로 감싸세요.
* 서브프로세스는 현재 환경이 아니라 허용 목록에 있는 환경 변수만 받습니다. `env=`로 여기에 추가합니다.
* 트랜스포트는 `async with x as (read, write)`로 쓸 수 있는 것이면 무엇이든 됩니다. `Client`는 서버 객체나 URL이 아닌 것은 모두 그 프로토콜에 그대로 넘깁니다.
* 트랜스포트는 `async with x as (read, write)`로 쓸 수 있는 것이면 무엇이든 됩니다. `Client`는 서버 객체, URL, `StdioServerParameters`가 아닌 것은 모두 그 프로토콜에 그대로 넘깁니다.
* `Client`를 생성하면 트랜스포트가 정해집니다. `async with`가 이를 엽니다.
트랜스포트가 열리면 양쪽은 프로토콜 버전에 합의해야 합니다. 보통은 신경 쓸 일이 없지만, 필요할 때는 **[프로토콜 버전](../protocol-versions.md)** 페이지를 확인하세요.

View file

@ -1,11 +1,11 @@
---
translation:
sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c]
sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931]
tool: 1
---
# 지원 중단 예정 기능 {#deprecated-features}
2026-07-28 사양은 다섯 가지를 퇴역시킵니다. SDK는 여전히 이 다섯 가지를 모두 구현하며, 이제 모두에 **지원 중단 예정(deprecated) 경고**가 붙습니다.
2026-07-28 사양은 다섯 가지를 퇴역시킵니다. SDK는 여전히 이 다섯 가지를 모두 구현하며, 이제 모두에 **지원 중단 예정(deprecated) 경고**가 붙습니다. SDK 헬퍼 하나는 별도의 이유로 지원 중단 예정이며 [페이지 끝](#deprecated-sdk-helpers)에 정리되어 있습니다.
아래 표는 지원 중단 예정인 각 기능의 이름, 사라지는 이유, 그리고 대신 사용할 대체 수단을 정리한 것입니다.
@ -55,6 +55,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE
이 두 기능은 클라이언트가 해당 콜백을 등록한 `mode="legacy"` 연결에서만 처음부터
끝까지 동작합니다.
## 레거시 세션에서의 `ping` {#ping-on-a-legacy-session}
**ping**은 상대가 여전히 응답하는지 확인하려고 어느 쪽이든 보낼 수 있는 빈 요청입니다. 2026-07-28 사양은 이를 제거합니다([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)). 최신 클라이언트가 보내는 모든 요청이 이미 서버가 살아 있음을 증명하고, 최신 서버에는 ping을 보낼 채널이 없기 때문입니다. 두 SDK 메서드 모두 핸드셰이크 시절의 세션에서는 여전히 동작합니다. 클라이언트에서는 다음과 같습니다.
```python
async def main() -> None:
async with Client("http://localhost:8000/mcp", mode="legacy") as client:
await client.send_ping() # warns; returns an EmptyResult
```
서버에서는 어느 핸들러 안에서든 다음과 같습니다.
```python
@mcp.tool()
async def check_client(ctx: Context) -> str:
"""A tool that still pings the client mid-call."""
await ctx.session.send_ping() # no warning; an EmptyResult while the client is connected
return "client answered"
```
* `client.send_ping()`은 호출할 때마다 `MCPDeprecationWarning` 경고를 냅니다. 기본(`2026-07-28`) 연결에서는 서버가 대신 `MCPError: Method not found`로 응답합니다.
* `ctx.session.send_ping()`에는 경고가 없습니다. 최신 연결에서는 다른 모든 서버 주도 요청과 마찬가지로 백채널 없음 오류를 발생시킵니다.
* 양쪽 모두 ping에 응답하려고 따로 등록하는 것은 없습니다.
## 루트 변경 알림 {#roots-change-notifications}
루트 기능을 선언한 2025년 시절 클라이언트는 `notifications/roots/list_changed`를 보내 작업 공간 폴더가 바뀌었음을 서버에 알릴 수 있고, 서버는 `roots/list`를 다시 요청하는 것으로 응답합니다. 2026-07-28 사양은 푸시 방식 루트 흐름의 나머지와 함께 이 알림을 제거합니다. 클라이언트에서는 `list_roots_callback=`을 전달하는 것(**[클라이언트 콜백](client/callbacks.md)**)이 곧 `"roots": {"listChanged": true}`를 선언하는 일이며, 호출 하나가 그 약속을 지킵니다.
```python
async def open_folder(client: Client, uri: str, name: str) -> None:
"""The user opened another folder: expose it through the roots callback, then tell the server."""
workspace.append(Root(uri=FileUrl(uri), name=name))
await client.send_roots_list_changed()
```
서버에서는 저수준 `Server`가 수신 핸들러를 받습니다.
```python
async def roots_changed(ctx: ServerRequestContext, params: NotificationParams | None) -> None:
"""The client's roots changed: ask for the new list."""
roots = (await ctx.session.list_roots()).roots
server = Server("Bookshop", on_roots_list_changed=roots_changed)
```
* `workspace`는 `list_roots_callback`이 반환하는 목록입니다. `client.send_roots_list_changed()`는 경고를 내며, `mode="legacy"` 클라이언트가 필요합니다. 최신 연결에서는 알림이 조용히 버려집니다. 이후에도 세션을 열어 두세요. 서버의 후속 `roots/list` 요청이 그 세션으로 도착하기 때문입니다.
* `MCPServer`에는 이 알림을 위한 훅이 없습니다. 저수준 `Server`에서는 `on_roots_list_changed=`가 핸들러를 등록합니다(이것도 지원 중단 예정이며 생성 시점에 경고를 냅니다). 알림에는 페이로드가 없으므로 핸들러가 `ctx.session.list_roots()`를 호출해 새 목록을 가져옵니다.
## 경고 끄기 {#silencing-the-warning}
새 코드에서는 끄지 마세요.
@ -75,15 +124,24 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning)
필터를 반대 방향으로 적용하면 회귀 테스트를 거저 얻습니다. pytest 설정의
`filterwarnings` 항목에 `"error::mcp.MCPDeprecationWarning"`을 추가하면 지원 중단 예정
호출이 경고 대신 예외를 **발생시킵니다**. 여전히 `ctx.info()`를 호출하는 `old_log`라는
도구는 더 이상 통과하지 못하고 다음과 같이 보고하기 시작합니다.
도구는 더 이상 통과하지 못합니다. 호출은 `Error executing tool old_log`와 함께
`is_error=True`로 돌아오고, 캡처된 서버 로그가 원인을 지목합니다.
```text
Error executing tool old_log: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
mcp.shared.exceptions.MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
```
pytest 설정 한 줄이면, 지원 중단 예정 호출이 테스트를 실패시키지 않고 코드베이스에
몰래 다시 들어오는 일은 결코 없습니다.
## 지원 중단 예정 SDK 헬퍼 {#deprecated-sdk-helpers}
이것은 사양 변경이 아니라 더 나은 대체 수단이 있는 SDK 내부 구현일 뿐입니다. 같은 `MCPDeprecationWarning`으로 경고하며 3.0에서 제거됩니다.
| 지원 중단 예정 | 대신 할 일 |
|---|---|
| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()`를 호출한 뒤 `FuncMetadata.call_fn()`을 호출하세요. `FuncMetadata`를 직접 다루는 코드(예를 들어 사용자 정의 `Tool` 하위 클래스)만 이 메서드를 호출했습니다. |
## 요약 {#recap}
* 2026-07-28 사양은 **루트**, 서버 주도 **샘플링**, 프로토콜 **로깅**을 지원 중단 예정으로 지정하고(모두 [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), **진행 상황**을 서버에서 클라이언트 방향으로 제한하며, **`ping`**을 제거합니다.
@ -91,6 +149,7 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning)
* 지원 중단 예정은 권고 사항입니다. 와이어 변경은 없고, 2026년 이전 세션에서는 모든 것이 계속 동작하며, 눈에 띄는 `MCPDeprecationWarning`이 나옵니다(`UserWarning`이므로 기본적으로 켜져 있습니다).
* 샘플링과 루트는 추가로 2026-07-28 세션에는 없는 백채널이 필요합니다. 최신 연결에서는 경고를 낸 뒤 예외를 발생시킵니다.
* `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)`은 카테고리 전체를 끄고, pytest의 `"error::mcp.MCPDeprecationWarning"`은 이를 테스트 실패로 바꿉니다.
* SDK 헬퍼 하나인 `FuncMetadata.call_fn_with_arg_validation()`은 별도로 지원 중단 예정이며 3.0에서 제거됩니다.
* 새 코드는 이 기능 중 어느 것에도 기반해서는 안 됩니다.
이 문서의 다른 모든 페이지는 현재 API를 설명합니다.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
sections: [3c4f2f06b4e978b6, 51ea5fbcb0e93563, 32d8808606ffdae0, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
tool: 1
---
# 실제 호스트에 연결하기 {#connect-to-a-real-host}
@ -11,7 +11,7 @@ translation:
## 서버 하나, 모든 호스트 {#one-server-every-host}
```python title="server.py" hl_lines="3 33-34"
```python title="server.py" hl_lines="4 34-35"
--8<-- "docs_src/real_host/tutorial001.py"
```
@ -50,8 +50,8 @@ uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
그리고 호스트란 MCP 클라이언트를 품은 애플리케이션에 지나지 않으므로, 직접 작성한 Python
코드도 호스트 역할을 할 수 있습니다. **[클라이언트 트랜스포트](../client/transports.md)**에서는
`stdio_client(...)`로 같은 파일을 서브프로세스로 실행하고, **[테스트](testing.md)**에서는
프로세스 없이 인메모리로 연결합니다.
`Client(StdioServerParameters(...))` 호출로 같은 파일을 서브프로세스로 실행하고,
**[테스트](testing.md)**에서는 프로세스 없이 인메모리로 연결합니다.
## Claude Desktop {#claude-desktop}

View file

@ -1,6 +1,6 @@
---
translation:
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef]
tool: 1
---
# 테스트 {#testing}
@ -84,9 +84,9 @@ async def test_call_add_tool(client: Client):
잘못될 수 있는 일은 서로 다른 두 가지이고, 이 플래그는 그중 하나에만 관여합니다.
**작성한 도구** 안에서 발생한 예외는 프로토콜 실패가 아닙니다. `is_error=True`인 정상적인 결과가
되고, 모델이 그 메시지를 읽습니다. `raise_exceptions`는 이 점을 바꾸지 않습니다. 플래그가 있든
없든 `call_tool`은 똑같은 `is_error=True` 결과를 반환합니다. 이 주제를 통째로 다루는 페이지가
따로 있습니다. **[오류 처리](../servers/handling-errors.md)**를 참고하세요.
됩니다(`ToolError`였다면 모델이 작성한 메시지를 읽습니다). `raise_exceptions`는 이 점을 바꾸지
않습니다. 플래그가 있든 없든 `call_tool`은 똑같은 `is_error=True` 결과를 반환합니다. 이 주제를
통째로 다루는 페이지가 따로 있습니다. **[오류 처리](../servers/handling-errors.md)**를 참고하세요.
도구 본문 **바깥**에서 일어난 실패는 다릅니다. `Client(mcp)`가 제공하는 연결에서는 클라이언트가
보기 전에 서버가 이 실패를 일반적인 `"Internal server error"`로 정제합니다. 예상치 못한 크래시의

View file

@ -1,6 +1,6 @@
---
translation:
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
tool: 1
---
# 엘리시테이션 {#elicitation}
@ -90,6 +90,7 @@ translation:
엘리시테이션 스키마는 도구의 입력 스키마만큼 표현력이 높지 않습니다. 평평한 원시 타입 필드만
가능합니다. `str`, `int`, `float`, `bool`, 또는 문자열 `Literal`(`enum`이 됩니다)입니다.
모델 안에 모델을 넣으면 클라이언트에 아무것도 보내기 전에 `ctx.elicit`이 예외를 일으킵니다.
도구 호출은 `Error executing tool <name>`으로 실패하고, 그 이유는 서버 로그에 남습니다.
```text
TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition
@ -112,8 +113,8 @@ translation:
!!! tip
답은 코드가 보기 전에 모델을 기준으로 검증됩니다. `bool` 자리에 `"maybe"`를 보내는 클라이언트가
예약을 망가뜨리지는 않습니다. 호출은 스키마 불일치 오류로 실패하고, `if` 문은 실행되지
않습니다.
예약을 망가뜨리지는 않습니다. `ctx.elicit`이 `ValueError`를 일으키고, 호출은 실패하며, `if` 문은
실행되지 않습니다.
## 사용자를 URL로 보내기 {#send-the-user-to-a-url}

View file

@ -1,6 +1,6 @@
---
translation:
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5]
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5]
tool: 1
---
# 로깅 {#logging}
@ -55,6 +55,8 @@ MCP에는 프로토콜 수준의 **로깅 기능**이 있습니다. 서버가 `C
`logging.basicConfig()`는 이미 존재하는 핸들러를 절대 교체하지 않습니다. 서버를 만들기 전에 로깅을 직접 설정했다면 그 설정이 우선합니다.
실패를 기록하려는 목적만으로 모든 핸들러에 `try`/`except`를 넣을 필요도 없습니다. 도구나 리소스 함수가 예외를 일으키면 SDK가 대신 로그로 남깁니다. 무엇이 어떤 레벨로 기록되는지는 **[오류 처리](../servers/handling-errors.md#any-other-exception)**에서 설명합니다.
## 직접 해 보기 {#try-it}
MCP Inspector로 서버를 실행하세요.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
tool: 1
---
# 서버 실행하기 {#running-your-server}
@ -72,7 +72,7 @@ Inspector는 실제 호스트가 하는 일을 그대로 합니다. `server.py`
* `streamable_http_path`: MCP 엔드포인트가 위치하는 경로. 기본값은 `/mcp`입니다.
* `json_response=True`: 각 POST에 SSE 스트림 대신 단일 JSON 본문으로 응답합니다. 이 본문에는 응답 외에 다른 것을 담을 자리가 없으므로, 요청 도중 클라이언트를 다시 호출하는 도구(`ctx.elicit()`, 샘플링)는 이 구간에서 `NoBackChannelError`를 발생시키고, 진행 중인 호출에 묶인 알림(`ctx.report_progress()`의 진행 상황, 호출별 로그 메시지)은 버려집니다. 독립된 `GET` 스트림은 관련 없는 알림을 여전히 전달합니다.
* `stateless_http=True`: 요청마다 새 트랜스포트를 만들고 세션을 추적하지 않습니다.
* `max_request_body_size`: 허용되는 POST 본문의 최대 크기(바이트). 기본값은 4MiB이며, 더 큰 요청은
* `max_request_body_size`: 허용되는 요청 본문의 최대 크기(바이트). 기본값은 4MiB이며, 더 큰 요청은
파싱이나 세션 생성 전에 HTTP 413을 받습니다. 정상적인 MCP 메시지가 이 크기를 넘을 때만
올리세요.
* `event_store`, `retry_interval`, `transport_security`: 재개 가능성과 DNS 리바인딩 보호. localhost가 아닌 곳에 배포하기 전까지는 미뤄도 됩니다. `transport_security`는 **[배포와 확장](deploy.md)**에서 다룹니다.

View file

@ -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)**에서 다룹니다.

View file

@ -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=[...]`는 서버, 도구, 리소스, 프롬프트에서 동작하며, 클라이언트는 대응하는 객체에서 아이콘을 찾습니다.

View file

@ -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)**에서 다룹니다.

View file

@ -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)**입니다.

View file

@ -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가 거부합니다.

View file

@ -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}

View file

@ -1,6 +1,6 @@
---
translation:
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 4835ed1772f1d113, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, 954dc21efdb532a3]
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 8a6e351ec756904d, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, e4355f4c7cf4fb2e]
tool: 1
---
# 문제 해결 {#troubleshooting}
@ -81,11 +81,11 @@ async def main() -> None:
연결을 끊는 일은 `__aexit__`에서 일어나므로, 잊어버릴 `client.close()` 같은 것은 없습니다. **[테스트](get-started/testing.md)**는 바로 이 패턴 위에 만들어져 있습니다.
## `Error executing tool <name>: <message>` 및 `Unknown tool: <name>` {#error-executing-tool-name-message-and-unknown-tool-name}
## `Error executing tool <name>: <message>`, `Error executing tool <name>`, `Unknown tool: <name>` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name}
지금 보고 있는 것은 예외가 아니라 **결과**입니다. `call_tool`은 예외를 일으키지 않았고, 실패한 도구에 대해서는 앞으로도 절대 일으키지 않습니다.
서버가 모르는 도시로 `forecast`를 호출하면, 도구가 일으킨 예외는 **성공**으로 표시된 요청에 담겨 돌아옵니다.
서버가 모르는 도시로 `forecast`를 호출하면, 도구가 일으킨 `ToolError`는 **성공**으로 표시된 요청에 담겨 돌아옵니다.
```python
result.is_error # True
@ -97,6 +97,8 @@ result.structured_content # None
해결책은 클라이언트 쪽에 있습니다. **`result.is_error`를 확인하세요.** `call_tool`을 `try/except`로 감싸도 잡히는 것은 하나도 없습니다. 잡을 것이 없기 때문입니다. 이것은 의도된 설계이며, 이 페이지에서 가장 체득할 가치가 있는 한 가지입니다. 호출을 선택한 것은 **모델**이므로, 메시지를 받고 다시 시도할 기회를 얻는 것도 모델입니다. 예외를 **실제로** 일으키는 `MCPError` 경로를 포함해 자세한 내용은 **[오류 처리](servers/handling-errors.md)**에서 확인하세요.
메시지 없이 `Error executing tool <name>`만 나오는 형태는 도구가 **죽었다**는 뜻입니다. 도구가 예상하지 못한 예외가 빠져나갔고(또는 반환값이 출력 스키마를 통과하지 못했고), 그 예외의 텍스트는 와이어에 실리지 않습니다. 트레이스백은 **서버 로그**에 `ERROR` 수준으로, `Tool '<name>' raised an unexpected exception`이라는 메시지와 함께 남습니다.
## `TypeError: The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool` {#typeerror-the-tool-decorator-was-used-incorrectly-did-you-forget-to-call-it-use-tool-instead-of-tool}
`@mcp.tool()` 대신 `@mcp.tool`을 쓴 경우입니다. `tool()`은 데코레이터 **팩토리**이므로, 괄호가 없으면 Python은 함수를 `name=` 매개변수에 넘겨 버립니다.
@ -409,7 +411,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key
## 요약 {#recap}
* `ExceptionGroup: unhandled errors in a TaskGroup`은 절대 진짜 오류가 아닙니다. **마지막 줄**을 읽으세요. `async with Client(...)` 블록 **안에서** `MCPError`를 잡으면 감싸기를 완전히 건너뜁니다.
* `call_tool`은 실패한 도구에 대해 예외를 일으키지 않습니다. `Error executing tool ...` 및 `Unknown tool: ...` 메시지는 결과이므로 `result.is_error`를 확인하세요.
* `call_tool`은 실패한 도구에 대해 예외를 일으키지 않습니다. `Error executing tool ...` 및 `Unknown tool: ...` 메시지는 결과이므로 `result.is_error`를 확인하세요. 도구 이름 뒤에 메시지가 없으면 도구가 죽은 것이며, 트레이스백은 서버 로그에 있습니다.
* `Client must be used within an async context manager` -> `async with`를 사용하세요. `Use @tool() instead of @tool` -> 괄호를 추가하세요.
* 서버 로그의 `Tool already exists:`는 이름이 같은 두 도구가 하나로 합쳐졌다는 유일한 신호입니다.
* 421 하나에 표기는 세 가지입니다. `Server returned an error response`(Python `Client`), `421 Misdirected Request` / `Invalid Host header`(그 밖의 모든 곳), `Invalid Host header: <host>`(서버 로그). 해결책은 `transport_security=TransportSecuritySettings(allowed_hosts=[...])`입니다.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e]
sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e]
tool: 1
---
# v2에서 달라진 점 {#whats-new-in-v2}
@ -46,9 +46,9 @@ v1은 세 겹으로 중첩된 계층을 건네주었습니다. 원시 스트림
--8<-- "docs_src/client/tutorial001.py"
```
`Client`는 서버 객체(트랜스포트 없이 인메모리로 동작하며, 테스트에 쓰는 방식입니다), URL(Streamable HTTP), 또는 `stdio_client(...)` 같은 임의의 트랜스포트 컨텍스트 매니저를 받습니다. `async with`에 진입하면 서버가 어느 시대의 프로토콜을 말하든 연결을 맺고 프로토콜 버전을 협상합니다. 그 뒤에는 `client.server_capabilities`와 `client.protocol_version`이 그냥 준비되어 있고, 서버가 자신을 식별하는 경우에는 `client.server_info`도 마찬가지입니다(2026 시대에는 식별 정보가 선택 사항이므로 이제 타입은 `Implementation | None`입니다). v1에서 등록한 샘플링 및 엘리시테이션 콜백은 여전히 동작하며(콜백 본문에는 이 페이지의 다른 모든 것과 마찬가지로 snake_case 속성 이름 변경이 적용됩니다), 이제 2026 방식의 결과 속 요청(아래 참고)에도 응답하고, 한 번에 하나씩이 아니라 동시에 실행됩니다. 저수준 인터페이스를 원하는 경우를 위해 `ClientSession`은 여전히 그 아래에 있으며 `client.session`으로 얻을 수 있습니다. 다만 이 클래스 역시 바뀌었으므로(새 디스패처 엔진 위에서 실행되고, 자체 시그니처 일부도 변경되었습니다) 아래 계층으로 내려가기 전에 **[마이그레이션 가이드](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**를 읽어 보세요.
`Client`는 서버 객체(트랜스포트 없이 인메모리로 동작하며, 테스트에 쓰는 방식입니다), URL(Streamable HTTP), `StdioServerParameters`(stdio 하위 프로세스), 또는 `sse_client(...)` 같은 그 밖의 임의의 트랜스포트 컨텍스트 매니저를 받습니다. `async with`에 진입하면 서버가 어느 시대의 프로토콜을 말하든 연결을 맺고 프로토콜 버전을 협상합니다. 그 뒤에는 `client.server_capabilities`와 `client.protocol_version`이 그냥 준비되어 있고, 서버가 자신을 식별하는 경우에는 `client.server_info`도 마찬가지입니다(2026 시대에는 식별 정보가 선택 사항이므로 이제 타입은 `Implementation | None`입니다). v1에서 등록한 샘플링 및 엘리시테이션 콜백은 여전히 동작하며(콜백 본문에는 이 페이지의 다른 모든 것과 마찬가지로 snake_case 속성 이름 변경이 적용됩니다), 이제 2026 방식의 결과 속 요청(아래 참고)에도 응답하고, 한 번에 하나씩이 아니라 동시에 실행됩니다. 저수준 인터페이스를 원하는 경우를 위해 `ClientSession`은 여전히 그 아래에 있으며 `client.session`으로 얻을 수 있습니다. 다만 이 클래스 역시 바뀌었으므로(새 디스패처 엔진 위에서 실행되고, 자체 시그니처 일부도 변경되었습니다) 아래 계층으로 내려가기 전에 **[마이그레이션 가이드](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**를 읽어 보세요.
**[클라이언트](client/index.md)**에서 소개하고, **[클라이언트 트랜스포트](client/transports.md)**에서 세 가지 연결 형태를, **[클라이언트 콜백](client/callbacks.md)**에서 콜백 자체를 다루며, **[테스트](get-started/testing.md)**에서는 v1의 `create_connected_server_and_client_session()` 헬퍼를 대체하는 인메모리 패턴을 보여 줍니다.
**[클라이언트](client/index.md)**에서 소개하고, **[클라이언트 트랜스포트](client/transports.md)**에서 네 가지 연결 형태를, **[클라이언트 콜백](client/callbacks.md)**에서 콜백 자체를 다루며, **[테스트](get-started/testing.md)**에서는 v1의 `create_connected_server_and_client_session()` 헬퍼를 대체하는 인메모리 패턴을 보여 줍니다.
### 저수준 `Server`: 이름 변경이 아닌 재구축 {#the-low-level-server-was-rebuilt-not-renamed}
@ -134,7 +134,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB
이름 변경은 스스로 존재를 알립니다. 다음 항목은 그렇지 않습니다.
* **동기 함수는 워커 스레드에서 실행됩니다.** `def` 도구(또는 리소스, 프롬프트, 리졸버)는 더 이상 이벤트 루프를 막지 않습니다. 그 대가로 본문이 더 이상 이벤트 루프 스레드 **위에서** 실행되지 않으며, 이는 특정 스레드에서 실행되어야 하는 코드에는 중요한 차이입니다. `async def` 핸들러는 영향이 없습니다. **[마이그레이션 가이드](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**를 참고하세요.
* **도구 안에서 발생시킨 `MCPError`(v1의 `McpError`)는 이제 프로토콜 오류입니다.** 모델은 이 오류를 보지 못합니다. 그 밖의 모든 예외는 여전히 모델이 읽고 반응할 수 있는 `is_error=True` 결과가 됩니다. 이 구분은 **[오류 처리](servers/handling-errors.md)**에서 다룹니다.
* **도구 안에서 발생시킨 `MCPError`(v1의 `McpError`)는 이제 프로토콜 오류입니다.** 모델은 이 오류를 보지 못합니다. 그 밖의 모든 예외는 여전히 `is_error=True` 결과가 되지만, 모델에 전달되는 메시지는 `ToolError`의 메시지뿐입니다. 다른 예외는 이제 `Error executing tool <name>`으로 표시되고, 트레이스백은 서버 로그에 남습니다. 이 구분은 **[오류 처리](servers/handling-errors.md)**에서 다룹니다.
* **결과는 나가기 전에 검증됩니다.** `input_schema`가 `{}`인 손수 만든 `Tool`은 이제 `tools/list`에서 실패합니다(사양은 `"type": "object"`를 요구합니다). `@mcp.tool()`로 만든 서버는 이 문제를 겪지 않습니다. 스키마를 SDK가 작성하기 때문입니다.
* **클라이언트는 받은 것을 검증합니다.** `list_tools()`와 `call_tool()`은 서버의 응답을 협상한 프로토콜 버전에 맞춰 검사하므로, v1의 관대한 파싱이 눈감아 주던 완전히 유효하지는 않은 서버는 이제 `pydantic.ValidationError`를 발생시킵니다. 직접 제어하지 않는 서버에 연결한다면 그런 서버를 가장 먼저 발견하는 쪽이 될 것을 예상하세요. 자세한 내용은 **[마이그레이션 가이드](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)**에 있습니다.
* **URI 템플릿은 이제 진짜 RFC 6570입니다.** `{+path}`, `{?query}` 등이 동작하고, 매칭은 정규식처럼 느슨한 것이 아니라 정확하며, 추출된 값의 경로 탐색(path traversal)은 기본적으로 거부됩니다. 더 엄격해진 템플릿은 첫 요청 때가 아니라 데코레이터를 적용하는 시점에 실패합니다. **[URI 템플릿](servers/uri-templates.md)**을 참고하세요.