feat: lokaler MCP-Server, Phase 1 (Infrastruktur + Read-Tools)
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>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LehrerApp.Core\LehrerApp.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,84 @@
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user