Files
adminandClaude Sonnet 5 9567d8d616 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>
2026-09-11 20:14:51 +02:00

71 lines
3.2 KiB
C#

using System.ComponentModel;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
namespace LehrerApp.Desktop.Services.Mcp.Tools;
/// <summary>MCP-Tools "get_grades" (Phase 1) und "create_grade_entry" (Phase 2), siehe
/// Planungsdokument.</summary>
public class GradeTools(IGradeRepository grades, IStudentRepository students, IMcpConfirmationService confirmation)
{
[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();
}
[Description("Schlägt eine neue Note für einen Schüler vor. Muss der Nutzer erst in einem Dialog in LehrerApp bestätigen, bevor sie gespeichert wird.")]
public async Task<WriteResultDto> CreateGradeEntry(
[Description("Schüler-ID.")] Guid studentId,
[Description("Lerngruppen-ID.")] Guid groupId,
[Description("Kategorie: Oral, Homework, Participation, Project oder Other.")] GradeCategory category,
[Description("Notenwert als Text, z.B. \"2+\" oder \"gut\".")] string value,
[Description("Datum, Format YYYY-MM-DD.")] DateOnly date,
[Description("Gewichtung, Standard 1.0.")] double weight = 1.0,
[Description("Optionale Notiz.")] string? note = null,
CancellationToken ct = default)
{
var student = students.GetById(studentId);
if (student is null)
return new WriteResultDto(false, null, "Unbekannte Schüler-ID.");
var message =
$"Neue Note für {student.FullName}: {GradeCategoryDisplayName(category)} = {value}" +
(weight != 1.0 ? $" (Gewichtung {weight:0.##})" : "") +
$", am {date:dd.MM.yyyy}" +
(string.IsNullOrWhiteSpace(note) ? "" : $"\n„{note}“");
if (!await confirmation.ConfirmAsync("Note anlegen?", message, ct))
return new WriteResultDto(false, null, "Vom Nutzer abgelehnt oder nicht bestätigt.");
var grade = new Grade
{
StudentId = studentId,
GroupId = groupId,
Category = category,
Value = value,
Date = date,
Weight = weight,
Note = note,
};
grades.Save(grade);
return new WriteResultDto(true, grade.Id, "Note gespeichert.");
}
// Eigene, schlanke Beschriftung statt Wiederverwendung von ViewModels.Groups.GradeCategoryDisplay:
// Tool-Klassen unter Services/Mcp sollen nicht von ViewModel-Klassen abhängen.
private static string GradeCategoryDisplayName(GradeCategory c) => c switch
{
GradeCategory.Oral => "Mündlich",
GradeCategory.Homework => "Hausaufgaben",
GradeCategory.Participation => "Mitarbeit",
GradeCategory.Project => "Projekt",
GradeCategory.Other => "Sonstiges",
_ => c.ToString(),
};
}