Zum Inhalt

Der Context

Maschinelle Übersetzung

Diese Seite wurde automatisch aus der englischen Dokumentation übersetzt, und die englische Seite ist die maßgebliche Fassung. Wenn sich etwas falsch liest, erklärt Übersetzungen, wie du es melden kannst.

Die Argumente eines Tools kommen vom Modell. Alles andere (der Request, den du gerade bearbeitest, der Server, in dem du lebst, ein Weg zurück zum Client) kommt aus einem einzigen Objekt: dem Context.

Du erzeugst ihn nicht selbst und konfigurierst ihn auch nicht. Du forderst ihn einfach an.

Anfordern

Füge einem beliebigen Tool einen Parameter hinzu, der mit Context annotiert ist:

server.py
from mcp.server import MCPServer
from mcp.server.mcpserver import Context

mcp = MCPServer("Bookshop")


@mcp.tool()
def search_books(query: str, ctx: Context) -> str:
    """Search the catalog by title or author."""
    return f"[request {ctx.request_id}] Found 3 books matching {query!r}."
  • Das SDK baut für jeden Request einen frischen Context und übergibt ihn.
  • Der Name des Parameters spielt keine Rolle. ctx, context, c: Das SDK findet ihn über seine Annotation.
  • Ressourcen und Prompts können ebenfalls einen deklarieren, auf dieselbe Weise.
  • ctx.request_id ist die ID des Requests, den deine Funktion gerade bearbeitet.

Info

Wenn du FastAPI kennst, kennst du diesen Kniff: Deklariere einen Parameter mit dem frameworkeigenen Typ (Request dort, Context hier), und das Framework liefert ihn. Nichts zu registrieren, nichts zu konfigurieren: Die Typannotation ist der ganze Mechanismus.

Für das Modell unsichtbar

Das ist der Teil, den du verinnerlichen solltest. Hier ist das Eingabeschema, das tools/list für search_books meldet:

{
  "type": "object",
  "properties": {
    "query": {"title": "Query", "type": "string"}
  },
  "required": ["query"],
  "title": "search_booksArguments"
}

Eine Eigenschaft. ctx ist kein Argument: Es taucht nie im Schema auf, das Modell erfährt nie davon, und kein Client kann es ausfüllen. Es ist ein Vertrag zwischen dir und dem SDK, unsichtbar auf der Leitung.

Ausprobieren

Starte den Server mit dem MCP Inspector:

uv run mcp dev server.py

Das Formular für search_books hat ein einziges Feld query. Rufe es mit dune auf:

[request 3] Found 3 books matching 'dune'.

Die Zahl ist die Nummer des Requests, der es zufällig war. Rufe das Tool noch einmal auf, und sie ändert sich: Jeder Request bekommt seinen eigenen Context.

Was er dir bietet

Das injizierte Objekt ist klein. Neben request_id:

  • await ctx.read_resource(uri): eine der eigenen Ressourcen des Servers aus einem Tool heraus lesen. Der nächste Abschnitt.
  • await ctx.report_progress(progress, total, message): während eines langen Aufrufs Fortschritt an den Aufrufer zurückstreamen. Alles Weitere steht in Fortschritt.
  • await ctx.elicit(message, schema) und await ctx.elicit_url(...): das Tool anhalten und der Person am Host eine Frage stellen. Das ist Elicitation (Rückfrage bei der Person am Host).
  • ctx.session: die Server-Seite des Gesprächs mit diesem Client. Benachrichtigungen, die du an den Client schickst, leben hier; der letzte Abschnitt nutzt sie.
  • ctx.headers: die Request-Header, die der Transport mitgebracht hat, oder None bei stdio. Einen eigenen Header liest du mit (ctx.headers or {}).get("x-..."). Header sind vom Client gelieferte Eingaben – in Ordnung für eine Locale oder ein Feature-Flag, nie für eine Identität.
  • ctx.request_context: der rohe Datensatz pro Request. Das Feld, nach dem du greifen wirst, ist lifespan_context, das Objekt, das dein Startcode per yield geliefert hat (siehe Lifespan).

Logging steht bewusst nicht auf dieser Liste. Ein Server loggt mit Pythons Modul logging, wie jedes andere Python-Programm. Logging ist die kurze Seite, die erklärt, warum.

Tip

Injiziert wird nur in die Funktion, die du registriert hast. Eine Hilfsfunktion, die dein Tool aufruft, bekommt keinen eigenen Context; reiche ctx als gewöhnliches Argument weiter. Es gibt keinen umgebenden „aktuellen Kontext“, den du von woanders holen könntest.

Eigene Ressourcen lesen

Die Ressourcen eines Servers sind nicht nur für Clients da. Auch ein Tool kann sie lesen:

server.py
from mcp.server import MCPServer
from mcp.server.mcpserver import Context

mcp = MCPServer("Bookshop")


@mcp.resource("catalog://genres")
def genres() -> str:
    """The genres the catalog is organised into."""
    return "fiction, non-fiction, poetry"


@mcp.tool()
async def describe_catalog(ctx: Context) -> str:
    """Describe how the catalog is organised."""
    [contents] = await ctx.read_resource("catalog://genres")
    return f"The catalog is organised into: {contents.content}"

ctx.read_resource löst den URI über dieselbe Registry auf, die auch resources/read bedient. Ein Tool bekommt also, was ein Client bekäme: ein Iterable von ReadResourceContents, eines pro Content-Block. Für diesen URI gibt es einen:

contents.content    # 'fiction, non-fiction, poetry'
contents.mime_type  # 'text/plain'
  • content ist genau das, was genres() zurückgegeben hat. Eine einzige Quelle der Wahrheit: Der Client durchstöbert die Ressource, deine Tools konsumieren sie, niemand kopiert den String.
  • Der einzige Parameter von describe_catalog ist der Context, daher hat sein Eingabeschema überhaupt keine Eigenschaften. Das Modell ruft es mit {} auf.

Dem Client mitteilen, dass sich die Liste geändert hat

Was ein Server anbietet, steht nicht zur Importzeit fest. Registriere ein Tool zur Laufzeit und teile es dann dem Client mit:

server.py
from mcp.server import MCPServer
from mcp.server.mcpserver import Context

mcp = MCPServer("Bookshop")


def recommend_book(genre: str) -> str:
    """Recommend a book in the given genre."""
    return f"In {genre}, try 'Dune'."


@mcp.tool()
async def enable_recommendations(ctx: Context) -> str:
    """Switch on the recommendation tool."""
    mcp.add_tool(recommend_book)
    await ctx.session.send_tool_list_changed()
    return "Recommendations are now available."
  • mcp.add_tool(recommend_book) registriert eine gewöhnliche Funktion als Tool: Name, Beschreibung und Schema werden genau so abgeleitet, wie @mcp.tool() es getan hätte.
  • await ctx.session.send_tool_list_changed() sendet notifications/tools/list_changed. Ein Client, der das empfängt, ruft tools/list erneut auf und sieht recommend_book.

Die Geschwister sind send_resource_list_changed(), send_prompt_list_changed() und send_resource_updated(uri) für eine Änderung an einer bestimmten Ressource.

Auf einer Verbindung mit 2026-07-28 empfangen Clients Änderungsbenachrichtigungen nur auf einem subscriptions/listen-Stream, den sie selbst geöffnet haben. Die send_*-Methoden oben erreichen diese Streams daher nicht. Die Publish-Methoden des Context liefern an alle abonnierten Streams gleichzeitig aus: await ctx.notify_tools_changed(), await ctx.notify_prompts_changed(), await ctx.notify_resources_changed() und await ctx.notify_resource_updated(uri). Alles Weitere, einschließlich der horizontalen Skalierung über Replikate, steht in Abonnements.

Check

Bevor jemand enable_recommendations ausführt, existiert das Tool, das du versprichst, nicht. Rufst du es trotzdem auf, ist das Ergebnis ein Fehler, den das Modell lesen kann:

Unknown tool: recommend_book

Führe enable_recommendations aus, und genau derselbe Aufruf gelingt. Die Tool-Liste ist wirklich dynamisch: tools/list spiegelt wider, was gerade jetzt registriert ist.

Zusammenfassung

  • Annotiere einen Parameter mit Context (in einem Tool, einer Ressource oder einem Prompt), und das SDK injiziert ihn. Der Name gehört dir.
  • Er ist für das Modell unsichtbar: Das Eingabeschema enthält immer nur deine echten Argumente.
  • ctx.request_id identifiziert den Request; ctx.request_context.lifespan_context ist das, was dein Startcode per yield geliefert hat.
  • Mit await ctx.read_resource(uri) liest ein Tool die eigenen Ressourcen des Servers.
  • ctx.session ist der Kanal zurück zum Client: send_tool_list_changed() und seine Geschwister sagen ihm, dass er eine Liste, die du geändert hast, erneut abrufen soll.
  • Auch Fortschrittsmeldungen und Elicitation beginnen beim Context; beide haben ihre eigene Seite.

Parameter, die das Modell nie sieht und die deine eigenen Funktionen füllen, sind Abhängigkeiten.