using System.IO.Pipes; using System.Text.Json; using System.Text.Json.Nodes; using LehrerApp.Core.Mcp; // 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. const int ConnectTimeoutMs = 3000; await using var pipe = new NamedPipeClientStream( ".", McpPipeConstants.PipeName, PipeDirection.InOut, PipeOptions.Asynchronous); try { 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."); await using var stdin = Console.OpenStandardInput(); await using var stdout = Console.OpenStandardOutput(); 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() { 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(); }