docs: publish translated docs in twelve languages and the tool that maintains them (#3280)

This commit is contained in:
Max 2026-08-14 17:07:34 +01:00 • committed by GitHub
commit 52ad0a8876
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 89737 additions and 54 deletions

View file

@ -0,0 +1,143 @@
---
translation:
sections: [0d6c05bcbf836bf3, 59a7b14eeefc68c1, 7114d8d6daba203f, e8bbb56a98ba7bc9, 5138010f6159901c, f78da7c7c363d4c6, 220a939cab348686]
tool: 1
---
# 첫걸음 {#first-steps}
**[랜딩 페이지](../index.md)**는 빠르게 진행합니다. 서버를 작성하고, 실행하고, 도구를 호출합니다.
이 페이지는 천천히 진행합니다. 서버가 노출할 수 있는 세 가지를 모두 다루고, 그 과정에서 등장하는 모든 것에 이름을 붙입니다.
## 호스트, 클라이언트, 서버 {#host-client-and-server}
지금부터 모든 페이지에서 마주칠 세 단어입니다.
* **호스트**는 LLM 애플리케이션입니다. Claude, IDE, 에이전트 런타임이 여기에 해당하며, 사용자가 대화하는 상대가 바로 호스트입니다.
* **클라이언트**는 호스트 안에 있으며 MCP로 통신합니다. 호스트는 연결된 서버마다 클라이언트를 하나씩 실행합니다.
* **서버**는 이 SDK로 만드는 것입니다. 서버는 클라이언트에 여러 가지를 노출하며, 모델과 직접 대화하는 일은 없습니다.
직접 작성하는 것은 서버입니다. 호스트는 다른 누군가가 만든 제품입니다. SDK는 `Client`도 제공합니다. 서버를 테스트할 때 쓰게 되며, 이 페이지 뒷부분에서 다시 등장합니다.
## 세 가지 프리미티브 {#the-three-primitives}
서버가 노출하는 것은 정확히 세 종류입니다. 셋을 가르는 기준은 **누가 사용을 결정하는가**입니다.
| 프리미티브 | 제어 주체 | 설명 | 예시 |
|---------------|-----------------|-----------------------------------------------------|------------------------------------|
| **도구** | 모델 | 모델이 어떤 동작을 수행하려고 호출하는 함수 | API 호출, 데이터베이스 쓰기 |
| **리소스** | 애플리케이션 | 호스트가 모델의 컨텍스트에 불러오는 데이터 | 파일 내용, API 응답 |
| **프롬프트** | 사용자 | 사용자가 이름으로 호출하는 재사용 가능한 메시지 템플릿 | 슬래시 명령, 메뉴 항목 |
"제어 주체"가 이 구분의 핵심입니다. 도구는 **모델**이 호출하기로 결정했기 때문에 실행됩니다. 리소스는 **애플리케이션**이 모델에 필요하다고 판단했기 때문에 첨부됩니다. 프롬프트는 **사용자**가 골랐기 때문에 실행됩니다.
!!! info
웹 API를 만들어 본 적이 있다면 필요한 감각은 이미 대부분 갖추고 있습니다. **리소스**는 `GET`(데이터를
불러오고 아무것도 바꾸지 않음)이고 **도구**는 `POST`(작업을 수행하며 부작용이 있을 수 있음)입니다.
**프롬프트**는 HTTP에 대응하는 것이 없으며, 사용자가 이름으로 실행하는 저장된 쿼리에 더 가깝습니다.
## 서버 하나에 세 가지 모두 {#one-server-all-three}
```python title="server.py" hl_lines="6 12 18"
--8<-- "docs_src/first_steps/tutorial001.py"
```
평범한 함수 셋, 데코레이터 셋입니다. 각 데코레이터가 곧 등록의 전부입니다.
* `@mcp.tool()`은 `add`를 **도구**로 만듭니다.
* `@mcp.resource("greeting://{name}")`은 `greeting`을 **리소스 템플릿**으로 만듭니다. URI의 `{name}`이 함수의 매개변수입니다.
* `@mcp.prompt()`는 `summarize`를 **프롬프트**로 만듭니다. 이 함수가 반환하는 문자열은 사용자 메시지가 됩니다.
나머지(이름, 설명, 인자 스키마)는 모두 SDK가 함수 자체에서 읽어 냅니다. 함수 이름, 독스트링, 타입 힌트에서 가져오는 것입니다. 어느 것도 따로 선언하지 않았습니다.
!!! tip
SDK의 두 부분은 임포트 경로도 둘입니다. `from mcp import Client`와
`from mcp.server import MCPServer`입니다. `from mcp import MCPServer`는 없습니다.
### 직접 해 보기 {#try-it}
MCP Inspector로 실행하세요.
```console
uv run mcp dev server.py
```
출력되는 URL을 여세요. Inspector에는 프리미티브마다 탭이 하나씩 있습니다. 순서대로 살펴보세요.
**도구.** 항목은 `add` 하나이며, *Add two numbers.*라는 설명이 붙어 있습니다. 폼에는 필수 정수 필드가 `a`에 하나, `b`에 하나 있습니다. 값을 채워 호출하면 결과는 `3`입니다. Inspector는 `a: int, b: int`를 보고 이 폼을 만들었습니다. 다른 모든 클라이언트도 마찬가지입니다.
**리소스.** *Resources* 목록은 비어 있습니다. `greeting`은 **Resource Templates** 아래에 있습니다. `greeting://{name}`에 매개변수가 있어서, 누군가 `name`을 제공하기 전까지는 나열할 단일 리소스가 없기 때문입니다. `World`를 넣고 읽어 보세요.
```text
Hello, World!
```
**프롬프트.** 항목은 `summarize` 하나이며, 필수 인자는 `text` 하나입니다. 텍스트를 넣어 프롬프트를 가져오면 `role: user`와 렌더링된 문자열을 내용으로 하는 메시지 하나가 돌아옵니다. 프롬프트는 이것이 전부입니다. 메시지를 만드는 함수일 뿐입니다.
Inspector는 서버를 **stdio**로 실행했습니다. stdio는 MCP 서버가 사용할 수 있는 트랜스포트 중 하나입니다. 아직 트랜스포트를 고를 필요는 없습니다. 그 내용은 **[서버 실행하기](../run/index.md)** 페이지에서 다룹니다.
## 기능 {#capabilities}
Inspector에서 탭 세 개를 보았습니다. Inspector가 세 개라는 것을 어떻게 알았는지 살펴보겠습니다.
클라이언트가 연결하면 서버는 **기능**, 즉 어떤 부류의 요청에 응답할지를 선언합니다. 클라이언트는 이 선언을 보고 애초에 무엇을 요청할지 결정합니다. 이 선언을 작성한 적은 없습니다. `MCPServer`가 대신 선언합니다.
직접 확인해 보세요. SDK의 `Client`는 서버 객체를 그대로 받아 **인메모리**로 연결합니다(서브프로세스도, 포트도 없습니다).
```python
import asyncio
from mcp import Client
from server import mcp
async def main() -> None:
async with Client(mcp) as client:
print(client.server_capabilities.model_dump(exclude_none=True))
asyncio.run(main())
```
```text
{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}}
```
이 딕셔너리가 서버가 선언한 **기능**입니다. 연결하는 모든 클라이언트가 가장 먼저 알게 되는 내용입니다.
| 기능 | 클라이언트가 이제 호출할 수 있는 것 |
|-------------|------------------------------------------------------------|
| `tools` | `tools/list`, `tools/call` |
| `resources` | `resources/list`, `resources/templates/list`, `resources/read` |
| `prompts` | `prompts/list`, `prompts/get` |
`MCPServer`는 세 프리미티브를 모두 제공하므로 셋 다 항상 선언됩니다.
없는 것에도 주목하세요. `completions`(리소스 템플릿과 프롬프트의 인자 자동 완성)에는 직접 작성하는 핸들러가 필요한데, 이 서버에는 핸들러가 없으므로 해당 기능이 빠져 있고, 올바르게 동작하는 클라이언트라면 요청하지 않습니다. 선택 사항은 모두 이 규칙을 따릅니다. 등록하면 기능이 나타납니다. **[자동 완성](../servers/completions.md)** 페이지가 이를 보여 줍니다.
!!! info
`Client(mcp)`는 이 문서의 모든 예제를 테스트하는 데 쓰이는 바로 그 인메모리 클라이언트이며,
작성한 서버도 같은 방식으로 테스트하게 됩니다. 이를 다루는 페이지가 따로 있습니다. **[테스트](testing.md)**입니다.
## 작성하지 않은 것 {#what-you-did-not-write}
이 페이지를 되돌아보세요. 작성한 것은 작은 Python 함수 세 개입니다. 다음은 작성하지 **않았습니다**.
* JSON Schema. `a: int, b: int`가 **곧** `add`의 스키마입니다.
* 요청 핸들러. `tools/list`, `resources/read`, `prompts/get`은 모두 대신 처리됩니다.
* 기능 선언. `MCPServer`가 대신 만들었습니다.
* 프로토콜 코드 단 한 줄. 버전 협상, JSON-RPC 프레이밍, 기능 교환은 모두 `mcp dev`와 `Client(mcp)` 안에서 일어났고, 눈에 보이지도 않았습니다.
이 비율이야말로 SDK가 존재하는 이유입니다.
## 요약 {#recap}
* **호스트**는 LLM 앱이고, **클라이언트**는 그 안에서 MCP로 통신하는 부분이며, **서버**는 직접 만드는 것입니다.
* 도구는 **모델**이, 리소스는 **애플리케이션**이, 프롬프트는 **사용자**가 제어합니다.
* 프리미티브마다 데코레이터 하나면 됩니다. `@mcp.tool()`, `@mcp.resource(uri)`, `@mcp.prompt()`입니다. 이름, 설명, 스키마는 함수에서 가져옵니다.
* `{param}`이 들어간 URI는 리소스 **템플릿**을 만들며, 구체적인 리소스와는 따로 나열됩니다.
* 서버의 **기능**은 자동으로 선언되며, 클라이언트는 서버가 선언한 것만 요청합니다.
* `Client(mcp)`는 서버 객체에 인메모리로 연결합니다. 첫날부터 갖추는 테스트 하네스입니다.
다음은 **[실제 호스트에 연결하기](real-host.md)**입니다. 이 서버를 Claude Desktop이나 IDE 안에서 실제로 돌려 봅니다. 그다음은 **[테스트](testing.md)**입니다. 페이지 하나, 인메모리 클라이언트 하나면 동작하는지 추측할 일이 없어집니다. 그 뒤로는 프리미티브마다 전용 페이지가 이어지며, 모델이 주도하는 프리미티브인 **[도구](../servers/tools.md)**부터 시작합니다.

View file

@ -0,0 +1,57 @@
---
translation:
sections: [ed4a756b4c53c585, 97e2fb315b7fe398, 4d04f1c6f4bf6c1d, 577d73078fc62baf]
tool: 1
---
# 시작하기 {#get-started}
MCP가 처음이거나 이 SDK가 처음이라면 여기서 시작하세요. 이곳의 페이지는 아무것도 없는 상태에서 시작해,
테스트까지 마친 동작하는 서버를 완성하도록 안내합니다. [SDK 설치](installation.md),
[첫 번째 서버](first-steps.md) 만들기, [실제 호스트에 연결하기](real-host.md), 그리고 인메모리 클라이언트로
[테스트하기](testing.md) 순서로 진행합니다.
## 코드 실행하기 {#run-the-code}
모든 코드 블록은 그대로 복사해서 바로 사용할 수 있습니다. 하나하나가 완전하게 동작하는 파일입니다.
따라 하려면 코드 블록을 `server.py`에 붙여 넣고 MCP Inspector에서 여세요.
```console
uv run mcp dev server.py
```
코드를 직접 작성(또는 복사)하고, 수정하고, 로컬에서 실행해 보기를 **강력히 권장합니다**. 평소 쓰는 편집기에서 직접 다뤄 봐야 핵심이 제대로 와닿습니다. 작성할 코드가 얼마나 적은지, 자동 완성은 어떤지, 실행하기도 전에 타입 검사가 실수를 잡아내는 모습까지 확인할 수 있습니다.
## 추측할 필요 없는 예제 {#you-will-not-be-guessing}
이 문서의 모든 예제는 SDK 저장소의 [`docs_src/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/docs_src) 아래에 있는 완전한 파일이며, 하나도 빠짐없이 SDK의 테스트 스위트가 **인메모리 클라이언트**를 통해 실행합니다.
```python
import pytest
from mcp import Client
from server import mcp
@pytest.mark.anyio
async def test_add() -> None:
async with Client(mcp) as client:
result = await client.call_tool("add", {"a": 1, "b": 2})
assert result.structured_content == {"result": 3}
```
서브프로세스도, 포트도, 트랜스포트도 없습니다. `Client(mcp)`가 서버 객체에 직접 연결합니다.
SDK 변경으로 이 문서의 예제가 하나라도 깨지면, 페이지에 문제가 드러나기 전에 CI가 먼저 실패합니다. 여기서 읽는 코드가 곧 실제로 실행되는 코드입니다.
이 방식은 [테스트](testing.md)에서 직접 사용해 봅니다. 작성한 서버를 테스트하는 방법도 바로 이것입니다.
## 다음 단계 {#where-to-go-next}
서버를 실행하고 나면 나머지 문서는 강좌가 아니라 레퍼런스입니다.
모든 페이지가 독립적으로 읽히므로 필요한 곳으로 바로 이동하세요.
* 서버가 노출하는 것(도구, 리소스, 프롬프트)은 **[서버](../servers/index.md)**에서 다룹니다.
* 등록한 함수 안에서 사용할 수 있는 것은 **[핸들러 내부](../handlers/index.md)**에서 다룹니다.
* 클라이언트 앞에 내놓는 방법(stdio, HTTP, 기존 FastAPI 앱)은 **[서버 실행하기](../run/index.md)**에서 다룹니다.
* 반대편, 즉 MCP 서버를 **사용하는** 애플리케이션을 만드는 방법은 **[클라이언트](../client/index.md)**에서 다룹니다.

View file

@ -0,0 +1,47 @@
---
translation:
sections: [6e2f9bab94d5ed36, 8cf653388f69e28b, 6fd9ea2f65de0df6]
tool: 1
---
# 설치 {#installation}
Python SDK는 PyPI에 [`mcp`](https://pypi.org/project/mcp/)라는 이름으로 올라와 있습니다. **Python 3.10 이상**이 필요합니다.
이 문서는 현재 안정 릴리스 계열인 **v2**를 설명합니다.
=== "uv"
```bash
uv add "mcp[cli]"
```
=== "pip"
```bash
pip install "mcp[cli]"
```
!!! note "v1에서 옮겨 오는 경우"
v2는 호환성이 깨지는 변경이 포함된 메이저 버전이며, **[마이그레이션 가이드](../migration.md)**에서
그 변경을 하나도 빠짐없이 다룹니다. 작성한 **패키지**가 `mcp`에 의존하는데 아직 마이그레이션할 준비가 되지 않았다면,
버전을 고정하지 않은 의존성 해석이 1.x 계열에 머무르도록 `<2` 상한을 유지하세요(예: `mcp>=1.28,<2`).
## 설치되는 항목 {#what-gets-installed}
SDK를 사용하는 데 이런 내용을 알 필요는 전혀 없지만, 각 의존성이 어떤 용도인지 궁금하다면 다음을 참고하세요.
* `mcp-types`: 모든 프로토콜 타입(요청, 결과, 콘텐츠 블록)을 담은 별도 패키지로, SDK와 항상 같은 버전으로 맞춰 릴리스됩니다. `mcp`에 의존하는 코드는 이 패키지를 `mcp.types` 별칭을 통해 임포트합니다(이 문서에 나오는 모든 `from mcp.types import ...`가 그렇습니다). `mcp_types`를 직접 임포트하는 것은 SDK 없이 `mcp-types`만 설치하는 프로젝트에서만 하세요.
* [`anyio`](https://anyio.readthedocs.io/): 비동기 런타임입니다. SDK 전체가 anyio를 기반으로 작성되어 있으므로 `asyncio`와 `trio` 중 어느 쪽에서든 실행됩니다.
* [`pydantic`](https://docs.pydantic.dev/): 모든 `mcp.types` 모델이 이 위에 만들어져 있으며, 스키마 생성과 검증도 전부 담당합니다.
* [`httpx2`](https://pypi.org/project/httpx2/): Streamable HTTP와 SSE **클라이언트** 트랜스포트의 기반이 되는 HTTP 클라이언트로, server-sent events 지원이 내장되어 있습니다.
* [`starlette`](https://www.starlette.io/), [`uvicorn`](https://www.uvicorn.org/), [`sse-starlette`](https://pypi.org/project/sse-starlette/), [`python-multipart`](https://pypi.org/project/python-multipart/): HTTP **서버** 트랜스포트를 구성합니다.
* [`jsonschema`](https://pypi.org/project/jsonschema/): 도구의 구조화된 출력이 선언된 출력 스키마에 맞는지 검증합니다.
* [`pyjwt[crypto]`](https://pyjwt.readthedocs.io/): 인가에 쓰이는 OAuth 토큰 처리를 담당합니다.
* [`opentelemetry-api`](https://opentelemetry-python.readthedocs.io/): 가벼운 API만 들어 있으므로, OpenTelemetry SDK와 익스포터를 직접 설치하지 않는 한 SDK의 트레이싱 미들웨어에는 아무 비용도 들지 않습니다.
* [`typing-extensions`](https://typing-extensions.readthedocs.io/)와 [`typing-inspection`](https://pypi.org/project/typing-inspection/): Python 3.10에서 최신 타이핑 기능을 쓸 수 있게 해 줍니다.
* [`pywin32`](https://pypi.org/project/pywin32/): Windows 전용으로, `stdio` 하위 프로세스 관리에 사용됩니다.
## 선택적 추가 기능 {#optional-extras}
* `mcp[cli]`는 `mcp` 명령줄 도구(`mcp dev`, `mcp run`, `mcp install`)에 필요한 [`typer`](https://typer.tiangolo.com/)와 [`python-dotenv`](https://pypi.org/project/python-dotenv/)를 추가합니다. 개발하는 동안에는 있는 편이 좋지만, 배포된 서버에는 필요하지 않을 수도 있습니다.
* `mcp[rich]`는 서버 로그를 더 보기 좋게 만들어 주는 [`rich`](https://rich.readthedocs.io/)를 추가합니다.

View file

@ -0,0 +1,184 @@
---
translation:
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
tool: 1
---
# 실제 호스트에 연결하기 {#connect-to-a-real-host}
**호스트**는 서버가 최종적으로 들어가 동작하는 애플리케이션입니다. Claude Desktop, Claude Code, IDE가 여기에 해당하며, 사용자가 대화하는 상대가 바로 호스트입니다. 호스트 안에서는 MCP **클라이언트**가 서버를 자식 프로세스로 실행하고, 그 프로세스의 stdin과 stdout을 통해 서버와 통신합니다.
따라서 호스트에 연결하는 일은 단 하나의 동작으로 끝납니다. **서버를 시작하는 명령**을 호스트에 알려 주는 것입니다. 이 페이지에 나오는 모든 것(CLI 명령 두 개, JSON 파일 세 개)은 바로 그 명령을 넣어 두는 서로 다른 위치일 뿐입니다.
## 서버 하나, 모든 호스트 {#one-server-every-host}
```python title="server.py" hl_lines="3 33-34"
--8<-- "docs_src/real_host/tutorial001.py"
```
도구 두 개와 리소스 하나가 파일 하나에 들어 있습니다. 이 파일에서 아래의 모든 호스트에 중요한 점은 세 가지입니다.
* 인자 없이 호출한 `mcp.run()`은 **stdio** 서버를 시작합니다. 블로킹 상태로 동작하며, stdin에서 프로토콜 메시지를 읽고 stdout에 씁니다. 이 페이지의 모든 호스트가 사용하는 트랜스포트가 바로 이것입니다. 호스트가 파일을 자식 프로세스로 시작하고 그 두 파이프를 소유하기 때문에, 연결은 언제나 "명령은 이것입니다"로 끝납니다. 포트를 고를 일이 없고, 포트에서 대기하는 것도 없습니다.
* `run()`은 `if __name__ == "__main__":` 아래에 있습니다. 아래에 나오는 모든 방법은 이 파일을 실행하는 대신 **임포트**하므로, 가드 없이 `run()`을 두면 무엇이든 이 모듈을 로드하는 순간 서버가 시작되어 버립니다.
* 서버 객체는 `mcp`라는 이름의 모듈 수준 전역 변수입니다. `mcp run`이 찾는 이름이 바로 이것입니다(`server`와 `app`도 동작합니다). 다른 이름을 쓴다면 `mcp run server.py:bookshop`처럼 명시적으로 지정합니다.
이것이 이 페이지의 마지막 Python 코드입니다. 여기서부터는 전부 호스트 설정입니다.
## 실행 명령 {#the-launch-command}
아래의 모든 호스트에는 같은 명령을 사용합니다.
```bash
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
```
모든 호스트에 명령 하나로 충분한 이유는 `uv run --with`가 그 자리에서 SDK를 새 환경에 설치해 주기 때문입니다. 어느 디렉터리에서든 동작하며, 프로젝트도 활성화할 가상 환경도 필요 없습니다. 이 점은 다른 어느 곳보다 여기서 중요합니다. 호스트는 셸이 아니라 **호스트 자신의** 작업 디렉터리에서, 거의 비어 있는 환경으로 서버를 실행하기 때문입니다.
이 명령은 `mcp install`이 Claude Desktop 설정에 대신 써 주는 명령이기도 합니다(아래 참고). 따라서 직접 입력하는 내용과 도구가 생성하는 내용은, 도구가 덧붙이는 정확한 버전 고정만 빼면 일치합니다.
!!! tip "호스트가 `uv`를 찾지 못할 때"
호스트는 최소한의 `PATH`로 서버를 실행하므로 `uv`가 그 안에 없을 수 있습니다. 그냥 `uv`라고
쓴 부분을 `which uv`(macOS/Linux) 또는 `where uv`(Windows)로 얻은 절대 경로로 바꾸세요.
`mcp install`이 쓰는 것도 정확히 이것입니다.
!!! note "이 페이지는 로컬 실행을 다룹니다"
여기 나오는 모든 방법은 호스트가 있는 바로 그 머신에서 서버를 실행합니다. 호스트가 파일을
stdio로 직접 실행하는 방식입니다. 개인용 도구나 머신 한 대에서 쓰는 도구라면 이것이 정확히
맞는 방법입니다. 파일을 **가지고 있지 않은** 사람들에게 서버를 제공하려면 명령이 아니라
**URL**을 건네야 합니다. 같은 `mcp` 객체를 Streamable HTTP로 서비스하는 것입니다.
**[서버 실행하기](../run/index.md)**는 그 결정을 표 하나로 정리하고,
**[배포와 확장](../run/deploy.md)**은 거기서 실제 호스트 이름까지 가는 길을 안내합니다.
그리고 호스트란 MCP 클라이언트를 품은 애플리케이션에 지나지 않으므로, 직접 작성한 Python
코드도 호스트 역할을 할 수 있습니다. **[클라이언트 트랜스포트](../client/transports.md)**에서는
`stdio_client(...)`로 같은 파일을 서브프로세스로 실행하고, **[테스트](testing.md)**에서는
프로세스 없이 인메모리로 연결합니다.
## Claude Desktop {#claude-desktop}
SDK가 대신 설정해 줄 수 있는 유일한 호스트입니다.
```bash
uv run mcp install server.py
```
이게 전부입니다. `mcp install`은 파일을 임포트해 서버 이름을 읽고, Claude Desktop의 설정 파일을 찾아 실행 명령을 써 넣습니다. 그 과정에서 경로를 절대 경로로 바꿔 주므로 직접 할 필요가 없습니다.
감춰진 것은 아무것도 없습니다. 써 넣는 항목은 다음과 같습니다.
```json
{
"mcpServers": {
"Bookshop": {
"command": "/absolute/path/to/uv",
"args": [
"run",
"--frozen",
"--with",
"mcp[cli]==2.0.0",
"mcp",
"run",
"/absolute/path/to/server.py"
]
}
}
}
```
앞 절의 실행 명령에 세 가지가 더해진 것입니다. `uv`의 절대 경로, 근처에 있는 락파일을 `uv`가 다시 쓰지 않도록 하는 `--frozen`, 그리고 설치된 `mcp` 버전을 정확히 지정하는 버전 고정입니다. 이 항목은 `claude_desktop_config.json`에 들어가며, 파일 위치는 다음과 같습니다.
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
이 파일은 손으로 직접 써도 됩니다. `mcp install`은 그 과정에서 흔히 하는 실수(상대 경로)를 막기 위해 존재합니다.
Claude Desktop을 창만 닫지 말고 완전히 종료한 뒤 다시 여세요.
!!! warning
Claude Desktop의 설정 **디렉터리**가 아직 없으면 `mcp install`은 `Claude app not found` 오류로
실패합니다. Claude Desktop을 설치하고 한 번 실행하세요. 디렉터리는 그때 만들어집니다.
!!! tip
Claude Desktop은 서버를 별도의 프로세스로 시작하므로 셸의 환경 변수는 거기에 없습니다.
`uv run mcp install server.py -v API_KEY=abc123` 명령(또는 `-f .env` 옵션)을 사용하면 환경
변수가 항목의 `env` 필드에 기록됩니다. `--name` 옵션은 항목 이름을 덮어쓰며, 기본값은 서버의
`name`입니다.
## Claude Code {#claude-code}
편집할 파일은 없습니다. `claude` CLI로 서버를 등록하세요. `--` 뒤에 오는 모든 것이 실행 명령입니다.
```bash
claude mcp add bookshop -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
```
Claude Code 세션 안에서 `/mcp`를 실행해 `bookshop`이 연결되어 있고 도구가 나열되는지 확인하세요.
## Cursor {#cursor}
프로젝트 루트에 `.cursor/mcp.json` 파일을 만드세요.
```json
{
"mcpServers": {
"bookshop": {
"command": "uv",
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
}
}
}
```
Claude Desktop이 쓰는 것과 같은 `mcpServers` 키 아래에, 같은 `command`와 `args`가 들어갑니다. 서버는 Cursor의 MCP 설정에 두 도구와 함께 나타납니다.
## VS Code {#vs-code}
프로젝트 루트에 `.vscode/mcp.json` 파일을 만드세요.
```json
{
"servers": {
"bookshop": {
"type": "stdio",
"command": "uv",
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
}
}
}
```
Cursor의 파일과 다른 점은 두 가지이며, 정확히 그 두 가지뿐입니다. 감싸는 키가 `mcpServers`가 아니라 `servers`라는 점, 그리고 각 항목이 `type`을 선언한다는 점입니다. 신뢰 여부를 묻는 메시지를 확인한 뒤 명령 팔레트에서 **MCP: List Servers**를 실행하면 `bookshop`이 실행 중으로 표시됩니다.
!!! note
VS Code 1.99 이상이 필요하고 **GitHub Copilot** 확장에 로그인되어 있어야 합니다(Copilot Free면
충분합니다). 또한 Copilot Chat은 **Agent** 모드여야 합니다. 다른 모드는 도구를 호출하지 않기
때문입니다.
## 서버가 나타나지 않을 때 {#it-doesnt-show-up}
호스트 설정을 건드리기 전에 실행 명령을 직접 실행해 보세요.
```bash
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
```
아무것도 출력되지 않고, 명령이 반환되지도 않습니다. 이 침묵이 정상입니다. stdio 서버는 호스트가 stdin으로 먼저 말을 걸기를 기다리고 있습니다(멈추려면 `Ctrl-C`를 누르세요). 트레이스백이 뜨거나 즉시 종료된다면 그것이 진짜 버그이며, 이제 호스트 너머로 추측하는 대신 직접 읽을 수 있습니다.
이 명령이 가만히 대기하는 상태가 되었다면, 남은 원인은 거의 항상 다음 세 가지 중 하나입니다.
* **상대 경로.** 호스트는 등록할 때 있던 디렉터리가 아니라 **호스트 자신의** 작업 디렉터리에서 서버를 실행합니다. `/absolute/path/to/server.py`가 필요한 자리에 `server.py`를 쓰는 것이 단연 가장 흔한 실패 원인입니다. 호스트가 `uv`도 찾지 못한다면 그 경로 역시 절대 경로여야 합니다.
* **호스트가 아직 예전 설정으로 동작 중인 경우.** 호스트는 시작할 때 설정을 읽습니다. 특히 Claude Desktop은 창만 닫는 것이 아니라 **완전히 종료**한 뒤 다시 열어야 `claude_desktop_config.json`을 수정한 내용이 반영됩니다.
* **우회 구간 밖에서 무언가가 stdout에 도달한 경우.** stdio에서는 stdout이 **곧** 프로토콜입니다. SDK는 서버가 동작하는 동안 플러시된 엉뚱한 출력을 stderr로 돌려 보내지만, 그 전에 stdout으로 플러시된 출력(래퍼 스크립트의 echo, 버퍼링하지 않는 프로세스에서 임포트 시점에 실행된 `print()`)이나 인터프리터 종료 시점에 비워지는 버퍼링된 `print()`는 호스트에 손상된 메시지를 건네게 되고, 호스트는 연결을 끊어 버립니다. 기본 `logging` 설정으로 로그를 남기세요. 기본 설정의 stderr 핸들러는 레코드마다 플러시합니다. 사용자 정의 핸들러도 stdout을 피해야 합니다. 자세한 내용은 **[로깅](../handlers/logging.md)**에서 확인하세요.
Claude Desktop은 서버마다 로그를 남깁니다. `mcp-server-<NAME>.log`가 서버의 stderr이고, 연결을 기록하는 `mcp.log`가 그 옆에 있습니다. 위치는 macOS에서 `~/Library/Logs/Claude`, Windows에서 `%APPDATA%\Claude\logs`입니다.
이 세 가지를 넘어서는 문제는 **[문제 해결](../troubleshooting.md)** 페이지를 참고하세요.
## 요약 {#recap}
* **호스트**(Claude Desktop, IDE)는 MCP 클라이언트를 실행하고, 이 클라이언트가 서버를 자식 프로세스로 띄워 stdio로 통신합니다. 연결한다는 것은 호스트에 실행 명령 하나를 알려 주는 것입니다.
* 그 명령은 `uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py`입니다. 활성화할 가상 환경이 필요 없고, 어느 디렉터리에서든 동작합니다.
* **Claude Desktop**은 `mcp install`이 대신 설정해 주는 유일한 호스트입니다. 바로 그 명령에 `uv`의 절대 경로, `--frozen`, 설치된 버전을 정확히 지정하는 버전 고정을 더해 `claude_desktop_config.json`에 써 주므로 직접 쓸 일이 전혀 없습니다.
* **Claude Code**는 `claude mcp add bookshop -- <launch command>`입니다. **Cursor**는 `.cursor/mcp.json`의 `mcpServers` 아래입니다. **VS Code**는 `.vscode/mcp.json`의 `servers` 아래이며, 각 항목에 `type`을 둡니다.
* 어디서나 절대 경로를 쓰고, 설정을 수정한 뒤에는 호스트를 다시 시작하며, SDK 외에는 무엇도 stdout에 쓰지 못하게 하세요.
이 페이지의 모든 호스트가 같은 파일에, 같은 명령으로 연결했습니다. 그 파일이 무엇을 **노출할 수 있는지**는 이 문서의 나머지가 다룹니다. **[도구](../servers/tools.md)**, **[리소스](../servers/resources.md)**, 그리고 stdio 외의 모든 트랜스포트는 **[서버 실행하기](../run/index.md)**에서 확인하세요.

View file

@ -0,0 +1,114 @@
---
translation:
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
tool: 1
---
# 테스트 {#testing}
Python SDK는 **인메모리 트랜스포트**를 갖춘 `Client` 클래스를 제공합니다. 서버 객체를 넘기면 그 서버에 직접 연결합니다.
서브프로세스도 없습니다. 포트도 없습니다. 트랜스포트도 전혀 없습니다. FastAPI의 `TestClient`와 같은 발상입니다.
## 기본 사용법 {#basic-usage}
도구가 하나뿐인 간단한 서버가 있다고 해 보겠습니다.
```python title="server.py"
--8<-- "docs_src/testing/tutorial001.py"
```
아래 테스트를 실행하려면 (개발용) 의존성 두 개가 더 필요합니다.
=== "uv"
```bash
uv add --dev pytest inline-snapshot
```
=== "pip"
```bash
pip install pytest inline-snapshot
```
!!! info
이 문서는 [`pytest`](https://docs.pytest.org/en/stable/)를 이미 알고 있다고 가정합니다.
아래 테스트에서는 [`inline-snapshot`](https://15r10nk.github.io/inline-snapshot/latest/)으로
결과 객체 전체를 한 줄에 검증합니다. 테스트의 출력을 코드에 보이는 `snapshot(...)` 리터럴
형태로 기록해 주는 라이브러리입니다. 쓰고 싶지 않다면 import를 빼고, 여느 테스트에서 하듯
관심 있는 필드(`result.content[0].text == "3"`)를 직접 검증하세요.
이제 테스트 코드입니다.
```python title="test_server.py"
import pytest
from inline_snapshot import snapshot
from mcp import Client
from mcp.types import CallToolResult, TextContent
from server import mcp
@pytest.fixture
def anyio_backend(): # (1)!
return "asyncio"
@pytest.fixture
async def client(): # (2)!
async with Client(mcp, raise_exceptions=True) as c:
yield c
@pytest.mark.anyio
async def test_call_add_tool(client: Client):
result = await client.call_tool("add", {"a": 1, "b": 2})
# Drop the server identity stamp in `_meta`; it is not what this test is about.
result.meta = None
assert result == snapshot(
CallToolResult(
content=[TextContent(type="text", text="3")],
structured_content={"result": 3},
)
)
```
1. `trio`를 사용한다면 대신 `"trio"`를 반환하세요. 자세한 내용은 [anyio 문서](https://anyio.readthedocs.io/en/stable/testing.html#specifying-the-backends-to-run-on)를 참고하세요.
2. 이 픽스처는 연결을 마친 클라이언트를 yield합니다. `client`를 받는 모든 테스트는 같은 서버로 이어지는 새 인메모리 연결을 받습니다.
다 됐습니다. 이제 더 많은 시나리오를 다루도록 테스트를 확장할 수 있습니다.
## `raise_exceptions=True`를 쓰는 이유 {#why-raise_exceptionstrue}
잘못될 수 있는 일은 서로 다른 두 가지이고, 이 플래그는 그중 하나에만 관여합니다.
**작성한 도구** 안에서 발생한 예외는 프로토콜 실패가 아닙니다. `is_error=True`인 정상적인 결과가
되고, 모델이 그 메시지를 읽습니다. `raise_exceptions`는 이 점을 바꾸지 않습니다. 플래그가 있든
없든 `call_tool`은 똑같은 `is_error=True` 결과를 반환합니다. 이 주제를 통째로 다루는 페이지가
따로 있습니다. **[오류 처리](../servers/handling-errors.md)**를 참고하세요.
도구 본문 **바깥**에서 일어난 실패는 다릅니다. `Client(mcp)`가 제공하는 연결에서는 클라이언트가
보기 전에 서버가 이 실패를 일반적인 `"Internal server error"`로 정제합니다. 예상치 못한 크래시의
세부 내용을 원격 호출자에게 흘려서는 절대 안 됩니다. 하지만 테스트에서는 바로 이것이 원하지
**않는** 동작이며, `raise_exceptions=True`가 바꾸는 것도 바로 이 부분입니다. 테스트는 정제된
메시지 대신 실제 메시지를 보게 됩니다.
테스트에서는 켜 두세요. 프로덕션 코드에서는 아무 의미가 없습니다.
## 기본값은 인프로세스 연결 {#in-process-by-default}
!!! note
`Client(mcp)`는 인프로세스로 연결하며 기본적으로 **세대 중립적**(era-neutral)입니다. 서버를
조사해 알맞은 프로토콜 경로를 고릅니다. 테스트가 레거시 전용 동작, 즉 샘플링이나
엘리시테이션(elicitation) 푸시, `message_handler` 같은 것을 검증한다면 `mode="legacy"`로
고정하고, 그 경우에는 `raise_exceptions=True`를 빼세요. 레거시 연결은 애초에 정제를 하지
않으며, 이 플래그는 실패를 테스트가 아니라 서버 태스크 안에서 다시 발생시키기 때문입니다.
이 문서의 예제가 실제로 동작한다고 약속할 수 있는 것도 바로 그 한 줄 덕분입니다. 모든 예제
파일은 SDK 자체의 테스트 스위트에서 실행되며, 거의 전부가 정확히 이 클라이언트를 거칩니다.
SDK가 스스로를 검증하는 데 쓰는 바로 그 도구를 사용하고 있는 것입니다.
이제 동작하고 테스트까지 거친 서버가 생겼습니다. 이 서버를 실제 애플리케이션(Claude Desktop,
IDE)에 넣는 방법은 **[실제 호스트에 연결하기](real-host.md)**에서, 그 밖에 서버를 구동하는 모든
방법은 **[서버 실행하기](../run/index.md)**에서 다룹니다.