docs: publish translated docs in twelve languages and the tool that maintains them (#3280)
This commit is contained in:
parent
14aa889992
commit
52ad0a8876
5 changed files with 89737 additions and 54 deletions
143
i18n/ko/pages/get-started/first-steps.md
Normal file
143
i18n/ko/pages/get-started/first-steps.md
Normal 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)**부터 시작합니다.
|
||||
57
i18n/ko/pages/get-started/index.md
Normal file
57
i18n/ko/pages/get-started/index.md
Normal 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)**에서 다룹니다.
|
||||
47
i18n/ko/pages/get-started/installation.md
Normal file
47
i18n/ko/pages/get-started/installation.md
Normal 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/)를 추가합니다.
|
||||
184
i18n/ko/pages/get-started/real-host.md
Normal file
184
i18n/ko/pages/get-started/real-host.md
Normal 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)**에서 확인하세요.
|
||||
114
i18n/ko/pages/get-started/testing.md
Normal file
114
i18n/ko/pages/get-started/testing.md
Normal 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)**에서 다룹니다.
|
||||
Loading…
Reference in a new issue