Zum Inhalt

Abbruch

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.

Ein Client kann einen Aufruf aufgeben: Die Person am Host hat auf Stopp gedrückt, oder ein Timeout ist abgelaufen.

In diesem Fall bricht das SDK deinen Handler ab. Das await, an dem er gerade wartet, löst eine Exception aus, die Funktion wird abgewickelt, und nichts, was sie zurückgibt, wird gesendet. Die meisten Handler müssen dafür nichts tun.

Für zwei Arten gilt das nicht: einen Handler, der etwas aufzuräumen hat, und einen Handler, der ein einfaches def ist.

In einem async def-Tool aufräumen

Lege den Aufräumcode in ein finally:

server.py
import anyio

from mcp.server import MCPServer

mcp = MCPServer("Bookshop")

holds: set[str] = set()


async def take_payment(title: str) -> None:
    await anyio.sleep(30)  # the customer is typing a card number


async def release_hold(title: str) -> None:
    await anyio.sleep(0.1)  # a round trip to the stock system
    holds.discard(title)


@mcp.tool()
async def order_book(title: str) -> str:
    """Hold a copy of a book while the customer pays for it."""
    holds.add(title)
    try:
        await take_payment(title)
        return f"Ordered {title!r}."
    finally:
        with anyio.move_on_after(5, shield=True):
            await release_hold(title)
  • Das finally läuft, egal wie das Tool endet: ob es zurückgekehrt ist, eine Exception ausgelöst hat oder abgebrochen wurde.
  • Aufräumcode, der await verwenden muss, braucht shield=True. In einem abgebrochenen Handler löst auch jedes weitere await eine Exception aus. Ohne die Abschirmung würde release_hold also schon in seiner ersten Zeile stoppen.
  • Einen abgeschirmten Block kann nichts abbrechen, gib ihm also ein Zeitlimit. Hier sind das 5 Sekunden.

Tip

Greife zu finally, nicht zu except. Der Abbruch muss weiter nach oben wandern, sobald du aufgeräumt hast, und ein finally lässt das zu.

In einem einfachen def-Tool vorzeitig stoppen

Ein einfaches def-Tool läuft in einem Thread, und einen Thread kann nichts von außen unterbrechen. Das Tool muss selbst nachfragen:

server.py
import time

import anyio.from_thread

from mcp.server import MCPServer

mcp = MCPServer("Bookshop")

offline: set[str] = set()


def index_book(title: str) -> None:
    time.sleep(1)  # slow work with nothing to await


@mcp.tool()
def rebuild_index(titles: list[str]) -> str:
    """Take search offline and rebuild its index, one book at a time."""
    offline.add("search")
    try:
        for title in titles:
            anyio.from_thread.check_cancelled()
            index_book(title)
        return f"Indexed {len(titles)} books."
    finally:
        offline.discard("search")
  • anyio.from_thread.check_cancelled() tut nichts, solange der Aufruf aktiv ist, und löst eine Exception aus, sobald er abgebrochen wurde. Rufe die Funktion zwischen den einzelnen Arbeitsschritten auf.
  • Auch hier gehört der Aufräumcode in ein finally. In einem Thread gibt es kein await, er braucht also keine Abschirmung.
  • Ein def-Tool, das nie nachfragt, läuft bis zum Ende, und sein Ergebnis wird verworfen.

Geltungsbereich

Prompt- und Ressourcenfunktionen werden genauso abgebrochen wie Tools.

Über stdio und Streamable HTTP funktioniert das gleich. Mit dem Client dieses SDK heißt aufgeben: den Task abbrechen, der auf call_tool wartet, oder dessen read_timeout_seconds ablaufen lassen.

Warning

Bei zwei Streamable-HTTP-Optionen erfährt dein Handler nichts davon: json_response=True auf einer 2026-07-28-Verbindung und stateless_http=True auf einer Legacy-Verbindung. Dort läuft der Handler bis zum Ende, egal was der Client getan hat.

Zusammenfassung

  • Gibt der Client einen Aufruf auf, bricht das SDK den Handler ab: Tool, Prompt oder Ressource.
  • async def: Räume in einem finally auf und lege Aufräumcode, der await braucht, in anyio.move_on_after(seconds, shield=True).
  • Einfaches def: Rufe zwischen den Arbeitsschritten anyio.from_thread.check_cancelled() auf, sonst läuft das Tool bis zum Ende. Zum Aufräumen genügt ein einfaches finally.
  • json_response=True (moderne Verbindungen) und stateless_http=True (Legacy-Verbindungen) schalten den Abbruch ab.

Fortschritt und Abbruch spielen sich zwischen einem laufenden Tool und seinem Aufrufer ab. Die Zeilen, die es für dich loggt, also für die Person, die den Server betreibt, sind ein anderer Kanal: Logging.