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:
@@ -94,6 +94,11 @@ public class App : Application
|
||||
_exitHandlerAttached = true;
|
||||
}
|
||||
|
||||
// MCP-Server (lokal, Phase 1): Start ist ohne Wirkung, falls in den Einstellungen nicht
|
||||
// aktiviert (siehe McpServerHostedService.Start). Kein Live-Reload beim Umschalten des
|
||||
// Opt-in - ein Neustart der App richtet den Pipe-Listener neu ein.
|
||||
Services.GetRequiredService<Services.Mcp.McpServerHostedService>().Start();
|
||||
|
||||
var mainVm = Services.GetRequiredService<MainWindowViewModel>();
|
||||
WireCallbacks(mainVm);
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ using LehrerApp.Core.Services;
|
||||
using LehrerApp.Data;
|
||||
using LehrerApp.Data.Repositories;
|
||||
using LehrerApp.Desktop.Services;
|
||||
using LehrerApp.Desktop.Services.Mcp;
|
||||
using LehrerApp.Desktop.Services.Mcp.Tools;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using LehrerApp.Desktop.ViewModels.ClassTeacher;
|
||||
using LehrerApp.Desktop.ViewModels.Exams;
|
||||
@@ -213,6 +215,15 @@ public static class AppBootstrapper
|
||||
});
|
||||
services.AddSingleton<AiPlanningService>();
|
||||
|
||||
// ── MCP-Server (lokal, Phase 1 – siehe Planungsdokument, optional per Opt-in) ─────────
|
||||
services.AddSingleton(_ => new McpSettingsService(appData));
|
||||
services.AddSingleton<StudentTools>();
|
||||
services.AddSingleton<ExamTools>();
|
||||
services.AddSingleton<GradeTools>();
|
||||
services.AddSingleton<ScheduleTools>();
|
||||
services.AddSingleton<TimeEntryTools>();
|
||||
services.AddSingleton<McpServerHostedService>();
|
||||
|
||||
// ── WebUntis-iCal-Abgleich (optional – nur wenn URL hinterlegt und aktiviert) ─────────
|
||||
var untisSettings = new WebUntisSettingsService(appData);
|
||||
services.AddSingleton(untisSettings);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="QuestPDF" />
|
||||
<PackageReference Include="ModelContextProtocol.Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
using System.IO.Pipes;
|
||||
using LehrerApp.Core.Mcp;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.Services.Mcp.Tools;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace LehrerApp.Desktop.Services.Mcp;
|
||||
|
||||
/// <summary>
|
||||
/// In-Process-MCP-Server (Phase 1, siehe Planungsdokument). Lauscht auf der Named Pipe
|
||||
/// <see cref="McpPipeConstants.PipeName"/> und bedient jede eingehende Verbindung (eine je
|
||||
/// LehrerApp.McpBridge-Instanz) als eigene MCP-Session über <see cref="StreamServerTransport"/> —
|
||||
/// ein <see cref="NamedPipeServerStream"/> ist ein normaler <see cref="Stream"/> und kann direkt
|
||||
/// als Ein-/Ausgabe der Session übergeben werden, ohne eigenes JSON-RPC-Parsing.
|
||||
///
|
||||
/// Nur aktiv, wenn <see cref="McpSettingsService.Enabled"/> — sonst tut <see cref="Start"/> nichts.
|
||||
/// Repositories sind im DI-Container Singletons (siehe AppBootstrapper), deshalb reicht es, die
|
||||
/// Tool-Instanzen und die daraus gebaute <see cref="McpServerOptions"/> einmalig zu bauen und für
|
||||
/// alle Sessions zu teilen.
|
||||
/// </summary>
|
||||
public sealed class McpServerHostedService : IAsyncDisposable
|
||||
{
|
||||
private readonly McpSettingsService _settings;
|
||||
private readonly AppLogger _logger;
|
||||
private readonly McpServerOptions _serverOptions;
|
||||
private CancellationTokenSource? _cts;
|
||||
private Task? _acceptLoop;
|
||||
|
||||
public McpServerHostedService(
|
||||
McpSettingsService settings, AppLogger logger,
|
||||
StudentTools studentTools, ExamTools examTools, GradeTools gradeTools,
|
||||
ScheduleTools scheduleTools, TimeEntryTools timeEntryTools)
|
||||
{
|
||||
_settings = settings;
|
||||
_logger = logger;
|
||||
_serverOptions = BuildServerOptions(studentTools, examTools, gradeTools, scheduleTools, timeEntryTools);
|
||||
}
|
||||
|
||||
/// <summary>Setzt die Pipe-Server-Accept-Loop auf, falls aktiviert. Ohne Wirkung, falls
|
||||
/// bereits gestartet oder in den Einstellungen deaktiviert (dann bleibt keine Pipe offen).</summary>
|
||||
public void Start()
|
||||
{
|
||||
if (!_settings.Enabled || _cts is not null) return;
|
||||
_cts = new CancellationTokenSource();
|
||||
_acceptLoop = Task.Run(() => AcceptLoopAsync(_cts.Token));
|
||||
_logger.Info("MCP-Server gestartet, lauscht auf Pipe '" + McpPipeConstants.PipeName + "'.");
|
||||
}
|
||||
|
||||
private async Task AcceptLoopAsync(CancellationToken ct)
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
var pipe = new NamedPipeServerStream(
|
||||
McpPipeConstants.PipeName, PipeDirection.InOut,
|
||||
NamedPipeServerStream.MaxAllowedServerInstances,
|
||||
PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
||||
try
|
||||
{
|
||||
await pipe.WaitForConnectionAsync(ct);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
await pipe.DisposeAsync();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("MCP: Fehler beim Warten auf eine Bridge-Verbindung.", ex);
|
||||
await pipe.DisposeAsync();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Nicht awaiten: die Accept-Loop muss sofort weiterlaufen, damit mehrere gleichzeitige
|
||||
// Bridge-Instanzen (mehrere KI-Client-Sitzungen) unabhängig bedient werden.
|
||||
_ = RunSessionAsync(pipe, ct);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunSessionAsync(NamedPipeServerStream pipe, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var transport = new StreamServerTransport(pipe, pipe, "LehrerApp");
|
||||
await using var server = McpServer.Create(transport, _serverOptions, loggerFactory: null, serviceProvider: null);
|
||||
await server.RunAsync(ct);
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
_logger.Warn($"MCP: Session beendet ({ex.Message}).");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await pipe.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_cts is null) return;
|
||||
await _cts.CancelAsync();
|
||||
if (_acceptLoop is not null)
|
||||
{
|
||||
try { await _acceptLoop; }
|
||||
catch { /* Beenden über Cancellation ist der Normalfall hier */ }
|
||||
}
|
||||
_cts.Dispose();
|
||||
}
|
||||
|
||||
private static McpServerOptions BuildServerOptions(
|
||||
StudentTools studentTools, ExamTools examTools, GradeTools gradeTools,
|
||||
ScheduleTools scheduleTools, TimeEntryTools timeEntryTools)
|
||||
{
|
||||
var toolCollection = new McpServerPrimitiveCollection<McpServerTool>();
|
||||
|
||||
void AddTool(Delegate handler, string name, string description)
|
||||
{
|
||||
toolCollection.Add(McpServerTool.Create(handler, new McpServerToolCreateOptions
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
ReadOnly = true,
|
||||
}));
|
||||
}
|
||||
|
||||
AddTool(studentTools.GetStudents, "get_students",
|
||||
"Listet Schüler, optional gefiltert nach Lerngruppe.");
|
||||
AddTool(examTools.GetExams, "get_exams",
|
||||
"Listet Klausuren, optional gefiltert nach Lerngruppe.");
|
||||
AddTool(gradeTools.GetGrades, "get_grades",
|
||||
"Listet Noten einer Lerngruppe, optional gefiltert auf einen Schüler.");
|
||||
AddTool(scheduleTools.GetSchedule, "get_schedule",
|
||||
"Listet Stundenplan-Einträge, optional gefiltert nach Lerngruppe.");
|
||||
AddTool(timeEntryTools.GetTimeEntries, "get_time_entries",
|
||||
"Listet eigene Zeiterfassungs-Einträge in einem Datumsbereich.");
|
||||
|
||||
System.Diagnostics.Debug.Assert(
|
||||
toolCollection.Select(t => t.ProtocolTool.Name).OrderBy(n => n)
|
||||
.SequenceEqual(McpToolScope.AllowedReadTools.OrderBy(n => n)),
|
||||
"Registrierte MCP-Tools weichen von McpToolScope.AllowedReadTools ab.");
|
||||
|
||||
return new McpServerOptions
|
||||
{
|
||||
ServerInfo = new Implementation { Name = "LehrerApp", Version = "1.0.0" },
|
||||
Capabilities = new ServerCapabilities { Tools = new ToolsCapability() },
|
||||
ToolCollection = toolCollection,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace LehrerApp.Desktop.Services.Mcp;
|
||||
|
||||
/// <summary>
|
||||
/// Allowlist der über MCP exponierten Tool-Namen. Dieselbe Absicherung wie
|
||||
/// LehrerApp.Api/PlainEventStore.cs (dort für den Klartext-Sync-Kanal): Gesprächsnotizen, Vorfälle
|
||||
/// und Förderpläne (Documentation/Vorgang) sind hier bewusst nie aufgeführt und werden von keiner
|
||||
/// Tool-Klasse referenziert — ein KI-Client kann diese Daten technisch nicht erreichen, unabhängig
|
||||
/// davon, wie vertrauenswürdig der lokale Modell-Client erscheint oder wie die Tool-Liste künftig
|
||||
/// wächst. <see cref="McpServerHostedService"/> registriert nur exakt diese Namen.
|
||||
/// </summary>
|
||||
public static class McpToolScope
|
||||
{
|
||||
public static readonly IReadOnlyCollection<string> AllowedReadTools =
|
||||
[
|
||||
"get_students",
|
||||
"get_exams",
|
||||
"get_grades",
|
||||
"get_schedule",
|
||||
"get_time_entries",
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using LehrerApp.Core.Models;
|
||||
|
||||
namespace LehrerApp.Desktop.Services.Mcp.Tools;
|
||||
|
||||
// Schlanke, bewusst nicht 1:1 zu den LiteDB-Entities gehaltene Rückgabetypen: verhindert, dass ein
|
||||
// später zum Modell hinzugefügtes Feld (z.B. ein neues personenbezogenes Attribut) unbeabsichtigt
|
||||
// über ein MCP-Tool nach außen dringt, nur weil es Teil der Entity-Klasse ist.
|
||||
|
||||
public record StudentDto(Guid Id, string FirstName, string LastName, bool IsActive);
|
||||
|
||||
public record ExamResultDto(Guid StudentId, double TotalPoints, string? Grade, bool Absent);
|
||||
|
||||
public record ExamDto(
|
||||
Guid Id, Guid GroupId, string Title, DateOnly Date, ExamStatus Status, Niveau? Niveau,
|
||||
List<ExamResultDto>? Results);
|
||||
|
||||
public record GradeDto(
|
||||
Guid Id, Guid StudentId, Guid GroupId, GradeCategory Category, string Value, DateOnly Date,
|
||||
double Weight, string? Note);
|
||||
|
||||
public record TimetableSlotDto(Guid Id, Guid GroupId, DayOfWeek Weekday, int PeriodNumber, string? Room);
|
||||
|
||||
public record TimeEntryDto(
|
||||
Guid Id, Guid? TaskId, string Category, Guid? GroupId, DateOnly Date,
|
||||
TimeOnly? StartTime, TimeOnly? EndTime, int DurationMinutes, string? Description);
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.ComponentModel;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
|
||||
namespace LehrerApp.Desktop.Services.Mcp.Tools;
|
||||
|
||||
/// <summary>MCP-Read-Tool "get_exams" (Phase 1, siehe Planungsdokument).</summary>
|
||||
public class ExamTools(IExamRepository exams, IExamResultRepository examResults)
|
||||
{
|
||||
[Description("Listet Klausuren, optional gefiltert nach Lerngruppe.")]
|
||||
public List<ExamDto> GetExams(
|
||||
[Description("Optionale Lerngruppen-ID zum Filtern.")] Guid? groupId = null,
|
||||
[Description("Ergebnisse je Schüler mitliefern (Standard: nein, hält die Antwort klein).")] bool includeResults = false)
|
||||
{
|
||||
var list = groupId is { } id ? exams.GetByGroup(id) : exams.GetAll();
|
||||
return list.Select(e => new ExamDto(
|
||||
e.Id, e.GroupId, e.Title, e.Date, e.Status, e.Niveau,
|
||||
includeResults
|
||||
? examResults.GetByExam(e.Id)
|
||||
.Select(r => new ExamResultDto(r.StudentId, r.TotalPoints, r.Grade, r.Absent))
|
||||
.ToList()
|
||||
: null)).ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
|
||||
namespace LehrerApp.Desktop.Services.Mcp.Tools;
|
||||
|
||||
/// <summary>MCP-Read-Tool "get_grades" (Phase 1, siehe Planungsdokument).</summary>
|
||||
public class GradeTools(IGradeRepository grades)
|
||||
{
|
||||
[Description("Listet Noten einer Lerngruppe, optional gefiltert auf einen einzelnen Schüler.")]
|
||||
public List<GradeDto> GetGrades(
|
||||
[Description("Lerngruppen-ID.")] Guid groupId,
|
||||
[Description("Optionale Schüler-ID zum Filtern auf einen einzelnen Schüler.")] Guid? studentId = null)
|
||||
{
|
||||
var list = studentId is { } sid ? grades.GetByStudentAndGroup(sid, groupId) : grades.GetByGroup(groupId);
|
||||
return list.Select(g => new GradeDto(
|
||||
g.Id, g.StudentId, g.GroupId, g.Category, g.Value, g.Date, g.Weight, g.Note)).ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.ComponentModel;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
|
||||
namespace LehrerApp.Desktop.Services.Mcp.Tools;
|
||||
|
||||
/// <summary>MCP-Read-Tool "get_schedule" (Phase 1, siehe Planungsdokument).</summary>
|
||||
public class ScheduleTools(ITimetableSlotRepository slots)
|
||||
{
|
||||
[Description("Listet Stundenplan-Einträge (Wochenraster), optional gefiltert nach Lerngruppe.")]
|
||||
public List<TimetableSlotDto> GetSchedule(
|
||||
[Description("Optionale Lerngruppen-ID zum Filtern.")] Guid? groupId = null)
|
||||
{
|
||||
var list = groupId is { } id ? slots.GetByGroup(id) : slots.GetAll();
|
||||
return list.Select(s => new TimetableSlotDto(s.Id, s.GroupId, s.Weekday, s.PeriodNumber, s.Room)).ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.ComponentModel;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
|
||||
namespace LehrerApp.Desktop.Services.Mcp.Tools;
|
||||
|
||||
/// <summary>MCP-Read-Tool "get_students" (Phase 1, siehe Planungsdokument). Reine Lesezugriffe auf
|
||||
/// die bestehenden Repositories, keine eigene Datenzugriffslogik.</summary>
|
||||
public class StudentTools(IStudentRepository students)
|
||||
{
|
||||
[Description("Listet Schüler, optional gefiltert nach Lerngruppe. Enthält standardmäßig nur aktive Schüler.")]
|
||||
public List<StudentDto> GetStudents(
|
||||
[Description("Optionale Lerngruppen-ID zum Filtern.")] Guid? groupId = null,
|
||||
[Description("Auch inaktive/ausgeschiedene Schüler einbeziehen.")] bool includeInactive = false)
|
||||
{
|
||||
var list = groupId is { } id ? students.GetByGroup(id) : students.GetAll(includeInactive);
|
||||
if (groupId is not null && !includeInactive)
|
||||
list = list.Where(s => s.IsActive).ToList();
|
||||
return list.Select(s => new StudentDto(s.Id, s.FirstName, s.LastName, s.IsActive)).ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
|
||||
namespace LehrerApp.Desktop.Services.Mcp.Tools;
|
||||
|
||||
/// <summary>MCP-Read-Tool "get_time_entries" (Phase 1, siehe Planungsdokument). Der Zeitraum ist
|
||||
/// Pflicht (nicht optional), damit eine unbedachte Anfrage nicht die gesamte Zeiterfassungshistorie
|
||||
/// zurückgibt.</summary>
|
||||
public class TimeEntryTools(ITimeEntryRepository timeEntries)
|
||||
{
|
||||
[Description("Listet eigene Zeiterfassungs-Einträge in einem Datumsbereich.")]
|
||||
public List<TimeEntryDto> GetTimeEntries(
|
||||
[Description("Startdatum (einschließlich), Format YYYY-MM-DD.")] DateOnly from,
|
||||
[Description("Enddatum (einschließlich), Format YYYY-MM-DD.")] DateOnly to) =>
|
||||
timeEntries.GetByDateRange(from, to).Select(t => new TimeEntryDto(
|
||||
t.Id, t.TaskId, t.Category, t.GroupId, t.Date, t.StartTime, t.EndTime,
|
||||
t.DurationMinutes, t.Description)).ToList();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace LehrerApp.Desktop.Services;
|
||||
|
||||
internal class McpSettingsConfig
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opt-in-Schalter für den lokalen MCP-Server (siehe Planungsdokument, Phase 1). Anders als
|
||||
/// <see cref="AiSettingsService"/> braucht Phase 1 kein Login/Token — die Named Pipe selbst ist die
|
||||
/// Vertrauensgrenze (lokaler Prozess, gleiche Windows-Session bzw. Unix-Dateirechte), siehe
|
||||
/// Begründung im Planungsdokument.
|
||||
/// </summary>
|
||||
public class McpSettingsService
|
||||
{
|
||||
private readonly string _configPath;
|
||||
private McpSettingsConfig _config;
|
||||
|
||||
public bool Enabled => _config.Enabled;
|
||||
|
||||
public McpSettingsService(string appDataPath)
|
||||
{
|
||||
_configPath = Path.Combine(appDataPath, "mcp-settings.json");
|
||||
_config = Load();
|
||||
}
|
||||
|
||||
public void SetEnabled(bool enabled)
|
||||
{
|
||||
_config.Enabled = enabled;
|
||||
Save();
|
||||
}
|
||||
|
||||
private void Save() => File.WriteAllText(_configPath, JsonSerializer.Serialize(_config));
|
||||
|
||||
private McpSettingsConfig Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_configPath))
|
||||
return JsonSerializer.Deserialize<McpSettingsConfig>(File.ReadAllText(_configPath))
|
||||
?? new McpSettingsConfig();
|
||||
}
|
||||
catch { /* beschädigte Konfiguration -> Standardwert */ }
|
||||
return new McpSettingsConfig();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using LehrerApp.Desktop.Services;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.Settings;
|
||||
|
||||
public partial class SettingsViewModel
|
||||
{
|
||||
// ── MCP-Server (lokal, Phase 1) ──────────────────────────────────────────
|
||||
|
||||
[ObservableProperty] private bool _mcpEnabled;
|
||||
|
||||
private void LoadMcpSettings() => McpEnabled = _mcpSettings.Enabled;
|
||||
|
||||
// Kein Live-Reload (siehe McpServerHostedService/Planungsdokument) - der Pipe-Listener wird
|
||||
// nur beim App-Start eingerichtet, deshalb wirkt eine Änderung hier erst nach Neustart.
|
||||
partial void OnMcpEnabledChanged(bool value) => _mcpSettings.SetEnabled(value);
|
||||
}
|
||||
@@ -40,6 +40,7 @@ public enum SettingsTab
|
||||
WebUntis = 13,
|
||||
Appearance = 14,
|
||||
Trash = 15,
|
||||
Mcp = 16,
|
||||
}
|
||||
|
||||
// ── Haupt-ViewModel ───────────────────────────────────────────────────────────
|
||||
@@ -73,6 +74,7 @@ public partial class SettingsViewModel : ObservableObject
|
||||
private readonly ISupervisionDutyRepository _supervisionDuties;
|
||||
private readonly AiSettingsService _aiSettings;
|
||||
private readonly AiPlanningService _aiPlanning;
|
||||
private readonly McpSettingsService _mcpSettings;
|
||||
private readonly WebUntisSettingsService _untisSettings;
|
||||
private readonly WebUntisIntegrationService? _untisIntegration;
|
||||
private readonly UntisSyncService? _untisSync;
|
||||
@@ -99,7 +101,7 @@ public partial class SettingsViewModel : ObservableObject
|
||||
IShorthandCodeRepository shorthandCodes, ISchoolHolidayRepository schoolHolidays,
|
||||
SchoolCalendarSettingsService calendarSettings, PeriodScheduleService periodSchedule,
|
||||
ISupervisionDutyRepository supervisionDuties, TemplateStore letterTemplates,
|
||||
AiSettingsService aiSettings, AiPlanningService aiPlanning,
|
||||
AiSettingsService aiSettings, AiPlanningService aiPlanning, McpSettingsService mcpSettings,
|
||||
WebUntisSettingsService untisSettings,
|
||||
AnnualPlanSettingsService annualPlanSettings,
|
||||
SyncSettingsService syncSettings, SyncAuthService syncAuth, EventQueue eventQueue,
|
||||
@@ -137,6 +139,7 @@ public partial class SettingsViewModel : ObservableObject
|
||||
_letterTemplates = letterTemplates;
|
||||
_aiSettings = aiSettings;
|
||||
_aiPlanning = aiPlanning;
|
||||
_mcpSettings = mcpSettings;
|
||||
_untisSettings = untisSettings;
|
||||
_untisIntegration = untisIntegration;
|
||||
_untisSync = untisSync;
|
||||
@@ -164,6 +167,7 @@ public partial class SettingsViewModel : ObservableObject
|
||||
LoadSupervisionDuties();
|
||||
LoadLetterTemplates();
|
||||
LoadAiSettings();
|
||||
LoadMcpSettings();
|
||||
LoadUntisSettings();
|
||||
LoadAnnualPlanSettings();
|
||||
LoadSyncSettings();
|
||||
|
||||
@@ -1142,6 +1142,21 @@
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentPage>
|
||||
|
||||
<!-- Tab: MCP-Server (lokal, Phase 1) -->
|
||||
<ContentPage Header="MCP-Server">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="460">
|
||||
|
||||
<TextBlock Text="MCP-Server" FontSize="16" FontWeight="SemiBold"/>
|
||||
<TextBlock FontSize="12" Opacity="0.6" TextWrapping="Wrap"
|
||||
Text="Erlaubt einem lokalen KI-Client (z.B. Claude Desktop) strukturierten Lesezugriff auf Schüler, Klausuren, Noten, Stundenplan und Zeiterfassung dieses Geräts über eine lokale Named Pipe. Es verlassen keine Daten das Gerät; Gesprächsnotizen, Vorfälle und Förderpläne sind nicht erreichbar. Eine Änderung wirkt erst nach einem Neustart der App."/>
|
||||
|
||||
<CheckBox Content="MCP-Server aktivieren" IsChecked="{Binding McpEnabled}"/>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentPage>
|
||||
</TabbedPage>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
Reference in New Issue
Block a user