feat: LehrerApp.McpBridge zu resilientem MCP-Proxy umgebaut

Claude Desktop deaktivierte den ganzen MCP-Server, sobald der Initialize-Handshake beim
Bridge-Start fehlschlug - der Regelfall, wenn LehrerApp.Desktop (z.B. nach Windows-Autostart
von Claude Desktop) noch nicht läuft. Die Bridge ist jetzt selbst ein MCP-Server gegenüber
Claude Desktop und gleichzeitig ein MCP-Client gegenüber dem echten Server in LehrerApp.Desktop:
der Handshake gelingt dadurch immer.

Verbunden spiegelt sie die echte Werkzeugliste 1:1; ohne Verbindung bietet sie nur ein
lehrerapp_status-Werkzeug an, das den Grund erklärt und erneut verbindet. Ein Hintergrund-Loop
versucht unabhängig davon alle 5s zu reconnecten und schaltet per notifications/tools/list_changed
automatisch auf die echten Werkzeuge um, sobald LehrerApp erreichbar ist. Bricht die Verbindung
während eines laufenden Aufrufs ab, kommt ein normales Tool-Fehlerergebnis statt eines
Prozessabsturzes zurück.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-12 21:45:03 +02:00
co-authored by Claude Sonnet 5
parent 3f8813df51
commit d979a84e7b
7 changed files with 378 additions and 73 deletions
@@ -0,0 +1,36 @@
using LehrerApp.Core.Mcp;
namespace LehrerApp.McpBridge;
/// <summary>
/// Unterscheidet an jeder Stelle, an der die Bridge versucht, sich mit LehrerApp.Desktop zu
/// verbinden, zwischen dem harmlosen Regelfall "LehrerApp läuft gerade nicht" und einem echten
/// Verbindungsfehler. <see cref="NamedPipeClientStream.ConnectAsync"/> wirft bei einer lokalen
/// Named Pipe (kein Netzwerk, kein Handshake) genau dann eine <see cref="TimeoutException"/>, wenn
/// innerhalb der Frist kein Prozess auf den Pipe-Namen lauschte - praktisch immer, weil
/// LehrerApp.Desktop nicht läuft oder der MCP-Server dort in den Einstellungen nicht aktiviert ist.
/// Jede andere Exception (z.B. <see cref="UnauthorizedAccessException"/> bei einem Rechte-/
/// ACL-Mismatch zwischen den Prozessen) deutet auf ein echtes Konfigurationsproblem hin.
/// </summary>
internal static class ConnectionDiagnostics
{
public const int NotRunningErrorCode = -32001;
public const int UnexpectedErrorCode = -32002;
public static (int Code, string ClientMessage, string StderrLine) Describe(Exception ex) => ex switch
{
TimeoutException => (
NotRunningErrorCode,
"LehrerApp läuft nicht oder der MCP-Server ist in den Einstellungen nicht aktiviert.",
"LehrerApp.McpBridge: Kein Kontakt zu LehrerApp (Pipe „" + McpPipeConstants.PipeName +
"“ nicht erreichbar). Das ist normal, wenn LehrerApp.Desktop gerade nicht läuft oder der " +
"MCP-Server dort in den Einstellungen nicht aktiviert ist - kein Hinweis auf eine " +
"fehlerhafte Bridge-/Client-Konfiguration."),
_ => (
UnexpectedErrorCode,
$"Unerwarteter Verbindungsfehler ({ex.GetType().Name}): {ex.Message}",
$"LehrerApp.McpBridge: Unerwarteter Fehler beim Verbindungsaufbau zu LehrerApp " +
$"({ex.GetType().Name}: {ex.Message}). Das ist vermutlich KEIN einfaches \"LehrerApp " +
"läuft noch nicht\", sondern deutet auf ein Konfigurations- oder Rechteproblem hin."),
};
}