diff --git a/LehrerApp.McpBridge/ConnectionDiagnostics.cs b/LehrerApp.McpBridge/ConnectionDiagnostics.cs
new file mode 100644
index 0000000..0328e48
--- /dev/null
+++ b/LehrerApp.McpBridge/ConnectionDiagnostics.cs
@@ -0,0 +1,36 @@
+using LehrerApp.Core.Mcp;
+
+namespace LehrerApp.McpBridge;
+
+///
+/// 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. wirft bei einer lokalen
+/// Named Pipe (kein Netzwerk, kein Handshake) genau dann eine , 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. bei einem Rechte-/
+/// ACL-Mismatch zwischen den Prozessen) deutet auf ein echtes Konfigurationsproblem hin.
+///
+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."),
+ };
+}
diff --git a/LehrerApp.McpBridge/LehrerApp.McpBridge.csproj b/LehrerApp.McpBridge/LehrerApp.McpBridge.csproj
index e2567f9..b49096c 100644
--- a/LehrerApp.McpBridge/LehrerApp.McpBridge.csproj
+++ b/LehrerApp.McpBridge/LehrerApp.McpBridge.csproj
@@ -5,5 +5,6 @@
+
diff --git a/LehrerApp.McpBridge/Program.cs b/LehrerApp.McpBridge/Program.cs
index 6ec4f47..7348f57 100644
--- a/LehrerApp.McpBridge/Program.cs
+++ b/LehrerApp.McpBridge/Program.cs
@@ -1,84 +1,45 @@
-using System.IO.Pipes;
-using System.Text.Json;
-using System.Text.Json.Nodes;
-using LehrerApp.Core.Mcp;
+using LehrerApp.McpBridge;
+using ModelContextProtocol.Protocol;
+using ModelContextProtocol.Server;
-// Zustandsloser Bridge-Prozess: reicht die vom KI-Client über stdin/stdout gesprochene
-// JSON-RPC-Verbindung unverändert an die Named Pipe des laufenden LehrerApp-Hauptprozesses durch.
-// Kein eigenes JSON-RPC-Verständnis nötig, außer im Fehlerfall (siehe unten) — stdout ist
-// ausschließlich für den durchgereichten Protokollstrom reserviert, jede Diagnose geht nach stderr.
+// Resilienter Bridge-Prozess (Nutzer-Feedback): meldet sich gegenüber dem KI-Client (z.B. Claude
+// Desktop) IMMER erfolgreich als MCP-Server, unabhängig davon, ob LehrerApp.Desktop beim Start
+// dieses Prozesses bereits läuft - Claude Desktop startet oft per Windows-Autostart, bevor der
+// Nutzer LehrerApp geöffnet hat. Die eigentliche Weiterleitung an den echten Server sowie das
+// Nachladen der echten Werkzeugliste, sobald LehrerApp erreichbar ist, übernimmt
+// RealServerBridge - Details und Begründung siehe dort.
-const int ConnectTimeoutMs = 3000;
+var bridge = new RealServerBridge();
+await bridge.TryReconnectAsync(CancellationToken.None);
+bridge.EnsureFallbackTool();
-await using var pipe = new NamedPipeClientStream(
- ".", McpPipeConstants.PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
+using var cts = new CancellationTokenSource();
+var reconnectLoop = bridge.RunBackgroundReconnectLoopAsync(TimeSpan.FromSeconds(5), cts.Token);
-try
+var serverOptions = new McpServerOptions
{
- await pipe.ConnectAsync(ConnectTimeoutMs);
-}
-catch (Exception ex)
-{
- await Console.Error.WriteLineAsync(
- $"LehrerApp.McpBridge: Verbindung zu LehrerApp fehlgeschlagen ({ex.Message}). " +
- "Läuft die App und ist der MCP-Server in den Einstellungen aktiviert?");
- await RespondWithConnectionErrorAsync();
- Environment.Exit(1);
- return;
-}
-
-await Console.Error.WriteLineAsync("LehrerApp.McpBridge: verbunden.");
+ ServerInfo = new Implementation { Name = "LehrerApp", Version = "1.0.0" },
+ // ListChanged = true: Voraussetzung dafür, dass Claude Desktop reagiert, wenn RealServerBridge
+ // das Status-Werkzeug nachträglich durch die echte Werkzeugliste ersetzt (oder umgekehrt, falls
+ // LehrerApp während der Sitzung beendet wird).
+ Capabilities = new ServerCapabilities { Tools = new ToolsCapability { ListChanged = true } },
+ ToolCollection = bridge.Tools,
+};
await using var stdin = Console.OpenStandardInput();
await using var stdout = Console.OpenStandardOutput();
+await using var transport = new StreamServerTransport(stdin, stdout, "LehrerApp.McpBridge");
+await using var server = McpServer.Create(transport, serverOptions, loggerFactory: null, serviceProvider: null);
-var toApp = stdin.CopyToAsync(pipe);
-var toClient = pipe.CopyToAsync(stdout);
-
-// Sobald eine Richtung endet (App beendet die Verbindung, oder der KI-Client schließt stdin),
-// ist die Session vorbei — die andere Kopie hängt sonst an einem offenen Handle.
-await Task.WhenAny(toApp, toClient);
-
-// Der Verbindungsaufbau ist fehlgeschlagen, bevor irgendetwas an die App durchgereicht wurde. Der
-// MCP-Client wartet zu diesem Zeitpunkt bereits auf eine Antwort auf seine erste Anfrage
-// ("initialize") — eine korrekte JSON-RPC-Fehlerantwort braucht deren "id", also wird diese eine
-// Zeile noch selbst gelesen und beantwortet, statt den Prozess kommentarlos zu beenden.
-static async Task RespondWithConnectionErrorAsync()
+try
{
- string? line;
- try
- {
- line = await Console.In.ReadLineAsync();
- }
- catch
- {
- return;
- }
- if (string.IsNullOrWhiteSpace(line)) return;
-
- JsonNode? requestId = null;
- try
- {
- requestId = JsonNode.Parse(line)?["id"];
- }
- catch (JsonException)
- {
- // Keine gültige JSON-RPC-Nachricht - ohne "id" ist keine korrekte Fehlerantwort möglich,
- // der Prozess beendet sich dann einfach mit einem Fehler-Exitcode.
- return;
- }
- if (requestId is null) return;
-
- var response = new JsonObject
- {
- ["jsonrpc"] = "2.0",
- ["id"] = requestId.DeepClone(),
- ["error"] = new JsonObject
- {
- ["code"] = -32001,
- ["message"] = "LehrerApp läuft nicht oder der MCP-Server ist in den Einstellungen nicht aktiviert.",
- },
- };
- await Console.Out.WriteLineAsync(response.ToJsonString());
- await Console.Out.FlushAsync();
+ // Läuft, bis der KI-Client stdin schließt (Sitzungsende) - danach ist nichts mehr zu tun.
+ await server.RunAsync(cts.Token);
+}
+finally
+{
+ await cts.CancelAsync();
+ try { await reconnectLoop; }
+ catch { /* Beenden über Cancellation ist der Normalfall hier */ }
+ await bridge.DisposeAsync();
}
diff --git a/LehrerApp.McpBridge/ProxyMcpServerTool.cs b/LehrerApp.McpBridge/ProxyMcpServerTool.cs
new file mode 100644
index 0000000..88f8a9e
--- /dev/null
+++ b/LehrerApp.McpBridge/ProxyMcpServerTool.cs
@@ -0,0 +1,21 @@
+using ModelContextProtocol.Protocol;
+using ModelContextProtocol.Server;
+
+namespace LehrerApp.McpBridge;
+
+///
+/// Reicht einen von LehrerApp.Desktop tatsächlich angebotenen MCP-Tool-Aufruf 1:1 an den echten
+/// Server weiter - Name, Beschreibung und Eingabeschema () stammen
+/// unverändert von dort (siehe ). Die Bridge muss dadurch kein
+/// eigenes Wissen über einzelne LehrerApp-Werkzeuge/-Parameter pflegen; ändert sich ein Tool in
+/// LehrerApp.Desktop, übernimmt die Bridge das beim nächsten Verbindungsaufbau automatisch.
+///
+internal sealed class ProxyMcpServerTool(Tool protocolTool, RealServerBridge bridge) : McpServerTool
+{
+ public override Tool ProtocolTool { get; } = protocolTool;
+ public override IReadOnlyList