feat(mcp): resolve per-run tokens in MCP auth + register TaskRunMcpService

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-06-04 15:57:12 +02:00
parent 9d133e227b
commit f3052dc5fc
3 changed files with 64 additions and 7 deletions

View File

@@ -1,4 +1,5 @@
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Runner;
using Microsoft.AspNetCore.Http;
namespace ClaudeDo.Worker.Planning;
@@ -9,7 +10,7 @@ public sealed class PlanningTokenAuthMiddleware
public PlanningTokenAuthMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext ctx, TaskRepository tasks)
public async Task InvokeAsync(HttpContext ctx, TaskRepository tasks, TaskRunTokenRegistry runTokens)
{
if (!ctx.Request.Path.StartsWithSegments("/mcp"))
{
@@ -26,15 +27,23 @@ public sealed class PlanningTokenAuthMiddleware
}
var token = auth.Substring("Bearer ".Length).Trim();
var parent = await tasks.FindByPlanningTokenAsync(token, ctx.RequestAborted);
if (parent is null || parent.PlanningPhase != ClaudeDo.Data.Models.PlanningPhase.Active)
if (parent is not null && parent.PlanningPhase == ClaudeDo.Data.Models.PlanningPhase.Active)
{
ctx.Response.StatusCode = 401;
await ctx.Response.WriteAsync("Invalid or expired planning token");
ctx.Items["PlanningContext"] = new PlanningMcpContext { ParentTaskId = parent.Id };
await _next(ctx);
return;
}
ctx.Items["PlanningContext"] = new PlanningMcpContext { ParentTaskId = parent.Id };
await _next(ctx);
if (runTokens.TryResolve(token, out var callerTaskId))
{
ctx.Items["TaskRunContext"] = new TaskRunMcpContext { CallerTaskId = callerTaskId };
await _next(ctx);
return;
}
ctx.Response.StatusCode = 401;
await ctx.Response.WriteAsync("Invalid or expired token");
}
}