feat: lokaler MCP-Server, Phase 2 (Write-Tools + Bestätigungsdialog + Lesson-Plans)
Neue Write-Tools create_time_entry, create_grade_entry und update_student_group_assignment schreiben nie direkt: jeder Aufruf zeigt zuerst einen menschenlesbaren Bestätigungsdialog (bestehender ConfirmDialog, über Dispatcher.UIThread aus dem Pipe-Session-Thread angezeigt) und schreibt erst nach Bestätigung, mit 2-Minuten-Timeout gegen eine hängende Session. Zusätzliches Read-Tool get_lesson_plans. create_note bewusst nicht umgesetzt (kollidiert mit dem bestehenden Dokumentations-Ausschluss aus Phase 1), create_lesson_plan/ update_lesson_plan wegen der Modellkomplexität von Lesson zurückgestellt (siehe TODO.md 4.5.26). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Threading;
|
||||
using LehrerApp.Desktop.Views.Shared;
|
||||
|
||||
namespace LehrerApp.Desktop.Services.Mcp;
|
||||
|
||||
/// <summary>
|
||||
/// Produktive <see cref="IMcpConfirmationService"/>-Implementierung: zeigt den bestehenden
|
||||
/// <see cref="ConfirmDialog"/> (Views/Shared) über dem Hauptfenster an. Der aufrufende Tool-Handler
|
||||
/// läuft auf einem Hintergrund-Thread (MCP-Pipe-Session in <see cref="McpServerHostedService"/>),
|
||||
/// deshalb Marshalling über <see cref="Dispatcher.UIThread"/>.
|
||||
///
|
||||
/// Ohne Reaktion des Nutzers würde die Pipe-Session (und damit der wartende KI-Client) unbegrenzt
|
||||
/// hängen bleiben — nach <see cref="Timeout"/> wird der Dialog automatisch geschlossen und die
|
||||
/// Änderung als abgelehnt gewertet.
|
||||
/// </summary>
|
||||
public sealed class AvaloniaMcpConfirmationService : IMcpConfirmationService
|
||||
{
|
||||
private static readonly TimeSpan Timeout = TimeSpan.FromMinutes(2);
|
||||
|
||||
public async Task<bool> ConfirmAsync(string title, string message, CancellationToken ct)
|
||||
{
|
||||
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
||||
return false;
|
||||
|
||||
// Die gesamte Warte-/Timeout-Logik läuft als ein Stück innerhalb des UI-Thread-Callbacks:
|
||||
// Avalonias Dispatcher-Synchronisationskontext sorgt dafür, dass die Fortsetzung nach
|
||||
// "await Task.WhenAny(...)" wieder auf dem UI-Thread läuft, sodass dialog.Close() dort
|
||||
// sicher aufgerufen werden kann.
|
||||
return await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
var dialog = new ConfirmDialog
|
||||
{
|
||||
DataContext = new ConfirmDialogInfo { Title = title, Message = message, ConfirmText = "Übernehmen" },
|
||||
};
|
||||
var dialogTask = dialog.ShowDialog<bool>(owner);
|
||||
var timeoutTask = Task.Delay(Timeout, ct);
|
||||
var completed = await Task.WhenAny(dialogTask, timeoutTask);
|
||||
if (completed != dialogTask)
|
||||
{
|
||||
dialog.Close(false);
|
||||
return false;
|
||||
}
|
||||
return await dialogTask;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user