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,144 @@
---
translation:
sections: [0d6c05bcbf836bf3, 59a7b14eeefc68c1, 7114d8d6daba203f, e8bbb56a98ba7bc9, 5138010f6159901c, f78da7c7c363d4c6, 220a939cab348686]
tool: 1
---
# Erste Schritte {#first-steps}
Die **[Startseite](../index.md)** legt ein hohes Tempo vor: einen Server schreiben, ihn starten, ein Tool aufrufen.
Diese Seite geht es langsam an – mit allen drei Dingen, die ein Server bereitstellen kann, und einem Namen für alles, was unterwegs auftaucht.
## Host, Client und Server {#host-client-and-server}
Drei Wörter, die dir ab hier auf jeder Seite begegnen:
* Ein **Host** ist die LLM-Anwendung: Claude, eine IDE, eine Agent-Laufzeitumgebung. Mit ihm spricht die Person.
* Ein **Client** lebt im Host und spricht MCP. Der Host betreibt einen Client pro Server, mit dem er verbunden ist.
* Ein **Server** ist das, was du mit diesem SDK baust. Er stellt Clients Dinge bereit. Mit dem Modell spricht er nie direkt.
Du schreibst den Server. Hosts sind das Produkt anderer. Das SDK gibt dir außerdem einen `Client`. Mit ihm testest du deine Server, und er taucht weiter unten auf dieser Seite auf.
## Die drei Primitive {#the-three-primitives}
Ein Server stellt genau drei Arten von Dingen bereit. Was sie unterscheidet, ist, **wer über ihren Einsatz entscheidet**:
| Primitiv | Gesteuert von | Was es ist | Beispiel |
|----------------|--------------------|--------------------------------------------------------------------|-----------------------------------------|
| **Tools** | Dem Modell | Eine Funktion, die das Modell aufruft, um etwas zu tun | Ein API-Aufruf, ein Datenbank-Schreibzugriff |
| **Ressourcen** | Der Anwendung | Daten, die der Host in den Kontext des Modells lädt | Der Inhalt einer Datei, eine API-Response |
| **Prompts** | Der Person am Host | Eine wiederverwendbare Nachrichtenvorlage, die die Person über ihren Namen aufruft | Ein Slash-Befehl, ein Menüeintrag |
„Gesteuert von“ ist der ganze Sinn dieser Aufteilung. Ein Tool läuft, weil das **Modell** entschieden hat, es aufzurufen. Eine Ressource wird angehängt, weil die **Anwendung** entschieden hat, dass das Modell sie braucht. Ein Prompt läuft, weil die **Person** ihn ausgewählt hat.
!!! info
Wenn du schon einmal eine Web-API gebaut hast, hast du das meiste Gespür bereits: Eine
**Ressource** ist ein `GET` (sie lädt Daten und ändert nichts) und ein **Tool** ist ein `POST`
(es erledigt Arbeit und kann Seiteneffekte haben). Ein **Prompt** hat keine HTTP-Entsprechung;
er ähnelt eher einer gespeicherten Abfrage, die die Person über ihren Namen ausführt.
## Ein Server, alle drei {#one-server-all-three}
```python title="server.py" hl_lines="6 12 18"
--8<-- "docs_src/first_steps/tutorial001.py"
```
Drei gewöhnliche Funktionen, drei Dekoratoren. Jeder Dekorator ist die gesamte Registrierung:
* `@mcp.tool()` macht `add` zu einem **Tool**.
* `@mcp.resource("greeting://{name}")` macht `greeting` zu einem **Ressourcen-Template**: Das `{name}` im URI ist der Parameter der Funktion.
* `@mcp.prompt()` macht `summarize` zu einem **Prompt**. Der String, den die Funktion zurückgibt, wird zu einer User-Nachricht.
Alles andere (den Namen, die Beschreibung, das Argument-Schema) liest das SDK aus der Funktion selbst: ihrem Namen, ihrem Docstring, ihren Type Hints. Du hast nichts davon separat deklariert.
!!! tip
Die beiden Hälften des SDK haben zwei Importpfade: `from mcp import Client` und
`from mcp.server import MCPServer`. Ein `from mcp import MCPServer` gibt es nicht.
### Ausprobieren {#try-it}
Starte ihn mit dem MCP Inspector:
```console
uv run mcp dev server.py
```
Öffne die URL, die er ausgibt. Der Inspector hat einen Tab pro Primitiv; geh sie der Reihe nach durch.
**Tools.** Ein Eintrag: `add`, beschrieben als *Add two numbers.* Das Formular hat ein erforderliches Ganzzahlfeld für `a` und ein weiteres für `b`. Füll sie aus, ruf das Tool auf, und das Ergebnis ist `3`. Der Inspector hat dieses Formular aus `a: int, b: int` gebaut. Jeder andere Client macht es genauso.
**Resources.** Die Liste *Resources* ist leer. `greeting` steht unter **Resource Templates**, weil `greeting://{name}` einen Parameter hat: Es gibt keine einzelne Ressource aufzulisten, bis jemand einen `name` liefert. Gib ihm `World` und lies sie:
```text
Hello, World!
```
**Prompts.** Ein Eintrag: `summarize`, mit einem einzigen erforderlichen Argument `text`. Ruf ihn mit etwas Text ab, und du erhältst eine Nachricht mit `role: user` und deinem gerenderten String als Inhalt. Mehr ist ein Prompt nicht: eine Funktion, die Nachrichten baut.
Der Inspector hat deinen Server über **stdio** betrieben, einen der Transporte, die ein MCP-Server sprechen kann. Du wählst noch keinen aus; dafür gibt es die Seite **[Den Server betreiben](../run/index.md)**.
## Capabilities {#capabilities}
Du hast im Inspector drei Tabs gesehen. Woher wusste er, dass es drei sind?
Wenn sich ein Client verbindet, deklariert der Server seine **Capabilities**: welche Familien von Requests er beantwortet. Der Client entscheidet anhand dieser Deklaration, wonach er überhaupt fragt. Du hast sie nie geschrieben; `MCPServer` deklariert sie für dich.
Sieh es dir selbst an. Der `Client` des SDK nimmt das Server-Objekt direkt entgegen und verbindet sich **im Speicher** damit (kein Subprozess, kein Port):
```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}}
```
Dieses Dictionary sind die deklarierten **Capabilities** deines Servers. Es ist das Erste, was jeder Client beim Verbinden erfährt:
| Capability | Der Client darf jetzt aufrufen |
|-------------|-------------------------------------------------------------|
| `tools` | `tools/list`, `tools/call` |
| `resources` | `resources/list`, `resources/templates/list`, `resources/read` |
| `prompts` | `prompts/list`, `prompts/get` |
`MCPServer` bedient alle drei Primitive, also werden immer alle drei deklariert.
Achte darauf, was fehlt. `completions` (die automatische Vervollständigung von Argumenten für Ressourcen-Templates und Prompts) braucht einen Handler, den du schreibst. Dieser Server hat keinen, also fehlt die Capability, und ein wohlerzogener Client fragt gar nicht erst. Das ist die Regel für alles Optionale: Registriere das Ding, und die Capability erscheint; **[Vervollständigungen](../servers/completions.md)** zeigt es.
!!! info
`Client(mcp)` ist derselbe In-Memory-Client, mit dem jedes Beispiel in dieser Dokumentation
getestet wird, und so testest du auch deine. Er bekommt eine ganze Seite: **[Testen](testing.md)**.
## Was du nicht geschrieben hast {#what-you-did-not-write}
Blick auf diese Seite zurück. Du hast drei kleine Python-Funktionen geschrieben. **Nicht** geschrieben hast du:
* Ein JSON-Schema. `a: int, b: int` *ist* das Schema für `add`.
* Einen Request-Handler. `tools/list`, `resources/read`, `prompts/get`: alles für dich bedient.
* Eine Capability-Deklaration. `MCPServer` hat sie für dich erstellt.
* Eine Zeile Protokoll. Die Versionsaushandlung, das JSON-RPC-Framing, der Austausch der Capabilities: Das alles passierte in `mcp dev` und `Client(mcp)`, und du hast es nie gesehen.
Dieses Verhältnis ist der ganze Sinn des SDK.
## Zusammenfassung {#recap}
* Ein **Host** ist die LLM-App, ein **Client** ist ihre MCP-sprechende Hälfte, ein **Server** ist das, was du baust.
* Tools steuert das **Modell**, Ressourcen steuert die **Anwendung**, Prompts steuert die **Person**.
* Ein Dekorator pro Primitiv: `@mcp.tool()`, `@mcp.resource(uri)`, `@mcp.prompt()`. Name, Beschreibung und Schema kommen aus der Funktion.
* Ein URI mit einem `{param}` ergibt ein Ressourcen-**Template**, das getrennt von konkreten Ressourcen aufgelistet wird.
* Die **Capabilities** des Servers werden für dich deklariert, und ein Client fragt nur nach dem, was ein Server deklariert.
* `Client(mcp)` verbindet sich im Speicher mit dem Server-Objekt: deine Testumgebung vom ersten Tag an.
Als Nächstes kommt **[Mit einem echten Host verbinden](real-host.md)**: dieser Server in Claude Desktop oder einer IDE, in echt. Danach **[Testen](testing.md)**: eine Seite, ein In-Memory-Client, und du musst nie raten, ob es funktioniert. Danach bekommt jedes Primitiv seine eigene Seite, angefangen mit dem, das das Modell steuert: **[Tools](../servers/tools.md)**.

View file

@ -0,0 +1,57 @@
---
translation:
sections: [ed4a756b4c53c585, 97e2fb315b7fe398, 4d04f1c6f4bf6c1d, 577d73078fc62baf]
tool: 1
---
# Einstieg {#get-started}
Neu bei MCP oder neu bei diesem SDK? Fang hier an. Diese Seiten bringen dich von null zu einem
funktionierenden, getesteten Server: [das SDK installieren](installation.md), den
[ersten Server](first-steps.md) bauen, [ihn mit einem echten Host verbinden](real-host.md) und
[ihn testen](testing.md) – mit einem In-Memory-Client.
## Den Code ausführen {#run-the-code}
Alle Codeblöcke lassen sich direkt kopieren und verwenden: Es sind vollständige, lauffähige Dateien.
Um mitzumachen, füge einen Block in eine `server.py` ein und öffne sie im MCP Inspector:
```console
uv run mcp dev server.py
```
Es wird **DRINGEND empfohlen**, den Code selbst zu schreiben (oder zu kopieren), ihn zu bearbeiten und lokal auszuführen. Erst im eigenen Editor zeigt sich, worum es geht: wie wenig du schreibst, die Autovervollständigung, die Typprüfungen, die Fehler abfangen, bevor du überhaupt etwas ausführst.
## Kein Rätselraten {#you-will-not-be-guessing}
Jedes Beispiel in dieser Dokumentation ist eine vollständige Datei unter [`docs_src/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/docs_src) im Repository des SDK selbst, und jedes einzelne wird von der Testsuite des SDK über einen **In-Memory-Client** ausgeführt:
```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}
```
Kein Subprozess, kein Port, kein Transport. `Client(mcp)` verbindet sich direkt mit dem Server-Objekt.
Wenn eine Änderung am SDK ein Beispiel auf einer dieser Seiten kaputt macht, wird die CI rot, bevor es die Seite tut. Der Code, den du hier liest, ist der Code, der läuft.
Das wirst du in [Testen](testing.md) selbst verwenden; so testest du auch deine eigenen Server.
## Wie es weitergeht {#where-to-go-next}
Sobald ein Server läuft, ist der Rest dieser Dokumentation ein Nachschlagewerk, kein Kurs.
Jede Seite steht für sich, spring also direkt zu dem, was du brauchst:
* Was ein Server bereitstellt (Tools, Ressourcen, Prompts), steht in **[Server](../servers/index.md)**.
* Was innerhalb der Funktionen, die du registrierst, verfügbar ist, steht in **[Im Handler](../handlers/index.md)**.
* Wie du ihn vor Clients bringst (stdio, HTTP, deine bestehende FastAPI-App), steht in **[Den Server betreiben](../run/index.md)**.
* Wie du die andere Seite baust, eine Anwendung, die MCP-Server *nutzt*, steht in **[Clients](../client/index.md)**.

View file

@ -0,0 +1,48 @@
---
translation:
sections: [6e2f9bab94d5ed36, 8cf653388f69e28b, 6fd9ea2f65de0df6]
tool: 1
---
# Installation {#installation}
Das Python-SDK liegt auf PyPI als [`mcp`](https://pypi.org/project/mcp/). Es setzt **Python 3.10+** voraus.
Diese Dokumentation beschreibt **v2**, die aktuelle stabile Release-Linie:
=== "uv"
```bash
uv add "mcp[cli]"
```
=== "pip"
```bash
pip install "mcp[cli]"
```
!!! note "Umstieg von v1?"
v2 ist eine Hauptversion mit inkompatiblen Änderungen; der **[Migrationsleitfaden](../migration.md)**
behandelt jede einzelne davon. Wenn dein *Paket* von `mcp` abhängt und noch nicht bereit für die
Migration ist, behalte eine Obergrenze `<2` bei (zum Beispiel `mcp>=1.28,<2`), damit eine nicht
gepinnte Auflösung auf der 1.x-Linie bleibt.
## Was installiert wird {#what-gets-installed}
Nichts davon musst du wissen, um das SDK zu nutzen. Falls du dich aber fragst, wozu die einzelnen Abhängigkeiten da sind:
* `mcp-types`: jeder Protokolltyp (Requests, Ergebnisse, Content-Blöcke) als eigenes Paket, im Gleichschritt mit dem SDK versioniert. Code, der von `mcp` abhängt, importiert es über den Alias `mcp.types` (jedes `from mcp.types import ...` in dieser Dokumentation); importiere `mcp_types` nur in einem Projekt direkt, das `mcp-types` ohne das SDK installiert.
* [`anyio`](https://anyio.readthedocs.io/): die asynchrone Laufzeit. Das gesamte SDK ist gegen anyio geschrieben und läuft daher sowohl auf `asyncio` als auch auf `trio`.
* [`pydantic`](https://docs.pydantic.dev/): die Grundlage jedes `mcp.types`-Modells, dazu die gesamte Schema-Generierung und Validierung.
* [`httpx2`](https://pypi.org/project/httpx2/): der HTTP-Client hinter den *Client*-Transporten für Streamable HTTP und SSE, mit eingebauter Unterstützung für Server-Sent Events.
* [`starlette`](https://www.starlette.io/), [`uvicorn`](https://www.uvicorn.org/), [`sse-starlette`](https://pypi.org/project/sse-starlette/) und [`python-multipart`](https://pypi.org/project/python-multipart/): die *Server*-Transporte für HTTP.
* [`jsonschema`](https://pypi.org/project/jsonschema/): validiert die strukturierte Ausgabe eines Tools gegen das deklarierte Output-Schema.
* [`pyjwt[crypto]`](https://pyjwt.readthedocs.io/): Verarbeitung von OAuth-Tokens für die Autorisierung.
* [`opentelemetry-api`](https://opentelemetry-python.readthedocs.io/): nur die schlanke API. Die Tracing-Middleware des SDK kostet also nichts, solange du nicht selbst ein OpenTelemetry-SDK samt Exporter installierst.
* [`typing-extensions`](https://typing-extensions.readthedocs.io/) und [`typing-inspection`](https://pypi.org/project/typing-inspection/): moderne Typing-Features auf Python 3.10.
* [`pywin32`](https://pypi.org/project/pywin32/): nur unter Windows, für die Verwaltung von `stdio`-Subprozessen.
## Optionale Extras {#optional-extras}
* `mcp[cli]` ergänzt [`typer`](https://typer.tiangolo.com/) und [`python-dotenv`](https://pypi.org/project/python-dotenv/) für das Kommandozeilen-Tool `mcp` (`mcp dev`, `mcp run`, `mcp install`). Während der Entwicklung wirst du das haben wollen; in einem bereitgestellten Server brauchst du es womöglich nicht.
* `mcp[rich]` ergänzt [`rich`](https://rich.readthedocs.io/) für schönere Server-Logs.

View file

@ -0,0 +1,186 @@
---
translation:
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
tool: 1
---
# Mit einem echten Host verbinden {#connect-to-a-real-host}
Ein **Host** ist die Anwendung, in der dein Server am Ende läuft: Claude Desktop, Claude Code, eine IDE. Der Host ist das, womit die Person spricht. In ihm startet ein MCP-**Client** deinen Server als Kindprozess und spricht mit ihm über stdin und stdout dieses Prozesses.
Das heißt: Sich mit einem Host zu verbinden ist eine einzige Handlung. Du nennst ihm **den Befehl, der deinen Server startet**. Alles auf dieser Seite (zwei CLI-Befehle, drei JSON-Dateien) ist nur ein anderer Ort für genau diesen Befehl.
## Ein Server, jeder Host {#one-server-every-host}
```python title="server.py" hl_lines="3 33-34"
--8<-- "docs_src/real_host/tutorial001.py"
```
Zwei Tools und eine Ressource, eine Datei. Drei Dinge an dieser Datei sind für jeden der folgenden Hosts wichtig:
* `mcp.run()` ohne Argumente startet einen **stdio**-Server: Er blockiert, liest Protokollnachrichten von stdin und schreibt sie auf stdout. Das ist der Transport, den jeder Host auf dieser Seite spricht. Der Host startet deine Datei als Kindprozess und besitzt diese beiden Pipes – deshalb bedeutet Verbinden immer nur „hier ist der Befehl“. Du wählst nie einen Port, und nichts lauscht auf einem.
* `run()` steht unter `if __name__ == "__main__":`. Alles Folgende **importiert** diese Datei, statt sie auszuführen. Ein ungeschütztes `run()` würde also einen Server starten, sobald irgendetwas das Modul lädt.
* Das Server-Objekt ist eine globale Variable auf Modulebene namens `mcp`. Nach diesem Namen sucht `mcp run` (`server` und `app` funktionieren auch). Nennst du es anders, gibst du den Namen explizit an: `mcp run server.py:bookshop`.
Das war die letzte Zeile Python auf dieser Seite. Ab hier geht es nur noch um Host-Konfiguration.
## Der Startbefehl {#the-launch-command}
Jeder der folgenden Hosts bekommt denselben Befehl:
```bash
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
```
Ein Befehl für alle, weil `uv run --with` das SDK an Ort und Stelle in eine frische Umgebung auflöst: Er funktioniert aus jedem Verzeichnis und braucht weder ein Projekt noch eine virtuelle Umgebung, die du aktivieren müsstest. Das zählt hier mehr als irgendwo sonst, denn ein Host startet deinen Server aus *seinem* Arbeitsverzeichnis mit einer fast leeren Umgebung, nicht aus deiner Shell.
Es ist außerdem der Befehl, den `mcp install` für dich in die Konfiguration von Claude Desktop schreibt (siehe unten). Was du von Hand tippst und was das Tool erzeugt, stimmt also überein – bis auf die exakte Versionsangabe, die das Tool ergänzt.
!!! tip "Wenn ein Host `uv` nicht findet"
Ein Host startet deinen Server mit einem minimalen `PATH`, und `uv` liegt womöglich nicht
darauf. Ersetze das bloße `uv` durch den absoluten Pfad aus `which uv` (macOS/Linux) oder
`where uv` (Windows). Genau das schreibt auch `mcp install`.
!!! note "Diese Seite beschreibt den lokalen Fall"
Alles hier betreibt deinen Server auf der Maschine, auf der auch der Host läuft: Der Host
startet deine Datei, über stdio. Für ein persönliches Tool oder eines für einen einzelnen
Rechner ist das genau richtig. Um einen Server an Leute zu geben, die deine Datei *nicht*
haben, verteilst du eine **URL**, keinen Befehl: dasselbe `mcp`-Objekt, ausgeliefert über
Streamable HTTP. **[Den Server betreiben](../run/index.md)** fasst diese Entscheidung in
einer Tabelle zusammen, und **[Bereitstellen und skalieren](../run/deploy.md)** ist der Weg
von dort zu einem echten Hostnamen.
Und ein Host ist nichts weiter als eine Anwendung mit einem MCP-Client darin. Dein eigenes
Python kann also die Rolle des Hosts übernehmen: **[Client-Transporte](../client/transports.md)**
startet genau diese Datei als Subprozess mit `stdio_client(...)`, und **[Testen](testing.md)**
verbindet sich im Speicher mit ihr, ganz ohne Prozess.
## Claude Desktop {#claude-desktop}
Der eine Host, den das SDK für dich konfigurieren kann:
```bash
uv run mcp install server.py
```
Das ist alles. `mcp install` importiert die Datei, um den Namen des Servers zu lesen, findet die Konfigurationsdatei von Claude Desktop und schreibt den Startbefehl hinein. Nebenbei wandelt es deinen Pfad in einen absoluten um, damit du es nicht tun musst.
Daran ist nichts Geheimnisvolles. Das ist der Eintrag, den es schreibt:
```json
{
"mcpServers": {
"Bookshop": {
"command": "/absolute/path/to/uv",
"args": [
"run",
"--frozen",
"--with",
"mcp[cli]==2.0.0",
"mcp",
"run",
"/absolute/path/to/server.py"
]
}
}
}
```
Das ist der Startbefehl aus dem Abschnitt oben mit drei Ergänzungen: dem absoluten Pfad zu `uv`, `--frozen`, damit `uv` nie ein Lockfile umschreibt, das zufällig in der Nähe liegt, und einer exakten Festlegung auf die `mcp`-Version, die du installiert hast. Er landet in `claude_desktop_config.json`, und die liegt hier:
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
Du kannst diese Datei von Hand schreiben. `mcp install` gibt es, damit dir dabei nicht der klassische Fehler (ein relativer Pfad) unterläuft.
Beende Claude Desktop vollständig (nicht nur das Fenster) und öffne es erneut.
!!! warning
`mcp install` schlägt mit `Claude app not found` fehl, wenn das *Konfigurationsverzeichnis*
von Claude Desktop noch nicht existiert. Installiere Claude Desktop und starte es einmal:
Dabei wird das Verzeichnis angelegt.
!!! tip
Claude Desktop startet deinen Server in einem eigenen Prozess, die Umgebungsvariablen deiner
Shell sind dort also nicht vorhanden. `uv run mcp install server.py -v API_KEY=abc123` (oder
`-f .env`) trägt sie in das Feld `env` des Eintrags ein. `--name` überschreibt den Namen des
Eintrags; standardmäßig ist es der `name` des Servers.
## Claude Code {#claude-code}
Es gibt keine Datei zu bearbeiten. Registriere den Server mit dem `claude`-CLI; alles nach `--` ist der Startbefehl.
```bash
claude mcp add bookshop -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
```
Führe `/mcp` in einer Claude-Code-Session aus, um zu prüfen, dass `bookshop` verbunden ist und seine Tools aufgelistet werden.
## Cursor {#cursor}
Lege `.cursor/mcp.json` im Wurzelverzeichnis deines Projekts an.
```json
{
"mcpServers": {
"bookshop": {
"command": "uv",
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
}
}
}
```
Dasselbe `command` plus `args`, unter demselben Schlüssel `mcpServers`, den auch Claude Desktop verwendet. Der Server erscheint in den MCP-Einstellungen von Cursor mit beiden Tools.
## VS Code {#vs-code}
Lege `.vscode/mcp.json` im Wurzelverzeichnis deines Projekts an.
```json
{
"servers": {
"bookshop": {
"type": "stdio",
"command": "uv",
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
}
}
}
```
Zwei Unterschiede zur Datei von Cursor, und es sind die einzigen zwei: Der umschließende Schlüssel heißt `servers`, nicht `mcpServers`, und jeder Eintrag deklariert seinen `type`. Bestätige die Vertrauensabfrage, dann zeigt **MCP: List Servers** in der Befehlspalette `bookshop` als laufend an.
!!! note
Du brauchst VS Code 1.99 oder neuer mit angemeldeter **GitHub Copilot**-Erweiterung (Copilot
Free genügt), und Copilot Chat muss im Modus **Agent** sein, denn kein anderer Modus ruft
Tools auf.
## Der Server erscheint nicht {#it-doesnt-show-up}
Bevor du irgendeine Host-Konfiguration anfasst, führe den Startbefehl selbst aus:
```bash
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
```
Es wird nichts ausgegeben, und der Befehl kehrt nicht zurück. Diese Stille ist richtig: Ein stdio-Server wartet darauf, dass ein Host zuerst auf stdin spricht (`Ctrl-C` beendet ihn). Ein Traceback oder ein sofortiges Beenden ist der eigentliche Fehler – und jetzt kannst du ihn lesen, statt ihn durch einen Host hindurch zu erraten.
Sobald der Befehl dasteht und wartet, bleibt fast immer eine von drei Ursachen:
* **Ein relativer Pfad.** Der Host startet deinen Server aus *seinem* Arbeitsverzeichnis, nicht aus dem, in dem du ihn registriert hast. `server.py`, wo `/absolute/path/to/server.py` nötig wäre, ist der mit Abstand häufigste Fehler. Findet der Host auch `uv` nicht, muss dieser Pfad ebenfalls absolut sein.
* **Der Host läuft noch mit seiner alten Konfiguration.** Hosts lesen ihre Konfiguration beim Start. Gerade Claude Desktop musst du *vollständig beenden* (nicht nur das Fenster schließen) und neu öffnen, bevor eine Änderung an `claude_desktop_config.json` wirkt.
* **Etwas hat stdout außerhalb des umgeleiteten Zeitfensters erreicht.** Bei stdio *ist* stdout das Protokoll. Das SDK leitet während des Betriebs geflushte Streuausgaben nach stderr um. Aber Ausgaben, die vorher auf stdout geflusht werden (ein Wrapper-Skript mit echo, ein `print()` zur Importzeit in einem ungepufferten Prozess), oder ein gepuffertes `print()`, das beim Beenden des Interpreters geleert wird, übergeben dem Host eine kaputte Nachricht, und er trennt die Verbindung. Logge mit der Standardkonfiguration von `logging`, deren stderr-Handler jeden Eintrag sofort flusht; eigene Handler müssen stdout ebenfalls meiden. Alles Weitere steht in **[Logging](../handlers/logging.md)**.
Claude Desktop führt pro Server ein Log: `mcp-server-<NAME>.log` ist das stderr deines Servers, neben `mcp.log` für Verbindungen, unter `~/Library/Logs/Claude` auf macOS und `%APPDATA%\Claude\logs` auf Windows.
Für alles jenseits dieser drei ist **[Fehlerbehebung](../troubleshooting.md)** die richtige Seite.
## Zusammenfassung {#recap}
* Ein **Host** (Claude Desktop, eine IDE) betreibt einen MCP-Client, der deinen Server als Kindprozess über stdio startet. Verbinden heißt, ihm einen einzigen Startbefehl zu geben.
* Dieser Befehl lautet `uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py`: kein venv zu aktivieren, funktioniert aus jedem Verzeichnis.
* **Claude Desktop** ist der eine Host, den `mcp install` für dich konfiguriert. Es schreibt genau diesen Befehl (plus den absoluten Pfad zu `uv`, `--frozen` und eine exakte Festlegung auf die installierte Version) in `claude_desktop_config.json`, damit du es nie selbst tun musst.
* **Claude Code** ist `claude mcp add bookshop -- <launch command>`. **Cursor** ist `.cursor/mcp.json` unter `mcpServers`. **VS Code** ist `.vscode/mcp.json` unter `servers`, jeder Eintrag mit einem `type`.
* Überall absolute Pfade, den Host nach jeder Änderung an seiner Konfiguration neu starten, und nie etwas anderes als das SDK auf stdout schreiben lassen.
Jeder Host auf dieser Seite hat sich mit derselben Datei verbunden, mit demselben Befehl. Was diese Datei *bereitstellen* kann, ist der Rest dieser Dokumentation: **[Tools](../servers/tools.md)**, **[Ressourcen](../servers/resources.md)** und jeder Transport außer stdio in **[Den Server betreiben](../run/index.md)**.

View file

@ -0,0 +1,115 @@
---
translation:
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
tool: 1
---
# Testen {#testing}
Das Python SDK bringt eine Klasse `Client` mit einem **In-Memory-Transport** mit: Übergib ihr dein Server-Objekt, und sie verbindet sich direkt damit.
Kein Subprozess. Kein Port. Überhaupt kein Transport. Die Idee ist dieselbe wie bei FastAPIs `TestClient`.
## Grundlegende Verwendung {#basic-usage}
Nehmen wir an, du hast einen einfachen Server mit einem einzigen Tool:
```python title="server.py"
--8<-- "docs_src/testing/tutorial001.py"
```
Um den Test unten auszuführen, brauchst du zwei zusätzliche (Entwicklungs-)Abhängigkeiten:
=== "uv"
```bash
uv add --dev pytest inline-snapshot
```
=== "pip"
```bash
pip install pytest inline-snapshot
```
!!! info
Diese Dokumentation geht davon aus, dass du [`pytest`](https://docs.pytest.org/en/stable/) bereits kennst.
[`inline-snapshot`](https://15r10nk.github.io/inline-snapshot/latest/) nutzt der Test unten,
um in einer Zeile auf das gesamte Ergebnisobjekt zu prüfen. Es zeichnet die Ausgabe eines Tests
als das `snapshot(...)`-Literal auf, das du siehst. Wenn du es lieber nicht verwenden möchtest,
lass den Import weg und prüfe die Felder, die dich interessieren (`result.content[0].text == "3"`),
wie in jedem anderen Test.
Jetzt der Test:
```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. Wenn du `trio` verwendest, gib stattdessen `"trio"` zurück. Die Details stehen in der [anyio-Dokumentation](https://anyio.readthedocs.io/en/stable/testing.html#specifying-the-backends-to-run-on).
2. Das Fixture liefert einen verbundenen Client. Jeder Test, der `client` entgegennimmt, bekommt eine frische In-Memory-Verbindung zum selben Server.
Das war's. Jetzt kannst du deine Tests um weitere Szenarien erweitern.
## Warum `raise_exceptions=True`? {#why-raise_exceptionstrue}
Zwei verschiedene Dinge können schiefgehen, und dieses Flag betrifft nur eines davon.
Eine Exception in einem **deiner Tools** ist kein Protokollfehler. Sie wird zu einem normalen Ergebnis mit
`is_error=True`, und das Modell liest die Meldung. `raise_exceptions` ändert daran nichts: Mit oder
ohne das Flag gibt `call_tool` dasselbe Ergebnis mit `is_error=True` zurück. Dazu gibt es eine ganze Seite:
**[Fehler behandeln](../servers/handling-errors.md)**.
Ein Fehler **außerhalb** eines Tool-Bodys ist etwas anderes. Auf der Verbindung, die dir `Client(mcp)` gibt,
bereinigt der Server ihn zu einem allgemeinen `"Internal server error"`, bevor der Client ihn sieht. Du solltest
die Details eines unerwarteten Absturzes niemals an einen entfernten Aufrufer durchsickern lassen. In einem Test ist das
genau das, was du *nicht* willst, und genau das ändert `raise_exceptions=True`: Dein Test sieht die echte Meldung
statt der bereinigten.
Lass es in Tests eingeschaltet. In Produktionscode hat es keine Bedeutung.
## Standardmäßig im selben Prozess {#in-process-by-default}
!!! note
`Client(mcp)` verbindet sich im selben Prozess und ist standardmäßig **generationsneutral** (era-neutral): Er prüft den Server und
wählt den passenden Protokollpfad. Lege `mode="legacy"` fest, wenn dein Test Legacy-spezifische
Semantik prüft (Sampling- oder Elicitation-Push – Elicitation ist die Rückfrage bei der Person am Host –, `message_handler`), und lass `raise_exceptions=True`
dort weg: Eine Legacy-Verbindung bereinigt von vornherein nie, und das Flag löst den
Fehler erneut in der Server-Task aus statt in deinem Test.
Diese eine Zeile ist auch der Grund, warum diese Dokumentation dir versprechen kann, dass ihre Beispiele funktionieren: Jede
Beispieldatei wird von der Test-Suite des SDK selbst ausgeführt, fast alle über genau diesen
Client. Du verwendest dasselbe Tool, das das SDK auf sich selbst anwendet.
Du hast einen funktionierenden, getesteten Server. Wie du ihn in eine echte Anwendung (Claude Desktop, eine
IDE) einbindest, steht in **[Mit einem echten Host verbinden](real-host.md)**; jede andere Art, ihn zu betreiben, in
**[Den Server betreiben](../run/index.md)**.