fix(worker): make external MCP filter params optional, surface tool errors

Nullable filter/patch params across the External/ MCP tool classes (ListTasks,
UpdateTask, AddSubtask, ReviewTask, SetMyDay, SetListConfig/SetTaskConfig,
CreateList/UpdateList) lacked C# default values, so the generated tool schema
marked them required — MCP clients omitting them (the common case) failed.
Gave every such parameter a default value.

Also registered a call-tool filter (ExternalMcpExceptionFilter) on the external
MCP host that translates InvalidOperationException/ArgumentException into
McpException, since the SDK's own catch-all discards ex.Message for any other
exception type and returns a generic "An error occurred invoking 'X'." string.

Added a reflection-based schema test sweeping every [McpServerToolType] class
to guard against reintroducing a required-but-nullable parameter.
This commit is contained in:
mika kuns
2026-07-23 18:21:46 +02:00
parent 3d668da65c
commit d7ebafd556
8 changed files with 176 additions and 18 deletions
+3 -2
View File
@@ -22,7 +22,7 @@ public sealed class ListMcpTools
[McpServerTool, Description("Create a new task list. workingDir sets the git repo tasks run against; commitType defaults to 'chore'.")]
public async Task<ListSummaryDto> CreateList(
string name, string? workingDir, string? commitType, CancellationToken cancellationToken)
string name, string? workingDir = null, string? commitType = null, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(name))
throw new InvalidOperationException("name is required.");
@@ -42,7 +42,8 @@ public sealed class ListMcpTools
[McpServerTool, Description("Rename a list and/or change its working dir and default commit type. Pass null to leave a field unchanged.")]
public async Task<ListSummaryDto> UpdateList(
string listId, string? name, string? workingDir, string? commitType, CancellationToken cancellationToken)
string listId, string? name = null, string? workingDir = null, string? commitType = null,
CancellationToken cancellationToken = default)
{
var entity = await _lists.GetByIdAsync(listId, cancellationToken)
?? throw new InvalidOperationException($"List {listId} not found.");