feat(mcp): add save_finding to the in-task server

This commit is contained in:
mika kuns
2026-08-10 10:27:13 +02:00
parent b312095fb2
commit 519f64c02e
2 changed files with 56 additions and 1 deletions
+4 -1
View File
@@ -182,10 +182,13 @@ builder.Services.AddScoped<ClaudeDoDbContext>(sp =>
builder.Services.AddScoped<TaskRepository>();
builder.Services.AddScoped<ListRepository>();
builder.Services.AddScoped<PlanningMcpService>();
builder.Services.AddScoped<IFindingsStoreLocator, FindingsStoreLocator>();
builder.Services.AddScoped<TaskRunFindingsMcpTools>();
builder.Services.AddMcpServer()
.WithHttpTransport()
.WithTools<PlanningMcpService>()
.WithTools<TaskRunMcpService>();
.WithTools<TaskRunMcpService>()
.WithTools<TaskRunFindingsMcpTools>();
// OnlineInboxConfig and OnlineTokenStore are always registered so hub methods work
// even when sync is disabled. The sync stack (api client, auth, hosted service) is
@@ -0,0 +1,52 @@
using System.ComponentModel;
using ClaudeDo.Worker.Findings;
using ClaudeDo.Worker.Git;
using ModelContextProtocol.Server;
namespace ClaudeDo.Worker.Runner;
public sealed record SaveTaskFindingResult(
bool Saved, string Slug, string Path, int TotalFindings, bool NearCapacity);
[McpServerToolType]
public sealed class TaskRunFindingsMcpTools
{
private readonly IFindingsStore _store;
private readonly IFindingsStoreLocator _locator;
private readonly TaskRunMcpContextAccessor _ctx;
public TaskRunFindingsMcpTools(
IFindingsStore store, IFindingsStoreLocator locator, TaskRunMcpContextAccessor ctx)
{
_store = store;
_locator = locator;
_ctx = ctx;
}
[McpServerTool, Description(
"Record a durable trap you just hit, so future runs read it instead of losing the same " +
"turns. Only for findings that are lasting, non-obvious and behaviour-changing (\"X looks " +
"like Y but is Z — do W instead\"); a bug you fixed is git history, not a finding. When " +
"unsure, don't. Re-using a slug overwrites it. Writes to this task's own project; " +
"nearCapacity=true means prune before adding more.")]
public async Task<SaveTaskFindingResult> SaveFinding(
[Description("Stable lowercase kebab-case id, max 60 chars, e.g. 'conpty-arg-quoting'. Same slug overwrites.")] string slug,
[Description("One full sentence stating the trap itself — this is the index line other agents scan.")] string title,
[Description("2-6 sentences: what happens, why it does not look like that, what to do instead.")] string body,
[Description("Repo-relative path or subsystem the finding applies to, e.g. 'src/ClaudeDo.Worker/Planning'.")] string scope = "",
CancellationToken cancellationToken = default)
{
var taskId = _ctx.Current.CallerTaskId;
var target = await _locator.ResolveForTaskAsync(taskId, cancellationToken);
var head = await GitHead.ShortAsync(target.WorkingDir, cancellationToken);
var outcome = await _store.SaveAsync(
target.WorkingDir,
new FindingInput(slug, title, body, scope, SourceTaskId: taskId, VerifiedAgainst: head),
cancellationToken,
target.Tracked);
return new SaveTaskFindingResult(
true, outcome.Slug, outcome.Path, outcome.TotalFindings, outcome.NearCapacity);
}
}