diff --git a/backend-tests/McpServerConfigurationTests.cs b/backend-tests/McpServerConfigurationTests.cs
new file mode 100644
index 0000000..58d4a51
--- /dev/null
+++ b/backend-tests/McpServerConfigurationTests.cs
@@ -0,0 +1,85 @@
+using Microsoft.Extensions.DependencyInjection;
+using ModelContextProtocol.Server;
+using Nexus.Api.Services;
+using Xunit;
+
+namespace Nexus.Api.Tests;
+
+///
+/// Verifies that the MCP server configuration is correct:
+/// all tools are registered, the streamable-http transport is configured
+/// via the service extensions, and MapMcp is called in Program.cs.
+///
+public sealed class McpServerConfigurationTests
+{
+ [Fact]
+ public void McpServer_CanBeRegistered_WithoutError()
+ {
+ var services = new ServiceCollection();
+ services.AddLogging();
+ services.AddOptions();
+ services.AddHttpContextAccessor();
+
+ // Simulate what AddNexusApplicationServices does
+ services.AddMcpServer()
+ .WithHttpTransport(options => options.Stateless = true)
+ .WithTools();
+
+ // Build the container — this should not throw
+ var provider = services.BuildServiceProvider();
+
+ // Verify the ToolType is properly decorated
+ var attr = typeof(NexusMcpTools).GetCustomAttributes(
+ typeof(McpServerToolTypeAttribute), inherit: false);
+ Assert.Single(attr);
+ }
+
+ [Fact]
+ public void McpServer_ToolTypeAttribute_IsPresent()
+ {
+ var attr = typeof(NexusMcpTools).GetCustomAttributes(
+ typeof(McpServerToolTypeAttribute), inherit: false);
+ Assert.Single(attr);
+ }
+
+ [Fact]
+ public void McpEndpoint_MapMcp_IsCalledInProgram()
+ {
+ // Source path relative to the test output directory
+ var programPath = ResolveSourcePath("backend", "Program.cs");
+ Assert.True(File.Exists(programPath), $"Program.cs not found at {programPath}");
+ var source = File.ReadAllText(programPath);
+ Assert.Contains("MapMcp", source, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void McpServerRegistration_IsCalledInServiceExtensions()
+ {
+ var extPath = ResolveSourcePath("backend", "Extensions", "ServiceCollectionExtensions.cs");
+ Assert.True(File.Exists(extPath), $"ServiceCollectionExtensions.cs not found at {extPath}");
+ var source = File.ReadAllText(extPath);
+ Assert.Contains("AddMcpServer", source, StringComparison.Ordinal);
+ Assert.Contains("WithHttpTransport", source, StringComparison.Ordinal);
+ Assert.Contains("Stateless", source, StringComparison.Ordinal);
+ Assert.Contains("WithTools", source, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void NuGetPackage_ModelContextProtocol_AspNetCore_IsReferenced()
+ {
+ var csprojPath = ResolveSourcePath("backend", "Nexus.Api.csproj");
+ Assert.True(File.Exists(csprojPath), $"Nexus.Api.csproj not found at {csprojPath}");
+ var source = File.ReadAllText(csprojPath);
+ Assert.Contains("ModelContextProtocol.AspNetCore", source, StringComparison.Ordinal);
+ }
+
+ private static string ResolveSourcePath(params string[] segments)
+ {
+ // Navigate from test output directory to the repo root
+ // Test DLL is at: backend-tests/bin/Debug/net10.0/Nexus.Api.Tests.dll
+ // We go up 4 levels (net10.0 → Debug → bin → backend-tests) to reach repo root
+ var baseDir = AppContext.BaseDirectory;
+ var repoRoot = Path.GetFullPath(Path.Combine(baseDir, "..", "..", "..", ".."));
+ return Path.Combine(new[] { repoRoot }.Concat(segments).ToArray());
+ }
+}
diff --git a/backend-tests/McpToolsTests.cs b/backend-tests/McpToolsTests.cs
new file mode 100644
index 0000000..5b4dd6f
--- /dev/null
+++ b/backend-tests/McpToolsTests.cs
@@ -0,0 +1,582 @@
+using System.ComponentModel;
+using System.Reflection;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Http;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging.Abstractions;
+using ModelContextProtocol.Server;
+using Nexus.Api.Data;
+using Nexus.Api.Integrations;
+using Nexus.Api.Models;
+using Nexus.Api.Repositories;
+using Nexus.Api.Services;
+using Xunit;
+
+namespace Nexus.Api.Tests;
+
+public sealed class McpToolsTests
+{
+ // ────────────────────────────────────────────────────────────────
+ // Enum Validation
+ // ────────────────────────────────────────────────────────────────
+
+ [Fact]
+ public void NexusMcpTaskState_HasExactlyFiveValidStates()
+ {
+ var values = Enum.GetValues();
+ Assert.Equal(5, values.Length);
+
+ var names = Enum.GetNames();
+ Assert.Contains("Backlog", names);
+ Assert.Contains("InProgress", names);
+ Assert.Contains("Blocked", names);
+ Assert.Contains("Done", names);
+ Assert.Contains("Review", names);
+ }
+
+ [Theory]
+ [InlineData(NexusMcpTaskState.Backlog, "Backlog")]
+ [InlineData(NexusMcpTaskState.InProgress, "In progress")]
+ [InlineData(NexusMcpTaskState.Blocked, "Blocked")]
+ [InlineData(NexusMcpTaskState.Done, "Done")]
+ [InlineData(NexusMcpTaskState.Review, "Review")]
+ public void NexusMcpTaskState_MapsToCorrectStateString(NexusMcpTaskState mcpState, string expectedBridgeState)
+ {
+ // Verify the TaskStateHelper roundtrip works
+ string stateString = mcpState switch
+ {
+ NexusMcpTaskState.Backlog => TaskStateHelper.ToStateString(TaskState.Backlog),
+ NexusMcpTaskState.InProgress => TaskStateHelper.ToStateString(TaskState.InProgress),
+ NexusMcpTaskState.Blocked => TaskStateHelper.ToStateString(TaskState.Blocked),
+ NexusMcpTaskState.Done => TaskStateHelper.ToStateString(TaskState.Done),
+ NexusMcpTaskState.Review => TaskStateHelper.ToStateString(TaskState.Review),
+ _ => throw new ArgumentOutOfRangeException(nameof(mcpState))
+ };
+
+ Assert.Equal(expectedBridgeState, stateString);
+ Assert.True(TaskStateHelper.IsValidState(stateString));
+ }
+
+ [Fact]
+ public void NexusMcpTaskState_EnumValuesMatchTaskStateEnum()
+ {
+ // The MCP state enum must cover exactly the canonical task states
+ foreach (var mcpState in Enum.GetValues())
+ {
+ var taskState = mcpState switch
+ {
+ NexusMcpTaskState.Backlog => TaskState.Backlog,
+ NexusMcpTaskState.InProgress => TaskState.InProgress,
+ NexusMcpTaskState.Blocked => TaskState.Blocked,
+ NexusMcpTaskState.Done => TaskState.Done,
+ NexusMcpTaskState.Review => TaskState.Review,
+ _ => throw new ArgumentOutOfRangeException(nameof(mcpState))
+ };
+ Assert.True(Enum.IsDefined(taskState));
+ }
+ }
+
+ // ────────────────────────────────────────────────────────────────
+ // Tool Registration
+ // ────────────────────────────────────────────────────────────────
+
+ [Fact]
+ public void AllRequiredTools_AreRegistered()
+ {
+ var toolMethods = typeof(NexusMcpTools)
+ .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
+ .Where(m => m.GetCustomAttribute() is not null)
+ .Select(m => m.GetCustomAttribute()!.Name!)
+ .OrderBy(n => n)
+ .ToList();
+
+ var expected = new[]
+ {
+ "nexus_agent_overview",
+ "nexus_append_activity",
+ "nexus_create_child_task",
+ "nexus_create_task",
+ "nexus_get_activity",
+ "nexus_get_board",
+ "nexus_get_children",
+ "nexus_get_task",
+ "nexus_handoff",
+ "nexus_update_status"
+ }.OrderBy(n => n).ToList();
+
+ Assert.Equal(expected, toolMethods);
+ Assert.Equal(10, toolMethods.Count);
+ }
+
+ [Fact]
+ public void NexusMcpTools_HasMcpServerToolTypeAttribute()
+ {
+ var attr = typeof(NexusMcpTools).GetCustomAttribute();
+ Assert.NotNull(attr);
+ }
+
+ [Fact]
+ public void AllTools_HaveDescriptionAttribute()
+ {
+ var methods = typeof(NexusMcpTools)
+ .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
+ .Where(m => m.GetCustomAttribute() is not null);
+
+ foreach (var method in methods)
+ {
+ var desc = method.GetCustomAttribute();
+ Assert.NotNull(desc);
+ Assert.False(string.IsNullOrWhiteSpace(desc!.Description),
+ $"Tool {method.Name} is missing a description.");
+ }
+ }
+
+ [Fact]
+ public void NexusMcpTools_AllMethodsAreAsync()
+ {
+ var methods = typeof(NexusMcpTools)
+ .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
+ .Where(m => m.GetCustomAttribute() is not null);
+
+ foreach (var method in methods)
+ {
+ Assert.True(
+ method.ReturnType.Name.StartsWith("Task") ||
+ method.ReturnType.Name.StartsWith("ValueTask"),
+ $"Tool {method.Name} does not return Task.");
+ }
+ }
+
+ // ────────────────────────────────────────────────────────────────
+ // Tool Behavior via Fixture (integration-style)
+ // ────────────────────────────────────────────────────────────────
+
+ [Fact]
+ public async Task CreateTask_ReturnsSuccess_ForValidInput()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("iris");
+
+ var result = await fixture.Tools.CreateTask("MCP Test Task", "MCP detail", "High", "iris");
+
+ Assert.NotNull(result);
+ Assert.True(result.Ok);
+ Assert.Equal("nexus_create_task", result.Command);
+ Assert.NotNull(result.Data);
+ Assert.Null(result.Error);
+ Assert.Equal("MCP Test Task", result.Data!.Title);
+ }
+
+ [Fact]
+ public async Task CreateChildTask_ReturnsSuccess_ForValidParent()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("iris");
+
+ var parent = await fixture.Tools.CreateTask("Parent Task", "Parent detail");
+ Assert.True(parent.Ok && parent.Data is not null);
+
+ var child = await fixture.Tools.CreateChildTask(
+ parent.Data!.Id, "Child Task", "Child detail", "Normal", "programmer");
+
+ Assert.True(child.Ok);
+ Assert.Equal("nexus_create_child_task", child.Command);
+ Assert.NotNull(child.Data);
+ Assert.Equal("Child Task", child.Data!.Title);
+ Assert.Equal(parent.Data.Id, child.Data.ParentTaskId);
+ }
+
+ [Fact]
+ public async Task CreateChildTask_ReturnsError_ForMissingParent()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("iris");
+
+ var result = await fixture.Tools.CreateChildTask(
+ Guid.NewGuid(), "Orphan Child");
+
+ Assert.False(result.Ok);
+ Assert.Contains("not found", result.Error, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public async Task GetBoard_ReturnsGroupedTasks()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("iris");
+
+ // Create test tasks
+ await fixture.Tools.CreateTask("Board Task 1", assignedTo: "iris");
+ await fixture.Tools.CreateTask("Board Task 2", assignedTo: "programmer");
+
+ var board = await fixture.Tools.GetBoard();
+
+ Assert.NotNull(board);
+ Assert.NotNull(board.Offen);
+ Assert.NotNull(board.InProgress);
+ Assert.NotNull(board.Review);
+ Assert.NotNull(board.Blocked);
+ Assert.NotNull(board.Done);
+ Assert.True(board.Offen.Count >= 2);
+ }
+
+ [Fact]
+ public async Task GetTask_ReturnsTask_WhenFound()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("iris");
+
+ var created = await fixture.Tools.CreateTask("GetTask Test");
+ Assert.True(created.Ok && created.Data is not null);
+
+ var fetched = await fixture.Tools.GetTask(created.Data.Id);
+
+ Assert.True(fetched.Ok);
+ Assert.Equal("GetTask Test", fetched.Data!.Title);
+ }
+
+ [Fact]
+ public async Task GetTask_ReturnsError_WhenNotFound()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("iris");
+
+ var result = await fixture.Tools.GetTask(Guid.NewGuid());
+
+ Assert.False(result.Ok);
+ Assert.Contains("not found", result.Error, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public async Task GetChildren_ReturnsChildTasks()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("iris");
+
+ var parent = await fixture.Tools.CreateTask("Parent for Children");
+ Assert.True(parent.Ok && parent.Data is not null);
+
+ await fixture.Tools.CreateChildTask(parent.Data.Id, "Child 1");
+ await fixture.Tools.CreateChildTask(parent.Data.Id, "Child 2");
+
+ var children = await fixture.Tools.GetChildren(parent.Data.Id);
+
+ Assert.NotNull(children);
+ Assert.Equal(2, children.Count);
+ }
+
+ [Fact]
+ public async Task UpdateStatus_AdvancesState()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("iris");
+
+ var task = await fixture.Tools.CreateTask("Status Test");
+ Assert.True(task.Ok && task.Data is not null);
+ Assert.Equal("Backlog", task.Data.State);
+
+ var inProgress = await fixture.Tools.UpdateStatus(task.Data.Id, NexusMcpTaskState.InProgress);
+ Assert.True(inProgress.Ok);
+ Assert.Equal("In progress", inProgress.Data!.State);
+
+ var done = await fixture.Tools.UpdateStatus(task.Data.Id, NexusMcpTaskState.Done);
+ Assert.True(done.Ok);
+ Assert.Equal("Done", done.Data!.State);
+ }
+
+ [Fact]
+ public async Task UpdateStatus_Unauthorized_ForSubAgent()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("programmer");
+
+ var task = await fixture.Tools.CreateTask("SubAgent Status Test");
+ Assert.True(task.Ok && task.Data is not null);
+
+ // The programmer creates the task fine, but cannot change status
+ var result = await fixture.Tools.UpdateStatus(task.Data.Id, NexusMcpTaskState.InProgress);
+
+ Assert.False(result.Ok);
+ Assert.Contains("not authorized", result.Error, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Theory]
+ [InlineData(NexusMcpTaskState.Backlog)]
+ [InlineData(NexusMcpTaskState.InProgress)]
+ [InlineData(NexusMcpTaskState.Review)]
+ [InlineData(NexusMcpTaskState.Blocked)]
+ [InlineData(NexusMcpTaskState.Done)]
+ public async Task UpdateStatus_AcceptsAllValidStates(NexusMcpTaskState state)
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("iris");
+
+ var task = await fixture.Tools.CreateTask($"StateTest-{state}");
+ Assert.True(task.Ok && task.Data is not null);
+
+ var result = await fixture.Tools.UpdateStatus(task.Data.Id, state);
+ Assert.True(result.Ok);
+ }
+
+ [Fact]
+ public async Task AppendActivity_WritesActivityEntry()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("iris");
+
+ var task = await fixture.Tools.CreateTask("Activity Test");
+ Assert.True(task.Ok && task.Data is not null);
+
+ var result = await fixture.Tools.AppendActivity(task.Data.Id, "Test checkpoint", "checkpoint");
+ Assert.True(result.Ok);
+ Assert.NotNull(result.Data);
+ Assert.Equal("Test checkpoint", result.Data!.Message);
+
+ var activities = await fixture.Tools.GetActivity(task.Data.Id);
+ Assert.NotEmpty(activities);
+ }
+
+ [Fact]
+ public async Task Handoff_UpdatesExpectedFrom()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("iris");
+
+ var task = await fixture.Tools.CreateTask("Handoff Test");
+ Assert.True(task.Ok && task.Data is not null);
+
+ var result = await fixture.Tools.Handoff(task.Data.Id, "programmer", "Please implement");
+ Assert.True(result.Ok);
+ Assert.Equal("programmer", result.Data!.ExpectedFrom);
+ }
+
+ [Fact]
+ public async Task GetAgentOverview_ReturnsGroupedWorkflow()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("iris");
+
+ var overview = await fixture.Tools.GetAgentOverview(staleHours: 2);
+
+ Assert.NotNull(overview);
+ Assert.NotNull(overview.WaitingForBao);
+ Assert.NotNull(overview.WaitingForIris);
+ Assert.NotNull(overview.WaitingForOthers);
+ Assert.NotNull(overview.StaleTasks);
+ }
+
+ // ────────────────────────────────────────────────────────────────
+ // Auth Resolution
+ // ────────────────────────────────────────────────────────────────
+
+ [Fact]
+ public async Task ResolveCaller_AcceptsValidXAgentId()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("programmer");
+
+ // Simply verify a tool call succeeds with a valid agent header
+ var result = await fixture.Tools.CreateTask("Auth Test via header");
+ Assert.True(result.Ok);
+ }
+
+ [Fact]
+ public async Task ResolveCaller_AcceptsJwtClaim()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerUser("iris", "member");
+
+ var result = await fixture.Tools.CreateTask("Auth Test via JWT");
+ Assert.True(result.Ok);
+ }
+
+ [Fact]
+ public async Task ResolveCaller_AcceptsOwnerRole()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerUser("owner", "owner");
+
+ var result = await fixture.Tools.CreateTask("Auth Test via owner JWT");
+ Assert.True(result.Ok);
+ }
+
+ [Fact]
+ public async Task ResolveCaller_AcceptsServiceKey()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerServiceKey("test-service-key");
+
+ var result = await fixture.Tools.CreateTask("Auth Test via service key");
+ Assert.True(result.Ok);
+ }
+
+ [Fact]
+ public async Task ResolveCaller_RejectsUnknownAgentId()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ fixture.SetCallerAgent("hacker");
+
+ await Assert.ThrowsAsync(
+ () => fixture.Tools.CreateTask("Should fail"));
+ }
+
+ [Fact]
+ public async Task ResolveCaller_RejectsMissingAuth()
+ {
+ await using var fixture = await McpToolsFixture.CreateAsync();
+ // No auth set = should reject
+
+ await Assert.ThrowsAsync(
+ () => fixture.Tools.CreateTask("Should fail"));
+ }
+}
+
+// ──────────────────────────────────────────────────────────────────
+// Test Fixture
+// ──────────────────────────────────────────────────────────────────
+
+internal sealed class McpToolsFixture : IAsyncDisposable
+{
+ private readonly NexusDbContext _db;
+
+ private McpToolsFixture(
+ NexusDbContext db,
+ NexusMcpTools tools,
+ HttpContextAccessor httpContextAccessor,
+ ITaskBridgeService taskBridgeService,
+ IAgentService agentService)
+ {
+ _db = db;
+ Tools = tools;
+ HttpContextAccessor = httpContextAccessor;
+ TaskBridgeService = taskBridgeService;
+ AgentService = agentService;
+ }
+
+ public NexusMcpTools Tools { get; }
+ public HttpContextAccessor HttpContextAccessor { get; }
+ public ITaskBridgeService TaskBridgeService { get; }
+ public IAgentService AgentService { get; }
+
+ public static async Task CreateAsync()
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseInMemoryDatabase(Guid.NewGuid().ToString())
+ .Options;
+
+ var db = new NexusDbContext(options);
+ await db.Database.EnsureCreatedAsync();
+
+ var configPath = CreateAgentConfigFile();
+ var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["AgentConfigPath"] = configPath,
+ ["NexusApiKey"] = "test-service-key"
+ })
+ .Build();
+
+ var agentService = new AgentService(configuration, new FakeRuntime());
+ var liveUpdateService = new LiveUpdateService();
+ var activityRepository = new ActivityRepository(db, liveUpdateService);
+ var taskRepository = new TaskRepository(db);
+ var notificationService = new NotificationService(db, liveUpdateService);
+
+ var httpContextAccessor = new HttpContextAccessor();
+
+ var staleTaskRecoveryService = new StaleTaskRecoveryService(
+ taskRepository, activityRepository, liveUpdateService);
+
+ var taskService = new TaskService(
+ taskRepository,
+ activityRepository,
+ notificationService,
+ agentService,
+ httpContextAccessor,
+ liveUpdateService,
+ staleTaskRecoveryService);
+
+ var taskBridgeService = new TaskBridgeService(
+ taskService,
+ agentService,
+ activityRepository,
+ notificationService,
+ liveUpdateService);
+
+ var logger = NullLogger.Instance;
+
+ var tools = new NexusMcpTools(
+ taskBridgeService,
+ agentService,
+ httpContextAccessor,
+ configuration,
+ logger);
+
+ return new McpToolsFixture(db, tools, httpContextAccessor, taskBridgeService, agentService);
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ await _db.DisposeAsync();
+ }
+
+ public void SetCallerAgent(string agentId)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Headers["X-Agent-Id"] = agentId;
+ httpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
+ HttpContextAccessor.HttpContext = httpContext;
+ }
+
+ public void SetCallerUser(string userId, string role)
+ {
+ var claims = new[]
+ {
+ new Claim(ClaimTypes.NameIdentifier, userId),
+ new Claim(ClaimTypes.Role, role)
+ };
+ var httpContext = new DefaultHttpContext();
+ httpContext.User = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth"));
+ HttpContextAccessor.HttpContext = httpContext;
+ }
+
+ public void SetCallerServiceKey(string key)
+ {
+ var claims = new[] { new Claim(ClaimTypes.Role, "Service") };
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Headers["X-Nexus-Api-Key"] = key;
+ httpContext.User = new ClaimsPrincipal(new ClaimsIdentity(claims, "ApiKey"));
+ HttpContextAccessor.HttpContext = httpContext;
+ }
+
+ private static string CreateAgentConfigFile()
+ {
+ var path = Path.Combine(Path.GetTempPath(), $"agent-config-{Guid.NewGuid():N}.json");
+ File.WriteAllText(path,
+ """
+ {
+ "agents": {
+ "defaults": {
+ "workspace": "/workspace/default",
+ "model": {
+ "primary": "deepseek/deepseek-v4-flash"
+ }
+ },
+ "list": [
+ { "id": "iris", "name": "iris", "model": { "primary": "openai/gpt-5.5" } },
+ { "id": "product-owner", "name": "product-owner" },
+ { "id": "programmer", "name": "programmer" },
+ { "id": "programmer-fast", "name": "programmer-fast" },
+ { "id": "reviewer", "name": "reviewer" },
+ { "id": "architekt", "name": "architekt" },
+ { "id": "executor", "name": "executor" },
+ { "id": "researcher", "name": "researcher" }
+ ]
+ }
+ }
+ """);
+
+ return path;
+ }
+}