feat(worker): add external MCP endpoint with API-key auth
A second WebApplication runs the external MCP server on its own port (default 47822) so it can expose a different tool set under different auth than the internal /mcp endpoint. Shared singletons (config, broadcaster, queue, db factory) are injected by instance so both apps share runtime state. ExternalMcpAuthMiddleware enforces an optional X-ClaudeDo-Key header; loopback-only trust when no key is configured. Tools: ListTaskLists, ListTasks, GetTask, AddTask, UpdateTaskStatus, RunTaskNow, CancelTask. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Git;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Worker.Config;
|
||||
using ClaudeDo.Worker.External;
|
||||
using ClaudeDo.Worker.Hub;
|
||||
using ClaudeDo.Worker.Planning;
|
||||
using ClaudeDo.Worker.Runner;
|
||||
@@ -72,6 +73,7 @@ builder.Services.AddScoped<PlanningMcpContextAccessor>();
|
||||
builder.Services.AddScoped<ClaudeDoDbContext>(sp =>
|
||||
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>().CreateDbContext());
|
||||
builder.Services.AddScoped<TaskRepository>();
|
||||
builder.Services.AddScoped<ListRepository>();
|
||||
builder.Services.AddScoped<PlanningMcpService>();
|
||||
builder.Services.AddMcpServer()
|
||||
.WithHttpTransport()
|
||||
@@ -108,4 +110,44 @@ app.MapMcp("/mcp");
|
||||
app.Logger.LogInformation("ClaudeDo.Worker listening on http://127.0.0.1:{Port} (db: {Db})",
|
||||
cfg.SignalRPort, cfg.DbPath);
|
||||
|
||||
app.Run();
|
||||
// Build the external MCP endpoint as a separate WebApplication on its own port.
|
||||
// Rationale: ModelContextProtocol.AspNetCore registers one server per DI container,
|
||||
// so we need a second app to expose a different tool set under different auth.
|
||||
// Shared singletons (QueueService, HubBroadcaster, WorkerConfig, db factory) are
|
||||
// injected by instance so both apps operate on the same runtime state.
|
||||
WebApplication? externalApp = null;
|
||||
if (cfg.ExternalMcpPort > 0)
|
||||
{
|
||||
var externalBuilder = WebApplication.CreateBuilder();
|
||||
externalBuilder.Services.AddSingleton(cfg);
|
||||
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<HubBroadcaster>());
|
||||
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<QueueService>());
|
||||
externalBuilder.Services.AddSingleton(app.Services.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>());
|
||||
externalBuilder.Services.AddScoped<ClaudeDoDbContext>(sp =>
|
||||
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>().CreateDbContext());
|
||||
externalBuilder.Services.AddScoped<TaskRepository>();
|
||||
externalBuilder.Services.AddScoped<ListRepository>();
|
||||
externalBuilder.Services.AddScoped<ExternalMcpService>();
|
||||
externalBuilder.Services.AddMcpServer()
|
||||
.WithHttpTransport()
|
||||
.WithTools<ExternalMcpService>();
|
||||
externalBuilder.WebHost.UseUrls($"http://127.0.0.1:{cfg.ExternalMcpPort}");
|
||||
|
||||
externalApp = externalBuilder.Build();
|
||||
externalApp.UseMiddleware<ExternalMcpAuthMiddleware>();
|
||||
externalApp.MapMcp("/mcp");
|
||||
|
||||
externalApp.Logger.LogInformation(
|
||||
"ClaudeDo.Worker external MCP listening on http://127.0.0.1:{Port} (auth: {Auth})",
|
||||
cfg.ExternalMcpPort,
|
||||
string.IsNullOrEmpty(cfg.ExternalMcpApiKey) ? "loopback-only" : "X-ClaudeDo-Key");
|
||||
}
|
||||
|
||||
if (externalApp is null)
|
||||
{
|
||||
await app.RunAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.WhenAll(app.RunAsync(), externalApp.RunAsync());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user