8 Min. Lesezeit

Verwaiste MCP-Prozesse können weiterhin Arbeit hinterlassen

Verwaiste MCP-Prozesse brauchen mehr als einen kill-Befehl. Finde veraltete stdio-Server, widerrufe ihre Berechtigungen und verknüpfe die letzten Aufrufe mit der zuständigen Sitzung.

Verwaiste MCP-Prozesse können weiterhin Arbeit hinterlassen

Ein Absturz des Clients sagt dir nicht, ob sein MCP-Server beendet wurde. Er sagt nur, dass ein Prozess beendet wurde oder zumindest nicht mehr reagiert. Der Unterschied ist wichtig, denn stdio ist ein Transportweg und kein Abschalter für Arbeit, die bereits eine Prozessgrenze überschritten hat.

Ich habe erlebt, wie Teams eine Ansammlung alter Hilfsprozesse als Aufräumproblem behandelten und dann feststellten, dass einer davon noch ein Cloud-Token, einen SSH-Kontrollsocket oder eine Aufgabenwarteschlange besaß, die niemand dem fehlgeschlagenen Lauf zugeordnet hatte. Der Bereinigungsbefehl war einfach. Nachzuvollziehen, wer die letzte Aktion autorisiert hatte, war der ausgelassene Teil.

Die richtige Reaktion hat drei getrennte Aufgaben: den übrig gebliebenen Prozess genau identifizieren, nachweisen, ob er noch handeln kann, und den Datensatz bewahren, der seine letzten Aufrufe mit der zuständigen Sitzung verbindet. Erledige diese Aufgaben in dieser Reihenfolge. Wer zuerst beendet, stoppt zwar die Störung, kann aber die besten Beweise für den Ablauf löschen.

Ein elternloser Prozess ist ein Hinweis, kein Urteil

Ein verwaister Prozess ist ein Kindprozess, dessen ursprünglicher Elternprozess beendet wurde und dessen Betriebssystem-Elternprozess sich geändert hat, oft zu PID 1. Das ist ein nützlicher Beleg, aber nicht die Definition eines unsicheren MCP-Servers. Prozessüberwacher, Shells, IDEs und Startdienste können gesunde Kindprozesse während des normalen Betriebs neu zuordnen.

Bei einem stdio-MCP-Server solltest du eine engere Frage stellen: Gehört dieser Prozess noch zu einer aktiven Client-Verbindung, oder läuft er weiter, nachdem der Client, dem stdin und stdout gehörten, verschwunden ist? Diese Frage verbindet Prozessabstammung mit offenen Deskriptoren, verstrichener Zeit und einem Aktivitätsdatensatz.

Die Dokumentation des Model Context Protocol beschreibt stdio als lokale, durch einen Prozess gestartete Integration. Der Client startet einen Befehl und tauscht zeilengetrenntes JSON-RPC über die Standardeingabe und -ausgabe des Servers aus. Für einen sauber arbeitenden Server ergibt sich daraus ein einfaches Signal für das Lebensende: Wenn seine Eingabe EOF erreicht, ist die Clientseite verschwunden.

EOF ist ein Hinweis auf einen unterbrochenen oder geschlossenen Transport. Es beweist nicht, dass jeder Worker, Kindprozess, Netzwerkverbindung, Timer oder Remote-Job beendet wurde. Ein Server kann einen Tool-Aufruf empfangen, mit der Arbeit beginnen und dann den Client verlieren, bevor er eine JSON-RPC-Antwort erzeugt. Wenn der Handler einen Unterprozess gestartet oder eine Anfrage an ein entferntes System gesendet hat, kann diese Arbeit eine eigene Lebensdauer haben.

Hier werden zwei unterschiedliche Fehler oft vermischt:

  • Ein Betriebssystem-Waise hat seinen ursprünglichen Elternprozess verloren oder sich aus dem erwarteten Prozessbaum gelöst.
  • Ein logischer Waise hat die Sitzung verloren, die seiner Arbeit Bedeutung gab, selbst wenn die übergeordnete PID noch normal aussieht.

Der erste kann Speicher verschwenden oder einen Port offen halten. Der zweite kann eine unerwünschte externe Änderung verursachen. Du musst beide Fälle untersuchen.

Ein Prozess mit PPID 1, hoher Laufzeit und ohne offene stdin ist verdächtig. Ein Prozess, der noch an einem aktiven Terminal hängt, kann ebenfalls gefährlich sein, wenn sein zuständiger Client unbemerkt feststeckt und der Prozess über Zugangsdaten oder eine wiederverwendbare Verbindung verfügt. Umgekehrt kann ein Prozess nach einem Absturz harmlos sein, wenn er keine Berechtigung besitzt und keinen Aktionskanal erreichen kann.

Baue keine Regel nach dem Muster «PPID 1 bedeutet beenden». Erstelle stattdessen einen Datensatz, der erklärt, warum dieser Prozess existiert, welcher Lauf ihn erzeugt hat, was er noch erreichen kann und was du mit ihm getan hast.

Beginne mit einem belastbaren Prozessinventar

Erstelle einen Snapshot, bevor du irgendein Signal sendest. Nach der Beendigung kannst du Befehlszeile, Prozessgruppe oder offene Deskriptoren nicht mehr zurückholen. Durch die Wiederverwendung von PIDs werden lose Notizen erstaunlich schnell zu Vermutungen.

Beginne auf macOS mit der vollständigen Ansicht statt mit einem cleveren Einzeiler, der die benötigten Beweise herausfiltert:

ps -axo user,pid,ppid,pgid,stat,etime,command

Suche nach den Befehlen, die dein MCP-Client startet. Das kann eine direkte Server-Binärdatei, ein Interpreter wie node oder python, ein Paketstarter, ein SSH-Hilfsprozess oder der sp mcp-Shim sein. Kopiere die relevanten Zeilen in eine Incident-Notiz, bevor du die Suche eingrenzt.

Die Felder beantworten unterschiedliche Fragen:

  • pid identifiziert den Prozess nur für diese Untersuchung.
  • ppid zeigt, ob der unmittelbare Elternprozess noch existiert.
  • pgid identifiziert die Prozessgruppe und macht oft gemeinsam gestartete Geschwister-Hilfsprozesse sichtbar.
  • stat kann anzeigen, ob sich ein Prozess im Schlafzustand befindet, angehalten wurde oder nicht unterbrechbar ist.
  • etime zeigt, ob ein angeblich aktueller Lauf schon seit Stunden oder Tagen besteht.
  • command ist oft der einzige erhaltene Datensatz der ausführbaren Datei und der Argumente, mit denen sie gestartet wurde.

Untersuche danach jeden Kandidaten direkt. Ersetze die PID durch den tatsächlichen Wert und speichere die Ausgabe mit Zeitstempel in deinem Incident-Datensatz.

ps -o pid=,ppid=,pgid=,stat=,etime=,user=,command= -p 48271
lsof -nP -p 48271

Der erste Befehl liefert einen kompakten Identitätsdatensatz. Der zweite zeigt, was der Prozess noch geöffnet hat. Achte besonders auf die Dateideskriptoren 0, 1 und 2. Ein normaler stdio-Server hat meist ein Leseende für stdin sowie Schreibenden für stdout und stderr. Wenn stdin EOF erreicht hat, kann der Prozess weiterhin einen Pipe-Deskriptor anzeigen. Schließe daher nicht allein aus dessen Vorhandensein auf Lebendigkeit. Entscheidend ist das Gesamtbild: Peer-Prozess, Terminals, reguläre Dateien, Unix-Sockets, TCP-Verbindungen und Kind-Worker.

lsof kann auch einen Fehler sichtbar machen, den Prozesslisten verbergen. Angenommen, der Elternprozess des Servers ist verschwunden, aber der Server besitzt weiterhin eine aufgebaute TCP-Verbindung zu einer internen API. Das beweist nicht, dass er eine neue Anfrage stellen kann, liefert dir aber eine konkrete Fähigkeit, die du untersuchen solltest. Besitzt er einen SSH-Kontrollsocket oder einen lokalen Unix-Socket zu einem Zugangsdaten-Hilfsdienst, behandle das als Spur und nicht als Anlass, automatisch von Sicherheit auszugehen.

Prüfe die Prozessgruppe, bevor du handelst:

ps -axo pid,ppid,pgid,stat,etime,command | awk '$3 == 48271 || $2 == 48271'

Dieses Beispiel setzt voraus, dass 48271 die beobachtete Gruppen-ID ist. Passe den Wert an. Die Ausgabe kann einen Wrapper, Server und Hilfsprozess zeigen, die ein einfaches kill gegen eine einzelne PID zurücklassen würde. Sie kann auch bestätigen, dass ein Prozess keine Geschwister mehr besitzt und leichter isoliert werden kann.

Vermeide eine verbreitete Abkürzung: Suche nicht nur nach node, python oder npx und beende jeden Treffer. Diese Namen bezeichnen Laufzeitumgebungen, keine Identitäten. Eine weit gefasste Bereinigung kann eine Editor-Erweiterung, einen lokalen Testlauf, eine Build-Aufgabe oder eine unabhängige Automatisierung unterbrechen. Vergleiche die vollständige Befehlszeile mit der erwarteten Serverkonfiguration und untersuche anschließend den zugehörigen Elternprozess und die Prozessgruppe.

Wenn dein Client den Serverbefehl beim Start protokolliert, speichere diesen Befehl neben dem Sitzungsdatensatz. Eine spätere Untersuchung wird dadurch von einer ungenauen Suche nach Prozessnamen zu einem direkten Vergleich.

Die stdio-Pipe definiert nicht die Aktionsgrenze

Ein korrekt geschriebener stdio-Server sollte keine neuen Anfragen mehr annehmen, wenn stdin geschlossen wird. Er sollte noch nicht gestartete Arbeit abbrechen, seinen Transport schließen und nach begrenzter Bereinigung beenden. Auch der offizielle Leitfaden des TypeScript-MCP-SDK weist in seinem Abschnitt zum Herunterfahren darauf hin: Das Schließen eines Transports leert laufende Tool-Handler nicht automatisch, bevor der Prozess endet.

Dieser letzte Punkt verdient mehr Aufmerksamkeit. Wenn ein Client abstürzt, kann sich ein Tool-Handler in einem von mehreren Zuständen befinden:

  1. Er hat noch keine externe Arbeit begonnen und kann sauber abgebrochen werden.
  2. Er hat eine Anfrage gesendet, aber noch keine Antwort erhalten.
  3. Er hat die entfernte Änderung abgeschlossen, aber den Antwortweg verloren, bevor er den Erfolg melden konnte.
  4. Er hat einen lokalen Kindprozess oder einen Remote-Job gestartet, der den Handler überlebt.
  5. Er wartet blockiert auf eine externe Abhängigkeit und kann später fortgesetzt werden.

Nur der erste Zustand lässt sich sicher als «Es ist nichts passiert» beschreiben. Für die anderen vier brauchst du Datensätze außerhalb des toten Client-Prozesses.

Ein konkretes Beispiel: Ein Server erhält einen Tool-Aufruf, der einen Build ausrollt. Er schreibt die Anfrage an eine entfernte Build-API, dann stürzt der Client ab, während die API sie verarbeitet. Der Server erkennt eine unterbrochene stdout-Pipe. Wenn er sofort beendet wird, kann der Build trotzdem weiterlaufen. Wenn er ohne Idempotenzmechanismus wiederholt, kann er einen zweiten Build starten. Wenn er mit einer langlebigen Zugangsdaten im Speicher weiterläuft, kann er nach dem Verschwinden des ursprünglichen Clients abfragen, wiederholen oder Folgearbeit starten.

Der Fehler besteht nicht darin, dass der Server weiterlief. Der Fehler besteht darin, das Schließen des Transports als vollständige Aussage über Autorisierung, Abbruch und den Zustand des entfernten Systems zu behandeln.

Definiere für jeden Aktionskanal die Grenze ausdrücklich:

  • Welches Ereignis verhindert, dass neue Arbeit in den Server gelangt?
  • Mit welcher Kennung kannst du bereits gesendete Arbeit abfragen oder abbrechen?
  • Unterstützt die entfernte Operation Idempotenz oder ein Anfrage-Token?
  • Welcher Prozess besitzt die Zugangsdaten noch, nachdem der Client verschwunden ist?
  • Wo wird das Endergebnis gespeichert, wenn die JSON-RPC-Antwort nicht zugestellt werden kann?

Wenn du diese Fragen für ein Tool mit hoher Auswirkung nicht beantworten kannst, nenne es nicht sicher, nur weil es stdio verwendet.

Dasselbe gilt für SSH. Ein interaktiv wirkender Befehl kann auf dem entfernten Host trotzdem einen Hintergrundprozess gestartet haben. Das Schließen des lokalen Clients kann den lokalen Kanal schließen, während ein vom Shell-Prozess gelöster Remote-Befehl weiterläuft. Ein Server braucht ein Befehlsdesign, das Remote-Arbeit beobachtbar und abbrechbar macht, statt auf eine vermeintliche Wirkung von Terminaltrennungen zu vertrauen.

Weise nach, ob der übrig gebliebene Prozess noch handeln kann

Die Existenz eines Prozesses ist keine Autorisierung. Du musst den Aktionspfad prüfen, ohne eine echte Aktion auszulösen.

Finde zuerst heraus, wo die Berechtigung liegt. Ein Server, der ein API-Token aus seiner eigenen Umgebung liest, hat ein anderes Risikoprofil als einer, der für jede Aktion einen separaten Broker befragt. Ein Server mit einem privaten SSH-Schlüssel in einer Datei unterscheidet sich von einem Server, der über einen kurzlebigen lokalen Hilfsdienst delegiert. Halte die Antwort für den konkret untersuchten Server fest.

Untersuche anschließend, was der Prozess noch geöffnet hat und erreichen kann. lsof ist ein Ausgangspunkt, zeigt aber nicht jedes im Speicher befindliche Geheimnis und nicht jede authentifizierte Sitzung. Kombiniere die Ausgabe mit deinen Aktionsprotokollen und, falls verfügbar, dem Audit-Trail des externen Systems.

Eine sinnvolle Untersuchungsfolge sieht so aus:

  1. Zeichne PID, Befehl, PPID, Prozessgruppe sowie offene Netzwerk- oder Unix-Sockets auf.
  2. Finde die jüngste externe Aktion, die zu diesem Prozess oder seiner Sitzung gehört.
  3. Prüfe, ob nach dem Absturzzeitpunkt des Clients eine neue Aktion stattfand.
  4. Widerrufe oder deaktiviere den Autorisierungspfad des Prozesses.
  5. Beobachte, ob der Prozess eine weitere Aktion versucht und ob das Gateway sie ablehnt.

Teste nicht, indem du den verwaisten Server zu einem harmlos wirkenden Schreibvorgang aufforderst. Viele Systeme kennen keinen wirklich harmlosen Schreibvorgang. Ein Testaufruf kann Ratenlimits verändern, Audit-Rauschen erzeugen, Zustand rotieren oder Automatisierung auslösen. Bevorzuge einen schreibgeschützten Health- oder Identitätsendpunkt, falls dein Design einen solchen vorsieht. Noch besser ist es, die Ablehnung durch das Gateway oder den Zugangsdaten-Broker nach dem Widerruf zu prüfen.

Es gibt einen wichtigen Unterschied: Ein Prozess mit Netzwerkzugriff kann nicht zwangsläufig handeln, und ein Prozess ohne sichtbare Netzwerkverbindung kann später dennoch handeln. DNS-Auflösung, ein Proxy, ein lokaler Hilfsdienst, ein geplanter Timer oder ein Kindprozess können den Pfad wieder öffnen. Deshalb ist der Widerrufstest wichtiger als ein Snapshot der Sockets.

Wenn der Server ein wiederverwendbares Geheimnis direkt besitzt, muss die Bereinigung auch Rotation oder Widerruf dieses Geheimnisses umfassen. Durch das Beenden des Prozesses entfernst du eine Kopie aus dem Speicher, änderst aber nicht, was ein Angreifer, ein aus dem Prozessspeicher erstelltes Abbild oder ein entfernter Dienst mit demselben Geheimnis tun kann. Teams vermeiden Rotation oft, weil sie Arbeit verursacht. Das ist verständlich, aber kein Sicherheitsargument.

Ein Gateway-Modell verändert die Untersuchung. Der Server kann als nicht vertrauenswürdiger Prozess weiterlaufen und trotzdem keinen neuen geschützten Aufruf ausführen, weil er das Geheimnis nie besaß und keine genehmigte aktive Sitzung mehr hat. Sallyport bewahrt API- und SSH-Geheimnisse in seinem verschlüsselten Tresor auf und führt die Aktion selbst aus. Der Agent erhält das Geheimnis daher weder im Klartext noch als Ersatzwert. Das verringert den möglichen Schaden eines verirrten stdio-Prozesses. Trotzdem solltest du einen verdächtigen Lauf widerrufen, statt anzunehmen, dass ein Prozessabsturz diese Aufgabe automatisch erledigt hat.

Der praktische Maßstab ist einfach: Nach dem Widerruf der Sitzung oder des Aktionspfads muss ein geschützter Aufruf abgewiesen werden, und die Ablehnung muss aufgezeichnet werden. Kannst du das nicht nachweisen, hast du die Eindämmung nicht bestätigt.

Zeichne die letzten Aufrufe auf, bevor die Bereinigung die Geschichte verändert

Sensible Schlüssel hinter eine Freigabe stellen
Fordere für Schlüssel, die eine unsichere Sitzung nicht überleben sollen, bei jeder Verwendung eine Freigabe an.

Ein Incident-Datensatz braucht stabile Kennungen, keine aus dem Gedächtnis zusammengesetzte Erzählung. Eine PID ist vorübergehend und kann wiederverwendet werden. Eine Prozessbefehlszeile kann sich nach einem Update ändern. Eine Sitzungskennung und ein manipulationssicherer Audit-Datensatz bieten einen deutlich besseren Anker.

Erstelle mindestens eine Zeile pro verdächtigem Prozess mit diesen Feldern:

Beobachtet um:
PID und Befehl des Client-Prozesses:
PID und Befehl des MCP-Servers:
Eltern-PID und Prozessgruppe:
Sitzungskennung:
Autorisierungsstatus:
Zeitpunkt der letzten erfolgreichen Aktion:
Zeitpunkt des letzten versuchten Aufrufs:
Aktionsziel und Operation:
Ergebnis oder Kennung des Remote-Jobs:
Zeitpunkt des Widerrufs und Operator:
Beendigungssignal und Beendigungsergebnis:
Erforderliche Nacharbeit:

Das entscheidende Paar sind die Sitzungskennung und der letzte versuchte Aufruf. Viele Teams zeichnen nur erfolgreiche Aufrufe auf. Dadurch bleibt gerade der aufschlussreichste Moment einer Absturzuntersuchung verborgen: die Anfrage, die den lokalen Rechner verlassen, aber nie ein Ergebnis erhalten hat.

Halte drei Zeitpunkte getrennt fest. Notiere, wann der Client nicht mehr verfügbar war, wann die letzte bekannte Aktion des Servers begann und wann du die Berechtigung widerrufen hast. Wenn du nur eine «Incident-Zeit» verwendest, kannst du nicht erkennen, ob ein Aufruf vor dem Absturz, in einem unsicheren Zeitfenster oder nach dem erwarteten Beginn der Eindämmung stattfand.

Bewahre außerdem das Aktionsziel auf. «Cloud-API aufgerufen» reicht nicht. Notiere Konto oder Endpunktklasse, Methode oder SSH-Befehlskategorie sowie alle Anfrage- oder Jobkennungen, mit denen ein Operator den entfernten Dienst durchsuchen kann. Vermeide sensible Anfrageinhalte in einer allgemeinen Incident-Notiz. Du brauchst genug Informationen, um die Nebenwirkung abzugleichen, aber keine zweite Kopie jedes Geheimnisses und jedes Kundendatensatzes.

Bei einem Gateway mit getrennten Sitzungs- und Aktivitätsjournalen solltest du beide verwenden. Der Sitzungsdatensatz beantwortet, wer den Agent-Prozess ausgeführt hat und ob dieser Lauf noch autorisiert ist. Der Aktivitätsdatensatz beantwortet, welche einzelnen Aufrufe in welcher Reihenfolge stattfanden. Das sind unterschiedliche Fragen. Werden sie in einen einzigen breiten Ereignisstrom gepackt, lassen sich beide unter Zeitdruck schwerer beantworten.

Sallyport erstellt beide Journale aus einem verschlüsselten, hash-verketteten Audit-Log. Nachdem du die relevanten Sitzungs- und Aktivitätseinträge erfasst hast, führe die Offline-Integritätsprüfung aus:

sp audit verify

Speichere das exakte Ergebnis des Befehls zusammen mit dem Incident-Datensatz. Die Prüfung entscheidet nicht, ob die Aktion autorisiert oder klug war. Sie zeigt, ob die Audit-Kette, auf die du dich stützt, weiterhin verifiziert werden kann, ohne Zugriff auf den Tresor zu benötigen. Das ist hilfreich, wenn die Person, die den Vorfall prüft, keine Zugangsdaten erhalten soll, nur um die Historie zu kontrollieren.

Warte nicht auf einen formellen Sicherheitsvorfall, um diese Dokumentation zu üben. Bei einer routinemäßigen Absturzprüfung findest du fehlende Sitzungs-IDs, unklare Befehle und Protokolle, die nur auf dem toten Rechner existieren. Diese Lücken in einer ruhigen Bereinigung zu schließen ist deutlich günstiger, als sie nach einem produktiven Schreibvorgang zu entdecken.

Widerrufe die Berechtigung vor der Beendigung des Prozesses

Die sicherste Reihenfolge lautet: widerrufen, Ablehnung prüfen, dann den Prozess stoppen. Die umgekehrte Reihenfolge wirkt schneller, weil der Prozess sofort verschwindet. Sie kann aber eine aktive Autorisierung zurücklassen und die spätere Zuordnung erschweren.

Beginne mit dem engsten wirksamen Widerruf. Wenn das System die Autorisierung pro Agent-Lauf verfolgt, widerrufe diesen Lauf. Wenn eine einzelne Zugangsdaten an den Server gelangt ist, deaktiviere oder rotiere sie. Wenn ein Remote-Job einen Abbruchgriff besitzt, brich diesen Job separat ab. Das sind unterschiedliche Aktionen, weil sie unterschiedliche Lebensdauern betreffen.

Nimm nicht an, dass das Beenden eines Servers Remote-Arbeit abbricht. Ein Prozess kann verschwunden sein, während seine Anfrage noch in einer Warteschlange steht. Nimm auch nicht an, dass ein Remote-Abbruch den Server stoppt. Behält der Prozess seine Berechtigung, kann er erneut versuchen oder eine weitere Anfrage senden. Die Eindämmung erfordert, dass lokale und entfernte Seite übereinstimmen, dass der Lauf beendet ist.

Nachdem du den Aktionspfad widerrufen hast, prüfe den Zustand mit passenden Belegen. Das kann eine aufgezeichnete Ablehnung im Aktivitätsjournal des Gateways, eine fehlgeschlagene authentifizierte Anfrage an einen sicheren Identitätsendpunkt oder ein Remote-Audit-Eintrag sein, der die Deaktivierung des Tokens zeigt. Die genaue Methode hängt von deiner Umgebung ab. Das Prinzip bleibt gleich: Weise nach, dass ein überlebender Prozess keinen geschützten Aufruf mehr ausführen kann.

Sende anschließend ein normales Beendigungssignal an die identifizierte PID:

kill -TERM 48271
sleep 2
ps -p 48271 -o pid=,ppid=,stat=,etime=,command=

Wenn der letzte Befehl keine Prozesszeile zurückgibt, notiere dieses Ergebnis. Falls der Prozess bestehen bleibt, prüfe, ob er herunterfährt, auf I/O wartet oder Kindarbeit am Leben hält. Untersuche die Gruppe und die Kindprozesse erneut, bevor du eskalierst. Möglicherweise musst du einen bekannten Hilfsprozess separat beenden. Beende jedoch nicht blind eine ganze Prozessgruppe, sofern du nicht bestätigt hast, dass jedes Mitglied zum selben fehlgeschlagenen Lauf gehört.

Verwende kill -KILL nur, wenn die normale Beendigung scheitert und du die Beweise gesichert hast. SIGKILL gibt dem Prozess keine Gelegenheit, Dateien zu schließen, Arbeit abzubrechen, ein letztes Protokoll zu schreiben oder temporären Zustand zu entfernen. Manchmal ist das der richtige Kompromiss. Nenne es korrekt: erzwungene Eindämmung mit möglicherweise unvollständiger Bereinigung.

Der Aktivitätsmonitor von macOS kann helfen, wenn die Befehlszeile nicht ausreicht. Apple dokumentiert eine normale Option zum Beenden und eine Option zum sofortigen Beenden. Außerdem kann der Monitor Prozesse hierarchisch anzeigen. Diese Hierarchie hilft beim Bestätigen von Eltern- und Kindbeziehungen. Sie ersetzt den Incident-Datensatz nicht, denn eine GUI-Liste bewahrt weder Sitzungs- noch Aktionsbeweise für später.

Mache das Herunterfahren des Servers zu einer Designanforderung

Den fehlgeschlagenen Agent-Lauf zurückverfolgen
Das Sitzungsjournal verknüpft einen Lauf mit seiner Autorisierung und ermöglicht sofortigen Widerruf, wenn der Client ausfällt.

Stdio-Server sollten das Verschwinden des Clients als reguläres Ereignis behandeln, nicht als seltene Ausnahme. Client-Abstürze gehören zum normalen Verhalten von Software. Laptops wechseln in den Ruhezustand, Terminals schließen, IDEs starten neu, Updates unterbrechen Prozesse und Agents können nach einem Modellfehler abbrechen.

Ein Server sollte einen ausdrücklichen Herunterfahrpfad mit vier Eigenschaften besitzen. Er nimmt keine neuen Anfragen mehr an, wenn die Eingabe geschlossen wird. Er verfolgt jede gestartete Aktion mit einer Korrelationskennung. Er gibt aktiver Arbeit eine begrenzte Chance, abgebrochen zu werden oder einen bekannten Zustand zu erreichen. Danach beendet er sich, statt versehentlich zu einem dauerhaften Hintergrunddienst zu werden.

Verwechsle ein sauberes Herunterfahren nicht mit endlosem Warten. Ein Server, der EOF erhält und unbegrenzt auf eine externe API wartet, ist ein höflicherer, aber weiterhin verwaister Prozess. Setze für die Bereinigung eine Frist, protokolliere ungelöste Arbeit und beende den Prozess. Die ungelöste Arbeit muss über ihre externe Jobkennung oder ihren Aktionsdatensatz auffindbar sein.

Auch die Verwaltung von Kindprozessen ist wichtig. Wenn ein Tool einen Compiler, Paketmanager, SSH-Hilfsdienst, Browser-Treiber oder Befehls-Wrapper startet, muss der Server wissen, ob dieser Kindprozess mit dem Server beendet werden soll. Richte Prozessgruppen bewusst ein. Erfasse die PIDs der Kindprozesse. Beende beim Herunterfahren nur Kinder, die zur Anfrage gehören, und notiere, ob sie beendet wurden.

Vermeide es, Arbeit über eine Shell in den Hintergrund zu schicken, sofern der Tool-Vertrag nicht ausdrücklich dauerhafte Hintergrundarbeit vorsieht. Ein Befehl wie some-command \u0026 erzeugt eine zweite Lebensdauer, die dein MCP-Server möglicherweise nicht beobachtet. Wenn du dauerhafte Arbeit brauchst, übergib sie einem Jobsystem, das eine Job-ID zurückgibt, und stelle anschließend bewusste Tools für Status und Abbruch bereit. Versteckte Hintergrundarbeit ist keine Dauerhaftigkeit. Sie ist eine Audit-Lücke.

Verwende Idempotenz, wenn der entfernte Dienst sie unterstützt. Gib jedem externen Schreibvorgang eine Anfragekennung, die aus Sitzung und Tool-Aufruf abgeleitet ist, und speichere diese Kennung, bevor du die Anfrage sendest. Stürzt der Client nach der Übermittlung ab, kannst du das entfernte System abfragen, statt über einen erneuten Versuch zu raten. Unterstützt der Dienst keine Idempotenz, dokumentiere die Unsicherheit und verlange vor der Wiederholung eines Schreibvorgangs eine Entscheidung durch einen Operator.

Ein Server sollte Diagnosen außerdem auf stderr und niemals auf stdout schreiben. stdout gehört zu MCPs zeilengetrenntem JSON-RPC-Stream. Eine einzelne Debug-Zeile kann das Protokoll beschädigen, einen Client-Fehler auslösen und genau das Absturzbild erzeugen, das du gerade bereinigen willst. Das klingt nach einem kleinen Detail, bis ein Produktionsserver beim Start eine Warnung aus einer Bibliothek ausgibt.

Lege den Besitz im Aktionsdatensatz fest, nicht in der Shell-Historie

Den beobachteten Prozess freigeben
Neue Agent-Prozesse brauchen eine sitzungsbezogene Freigabe, bevor Sallyport geschützte Aufrufe annimmt.

Die Shell-Historie ist praktisch, bis sie fehlt, abgeschnitten oder geteilt ist oder erst geschrieben wird, nachdem der Prozess beendet wurde. Der Aktionsdatensatz muss die Besitzinformationen während der laufenden Arbeit tragen.

Für jeden Aufruf, der ein externes System berühren kann, solltest du genug Kontext anhängen, um später vier Fragen zu beantworten: Welcher Agent-Lauf hat ihn angefordert, welcher lokale Prozess hat ihn übermittelt, welche Autorisierungsentscheidung hat ihn erlaubt und welche externe Operation ist daraus entstanden? Fehlt eine dieser Angaben, hinterlässt ein Client-Absturz eine Lücke, die der Untersuchende durch Schlussfolgerungen füllen muss.

Verwende die Code-Signatur nicht als Antwort auf jede Besitzfrage. Sie zeigt, wer die ausführbare Datei signiert hat. Das ist nützlich, um zu entscheiden, ob ein Prozess eine Freigabe verdient. Sie identifiziert jedoch weder den einzelnen Lauf noch die Eingabe, die zum Aufruf führte, noch die entfernte Anfrage. Nutze die Signatur für das Vertrauen beim Sitzungsstart und danach eine Sitzungskennung sowie Aufzeichnungen pro Aufruf für die operative Nachverfolgbarkeit.

Dasselbe gilt für Befehlszeilen. Ein Befehl kann zeigen, dass sp mcp oder eine Server-Binärdatei gestartet wurde. Er zeigt nicht zuverlässig, welches Tool zuletzt aufgerufen wurde, ob ein Benutzer ihn freigegeben hat oder ob das entfernte System ihn akzeptierte. Behandle die Prozessliste als unterstützenden Beleg, nicht als Quelle des Audit-Trails.

Ordne bei einem Absturz in dieser Reihenfolge zu:

  1. Finde den Agent-Lauf, dem der Client-Prozess zum Absturzzeitpunkt gehörte.
  2. Finde den Serverprozess oder die Prozessgruppe, die dieser Lauf gestartet hat.
  3. Ermittle die letzten Aktivitätseinträge dieses Laufs und vergleiche ihre Zeitstempel mit dem Prozess-Snapshot.
  4. Gleiche jede unfertige externe Operation anhand ihrer Anfrage-, Transaktions- oder Jobkennung ab.
  5. Zeichne Widerruf und Beendigung neben den letzten Aufrufen auf.

Diese Reihenfolge verhindert einen häufigen Fehler: Du findest eine veraltete PID, beendest sie und ordnest ihren letzten API-Aufruf später dem falschen Agent-Lauf zu, weil zwei Sitzungen denselben Serverbefehl verwendet haben. Befehle wiederholen sich. Sitzungsdatensätze sollten es nicht.

Wenn deine Umgebung diese Zuordnung heute nicht herstellen kann, ergänze sie, bevor du autonomen Tools Schreibzugriff gibst. Unvollständige Beobachtbarkeit kann bei schreibgeschützten Experimenten ausreichen. Für Änderungen in der Produktion reicht sie nicht.

Eine Absturzprobe zeigt Lücken bei geringem Risiko

Führe für jeden MCP-Server, der externe Änderungen vornehmen kann, eine kontrollierte Absturzprobe durch. Verwende ein Testkonto oder einen schreibgeschützten Aktionspfad und erfasse die Beweise, als würdest du einen echten Vorfall bearbeiten.

Starte eine normale Clientsitzung und führe eine Aktion mit bekannter Korrelationskennung aus. Beende den Client abrupt, während der Server aktiv ist. Untersuche danach Server-PID, PPID und Prozessgruppe, offene Deskriptoren und Aktionsjournal. Widerrufe die Sitzung. Bestätige, dass ein anschließender geschützter Aufruf abgelehnt wird. Beende schließlich den Server, falls er nach EOF nicht selbst beendet wurde, und gleiche die externe Aktion ab.

Die Probe sollte Antworten liefern, nicht nur ein «Bestanden» oder «Nicht bestanden». Du willst wissen, ob das Schließen von stdin den Server erreicht, ob Kindprozesse zurückbleiben, ob Remote-Jobs über Abbruchgriffe verfügen, ob deine Protokolle den Besitzer identifizieren und ob der Widerruf das Verhalten sofort verändert.

Achte auf den Zeitpunkt. Ein Absturz vor dem Versand der Anfrage verhält sich anders als einer, der eintritt, nachdem ein entfernter Dienst die Anfrage angenommen hat. Wiederhole die Probe an beiden Punkten, wenn der Server ausreichend instrumentiert ist. Die unschöne Grenze zwischen «gesendet» und «bestätigt» ist der Ort, an dem doppelte Schreibvorgänge und falsche Zusicherungen entstehen.

Schreibe für jeden Server die erwartete Bereinigung auf. Eine gute Erwartung ist konkret: Nach dem Schließen der Eingabe nimmt der Server keine Aufrufe mehr an, beendet sich innerhalb des konfigurierten Zeitlimits, hinterlässt keine eigenen Hilfsprozesse und erstellt für jede gestartete externe Aktion einen Aktivitätseintrag. Eine schwache Erwartung lautet: «Der Client bereinigt das normalerweise.» Normalverhalten ist keine Kontrolle.

Nach einigen Durchläufen wirken veraltete Prozesse nicht mehr rätselhaft. Sie werden zu einem definierten Fehlermodus mit Prozess-Snapshot, Besitzspur, Widerrufsaktion und Bereinigungsregel. Das ist der Standard, auf den du hinarbeiten solltest. Der Prozess kann abstürzen. Deine Fähigkeit, seine letzten Aufrufe zu erklären und einzudämmen, sollte es nicht.

FAQ

Was ist ein verwaister MCP-Prozess?

Ein lokaler MCP-Serverprozess, der den Client-Prozess oder die Verbindung überlebt, von der er gestartet wurde. Eine PID 1 als PPID ist ein nützlicher Hinweis, aber allein kein Beweis, da Starter und Dienstmanager gesunde Kindprozesse ebenfalls neu zuordnen können.

Kann ein verwaister MCP-Server weiterhin API-Aufrufe ausführen?

Manchmal. Wenn der Server ein API-Token, eine SSH-Zugangsdaten, eine wiederverwendbare authentifizierte Verbindung oder einen Hintergrundarbeiter besitzt, kann er auch nach dem Verschwinden des MCP-Clients externe Änderungen vornehmen. Eine unterbrochene stdio-Pipe beendet nur die Protokollunterhaltung. Sie bricht keine Arbeit ab, die der Server bereits an anderer Stelle gestartet hat.

Wie finde ich veraltete MCP-Server auf macOS?

Beginne mit Prozessidentität, übergeordneter PID, Prozessgruppe, Laufzeit, Befehlszeile und offenen Dateideskriptoren. Vergleiche danach die letzte beobachtete externe Aktion mit deinem Aktionsprotokoll. Eine Prozessliste allein zeigt nicht, ob der Prozess noch autorisiert ist.

Soll ich einen verwaisten MCP-Prozess sofort beenden?

Verwende zuerst ein normales Beendigungssignal. Prüfe anschließend, ob die PID verschwunden ist und keine Kindprozesse übrig sind. Force Quit oder SIGKILL ist erst sinnvoll, wenn du die Beweise gesichert hast und akzeptierst, dass eine laufende Operation mittendrin enden kann.

Ist eine MCP-Sitzungs-ID dasselbe wie eine PID?

Nein. Die Prozess-ID identifiziert einen Betriebssystemprozess, während eine MCP-Sitzung einen bestimmten Protokolllauf bezeichnet. Ein einzelner Client kann mehrere Serverprozesse erzeugen, und ein überlebender Prozess kann von der Sitzung getrennt sein, die ihn ursprünglich autorisiert hat.

Wie verhindere ich, dass veraltete Agents Zugangsdaten behalten?

Am sichersten ist es, Zugangsdaten außerhalb des Agents und des stdio-Servers zu halten und für jede Aktion einen aktiven Autorisierungspfad zu verlangen. Ein Prozess kann zwar im Speicher bleiben, aber wenig ausrichten, wenn er kein Geheimnis abrufen und keine freigegebene Aktion übermitteln kann.

Was sollte ein Incident-Datensatz zu einem MCP-Absturz enthalten?

Notiere den Agent-Prozess, die Server-PID, die Sitzungskennung, den Zeitpunkt der Aktion, das Ziel, die Methode oder Befehlskategorie, das Ergebnis und die Entscheidung zur Beendigung. Sichere diese Angaben vor der Bereinigung, da PIDs wiederverwendet werden und die Terminalhistorie kein verlässlicher Incident-Datensatz ist.

Erlischt eine Client-Freigabe, wenn ein MCP-Client abstürzt?

Wenn die Freigabe zu einem weiterlaufenden Agent-Prozess gehört, kann sie gültig bleiben, bis dieser Prozess endet oder ein Operator die Sitzung widerruft. Behandle einen Client-Absturz als Anlass, die Sitzung zu prüfen, nicht als Beweis dafür, dass alle zugehörigen Prozesse ihre Berechtigung verloren haben.

Wie sollte ein stdio-MCP-Server mit Client-Abstürzen umgehen?

Schließe stdin, behandle EOF als Herunterfahr-Ereignis, brich wartende Arbeit ab, begrenze die Laufzeit von Aufrufen und sorge dafür, dass Hilfsprozesse mit ihrem Elternprozess beendet werden. Der Server sollte die Identität der Anfrage außerdem protokollieren, bevor er eine externe Aktion startet, nicht erst danach.

Wie kann Sallyport bei der Untersuchung eines abgestürzten MCP-Clients helfen?

Nutze das Sitzungsjournal, um den Lauf zu finden und zu widerrufen. Verwende anschließend das Aktivitätsjournal, um die letzten Aufrufe vor und nach dem Absturz zu exportieren oder zu dokumentieren. Führe sp audit verify für die aufbewahrten Audit-Daten aus, damit du weißt, dass die gelesene Historie nicht verändert wurde.

Sallyport

Sallyport führt API-Aufrufe und SSH-Befehle für Ihren KI-Agenten aus. Die Schlüssel bleiben in einem lokalen Tresor auf Ihrem Mac; Sie geben jeden Lauf frei, und jede Aktion landet in einem versiegelten Journal.

© 2026 Sallyport · Open Source unter Apache-2.0 · Oleg Sotnikov