P1c: 5 mutierende MCP-Tools als ITaskBridgeService-Fassade mit State-Enum-Validation
- nexus_create_task: Fassade → bridge.CreateTaskAsync() - nexus_create_child_task: Fassade → bridge.CreateChildTaskAsync() - nexus_update_status: Enum-validiert via NexusMcpTaskState (0-4) MCP SDK lehnt ungültige Integer-Werte vor Tool-Aufruf ab ToStateString() hat exhaustiven Switch + InvalidEnumArgumentException-Fallback Bridge erzwingt CanChangeState-Autorisierung (nur iris/bao/nexus-system) - nexus_append_activity: Fassade → bridge.AppendActivityAsync() - nexus_handoff: Fassade → bridge.HandoffAsync() Neue NexusMcpTaskStateHelper.IsDefined() für testbare State-Validierung. Alle 5 Tools via [McpServerTool] auf McpServerToolType-Klasse registriert. Keine Businesslogik-Duplizierung — volle Delegation an ITaskBridgeService. Build: 0w 0e.
This commit is contained in:
@@ -61,15 +61,24 @@ public sealed class NexusMcpTools(
|
|||||||
return activity.Select(entry => new ActivityEntryDto(entry.Id, entry.Type, entry.Message, entry.CreatedAt)).ToList();
|
return activity.Select(entry => new ActivityEntryDto(entry.Id, entry.Type, entry.Message, entry.CreatedAt)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── P1c: Mutating MCP Tools ──
|
// ── P1c: Mutating MCP Tools (TaskBridgeService facade) ──
|
||||||
|
//
|
||||||
|
// Each tool delegates directly to ITaskBridgeService without introducing
|
||||||
|
// new business logic. Authorization, validation, and side-effects
|
||||||
|
// (notifications, live-update broadcasts) are handled by the bridge.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a top-level task on the Nexus board.
|
||||||
|
/// The caller (derived from X-Agent-Id or JWT) is set as the default
|
||||||
|
/// assignee and source. Priority defaults to "Normal".
|
||||||
|
/// </summary>
|
||||||
[McpServerTool(Name = "nexus_create_task")]
|
[McpServerTool(Name = "nexus_create_task")]
|
||||||
[Description("Create a top-level Nexus task.")]
|
[Description("Create a top-level Nexus task.")]
|
||||||
public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> CreateTask(
|
public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> CreateTask(
|
||||||
string title,
|
[Description("Task title (required).")] string title,
|
||||||
string? detail = null,
|
[Description("Optional long-form description.")] string? detail = null,
|
||||||
string? priority = "Normal",
|
[Description("Priority label. Defaults to 'Normal'.")] string? priority = "Normal",
|
||||||
string? assignedTo = null,
|
[Description("Agent ID to assign the task to. Defaults to the caller.")] string? assignedTo = null,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var caller = await ResolveCallerAsync(ct);
|
var caller = await ResolveCallerAsync(ct);
|
||||||
@@ -84,16 +93,21 @@ public sealed class NexusMcpTools(
|
|||||||
return ToResponse(result, "nexus_create_task");
|
return ToResponse(result, "nexus_create_task");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a visible child task under a Nexus parent for delegation.
|
||||||
|
/// If the parent is in Backlog, it is automatically moved to In progress
|
||||||
|
/// to signal that coordination has started.
|
||||||
|
/// </summary>
|
||||||
[McpServerTool(Name = "nexus_create_child_task")]
|
[McpServerTool(Name = "nexus_create_child_task")]
|
||||||
[Description("Create a visible child task under a Nexus parent task for delegation.")]
|
[Description("Create a visible child task under a Nexus parent task for delegation.")]
|
||||||
public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> CreateChildTask(
|
public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> CreateChildTask(
|
||||||
Guid parentTaskId,
|
[Description("ID of the parent task.")] Guid parentTaskId,
|
||||||
string title,
|
[Description("Child task title (required).")] string title,
|
||||||
string? detail = null,
|
[Description("Optional long-form description.")] string? detail = null,
|
||||||
string? priority = "Normal",
|
[Description("Priority label. Defaults to 'Normal'.")] string? priority = "Normal",
|
||||||
string? assignedTo = null,
|
[Description("Agent ID to assign. Defaults to the expected-from agent.")] string? assignedTo = null,
|
||||||
string? expectedFrom = null,
|
[Description("Agent who is expected to deliver this work.")] string? expectedFrom = null,
|
||||||
bool startsInProgress = false,
|
[Description("If true, the child starts in 'In progress' instead of Backlog.")] bool startsInProgress = false,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var caller = await ResolveCallerAsync(ct);
|
var caller = await ResolveCallerAsync(ct);
|
||||||
@@ -111,11 +125,21 @@ public sealed class NexusMcpTools(
|
|||||||
return ToResponse(result, "nexus_create_child_task");
|
return ToResponse(result, "nexus_create_child_task");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a task's lifecycle state.
|
||||||
|
/// The <paramref name="state"/> parameter is typed as
|
||||||
|
/// <see cref="NexusMcpTaskState"/> so the MCP SDK rejects unknown
|
||||||
|
/// integer values before the tool is ever invoked. Additionally,
|
||||||
|
/// <see cref="ToStateString"/> performs an exhaustive switch with a
|
||||||
|
/// defensive <see cref="InvalidEnumArgumentException"/> fallback.
|
||||||
|
/// The bridge enforces authorization (only iris/bao/nexus-system may
|
||||||
|
/// change state) and canonical-state validation.
|
||||||
|
/// </summary>
|
||||||
[McpServerTool(Name = "nexus_update_status")]
|
[McpServerTool(Name = "nexus_update_status")]
|
||||||
[Description("Update a Nexus task status. The schema only exposes canonical task states.")]
|
[Description("Update a Nexus task status. The schema only exposes canonical task states.")]
|
||||||
public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> UpdateStatus(
|
public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> UpdateStatus(
|
||||||
Guid taskId,
|
[Description("ID of the task to update.")] Guid taskId,
|
||||||
NexusMcpTaskState state,
|
[Description("New state: Backlog (0), InProgress (1), Blocked (2), Done (3), Review (4).")] NexusMcpTaskState state,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var caller = await ResolveCallerAsync(ct);
|
var caller = await ResolveCallerAsync(ct);
|
||||||
@@ -123,12 +147,16 @@ public sealed class NexusMcpTools(
|
|||||||
return ToResponse(result, "nexus_update_status");
|
return ToResponse(result, "nexus_update_status");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends an activity entry (comment, status note, or checkpoint) to a task.
|
||||||
|
/// This triggers a live-update broadcast so dashboards stay current.
|
||||||
|
/// </summary>
|
||||||
[McpServerTool(Name = "nexus_append_activity")]
|
[McpServerTool(Name = "nexus_append_activity")]
|
||||||
[Description("Append an activity/checkpoint entry to a Nexus task.")]
|
[Description("Append an activity/checkpoint entry to a Nexus task.")]
|
||||||
public async Task<TaskBridgeCommandResponse<ActivityEntryDto>> AppendActivity(
|
public async Task<TaskBridgeCommandResponse<ActivityEntryDto>> AppendActivity(
|
||||||
Guid taskId,
|
[Description("ID of the task to annotate.")] Guid taskId,
|
||||||
string message,
|
[Description("Activity message text (required).")] string message,
|
||||||
string? type = "comment",
|
[Description("Activity type: 'comment', 'status', 'agent-note', 'handoff'. Defaults to 'comment'.")] string? type = "comment",
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await ResolveCallerAsync(ct);
|
await ResolveCallerAsync(ct);
|
||||||
@@ -136,12 +164,17 @@ public sealed class NexusMcpTools(
|
|||||||
return ToActivityResponse(result, "nexus_append_activity");
|
return ToActivityResponse(result, "nexus_append_activity");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks a task handoff to another agent.
|
||||||
|
/// Updates ExpectedFrom (and AssignedTo for standalone tasks), appends
|
||||||
|
/// a handoff activity entry, and sends a notification to the target agent.
|
||||||
|
/// </summary>
|
||||||
[McpServerTool(Name = "nexus_handoff")]
|
[McpServerTool(Name = "nexus_handoff")]
|
||||||
[Description("Mark a task handoff to another known agent and append handoff activity.")]
|
[Description("Mark a task handoff to another known agent and append handoff activity.")]
|
||||||
public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> Handoff(
|
public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> Handoff(
|
||||||
Guid taskId,
|
[Description("ID of the task to hand off.")] Guid taskId,
|
||||||
string targetAgent,
|
[Description("Target agent ID (must be a known agent).")] string targetAgent,
|
||||||
string? note = null,
|
[Description("Optional handoff note.")] string? note = null,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await ResolveCallerAsync(ct);
|
await ResolveCallerAsync(ct);
|
||||||
@@ -226,11 +259,38 @@ public sealed class NexusMcpTools(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Canonical task states exposed via the MCP tool schema.
|
||||||
|
/// Integer values 0-4 map to the canonical string representations used
|
||||||
|
/// by <see cref="TaskStateHelper.AllStates"/>. The MCP SDK rejects
|
||||||
|
/// out-of-range integers before the tool is invoked.
|
||||||
|
/// </summary>
|
||||||
public enum NexusMcpTaskState
|
public enum NexusMcpTaskState
|
||||||
{
|
{
|
||||||
Backlog,
|
/// <summary>Task is in the backlog (not yet started).</summary>
|
||||||
InProgress,
|
Backlog = 0,
|
||||||
Blocked,
|
/// <summary>Work is actively in progress.</summary>
|
||||||
Done,
|
InProgress = 1,
|
||||||
Review
|
/// <summary>Work is blocked by an external dependency.</summary>
|
||||||
|
Blocked = 2,
|
||||||
|
/// <summary>Work is complete.</summary>
|
||||||
|
Done = 3,
|
||||||
|
/// <summary>Work is ready for review.</summary>
|
||||||
|
Review = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Static helpers for <see cref="NexusMcpTaskState"/>.
|
||||||
|
/// The <see cref="IsDefined"/> method provides a testable entry point
|
||||||
|
/// for verifying that <c>update_status</c> rejects invalid state values.
|
||||||
|
/// </summary>
|
||||||
|
public static class NexusMcpTaskStateHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true when <paramref name="state"/> is one of the five
|
||||||
|
/// canonical values defined in <see cref="NexusMcpTaskState"/>.
|
||||||
|
/// Rejects undefined cast values (e.g. <c>(NexusMcpTaskState)99</c>).
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsDefined(NexusMcpTaskState state) =>
|
||||||
|
Enum.IsDefined(state);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user