fix: GroupId ueber MCP auflösbar (get_groups + get_untis_hub_status)
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
Nutzer-Feedback aus echtem Live-Test der neuen Untis-MCP-Tools: groupId ist ueberall Pflichtparameter (get_grades, get_schedule, get_lesson_plans, get_untis_absence_rows, ...), war aber nirgends ueber MCP auflösbar - ein KI-Client kannte bestenfalls den Klarnamen einer Lerngruppe, nie ihre Id. - Neues GroupTools.cs mit get_groups (Read): listet Lerngruppen mit Id/Name/Typ/Schuljahr/ Klassenstufe/SubjectId/IsActive, optional nach Schuljahr gefiltert. - UntisHubStatusRowDto liefert zusaetzlich GroupId mit (null bei den drei dashboard-weiten Zeilen), damit fuer eine dort gelistete Gruppe nicht zusaetzlich get_groups noetig ist. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,9 +16,9 @@ public sealed class McpToolsTests
|
|||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
"download_lesson_attachment", "get_competency_catalog", "get_exams", "get_grades",
|
"download_lesson_attachment", "get_competency_catalog", "get_exams", "get_grades",
|
||||||
"get_lesson_plans", "get_named_untis_absence_pattern", "get_schedule", "get_students",
|
"get_groups", "get_lesson_plans", "get_named_untis_absence_pattern", "get_schedule",
|
||||||
"get_subjects", "get_time_entries", "get_untis_absence_rows", "get_untis_hub_status",
|
"get_students", "get_subjects", "get_time_entries", "get_untis_absence_rows",
|
||||||
"list_letter_templates", "render_letter",
|
"get_untis_hub_status", "list_letter_templates", "render_letter",
|
||||||
},
|
},
|
||||||
McpToolScope.AllowedReadTools.OrderBy(n => n, StringComparer.Ordinal));
|
McpToolScope.AllowedReadTools.OrderBy(n => n, StringComparer.Ordinal));
|
||||||
}
|
}
|
||||||
@@ -59,6 +59,26 @@ public sealed class McpToolsTests
|
|||||||
n.Contains("note", StringComparison.OrdinalIgnoreCase));
|
n.Contains("note", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── GroupTools ───────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetGroups_LiefertIdNameUndSchuljahrJeGruppe()
|
||||||
|
{
|
||||||
|
var group = new LearningGroup
|
||||||
|
{
|
||||||
|
Name = "9a", Type = GroupType.Class, SchoolYear = "2025/26", GradeLevel = 9, IsActive = true,
|
||||||
|
};
|
||||||
|
var tool = new GroupTools(new FakeGroups([group]));
|
||||||
|
|
||||||
|
var result = Assert.Single(tool.GetGroups());
|
||||||
|
|
||||||
|
Assert.Equal(group.Id, result.Id);
|
||||||
|
Assert.Equal("9a", result.Name);
|
||||||
|
Assert.Equal("2025/26", result.SchoolYear);
|
||||||
|
Assert.Equal(9, result.GradeLevel);
|
||||||
|
Assert.True(result.IsActive);
|
||||||
|
}
|
||||||
|
|
||||||
// ── StudentTools ─────────────────────────────────────────────────────────────────────────
|
// ── StudentTools ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ public static class AppBootstrapper
|
|||||||
services.AddSingleton<GroupMembershipTools>();
|
services.AddSingleton<GroupMembershipTools>();
|
||||||
services.AddSingleton<LetterTemplateTools>();
|
services.AddSingleton<LetterTemplateTools>();
|
||||||
services.AddSingleton<CompetencyTools>();
|
services.AddSingleton<CompetencyTools>();
|
||||||
|
services.AddSingleton<GroupTools>();
|
||||||
services.AddSingleton<UntisComparisonTools>();
|
services.AddSingleton<UntisComparisonTools>();
|
||||||
services.AddSingleton<McpServerHostedService>();
|
services.AddSingleton<McpServerHostedService>();
|
||||||
services.AddSingleton<McpClientRegistrationService>();
|
services.AddSingleton<McpClientRegistrationService>();
|
||||||
|
|||||||
@@ -32,13 +32,13 @@ public sealed class McpServerHostedService : IAsyncDisposable
|
|||||||
StudentTools studentTools, ExamTools examTools, GradeTools gradeTools,
|
StudentTools studentTools, ExamTools examTools, GradeTools gradeTools,
|
||||||
ScheduleTools scheduleTools, TimeEntryTools timeEntryTools, LessonPlanTools lessonPlanTools,
|
ScheduleTools scheduleTools, TimeEntryTools timeEntryTools, LessonPlanTools lessonPlanTools,
|
||||||
GroupMembershipTools groupMembershipTools, LetterTemplateTools letterTemplateTools,
|
GroupMembershipTools groupMembershipTools, LetterTemplateTools letterTemplateTools,
|
||||||
CompetencyTools competencyTools, UntisComparisonTools untisComparisonTools)
|
CompetencyTools competencyTools, UntisComparisonTools untisComparisonTools, GroupTools groupTools)
|
||||||
{
|
{
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serverOptions = BuildServerOptions(
|
_serverOptions = BuildServerOptions(
|
||||||
studentTools, examTools, gradeTools, scheduleTools, timeEntryTools, lessonPlanTools,
|
studentTools, examTools, gradeTools, scheduleTools, timeEntryTools, lessonPlanTools,
|
||||||
groupMembershipTools, letterTemplateTools, competencyTools, untisComparisonTools);
|
groupMembershipTools, letterTemplateTools, competencyTools, untisComparisonTools, groupTools);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Setzt die Pipe-Server-Accept-Loop auf, falls aktiviert. Ohne Wirkung, falls
|
/// <summary>Setzt die Pipe-Server-Accept-Loop auf, falls aktiviert. Ohne Wirkung, falls
|
||||||
@@ -115,7 +115,7 @@ public sealed class McpServerHostedService : IAsyncDisposable
|
|||||||
StudentTools studentTools, ExamTools examTools, GradeTools gradeTools,
|
StudentTools studentTools, ExamTools examTools, GradeTools gradeTools,
|
||||||
ScheduleTools scheduleTools, TimeEntryTools timeEntryTools, LessonPlanTools lessonPlanTools,
|
ScheduleTools scheduleTools, TimeEntryTools timeEntryTools, LessonPlanTools lessonPlanTools,
|
||||||
GroupMembershipTools groupMembershipTools, LetterTemplateTools letterTemplateTools,
|
GroupMembershipTools groupMembershipTools, LetterTemplateTools letterTemplateTools,
|
||||||
CompetencyTools competencyTools, UntisComparisonTools untisComparisonTools)
|
CompetencyTools competencyTools, UntisComparisonTools untisComparisonTools, GroupTools groupTools)
|
||||||
{
|
{
|
||||||
var toolCollection = new McpServerPrimitiveCollection<McpServerTool>();
|
var toolCollection = new McpServerPrimitiveCollection<McpServerTool>();
|
||||||
|
|
||||||
@@ -157,6 +157,8 @@ public sealed class McpServerHostedService : IAsyncDisposable
|
|||||||
|
|
||||||
AddReadTool(studentTools.GetStudents, "get_students",
|
AddReadTool(studentTools.GetStudents, "get_students",
|
||||||
"Listet Schüler, optional gefiltert nach Lerngruppe.");
|
"Listet Schüler, optional gefiltert nach Lerngruppe.");
|
||||||
|
AddReadTool(groupTools.GetGroups, "get_groups",
|
||||||
|
"Listet Lerngruppen (Klassen/Kurse) mit ihrer Id, optional gefiltert nach Schuljahr.");
|
||||||
AddReadTool(examTools.GetExams, "get_exams",
|
AddReadTool(examTools.GetExams, "get_exams",
|
||||||
"Listet Klausuren, optional gefiltert nach Lerngruppe.");
|
"Listet Klausuren, optional gefiltert nach Lerngruppe.");
|
||||||
AddReadTool(gradeTools.GetGrades, "get_grades",
|
AddReadTool(gradeTools.GetGrades, "get_grades",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public static class McpToolScope
|
|||||||
public static readonly IReadOnlyCollection<string> AllowedReadTools =
|
public static readonly IReadOnlyCollection<string> AllowedReadTools =
|
||||||
[
|
[
|
||||||
"get_students",
|
"get_students",
|
||||||
|
"get_groups",
|
||||||
"get_exams",
|
"get_exams",
|
||||||
"get_grades",
|
"get_grades",
|
||||||
"get_schedule",
|
"get_schedule",
|
||||||
|
|||||||
@@ -71,11 +71,22 @@ public record LetterTemplateDto(string Id, string Name, string Description, List
|
|||||||
public record LetterRenderResultDto(bool Success, string Message, string? Base64Pdf, string? SuggestedFileName);
|
public record LetterRenderResultDto(bool Success, string Message, string? Base64Pdf, string? SuggestedFileName);
|
||||||
|
|
||||||
/// <summary>Eine Zeile des Untis-Hub (siehe UntisHubService) - "Kind"/"DueState" als Text statt
|
/// <summary>Eine Zeile des Untis-Hub (siehe UntisHubService) - "Kind"/"DueState" als Text statt
|
||||||
/// Enum-Wert, damit ein KI-Client sie ohne Kenntnis des internen Enums lesen kann.</summary>
|
/// Enum-Wert, damit ein KI-Client sie ohne Kenntnis des internen Enums lesen kann. GroupId ist bei
|
||||||
|
/// den drei dashboard-weiten Zeilen (offene Stunden, Klassenbuch-/Hausaufgabenabgleich) null - siehe
|
||||||
|
/// UntisHubJobRow. Enthalten, damit ein KI-Client die Id nicht erst über get_groups nachschlagen
|
||||||
|
/// muss, um get_untis_absence_rows/get_named_untis_absence_pattern für eine hier gelistete Gruppe
|
||||||
|
/// aufzurufen (Nutzer-Feedback: die GroupId war nirgends exponiert).</summary>
|
||||||
public record UntisHubStatusRowDto(
|
public record UntisHubStatusRowDto(
|
||||||
string Kind, string GroupName, string DueState, string DueLabel,
|
string Kind, Guid? GroupId, string GroupName, string DueState, string DueLabel,
|
||||||
DateTime? LastRunAt, string? LastResultSummary);
|
DateTime? LastRunAt, string? LastResultSummary);
|
||||||
|
|
||||||
|
/// <summary>Lerngruppe (Klasse oder Kurs) - siehe GroupTools.GetGroups. Bewusst kein Verweis auf
|
||||||
|
/// SubjectId->Name aufgelöst (dafür get_subjects), um nicht bei jeder Gruppe implizit einen ganzen
|
||||||
|
/// Fach-Datensatz mitzuschleppen.</summary>
|
||||||
|
public record GroupDto(
|
||||||
|
Guid Id, string Name, GroupType Type, string SchoolYear, int GradeLevel,
|
||||||
|
Guid? SubjectId, bool IsActive);
|
||||||
|
|
||||||
/// <summary>Anonymisierte Fehlzeiten-Diskrepanz (siehe UntisComparisonTools.GetUntisAbsenceRows):
|
/// <summary>Anonymisierte Fehlzeiten-Diskrepanz (siehe UntisComparisonTools.GetUntisAbsenceRows):
|
||||||
/// bewusst KEIN Schülername/keine Klasse - <see cref="RowId"/> ist die einzige Kennung, über die
|
/// bewusst KEIN Schülername/keine Klasse - <see cref="RowId"/> ist die einzige Kennung, über die
|
||||||
/// UntisComparisonTools.ApplyUntisAbsenceStatus später zurückordnet.</summary>
|
/// UntisComparisonTools.ApplyUntisAbsenceStatus später zurückordnet.</summary>
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using LehrerApp.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace LehrerApp.Desktop.Services.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>MCP-Read-Tool "get_groups" (Nutzer-Nachtrag: die GroupId, die praktisch jedes andere
|
||||||
|
/// Tool als Pflichtparameter verlangt - get_grades, get_schedule, get_lesson_plans,
|
||||||
|
/// get_untis_absence_rows usw. - war nirgends über MCP auflösbar; ein KI-Client kannte bestenfalls
|
||||||
|
/// den Klarnamen einer Lerngruppe aus dem Gespräch, nie ihre Id). Reiner Lesezugriff auf das
|
||||||
|
/// bestehende Repository, keine eigene Datenzugriffslogik.</summary>
|
||||||
|
public class GroupTools(IGroupRepository groups)
|
||||||
|
{
|
||||||
|
[Description("Listet Lerngruppen (Klassen/Kurse) mit ihrer Id - Voraussetzung, um andere Tools (z.B. get_grades, get_schedule, get_untis_absence_rows) für eine bestimmte Gruppe aufzurufen, wenn nur ihr Name bekannt ist. Ohne schoolYear werden alle Schuljahre zurückgegeben.")]
|
||||||
|
public List<GroupDto> GetGroups(
|
||||||
|
[Description("Optionales Schuljahr zum Filtern, Format \"2025/26\". Ohne Angabe alle Schuljahre.")] string? schoolYear = null,
|
||||||
|
[Description("Auch inaktive/archivierte Gruppen einbeziehen.")] bool includeInactive = false)
|
||||||
|
{
|
||||||
|
var list = schoolYear is null
|
||||||
|
? groups.GetAll(includeInactive)
|
||||||
|
: groups.GetBySchoolYear(schoolYear, includeInactive);
|
||||||
|
return list.Select(g => new GroupDto(g.Id, g.Name, g.Type, g.SchoolYear, g.GradeLevel, g.SubjectId, g.IsActive)).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,7 +50,8 @@ public class UntisComparisonTools(
|
|||||||
[Description("Listet die Fälligkeit der Untis-Hub-Abgleiche (Fehlzeiten je Lerngruppe, offene Stunden, Klassenbuch-/Hausaufgabenabgleich) - reine Lesefunktion aus der lokalen Fälligkeits-Historie, kein eigener WebUntis-Zugriff.")]
|
[Description("Listet die Fälligkeit der Untis-Hub-Abgleiche (Fehlzeiten je Lerngruppe, offene Stunden, Klassenbuch-/Hausaufgabenabgleich) - reine Lesefunktion aus der lokalen Fälligkeits-Historie, kein eigener WebUntis-Zugriff.")]
|
||||||
public List<UntisHubStatusRowDto> GetUntisHubStatus() =>
|
public List<UntisHubStatusRowDto> GetUntisHubStatus() =>
|
||||||
hub.GetRows().Select(r => new UntisHubStatusRowDto(
|
hub.GetRows().Select(r => new UntisHubStatusRowDto(
|
||||||
r.Kind.ToString(), r.GroupName, r.DueState.ToString(), r.DueLabel, r.LastRunAt, r.LastResultSummary)).ToList();
|
r.Kind.ToString(), r.GroupId, r.GroupName, r.DueState.ToString(), r.DueLabel,
|
||||||
|
r.LastRunAt, r.LastResultSummary)).ToList();
|
||||||
|
|
||||||
[Description("""
|
[Description("""
|
||||||
Listet Fehlzeiten-Diskrepanzen einer Lerngruppe gegenüber WebUntis in einem Zeitraum, ANONYMISIERT:
|
Listet Fehlzeiten-Diskrepanzen einer Lerngruppe gegenüber WebUntis in einem Zeitraum, ANONYMISIERT:
|
||||||
|
|||||||
@@ -2086,6 +2086,17 @@ vollständig enthält.
|
|||||||
(unbekannter/nicht zulässiger Status, unbekannte/abgelaufene row-id, jeweils ohne Bestätigung
|
(unbekannter/nicht zulässiger Status, unbekannte/abgelaufene row-id, jeweils ohne Bestätigung
|
||||||
erreicht) sind in `UntisComparisonToolsTests.cs` abgedeckt.
|
erreicht) sind in `UntisComparisonToolsTests.cs` abgedeckt.
|
||||||
|
|
||||||
|
**Nachtrag (aus echtem Live-Test, 2026-09-13):** Nutzer versuchte die neuen Tools tatsächlich zu
|
||||||
|
benutzen und stieß darauf, dass `groupId` überall Pflichtparameter ist, aber nirgends über MCP
|
||||||
|
auflösbar war — ein KI-Client kannte bestenfalls den Klarnamen einer Lerngruppe aus dem Gespräch,
|
||||||
|
nie ihre Id (betrifft nicht nur die Untis-Tools, sondern z.B. auch `get_grades`/`get_schedule`/
|
||||||
|
`get_lesson_plans`). Beide vom Nutzer vorgeschlagenen Wege umgesetzt:
|
||||||
|
- Neues [GroupTools.cs](LehrerApp.Desktop/Services/Mcp/Tools/GroupTools.cs) mit `get_groups`
|
||||||
|
(Read; Id/Name/Typ/Schuljahr/Klassenstufe/SubjectId/IsActive, optional nach Schuljahr gefiltert).
|
||||||
|
- `UntisHubStatusRowDto` bekommt zusätzlich `GroupId` (null bei den drei dashboard-weiten
|
||||||
|
Zeilen), damit ein KI-Client für eine dort gelistete Gruppe nicht zusätzlich `get_groups`
|
||||||
|
aufrufen muss, nur um den Fehlzeitenabgleich für sie auszulösen.
|
||||||
|
|
||||||
### 4.4 Wochen-/Tagesansicht
|
### 4.4 Wochen-/Tagesansicht
|
||||||
- [x] **4.4.1** Kalenderansicht über alle Gruppen: Woche und Tag — siehe Nachtrag zu 4.3
|
- [x] **4.4.1** Kalenderansicht über alle Gruppen: Woche und Tag — siehe Nachtrag zu 4.3
|
||||||
("Heute"-Tab: Tagesliste unten angedockt, gruppenübergreifendes Wochenraster darüber, inkl.
|
("Heute"-Tab: Tagesliste unten angedockt, gruppenübergreifendes Wochenraster darüber, inkl.
|
||||||
|
|||||||
Reference in New Issue
Block a user