Erlaubt einem lokalen KI-Client (z.B. Claude Desktop) strukturierten Lesezugriff auf Schüler, Klausuren, Noten, Stundenplan und Zeiterfassung. Neuer LehrerApp.McpBridge-Prozess reicht stdio-JSON-RPC über eine Named Pipe an einen In-Process-MCP-Server im Avalonia-Hauptprozess durch (ModelContextProtocol.Core, StreamServerTransport direkt auf der Pipe). Standardmäßig deaktiviert, Opt-in über neuen Einstellungen-Tab. Dokumentationstypen (Gesprächsnotizen/Vorfälle/Förderpläne) sind auf Code-Ebene nie erreichbar (McpToolScope, analog PlainEventStore.Allowed). Write-Tools, Bestätigungsdialog-UI, Worksheets/Lesson-Plans-Tools und macOS-Packaging folgen in späteren Phasen (siehe TODO.md 4.5.25). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
85 lines
2.8 KiB
C#
85 lines
2.8 KiB
C#
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();
|
|
}
|