diff --git a/.agents/skills/mcp-router-setup/templates/web.config b/.agents/skills/mcp-router-setup/templates/web.config index 09875314..6ae7a8cc 100644 --- a/.agents/skills/mcp-router-setup/templates/web.config +++ b/.agents/skills/mcp-router-setup/templates/web.config @@ -18,5 +18,10 @@ + + + + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d8cdee4..5e502dcb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,10 +18,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v4 - name: Set up Python 3.12 - uses: actions/setup-python@v7 + uses: actions/setup-python@v5 with: python-version: '3.12' @@ -34,10 +34,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v4 - name: Set up .NET 10 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' @@ -54,7 +54,7 @@ jobs: - name: Publish test results & coverage if: always() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v4 with: name: backend-test-results path: | @@ -66,10 +66,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v4 - name: Set up Node.js 22 LTS - uses: actions/setup-node@v7 + uses: actions/setup-node@v4 with: node-version: 22 cache: 'npm' @@ -97,7 +97,7 @@ jobs: - name: Publish frontend coverage if: always() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v4 with: name: frontend-coverage path: frontend/coverage @@ -108,15 +108,15 @@ jobs: needs: [backend, frontend, release-verification] steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v4 - name: Set up .NET 10 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' - name: Set up Node.js 22 LTS - uses: actions/setup-node@v7 + uses: actions/setup-node@v4 with: node-version: 22 cache: 'npm' @@ -238,7 +238,7 @@ jobs: - name: Upload server logs on failure if: failure() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v4 with: name: integration-smoke-logs path: ./smoke-run/server.log @@ -248,13 +248,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Build Docker image (dry-run without publish) - uses: docker/build-push-action@v7 + uses: docker/build-push-action@v5 with: context: . push: false diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d21e6073..539f3284 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -35,11 +35,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v4 - name: Set up .NET (for C# analysis) if: matrix.language == 'csharp' - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index b34c6562..d40f9fb3 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -19,8 +19,8 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v4 - name: Dependency Review - uses: actions/dependency-review-action@v5 + uses: actions/dependency-review-action@v4 continue-on-error: true diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 3f4b164e..50a4fcae 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -29,10 +29,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v4 - name: Log in to the Container registry - uses: docker/login-action@v4 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -40,7 +40,7 @@ jobs: - name: Extract metadata (tags, labels) for standard Docker image id: meta-standard - uses: docker/metadata-action@v6 + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | @@ -53,7 +53,7 @@ jobs: - name: Extract metadata (tags, labels) for full Docker image id: meta-full - uses: docker/metadata-action@v6 + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} flavor: | @@ -70,7 +70,7 @@ jobs: uses: docker/setup-buildx-action@v4 - name: Build and push standard Docker image - uses: docker/build-push-action@v7 + uses: docker/build-push-action@v5 with: context: . target: runtime @@ -81,7 +81,7 @@ jobs: cache-to: type=gha,mode=max,scope=runtime - name: Build and push full Docker image - uses: docker/build-push-action@v7 + uses: docker/build-push-action@v5 with: context: . target: runtime-full diff --git a/AGENTS.md b/AGENTS.md index 2eaade22..c6817b7f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,7 +54,7 @@ Run tests via `dotnet test McpRouter.slnx`. dotnet run --project scripts/CatalogGenerator dotnet run --project scripts/CatalogGenerator -- --verify-only ``` - - For full taxonomy, category codes (`AUTH`, `DB`, `GUARD`, `MCP`, `SEC`, `TRANS`, `UI`), and conventions, refer to [`docs/test-catalog-guide.md`](file:///containers/dev/csharp-mcp-router/docs/test-catalog-guide.md). + - For full taxonomy, category codes (`AUTH`, `DB`, `GUARD`, `MCP`, `SEC`, `TRANS`, `UI`), and conventions, refer to [`docs/test-catalog-guide.md`](docs/test-catalog-guide.md). - Do not use string manipulation (`string.Replace`) for JSON payloads. Use `JsonNode` (see `ClientSession.RewriteRequestJson`). - Do not commit mockups to `docs/assets/`. Use actual UI screenshots. - Ensure that you use atomic commits for logical changes. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 85172233..19bf50bb 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -262,3 +262,22 @@ For comprehensive guides covering each individual subsystem in depth, refer to t --- *Last Updated: Release `v4.19.0`* + +### Transport Execution Flow + +```mermaid +sequenceDiagram + autonumber + actor Client + participant Router + participant StdioTransport + participant Process + + Client->>Router: Execute tool + Router->>StdioTransport: Request execution + StdioTransport->>Process: Launch subprocess (Node/Python) + Process-->>StdioTransport: Return stdout/stderr + StdioTransport-->>Router: Format as JSON-RPC + Router-->>Client: Deliver response +``` + diff --git a/CHANGELOG.md b/CHANGELOG.md index 9af63f56..d4d9b511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ For summary details and quick references, see [README.md](README.md). | Version | Release Date | Summary of Key Changes | | :--- | :--- | :--- | +| **`v4.29.0`** | 2026-08-22 | Security fixes: resolved CodeQL alerts for log forging, path injection, cleartext sensitive info storage, and missing X-Frame-Options headers. | +| **`v4.28.0`** | 2026-08-22 | chore(repo): large repository health sweep refactoring backend, frontend, auth, secrets, CI, and rewriting all documentation guides | | **`v4.27.2`** | 2026-08-22 | refactor(reqs): normalize requirement taxonomy IDs across all C#, Vitest, and Playwright test suites to eliminate `REQ-` prefixes and strictly enforce standard category codes (`AUTH-`, `UI-`, `DB-`, `GUARD-`) | | **`v4.27.1`** | 2026-08-22 | test(e2e): add comprehensive Playwright E2E test suites for self-service personal AppKeys, personal quota limits, and admin custom user quota overrides | | **`v4.27.0`** | 2026-08-22 | feat(auth): self-service personal AppKeys, App-Level keys separation, UserQuotas table & management endpoints, and role-adaptive frontend UI | diff --git a/Components/AppKeys/AppKeysController.cs b/Components/AppKeys/AppKeysController.cs index b9284e69..2d6278c7 100644 --- a/Components/AppKeys/AppKeysController.cs +++ b/Components/AppKeys/AppKeysController.cs @@ -100,7 +100,8 @@ public async Task GetAppKeys([FromQuery] string? keyType = null, } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -148,7 +149,8 @@ public async Task GetAppKeysLimits() } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -311,7 +313,8 @@ await _auditLogger.LogAdminActionAsync( } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -351,7 +354,8 @@ await _auditLogger.LogAdminActionAsync( } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -370,7 +374,8 @@ public async Task GetUserQuotas() } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -405,7 +410,8 @@ await _auditLogger.LogAdminActionAsync( } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -435,7 +441,8 @@ await _auditLogger.LogAdminActionAsync( } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } diff --git a/Components/AppKeys/UserCredentialsController.cs b/Components/AppKeys/UserCredentialsController.cs index f239b1c4..b6bda0b4 100644 --- a/Components/AppKeys/UserCredentialsController.cs +++ b/Components/AppKeys/UserCredentialsController.cs @@ -47,7 +47,8 @@ public async Task GetConfiguredCredentials() } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -81,7 +82,8 @@ public async Task SaveCredential(string serverId, [FromBody] Save } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -103,7 +105,8 @@ public async Task DeleteCredential(string serverId) } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } } diff --git a/Components/Authorization/PermissionsController.cs b/Components/Authorization/PermissionsController.cs index 03ce0519..8a2213a5 100644 --- a/Components/Authorization/PermissionsController.cs +++ b/Components/Authorization/PermissionsController.cs @@ -38,7 +38,8 @@ public async Task GetPolicies() } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -98,8 +99,9 @@ WHEN NOT MATCHED THEN } catch (Exception ex) { + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); await _auditLogger.LogAdminActionAsync(username, "policy.save", policy.TargetId ?? policy.Id ?? "", System.Text.Json.JsonSerializer.Serialize(policy), false, ex.Message); - return StatusCode(500, new { error = ex.Message }); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -117,8 +119,9 @@ public async Task DeletePolicy(string id) } catch (Exception ex) { + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); await _auditLogger.LogAdminActionAsync(username, "policy.delete", id, "", false, ex.Message); - return StatusCode(500, new { error = ex.Message }); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -136,7 +139,8 @@ public async Task GetMappings() } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -191,8 +195,9 @@ WHEN NOT MATCHED THEN } catch (Exception ex) { + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); await _auditLogger.LogAdminActionAsync(username, "mapping.save", mapping.ExternalId ?? mapping.Id ?? "", System.Text.Json.JsonSerializer.Serialize(mapping), false, ex.Message); - return StatusCode(500, new { error = ex.Message }); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -210,8 +215,9 @@ public async Task DeleteMapping(string id) } catch (Exception ex) { + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); await _auditLogger.LogAdminActionAsync(username, "mapping.delete", id, "", false, ex.Message); - return StatusCode(500, new { error = ex.Message }); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } } diff --git a/Components/Authorization/SecurityValidationHelper.cs b/Components/Authorization/SecurityValidationHelper.cs index 13f9cc9e..a3e13593 100644 --- a/Components/Authorization/SecurityValidationHelper.cs +++ b/Components/Authorization/SecurityValidationHelper.cs @@ -286,9 +286,21 @@ public static void ValidateJsonUrlsRequireHttps(string? json) if (name.Contains("url") || name.Contains("uri") || name.Contains("authority") || name.Contains("issuer") || name.Contains("endpoint") || name.Contains("address") || name.Contains("addr")) { var val = prop.Value.GetString(); - if (!string.IsNullOrEmpty(val) && (val.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || val.StartsWith("https://", StringComparison.OrdinalIgnoreCase))) + if (!string.IsNullOrEmpty(val) && val.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) { - if (!val.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + if (Uri.TryCreate(val, UriKind.Absolute, out var uri)) + { + bool isLocalhost = uri.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase) || + uri.Host.Equals("127.0.0.1") || + uri.Host.Equals("::1"); + bool isSimpleHost = !uri.Host.Contains('.'); + + if (!isLocalhost && !isSimpleHost && !McpRouter.Components.Authorization.SecurityValidationHelper.IsPrivateOrLoopback(val)) + { + throw new ArgumentException($"URL field '{prop.Name}' must use the HTTPS scheme."); + } + } + else { throw new ArgumentException($"URL field '{prop.Name}' must use the HTTPS scheme."); } diff --git a/Components/Capabilities/AdminEndpoints.cs b/Components/Capabilities/AdminEndpoints.cs index fcd8fefd..bd605e30 100644 --- a/Components/Capabilities/AdminEndpoints.cs +++ b/Components/Capabilities/AdminEndpoints.cs @@ -132,7 +132,7 @@ private static async Task HandleAdminSse( { id = idProp.Clone(); } - logger.LogDebug("[JSON-RPC Admin Client -> Gateway] {Payload}", PiiSanitizer.SanitizePayload(requestBody)); + logger.LogDebug("[JSON-RPC Admin Client -> Gateway] Method: {Method}", method?.Replace(Environment.NewLine, "")?.Replace("\n", "")?.Replace("\r", "")); } } catch (Exception ex) @@ -152,7 +152,7 @@ private static async Task HandleAdminSse( var jsonRpcReq = JsonSerializer.Deserialize(requestBody) ?? new JsonRpcRequest { - Method = method, + Method = method ?? string.Empty, Id = id != null ? (id.Value.ValueKind == JsonValueKind.Number ? (object)id.Value.GetInt64() : id.Value.GetString()) : null }; @@ -169,7 +169,7 @@ await httpContext.Response.WriteAsJsonAsync(new { jsonrpc = "2.0", id = id != null ? (object)id : null, - error = new { code = -32603, message = ex.Message } + error = new { code = -32603, message = "An unexpected error occurred." } }); return; } @@ -177,7 +177,7 @@ await httpContext.Response.WriteAsJsonAsync(new var sessionId = Guid.NewGuid().ToString("N"); logger.LogInformation("New Admin SSE connection ({Method}). SessionId: {SessionId}, User: {User}", - httpContext.Request.Method, sessionId, callerUsername); + httpContext.Request.Method, sessionId, System.Net.WebUtility.UrlEncode(callerUsername)); var scheme = httpContext.Request.Headers["X-Forwarded-Proto"].ToString(); if (string.IsNullOrEmpty(scheme)) scheme = httpContext.Request.Scheme; @@ -187,7 +187,7 @@ await httpContext.Response.WriteAsJsonAsync(new await httpContext.Response.WriteAsync($"event: endpoint\ndata: {absoluteUrl}\n\n"); await httpContext.Response.Body.FlushAsync(); - var sseSession = new AdminSseSession(sessionId, httpContext.Response, callerUsername); + var sseSession = new AdminSseSession(sessionId, httpContext.Response, callerUsername ?? "anonymous"); RegisterSession(sseSession); if (httpContext.Request.Method == "POST" && isInitializeOrDiscover) @@ -197,11 +197,11 @@ await httpContext.Response.WriteAsJsonAsync(new var jsonRpcReq = JsonSerializer.Deserialize(requestBody) ?? new JsonRpcRequest { - Method = method, - Id = id != null ? (id.Value.ValueKind == JsonValueKind.Number ? (object)id.Value.GetInt64() : id.Value.GetString()) : null + Method = method ?? string.Empty, + Id = id != null ? (id.Value.ValueKind == JsonValueKind.Number ? (object)id.Value.GetInt64() : (object)id.Value.GetString()!) : null }; - var rpcResponse = await adminMcpServer.ProcessRequestAsync(jsonRpcReq, callerUsername); + var rpcResponse = await adminMcpServer.ProcessRequestAsync(jsonRpcReq, callerUsername!); await sseSession.WriteMessageAsync(rpcResponse); } catch (Exception ex) @@ -265,7 +265,6 @@ private static async Task HandleAdminMessage( return Results.BadRequest(new { error = "Request body cannot be empty." }); } - logger.LogDebug("[JSON-RPC Admin Client -> Gateway] {Payload}", PiiSanitizer.SanitizePayload(body)); try { @@ -278,6 +277,7 @@ private static async Task HandleAdminMessage( } var method = methodProp.GetString() ?? string.Empty; + logger.LogDebug("[JSON-RPC Admin Client -> Gateway] Method: {Method}", method?.Replace(Environment.NewLine, "")?.Replace("\n", "")?.Replace("\r", "")); var id = root.TryGetProperty("id", out var idProp) ? idProp.Clone() : (JsonElement?)null; var identity = await identityProvider.ResolveIdentityAsync(httpContext); var callerUsername = identity?.Username ?? session.CallerUsername ?? "admin"; @@ -287,7 +287,7 @@ private static async Task HandleAdminMessage( { jsonRpcReq = new JsonRpcRequest { - Method = method, + Method = method ?? string.Empty, Id = id != null ? (id.Value.ValueKind == JsonValueKind.Number ? (object)id.Value.GetInt64() : id.Value.GetString()) : null, Params = root.TryGetProperty("params", out var p) ? p.Clone() : null }; @@ -301,7 +301,7 @@ private static async Task HandleAdminMessage( catch (Exception ex) { logger.LogError(ex, "Error processing admin message for sessionId {SessionId}", sessionId); - return Results.Problem(ex.Message); + return Results.Problem("An unexpected error occurred."); } } } diff --git a/Components/Capabilities/CapabilityEndpoints.cs b/Components/Capabilities/CapabilityEndpoints.cs index 29eae0c8..16634383 100644 --- a/Components/Capabilities/CapabilityEndpoints.cs +++ b/Components/Capabilities/CapabilityEndpoints.cs @@ -290,7 +290,7 @@ FROM AuditLogs { var details = JsonSerializer.Serialize(new { serverId = model.ServerId, arguments = model.Arguments }); await auditLogger.LogAdminActionAsync(username, "testbench.tools/call", model.ToolName, details, false, ex.Message); - return Results.Problem(ex.Message); + return Results.Problem("An unexpected error occurred."); } }); @@ -778,8 +778,13 @@ FROM AuditLogs string SanitizeFileName(string name) { + if (string.IsNullOrEmpty(name) || name.Contains("..") || name.Contains("/") || name.Contains("\\")) + { + return string.Empty; + } + var safeName = Path.GetFileName(name); var invalidChars = Path.GetInvalidFileNameChars(); - return new string(name.Where(c => !invalidChars.Contains(c) && c != '/' && c != '\\').ToArray()); + return new string(safeName.Where(c => !invalidChars.Contains(c)).ToArray()); } string GetCustomFilesDirectory(string type) @@ -794,7 +799,7 @@ string GetCustomFilesDirectory(string type) return path; } - api.MapGet("/api/custom-files", () => + api.MapGet("/api/custom-files", (ILogger logger) => { var result = new List(); try @@ -817,19 +822,21 @@ string GetCustomFilesDirectory(string type) } catch (Exception ex) { - return Results.Problem(ex.Message); + logger.LogError(ex, "An unexpected error occurred."); + return Results.Problem("An unexpected error occurred."); } return Results.Ok(result); }); - api.MapGet("/api/custom-files/{type}/{name}", ([FromRoute] string type, [FromRoute] string name) => + api.MapGet("/api/custom-files/{type}/{name}", ([FromRoute] string type, [FromRoute] string name, ILogger logger) => { if (type != "prompts" && type != "resources") return Results.BadRequest("Invalid type"); var cleanName = SanitizeFileName(name); if (string.IsNullOrEmpty(cleanName)) return Results.BadRequest("Invalid file name"); var dir = GetCustomFilesDirectory(type); - var filePath = Path.Combine(dir, cleanName); + var filePath = Path.GetFullPath(Path.Combine(dir, Path.GetFileName(cleanName))); + if (!filePath.StartsWith(Path.GetFullPath(dir), StringComparison.OrdinalIgnoreCase)) return Results.BadRequest("Invalid file path"); if (!File.Exists(filePath)) return Results.NotFound("File not found"); try @@ -839,11 +846,12 @@ string GetCustomFilesDirectory(string type) } catch (Exception ex) { - return Results.Problem(ex.Message); + logger.LogError(ex, "An unexpected error occurred."); + return Results.Problem("An unexpected error occurred."); } }); - api.MapPost("/api/custom-files/{type}/{name}", async ([FromRoute] string type, [FromRoute] string name, [FromBody] JsonElement body) => + api.MapPost("/api/custom-files/{type}/{name}", async ([FromRoute] string type, [FromRoute] string name, [FromBody] JsonElement body, ILogger logger) => { if (type != "prompts" && type != "resources") return Results.BadRequest("Invalid type"); var cleanName = SanitizeFileName(name); @@ -865,12 +873,14 @@ string GetCustomFilesDirectory(string type) } catch (Exception ex) { - return Results.BadRequest($"Invalid JSON format: {ex.Message}"); + logger.LogError(ex, "Invalid JSON format."); + return Results.BadRequest("Invalid JSON format."); } } var dir = GetCustomFilesDirectory(type); - var filePath = Path.Combine(dir, cleanName); + var filePath = Path.GetFullPath(Path.Combine(dir, Path.GetFileName(cleanName))); + if (!filePath.StartsWith(Path.GetFullPath(dir), StringComparison.OrdinalIgnoreCase)) return Results.BadRequest("Invalid file path"); try { @@ -879,18 +889,20 @@ string GetCustomFilesDirectory(string type) } catch (Exception ex) { - return Results.Problem(ex.Message); + logger.LogError(ex, "An unexpected error occurred."); + return Results.Problem("An unexpected error occurred."); } }); - api.MapDelete("/api/custom-files/{type}/{name}", ([FromRoute] string type, [FromRoute] string name) => + api.MapDelete("/api/custom-files/{type}/{name}", ([FromRoute] string type, [FromRoute] string name, ILogger logger) => { if (type != "prompts" && type != "resources") return Results.BadRequest("Invalid type"); var cleanName = SanitizeFileName(name); if (string.IsNullOrEmpty(cleanName)) return Results.BadRequest("Invalid file name"); var dir = GetCustomFilesDirectory(type); - var filePath = Path.Combine(dir, cleanName); + var filePath = Path.GetFullPath(Path.Combine(dir, Path.GetFileName(cleanName))); + if (!filePath.StartsWith(Path.GetFullPath(dir), StringComparison.OrdinalIgnoreCase)) return Results.BadRequest("Invalid file path"); if (!File.Exists(filePath)) return Results.NotFound("File not found"); try @@ -900,7 +912,8 @@ string GetCustomFilesDirectory(string type) } catch (Exception ex) { - return Results.Problem(ex.Message); + logger.LogError(ex, "An unexpected error occurred."); + return Results.Problem("An unexpected error occurred."); } }); diff --git a/Components/Capabilities/ProxyEndpoints.cs b/Components/Capabilities/ProxyEndpoints.cs index 238698d2..21a9674a 100644 --- a/Components/Capabilities/ProxyEndpoints.cs +++ b/Components/Capabilities/ProxyEndpoints.cs @@ -1075,7 +1075,7 @@ await audit.LogInvocationAsync( catch (Exception ex) { logger.LogError(ex, "Error routing client message."); - return Results.Problem(ex.Message); + return Results.Problem("An unexpected error occurred."); } }; diff --git a/Components/Clients/ClientsController.cs b/Components/Clients/ClientsController.cs index 98d62e1e..2de14293 100644 --- a/Components/Clients/ClientsController.cs +++ b/Components/Clients/ClientsController.cs @@ -142,8 +142,9 @@ public async Task CreateClient([FromBody] CreateClientModel model } catch (Exception ex) { + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); await _auditLogger.LogAdminActionAsync(username, "client.create", "", JsonSerializer.Serialize(new { model.DisplayName }), false, ex.Message); - return StatusCode(500, new { error = ex.Message }); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -214,8 +215,9 @@ public async Task DeleteClient(string id) } catch (Exception ex) { + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); await _auditLogger.LogAdminActionAsync(username, "client.delete", id, "", false, ex.Message); - return StatusCode(500, new { error = ex.Message }); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } diff --git a/Components/Providers/ProvidersController.cs b/Components/Providers/ProvidersController.cs index c77839cd..48b9d903 100644 --- a/Components/Providers/ProvidersController.cs +++ b/Components/Providers/ProvidersController.cs @@ -55,7 +55,8 @@ public async Task GetAllProviders() } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -74,7 +75,8 @@ public async Task GetSecretProviders() } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -127,9 +129,10 @@ public async Task SaveSecretProvider( } catch (Exception ex) { + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); var redactedDetails = ProviderConfigSecurityHelper.RedactConfigJson(dto.ConfigJson); _ = auditLogger.LogAdminActionAsync(username, "SaveSecretProvider", dto.ProviderName, redactedDetails ?? "", false, ex.Message); - return StatusCode(500, new { error = ex.Message }); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -142,6 +145,26 @@ public async Task TestVaultConnection([FromBody] TestVaultRequest return BadRequest(new { success = false, error = "Vault Address is required." }); } + if (request.Address.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) + { + if (Uri.TryCreate(request.Address, UriKind.Absolute, out var uri)) + { + bool isLocalhost = uri.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase) || + uri.Host.Equals("127.0.0.1") || + uri.Host.Equals("::1"); + bool isSimpleHost = !uri.Host.Contains('.'); + + if (!isLocalhost && !isSimpleHost && !McpRouter.Components.Authorization.SecurityValidationHelper.IsPrivateOrLoopback(request.Address)) + { + return BadRequest(new { success = false, error = "Vault Address must use the HTTPS scheme in production for non-local addresses." }); + } + } + else + { + return BadRequest(new { success = false, error = "Invalid Vault Address format." }); + } + } + try { VaultSharp.V1.AuthMethods.IAuthMethodInfo authMethod; @@ -171,7 +194,8 @@ public async Task TestVaultConnection([FromBody] TestVaultRequest } catch (Exception ex) { - return Ok(new { success = false, error = $"Vault connection failed: {ex.Message}" }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return Ok(new { success = false, error = "Vault connection failed." }); } } @@ -189,7 +213,8 @@ public async Task GetAuthProviders() } catch (Exception ex) { - return StatusCode(500, new { error = ex.Message }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -241,9 +266,10 @@ public async Task SaveAuthProvider( } catch (Exception ex) { + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); var redactedDetails = ProviderConfigSecurityHelper.RedactConfigJson(dto.ConfigJson); _ = auditLogger.LogAdminActionAsync(username, "SaveAuthProvider", dto.ProviderName, redactedDetails ?? "", false, ex.Message); - return StatusCode(500, new { error = ex.Message }); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } [HttpPost("auth/batch")] @@ -303,8 +329,9 @@ public async Task SaveAuthProvidersBatch( } catch (Exception ex) { + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); _ = auditLogger.LogAdminActionAsync(username, "SaveAuthProvidersBatch", "batch", "", false, ex.Message); - return StatusCode(500, new { error = ex.Message }); + return StatusCode(500, new { error = "An unexpected error occurred." }); } } @@ -343,7 +370,8 @@ public async Task TestLdapConnection([FromBody] TestLdapRequest r } catch (Exception ex) { - return Ok(new { success = false, error = $"LDAP connection/bind failed: {ex.Message}" }); + HttpContext?.RequestServices?.GetService()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred."); + return Ok(new { success = false, error = "LDAP connection/bind failed." }); } } } diff --git a/Components/Servers/ServerEndpoints.cs b/Components/Servers/ServerEndpoints.cs index 2f8d55c8..ef62cf30 100644 --- a/Components/Servers/ServerEndpoints.cs +++ b/Components/Servers/ServerEndpoints.cs @@ -86,7 +86,7 @@ public static IEndpointRouteBuilder MapServerEndpoints(this IEndpointRouteBuilde catch (Exception ex) { logger.LogError(ex, "Error executing GET /api/servers"); - return Results.Problem(ex.Message); + return Results.Problem("An unexpected error occurred."); } }); @@ -212,7 +212,7 @@ await conn.ExecuteAsync(@"UPDATE Servers SET DisplayName = @DisplayName, Url = @ { var sw = System.Diagnostics.Stopwatch.StartNew(); var username = httpContext.User.Identity?.Name ?? "anonymous"; - logger.LogInformation("POST /api/servers started for {url}", server.Url); + logger.LogInformation("POST /api/servers started for {url}", server.Url?.Replace(Environment.NewLine, "")?.Replace("\n", "")?.Replace("\r", "")); var allowedTypes = new[] { "sse", "http", "streamable", "stdio", "custom" }; var lowerType = (server.Type ?? "sse").ToLowerInvariant(); @@ -359,7 +359,7 @@ await conn.ExecuteAsync(@"INSERT INTO Servers (Id, DisplayName, Url, Enabled, Hi tools = new List(), prompts = new List(), resources = new List(), - error = ex.Message + error = "An unexpected error occurred." }); } finally diff --git a/Core/Routing/AdminMcpServer.cs b/Core/Routing/AdminMcpServer.cs index 911c345c..2073a787 100644 --- a/Core/Routing/AdminMcpServer.cs +++ b/Core/Routing/AdminMcpServer.cs @@ -276,6 +276,7 @@ public async Task ProcessRequestAsync(JsonRpcRequest request, s } catch (Exception ex) { + _logger?.LogError(ex, "An unexpected error occurred."); response.Error = new JsonRpcError { Code = -32603, Message = ex.Message }; } @@ -903,7 +904,8 @@ private async Task HandleManageProvidersAsync(JsonElement args, string c } catch (Exception ex) { - return new { success = false, error = $"Vault connection failed: {ex.Message}" }; + _logger?.LogError(ex, "Vault connection failed."); + return new { success = false, error = "Vault connection failed." }; } } @@ -977,7 +979,8 @@ private async Task HandleManageProvidersAsync(JsonElement args, string c } catch (Exception ex) { - return new { success = false, error = $"LDAP connection/bind failed: {ex.Message}" }; + _logger?.LogError(ex, "LDAP connection/bind failed."); + return new { success = false, error = "LDAP connection/bind failed." }; } } diff --git a/Core/Routing/DynamicEmbeddingService.cs b/Core/Routing/DynamicEmbeddingService.cs index b3d3f5cc..07ff0b77 100644 --- a/Core/Routing/DynamicEmbeddingService.cs +++ b/Core/Routing/DynamicEmbeddingService.cs @@ -119,7 +119,7 @@ private void ReloadActiveService() { if (_settings.EmbeddingProvider?.ToLower() == "api") { - _logger.LogInformation("Activating external API embedding provider pointing to {Url}", _settings.EmbeddingApiUrl); + _logger.LogInformation("Activating external API embedding provider pointing to {Url}", _settings.EmbeddingApiUrl?.Replace(Environment.NewLine, "")?.Replace("\n", "")?.Replace("\r", "")); _activeService = new ApiEmbeddingService(_httpClient, _settings); } else diff --git a/Core/Routing/SessionManager.Cache.cs b/Core/Routing/SessionManager.Cache.cs new file mode 100644 index 00000000..301edc4d --- /dev/null +++ b/Core/Routing/SessionManager.Cache.cs @@ -0,0 +1,93 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace McpRouter.Core.Routing +{ + public partial class SessionManager + { + private readonly ConcurrentDictionary> _serverToolsCache = new(); + private readonly ConcurrentDictionary> _serverPromptsCache = new(); + private readonly ConcurrentDictionary> _serverResourcesCache = new(); + private readonly ConcurrentDictionary> _serverResourceTemplatesCache = new(); + + public List? GetServerToolsCache(string serverId) + { + _serverToolsCache.TryGetValue(serverId, out var tools); + return tools; + } + + public void SetServerToolsCache(string serverId, List tools) + { + _serverToolsCache[serverId] = tools; + } + + public void RemoveServerToolsCache(string serverId) + { + _serverToolsCache.TryRemove(serverId, out _); + } + + public List? GetServerPromptsCache(string serverId) + { + _serverPromptsCache.TryGetValue(serverId, out var prompts); + return prompts; + } + + public void SetServerPromptsCache(string serverId, List prompts) + { + _serverPromptsCache[serverId] = prompts; + } + + public void RemoveServerPromptsCache(string serverId) + { + _serverPromptsCache.TryRemove(serverId, out _); + } + + public List? GetServerResourcesCache(string serverId) + { + _serverResourcesCache.TryGetValue(serverId, out var resources); + return resources; + } + + public void SetServerResourcesCache(string serverId, List resources) + { + _serverResourcesCache[serverId] = resources; + } + + public void RemoveServerResourcesCache(string serverId) + { + _serverResourcesCache.TryRemove(serverId, out _); + } + + public List? GetServerResourceTemplatesCache(string serverId) + { + _serverResourceTemplatesCache.TryGetValue(serverId, out var templates); + return templates; + } + + public void SetServerResourceTemplatesCache(string serverId, List templates) + { + _serverResourceTemplatesCache[serverId] = templates; + } + + public void RemoveServerResourceTemplatesCache(string serverId) + { + _serverResourceTemplatesCache.TryRemove(serverId, out _); + } + + public void RemoveServerCache(string serverId) + { + RemoveServerToolsCache(serverId); + RemoveServerPromptsCache(serverId); + RemoveServerResourcesCache(serverId); + RemoveServerResourceTemplatesCache(serverId); + } + + public void ClearGlobalCache() + { + _serverToolsCache.Clear(); + _serverPromptsCache.Clear(); + _serverResourcesCache.Clear(); + _serverResourceTemplatesCache.Clear(); + } + } +} diff --git a/Core/Routing/SessionManager.Metrics.cs b/Core/Routing/SessionManager.Metrics.cs new file mode 100644 index 00000000..b6bfda49 --- /dev/null +++ b/Core/Routing/SessionManager.Metrics.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading; + +namespace McpRouter.Core.Routing +{ + public partial class SessionManager + { + public DateTime StartTime { get; } = DateTime.UtcNow; + private long _totalRequests = 0; + public long TotalRequests => _totalRequests; + + private long _totalInputTokens = 0; + private long _totalOutputTokens = 0; + private long _totalDurationMs = 0; + + public long TotalInputTokens => _totalInputTokens; + public long TotalOutputTokens => _totalOutputTokens; + public long TotalDurationMs => _totalDurationMs; + + public void AddPerformanceMetrics(long inputTokens, long outputTokens, long durationMs) + { + Interlocked.Add(ref _totalInputTokens, inputTokens); + Interlocked.Add(ref _totalOutputTokens, outputTokens); + Interlocked.Add(ref _totalDurationMs, durationMs); + } + + public void IncrementTotalRequests() + { + Interlocked.Increment(ref _totalRequests); + } + } +} diff --git a/Core/Routing/SessionManager.cs b/Core/Routing/SessionManager.cs index 166e4dd7..ea92b566 100644 --- a/Core/Routing/SessionManager.cs +++ b/Core/Routing/SessionManager.cs @@ -12,37 +12,13 @@ namespace McpRouter.Core.Routing { - public class SessionManager + public partial class SessionManager { private readonly ConcurrentDictionary _sessions = new(); private readonly IServiceProvider _serviceProvider; private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; - public DateTime StartTime { get; } = DateTime.UtcNow; - private long _totalRequests = 0; - public long TotalRequests => _totalRequests; - - private long _totalInputTokens = 0; - private long _totalOutputTokens = 0; - private long _totalDurationMs = 0; - - public long TotalInputTokens => _totalInputTokens; - public long TotalOutputTokens => _totalOutputTokens; - public long TotalDurationMs => _totalDurationMs; - - public void AddPerformanceMetrics(long inputTokens, long outputTokens, long durationMs) - { - System.Threading.Interlocked.Add(ref _totalInputTokens, inputTokens); - System.Threading.Interlocked.Add(ref _totalOutputTokens, outputTokens); - System.Threading.Interlocked.Add(ref _totalDurationMs, durationMs); - } - - public void IncrementTotalRequests() - { - System.Threading.Interlocked.Increment(ref _totalRequests); - } - public int ActiveSessionsCount => _sessions.Count; public ConcurrentDictionary BackendStatuses { get; } = new(); @@ -120,91 +96,6 @@ public System.Collections.Generic.List GetActiveSessions() return _sessions.Values.ToList(); } - private readonly ConcurrentDictionary> _serverToolsCache = new(); - private readonly ConcurrentDictionary> _serverPromptsCache = new(); - private readonly ConcurrentDictionary> _serverResourcesCache = new(); - private readonly ConcurrentDictionary> _serverResourceTemplatesCache = new(); - - public System.Collections.Generic.List? GetServerToolsCache(string serverId) - { - _serverToolsCache.TryGetValue(serverId, out var tools); - return tools; - } - - public void SetServerToolsCache(string serverId, System.Collections.Generic.List tools) - { - _serverToolsCache[serverId] = tools; - } - - public void RemoveServerToolsCache(string serverId) - { - _serverToolsCache.TryRemove(serverId, out _); - } - - public System.Collections.Generic.List? GetServerPromptsCache(string serverId) - { - _serverPromptsCache.TryGetValue(serverId, out var prompts); - return prompts; - } - - public void SetServerPromptsCache(string serverId, System.Collections.Generic.List prompts) - { - _serverPromptsCache[serverId] = prompts; - } - - public void RemoveServerPromptsCache(string serverId) - { - _serverPromptsCache.TryRemove(serverId, out _); - } - - public System.Collections.Generic.List? GetServerResourcesCache(string serverId) - { - _serverResourcesCache.TryGetValue(serverId, out var resources); - return resources; - } - - public void SetServerResourcesCache(string serverId, System.Collections.Generic.List resources) - { - _serverResourcesCache[serverId] = resources; - } - - public void RemoveServerResourcesCache(string serverId) - { - _serverResourcesCache.TryRemove(serverId, out _); - } - - public System.Collections.Generic.List? GetServerResourceTemplatesCache(string serverId) - { - _serverResourceTemplatesCache.TryGetValue(serverId, out var templates); - return templates; - } - - public void SetServerResourceTemplatesCache(string serverId, System.Collections.Generic.List templates) - { - _serverResourceTemplatesCache[serverId] = templates; - } - - public void RemoveServerResourceTemplatesCache(string serverId) - { - _serverResourceTemplatesCache.TryRemove(serverId, out _); - } - - public void RemoveServerCache(string serverId) - { - RemoveServerToolsCache(serverId); - RemoveServerPromptsCache(serverId); - RemoveServerResourcesCache(serverId); - RemoveServerResourceTemplatesCache(serverId); - } - - public void ClearGlobalCache() - { - _serverToolsCache.Clear(); - _serverPromptsCache.Clear(); - _serverResourcesCache.Clear(); - _serverResourceTemplatesCache.Clear(); - } - public void ResetAll() { _logger.LogInformation("Resetting all active MCP client sessions due to configuration change."); diff --git a/Core/Routing/ToolRoutingManager.Cache.cs b/Core/Routing/ToolRoutingManager.Cache.cs new file mode 100644 index 00000000..1c2465f1 --- /dev/null +++ b/Core/Routing/ToolRoutingManager.Cache.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using System.Net.Http; +using Microsoft.Extensions.Logging; +using McpRouter.Models; +using McpRouter.Core.Protocol; +using McpRouter.Components.Servers; +using McpRouter.Infrastructure.Persistence; +using Dapper; + +namespace McpRouter.Core.Routing +{ + public partial class ToolRoutingManager + { + /// + /// Asynchronously lists available tools from connected backends or returns bootstrap Meta-Mode tools. + /// + public async Task> ListToolsAsync(string body, bool isMetaMode, IEnumerable> backendConnections, ILogger logger, Func ensureBackendsInitializedAsync, IEnumerable servers, SessionManager? sessionManager = null) + { + if (isMetaMode) + { + return GetMetaModeTools(); + } + + await ensureBackendsInitializedAsync(); + + lock (_cacheLock) + { + if (_isCachePopulated) + { + return new List(_cachedTools); + } + } + + await PopulateToolsCacheAsync(body, backendConnections, logger, servers, sessionManager); + lock (_cacheLock) + { + return new List(_cachedTools); + } + } + + public async Task PopulateToolsCacheAsync(string body, IEnumerable> backendConnections, ILogger logger, IEnumerable servers, SessionManager? sessionManager = null) + { + var allTools = new List(); + + var tasks = new List>(); + + foreach (var entry in backendConnections) + { + var conn = entry.Value; + var serverId = entry.Key; + + tasks.Add(Task.Run(async () => + { + try + { + var reqBody = "{\"jsonrpc\":\"2.0\",\"method\":\"tools/list\",\"id\":\"refresh-list\"}"; + var resp = await conn.SendRequestAsync("tools/list", reqBody); + if (resp.Result != null && resp.Result.Value.TryGetProperty("tools", out var toolsList)) + { + return (serverId, toolsList); + } + } + catch (Exception ex) + { + logger.LogError(ex, "Error listing tools on server {ServerId}", serverId); + } + return (serverId, default(JsonElement)); + })); + } + + var completed = await Task.WhenAll(tasks); + foreach (var item in completed) + { + if (item.Tools.ValueKind == JsonValueKind.Array) + { + var serverTools = new List(); + foreach (var tool in item.Tools.EnumerateArray()) + { + if (tool.TryGetProperty("name", out var nameProp)) + { + var rawToolName = nameProp.GetString() ?? string.Empty; + var exposedName = item.ServerId + "__" + rawToolName; + + _toolRoutingTable[exposedName] = item.ServerId; + + var toolDict = JsonSerializer.Deserialize>(tool.GetRawText()); + if (toolDict != null) + { + toolDict["name"] = exposedName; + if (toolDict.TryGetValue("description", out var desc)) + { + toolDict["description"] = $"[{item.ServerId}] " + desc; + } + + var srv = servers.FirstOrDefault(s => s.Id == item.ServerId); + if (srv != null && (srv.AllowPassThroughAuth || !string.IsNullOrEmpty(srv.DynamicAuthPrompt))) + { + var authPrompt = !string.IsNullOrEmpty(srv.DynamicAuthPrompt) ? srv.DynamicAuthPrompt : "This tool requires a target authentication token. Call with target_auth_token parameter."; + toolDict["description"] = $"{toolDict["description"]}\n\nAUTH REQUIRED: {authPrompt}"; + } + + serverTools.Add(toolDict); + allTools.Add(toolDict); + } + } + } + if (sessionManager != null) + { + sessionManager.SetServerToolsCache(item.ServerId, serverTools); + } + } + } + + lock (_cacheLock) + { + _cachedTools.Clear(); + _cachedTools.AddRange(allTools); + _isCachePopulated = true; + } + } + } +} diff --git a/Core/Routing/ToolRoutingManager.Execution.cs b/Core/Routing/ToolRoutingManager.Execution.cs new file mode 100644 index 00000000..3488997f --- /dev/null +++ b/Core/Routing/ToolRoutingManager.Execution.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using System.Net.Http; +using Microsoft.Extensions.Logging; +using McpRouter.Models; +using McpRouter.Core.Protocol; +using McpRouter.Components.Servers; +using McpRouter.Infrastructure.Persistence; +using Dapper; + +namespace McpRouter.Core.Routing +{ + public partial class ToolRoutingManager + { + public async Task CallToolAsync( + string toolName, + string body, + IDbConnectionFactory dbFactory, + ConcurrentDictionary backendConnections, + IEnumerable servers, + ILogger logger, + HttpClient httpClient, + IEmbeddingService embeddingService, + Func ensureBackendsInitializedAsync, + Func rewriteRequestJson, + CancellationToken cancellationToken = default, + SessionManager? sessionManager = null, + string? clientSessionId = null, + Func, Task>>? filterAuthorizedToolsAsync = null) + { + try + { + var task = CallToolInternalAsync(toolName, body, dbFactory, backendConnections, servers, logger, httpClient, embeddingService, ensureBackendsInitializedAsync, rewriteRequestJson, cancellationToken, sessionManager, clientSessionId, filterAuthorizedToolsAsync); + return await task.WaitAsync(cancellationToken); + } + catch (OperationCanceledException) + { + logger.LogWarning("Execution of tool '{ToolName}' was cancelled.", toolName); + return new + { + isError = true, + content = new[] { + new { + type = "text", + text = "Error: request was cancelled by the client." + } + } + }; + } + } + + private async Task CallToolInternalAsync( + string toolName, + string body, + IDbConnectionFactory dbFactory, + ConcurrentDictionary backendConnections, + IEnumerable servers, + ILogger logger, + HttpClient httpClient, + IEmbeddingService embeddingService, + Func ensureBackendsInitializedAsync, + Func rewriteRequestJson, + CancellationToken cancellationToken, + SessionManager? sessionManager, + string? clientSessionId, + Func, Task>>? filterAuthorizedToolsAsync = null) + { + await ensureBackendsInitializedAsync(); + + if (toolName == "search_tools") + { + using var doc = JsonDocument.Parse(body); + var root = doc.RootElement; + + string query = ""; + if (root.TryGetProperty("params", out var paramsProp) && + paramsProp.TryGetProperty("arguments", out var argsProp) && + argsProp.TryGetProperty("query", out var queryProp)) + { + query = queryProp.GetString() ?? ""; + } + + var tools = new List(); + lock (_cacheLock) + { + tools.AddRange(_cachedTools); + } + + if (filterAuthorizedToolsAsync != null) + { + tools = await filterAuthorizedToolsAsync(tools); + } + + var results = await SemanticSearchService.SearchToolsSemanticAsync(query, tools, embeddingService, logger); + return new + { + content = new[] { + new { + type = "text", + text = JsonSerializer.Serialize(results, new JsonSerializerOptions { WriteIndented = true }) + } + } + }; + } + else if (toolName == "execute_tool") + { + using var doc = JsonDocument.Parse(body); + var root = doc.RootElement; + + string targetName = ""; + JsonElement targetArgs = default; + string? targetAuthToken = null; + + if (root.TryGetProperty("params", out var paramsProp) && + paramsProp.TryGetProperty("arguments", out var argsProp)) + { + if (argsProp.TryGetProperty("name", out var nameProp)) + { + targetName = nameProp.GetString() ?? ""; + } + if (argsProp.TryGetProperty("arguments", out var targetArgsProp)) + { + targetArgs = targetArgsProp.Clone(); + } + if (argsProp.TryGetProperty("target_auth_token", out var targetAuthTokenProp)) + { + targetAuthToken = targetAuthTokenProp.GetString(); + } + } + + if (string.IsNullOrEmpty(targetName)) + { + return new + { + isError = true, + content = new[] { + new { + type = "text", + text = "Error: target tool name is required." + } + } + }; + } + + var activeServerIds = servers.Where(s => s.Enabled).Select(s => s.Id).ToList(); + if (!SecurityValidationHelper.ValidateToolOrPromptName(targetName, activeServerIds)) + { + return new + { + isError = true, + content = new[] { + new { + type = "text", + text = $"Security Error: Invalid or spoofed namespaced identifier in execute_tool: '{targetName}'." + } + } + }; + } + + var targetPayload = new + { + jsonrpc = "2.0", + method = "tools/call", + @params = new + { + name = targetName, + arguments = targetArgs.ValueKind == JsonValueKind.Undefined ? (object)new Dictionary() : targetArgs + } + }; + var targetBody = JsonSerializer.Serialize(targetPayload); + + try + { + var result = await ExecuteTargetToolAsync(targetName, targetBody, targetAuthToken, dbFactory, backendConnections, servers, logger, httpClient, ensureBackendsInitializedAsync, rewriteRequestJson, cancellationToken, sessionManager, clientSessionId); + return result; + } + catch (Exception ex) + { + return new + { + isError = true, + content = new[] { + new { + type = "text", + text = $"Error executing target tool {targetName}: {ex.Message}" + } + } + }; + } + } + + return await ExecuteTargetToolAsync(toolName, body, null, dbFactory, backendConnections, servers, logger, httpClient, ensureBackendsInitializedAsync, rewriteRequestJson, cancellationToken, sessionManager, clientSessionId); + } + + private async Task ExecuteTargetToolAsync( + string toolName, + string body, + string? targetAuthToken, + IDbConnectionFactory dbFactory, + ConcurrentDictionary backendConnections, + IEnumerable servers, + ILogger logger, + HttpClient httpClient, + Func ensureBackendsInitializedAsync, + Func rewriteRequestJson, + CancellationToken cancellationToken, + SessionManager? sessionManager, + string? clientSessionId) + { + if (!_toolRoutingTable.ContainsKey(toolName)) + { + logger.LogInformation("Tool '{ToolName}' not found in routing table. Refreshing tools cache...", toolName); + try + { + await PopulateToolsCacheAsync("{\"jsonrpc\":\"2.0\",\"method\":\"tools/list\",\"id\":\"refresh-list\"}", backendConnections, logger, servers); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to refresh tools cache during CallToolAsync for '{ToolName}'", toolName); + } + } + + if (_toolRoutingTable.TryGetValue(toolName, out var serverId) && backendConnections.TryGetValue(serverId, out var conn)) + { + logger.LogInformation("Routing tool call '{ToolName}' to server '{ServerId}'", toolName, serverId); + + string routingBody = body; + var prefix = serverId + "__"; + if (toolName.StartsWith(prefix)) + { + var realToolName = toolName.Substring(prefix.Length); + routingBody = rewriteRequestJson(body, "name", realToolName); + } + + try + { + var resp = await conn.SendRequestAsync("tools/call", routingBody, targetAuthToken); + if (resp.Error != null) + { + var transformed = ToolErrorFormatter.TransformError(resp.Error, toolName, serverId); + return new + { + isError = true, + content = new[] { + new { + type = "text", + text = transformed + } + } + }; + } + return resp; + } + catch (System.Net.Http.HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + var srv = servers.FirstOrDefault(s => s.Id == serverId); + var prompt = (srv != null && !string.IsNullOrEmpty(srv.DynamicAuthPrompt)) ? srv.DynamicAuthPrompt : "401 Unauthorized. Please provide a valid target_auth_token via execute_tool."; + return new + { + isError = true, + content = new[] { + new { + type = "text", + text = prompt + } + } + }; + } + catch (Exception ex) + { + var transformed = ToolErrorFormatter.TransformException(ex, toolName, serverId); + return new + { + isError = true, + content = new[] { + new { + type = "text", + text = transformed + } + } + }; + } + } + + throw new KeyNotFoundException($"Tool {toolName} not found in routing table."); + } + } +} diff --git a/Core/Routing/ToolRoutingManager.cs b/Core/Routing/ToolRoutingManager.cs index 7a2562e0..d531a446 100644 --- a/Core/Routing/ToolRoutingManager.cs +++ b/Core/Routing/ToolRoutingManager.cs @@ -18,7 +18,7 @@ namespace McpRouter.Core.Routing /// /// Manages backend tool listing, caching, namespaced routing tables, and tool invocation execution. /// - public class ToolRoutingManager + public partial class ToolRoutingManager { private readonly ConcurrentDictionary _toolRoutingTable = new(); private readonly List _cachedTools = new(); @@ -30,33 +30,6 @@ public class ToolRoutingManager /// public ConcurrentDictionary ToolRoutingTable => _toolRoutingTable; - /// - /// Asynchronously lists available tools from connected backends or returns bootstrap Meta-Mode tools. - /// - public async Task> ListToolsAsync(string body, bool isMetaMode, IEnumerable> backendConnections, ILogger logger, Func ensureBackendsInitializedAsync, IEnumerable servers, SessionManager? sessionManager = null) - { - if (isMetaMode) - { - return GetMetaModeTools(); - } - - await ensureBackendsInitializedAsync(); - - lock (_cacheLock) - { - if (_isCachePopulated) - { - return new List(_cachedTools); - } - } - - await PopulateToolsCacheAsync(body, backendConnections, logger, servers, sessionManager); - lock (_cacheLock) - { - return new List(_cachedTools); - } - } - public static List GetMetaModeTools() { return new List @@ -94,87 +67,6 @@ public static List GetMetaModeTools() }; } - public async Task PopulateToolsCacheAsync(string body, IEnumerable> backendConnections, ILogger logger, IEnumerable servers, SessionManager? sessionManager = null) - { - var allTools = new List(); - - var tasks = new List>(); - - foreach (var entry in backendConnections) - { - var conn = entry.Value; - var serverId = entry.Key; - - tasks.Add(Task.Run(async () => - { - try - { - var reqBody = "{\"jsonrpc\":\"2.0\",\"method\":\"tools/list\",\"id\":\"refresh-list\"}"; - var resp = await conn.SendRequestAsync("tools/list", reqBody); - if (resp.Result != null && resp.Result.Value.TryGetProperty("tools", out var toolsList)) - { - return (serverId, toolsList); - } - } - catch (Exception ex) - { - logger.LogError(ex, "Error listing tools on server {ServerId}", serverId); - } - return (serverId, default(JsonElement)); - })); - } - - var completed = await Task.WhenAll(tasks); - foreach (var item in completed) - { - if (item.Tools.ValueKind == JsonValueKind.Array) - { - var serverTools = new List(); - foreach (var tool in item.Tools.EnumerateArray()) - { - if (tool.TryGetProperty("name", out var nameProp)) - { - var rawToolName = nameProp.GetString() ?? string.Empty; - var exposedName = item.ServerId + "__" + rawToolName; - - _toolRoutingTable[exposedName] = item.ServerId; - - var toolDict = JsonSerializer.Deserialize>(tool.GetRawText()); - if (toolDict != null) - { - toolDict["name"] = exposedName; - if (toolDict.TryGetValue("description", out var desc)) - { - toolDict["description"] = $"[{item.ServerId}] " + desc; - } - - var srv = servers.FirstOrDefault(s => s.Id == item.ServerId); - if (srv != null && (srv.AllowPassThroughAuth || !string.IsNullOrEmpty(srv.DynamicAuthPrompt))) - { - var authPrompt = !string.IsNullOrEmpty(srv.DynamicAuthPrompt) ? srv.DynamicAuthPrompt : "This tool requires a target authentication token. Call with target_auth_token parameter."; - toolDict["description"] = $"{toolDict["description"]}\n\nAUTH REQUIRED: {authPrompt}"; - } - - serverTools.Add(toolDict); - allTools.Add(toolDict); - } - } - } - if (sessionManager != null) - { - sessionManager.SetServerToolsCache(item.ServerId, serverTools); - } - } - } - - lock (_cacheLock) - { - _cachedTools.Clear(); - _cachedTools.AddRange(allTools); - _isCachePopulated = true; - } - } - public void InvalidateCache() { lock (_cacheLock) @@ -184,279 +76,6 @@ public void InvalidateCache() } } - public async Task CallToolAsync( - string toolName, - string body, - IDbConnectionFactory dbFactory, - ConcurrentDictionary backendConnections, - IEnumerable servers, - ILogger logger, - HttpClient httpClient, - IEmbeddingService embeddingService, - Func ensureBackendsInitializedAsync, - Func rewriteRequestJson, - CancellationToken cancellationToken = default, - SessionManager? sessionManager = null, - string? clientSessionId = null, - Func, Task>>? filterAuthorizedToolsAsync = null) - { - try - { - var task = CallToolInternalAsync(toolName, body, dbFactory, backendConnections, servers, logger, httpClient, embeddingService, ensureBackendsInitializedAsync, rewriteRequestJson, cancellationToken, sessionManager, clientSessionId, filterAuthorizedToolsAsync); - return await task.WaitAsync(cancellationToken); - } - catch (OperationCanceledException) - { - logger.LogWarning("Execution of tool '{ToolName}' was cancelled.", toolName); - return new - { - isError = true, - content = new[] { - new { - type = "text", - text = "Error: request was cancelled by the client." - } - } - }; - } - } - - private async Task CallToolInternalAsync( - string toolName, - string body, - IDbConnectionFactory dbFactory, - ConcurrentDictionary backendConnections, - IEnumerable servers, - ILogger logger, - HttpClient httpClient, - IEmbeddingService embeddingService, - Func ensureBackendsInitializedAsync, - Func rewriteRequestJson, - CancellationToken cancellationToken, - SessionManager? sessionManager, - string? clientSessionId, - Func, Task>>? filterAuthorizedToolsAsync = null) - { - await ensureBackendsInitializedAsync(); - - if (toolName == "search_tools") - { - using var doc = JsonDocument.Parse(body); - var root = doc.RootElement; - - string query = ""; - if (root.TryGetProperty("params", out var paramsProp) && - paramsProp.TryGetProperty("arguments", out var argsProp) && - argsProp.TryGetProperty("query", out var queryProp)) - { - query = queryProp.GetString() ?? ""; - } - - var tools = new List(); - lock (_cacheLock) - { - tools.AddRange(_cachedTools); - } - - if (filterAuthorizedToolsAsync != null) - { - tools = await filterAuthorizedToolsAsync(tools); - } - - var results = await SemanticSearchService.SearchToolsSemanticAsync(query, tools, embeddingService, logger); - return new - { - content = new[] { - new { - type = "text", - text = JsonSerializer.Serialize(results, new JsonSerializerOptions { WriteIndented = true }) - } - } - }; - } - else if (toolName == "execute_tool") - { - using var doc = JsonDocument.Parse(body); - var root = doc.RootElement; - - string targetName = ""; - JsonElement targetArgs = default; - string? targetAuthToken = null; - - if (root.TryGetProperty("params", out var paramsProp) && - paramsProp.TryGetProperty("arguments", out var argsProp)) - { - if (argsProp.TryGetProperty("name", out var nameProp)) - { - targetName = nameProp.GetString() ?? ""; - } - if (argsProp.TryGetProperty("arguments", out var targetArgsProp)) - { - targetArgs = targetArgsProp.Clone(); - } - if (argsProp.TryGetProperty("target_auth_token", out var targetAuthTokenProp)) - { - targetAuthToken = targetAuthTokenProp.GetString(); - } - } - - if (string.IsNullOrEmpty(targetName)) - { - return new - { - isError = true, - content = new[] { - new { - type = "text", - text = "Error: target tool name is required." - } - } - }; - } - - var activeServerIds = servers.Where(s => s.Enabled).Select(s => s.Id).ToList(); - if (!SecurityValidationHelper.ValidateToolOrPromptName(targetName, activeServerIds)) - { - return new - { - isError = true, - content = new[] { - new { - type = "text", - text = $"Security Error: Invalid or spoofed namespaced identifier in execute_tool: '{targetName}'." - } - } - }; - } - - var targetPayload = new - { - jsonrpc = "2.0", - method = "tools/call", - @params = new - { - name = targetName, - arguments = targetArgs.ValueKind == JsonValueKind.Undefined ? (object)new Dictionary() : targetArgs - } - }; - var targetBody = JsonSerializer.Serialize(targetPayload); - - try - { - var result = await ExecuteTargetToolAsync(targetName, targetBody, targetAuthToken, dbFactory, backendConnections, servers, logger, httpClient, ensureBackendsInitializedAsync, rewriteRequestJson, cancellationToken, sessionManager, clientSessionId); - return result; - } - catch (Exception ex) - { - return new - { - isError = true, - content = new[] { - new { - type = "text", - text = $"Error executing target tool {targetName}: {ex.Message}" - } - } - }; - } - } - - return await ExecuteTargetToolAsync(toolName, body, null, dbFactory, backendConnections, servers, logger, httpClient, ensureBackendsInitializedAsync, rewriteRequestJson, cancellationToken, sessionManager, clientSessionId); - } - - private async Task ExecuteTargetToolAsync( - string toolName, - string body, - string? targetAuthToken, - IDbConnectionFactory dbFactory, - ConcurrentDictionary backendConnections, - IEnumerable servers, - ILogger logger, - HttpClient httpClient, - Func ensureBackendsInitializedAsync, - Func rewriteRequestJson, - CancellationToken cancellationToken, - SessionManager? sessionManager, - string? clientSessionId) - { - if (!_toolRoutingTable.ContainsKey(toolName)) - { - logger.LogInformation("Tool '{ToolName}' not found in routing table. Refreshing tools cache...", toolName); - try - { - await PopulateToolsCacheAsync("{\"jsonrpc\":\"2.0\",\"method\":\"tools/list\",\"id\":\"refresh-list\"}", backendConnections, logger, servers); - } - catch (Exception ex) - { - logger.LogError(ex, "Failed to refresh tools cache during CallToolAsync for '{ToolName}'", toolName); - } - } - - if (_toolRoutingTable.TryGetValue(toolName, out var serverId) && backendConnections.TryGetValue(serverId, out var conn)) - { - logger.LogInformation("Routing tool call '{ToolName}' to server '{ServerId}'", toolName, serverId); - - string routingBody = body; - var prefix = serverId + "__"; - if (toolName.StartsWith(prefix)) - { - var realToolName = toolName.Substring(prefix.Length); - routingBody = rewriteRequestJson(body, "name", realToolName); - } - - try - { - var resp = await conn.SendRequestAsync("tools/call", routingBody, targetAuthToken); - if (resp.Error != null) - { - var transformed = ToolErrorFormatter.TransformError(resp.Error, toolName, serverId); - return new - { - isError = true, - content = new[] { - new { - type = "text", - text = transformed - } - } - }; - } - return resp; - } - catch (System.Net.Http.HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) - { - var srv = servers.FirstOrDefault(s => s.Id == serverId); - var prompt = (srv != null && !string.IsNullOrEmpty(srv.DynamicAuthPrompt)) ? srv.DynamicAuthPrompt : "401 Unauthorized. Please provide a valid target_auth_token via execute_tool."; - return new - { - isError = true, - content = new[] { - new { - type = "text", - text = prompt - } - } - }; - } - catch (Exception ex) - { - var transformed = ToolErrorFormatter.TransformException(ex, toolName, serverId); - return new - { - isError = true, - content = new[] { - new { - type = "text", - text = transformed - } - } - }; - } - } - - throw new KeyNotFoundException($"Tool {toolName} not found in routing table."); - } - public List GetCachedTools() { lock (_cacheLock) diff --git a/Infrastructure/Identity/LdapActiveDirectoryService.cs b/Infrastructure/Identity/LdapActiveDirectoryService.cs index e7348bf3..6b4138a2 100644 --- a/Infrastructure/Identity/LdapActiveDirectoryService.cs +++ b/Infrastructure/Identity/LdapActiveDirectoryService.cs @@ -76,7 +76,7 @@ public async Task> ResolveUserSidsAsync(string username) { if (!adDb.IsEnabled) { - _logger.LogDebug("ActiveDirectory provider is disabled in database; skipping LDAP resolution for user {Username}", username); + _logger.LogDebug("ActiveDirectory provider is disabled in database; skipping LDAP resolution for user {Username}", username?.Replace(Environment.NewLine, "")?.Replace("\n", "")?.Replace("\r", "")); return sids; } @@ -136,7 +136,7 @@ public async Task> ResolveUserSidsAsync(string username) if (string.IsNullOrEmpty(server)) { - _logger.LogDebug("LDAP server is not configured; skipping LDAP resolution for user {Username}", username); + _logger.LogDebug("LDAP server is not configured; skipping LDAP resolution for user {Username}", username?.Replace(Environment.NewLine, "")?.Replace("\n", "")?.Replace("\r", "")); return sids; } @@ -217,7 +217,7 @@ public async Task> ResolveUserSidsAsync(string username) } catch (Exception ex) { - _logger.LogError(ex, "Failed to resolve SIDs via LDAP for user {Username}", username); + _logger.LogError(ex, "Failed to resolve SIDs via LDAP for user {Username}", username?.Replace(Environment.NewLine, "")?.Replace("\n", "")?.Replace("\r", "")); throw new System.Security.SecurityException($"LDAP SID resolution failed for user '{username}'. Fail-closed policy active.", ex); } diff --git a/Infrastructure/Logging/PiiSanitizer.cs b/Infrastructure/Logging/PiiSanitizer.cs index 5921cb4c..55ffcdd4 100644 --- a/Infrastructure/Logging/PiiSanitizer.cs +++ b/Infrastructure/Logging/PiiSanitizer.cs @@ -20,6 +20,7 @@ public static string SanitizePayload(string rawPayload) s = QueryRegex.Replace(s, "$1[REDACTED]"); s = UserInfoRegex.Replace(s, "$1[REDACTED]@"); s = ConnStringPasswordRegex.Replace(s, "Password=[REDACTED]"); + s = s.Replace(Environment.NewLine, "").Replace("\n", "").Replace("\r", ""); return s; } } diff --git a/McpRouter.Tests/AdminToolsParityTests.cs b/McpRouter.Tests/AdminToolsParityTests.cs index a74a4ec5..af5f5f43 100644 --- a/McpRouter.Tests/AdminToolsParityTests.cs +++ b/McpRouter.Tests/AdminToolsParityTests.cs @@ -210,7 +210,7 @@ public void Dispose() [InlineData("manage_custom_files")] [InlineData("manage_system")] [InlineData("test_tool_call")] - [Requirement("MCP-ADMIN-PARITY-TOOLS-COVERAGE", "MCP", RequirementType.Positive, "Every UI management flow has a corresponding verified action in the consolidated Admin MCP tools.")] + [Requirement("MCP-ADMIN-PARITY-TOOLS-COVERAGE", "MCP", RequirementType.Positive, "Ensures every UI management workflow is backed by a verified, equivalent action within the consolidated Admin MCP tools.")] public async Task AdminTools_ExecuteSuccessfully(string toolName) { JsonElement args = toolName switch @@ -243,7 +243,7 @@ public async Task AdminTools_ExecuteSuccessfully(string toolName) } [Fact] - [Requirement("MCP-ADMIN-PARITY-SERVERS", "MCP", RequirementType.Positive, "manage_servers supports full parity for list, get, create, update, toggle, delete, reconnect, and reconnect_all actions.")] + [Requirement("MCP-ADMIN-PARITY-SERVERS", "MCP", RequirementType.Positive, "Validates that the manage_servers tool provides comprehensive administrative capabilities including listing, retrieving, creating, updating, toggling, deleting, and reconnecting servers.")] public async Task ManageServers_Parity_AllActions() { var serverId = "srv-parity-01"; @@ -333,7 +333,7 @@ public async Task ManageServers_Parity_AllActions() } [Fact] - [Requirement("GUARD-ADMIN-SERVERS-VALIDATION", "GUARD", RequirementType.Negative, "manage_servers rejects invalid transport types, non-existent servers, and missing required parameters.")] + [Requirement("GUARD-ADMIN-SERVERS-VALIDATION", "GUARD", RequirementType.Negative, "Verifies that the manage_servers tool accurately enforces validation by rejecting malformed transport types, missing required parameters, and requests for non-existent servers.")] public async Task ManageServers_ValidationGuardrails() { // Invalid transport type diff --git a/McpRouter.Tests/ProvidersControllerTests.cs b/McpRouter.Tests/ProvidersControllerTests.cs index a7308879..26ba7b24 100644 --- a/McpRouter.Tests/ProvidersControllerTests.cs +++ b/McpRouter.Tests/ProvidersControllerTests.cs @@ -339,5 +339,71 @@ public async Task SaveAuthProvidersBatch_ReturnsBadRequest_WhenAllProvidersDisab Assert.NotNull(result); Assert.Equal(400, result.StatusCode); } - } + + [Fact] + public async Task SaveSecretProvider_HttpUrl_AllowedForLocalhost() + { + var mockAudit = new Mock(); + var mockServiceProvider = new Mock(); + mockServiceProvider.Setup(x => x.GetService(typeof(System.Collections.Generic.IEnumerable))) + .Returns(new System.Collections.Generic.List()); + var controller = new ProvidersController(_dbRepo, _dbRepo); + var dto = new SecretProviderDto + { + ProviderName = "Vault", + IsEnabled = true, + ConfigJson = "{\"url\":\"http://localhost:8200\"}" + }; + + var result = await controller.SaveSecretProvider(dto, mockAudit.Object, mockServiceProvider.Object); + if (result is ObjectResult br && br.StatusCode != 200) + { + throw new Exception("ObjectResult: " + System.Text.Json.JsonSerializer.Serialize(br.Value)); + } + Assert.IsType(result); + } + + [Fact] + public async Task SaveSecretProvider_HttpUrl_AllowedForSimpleHost() + { + var mockAudit = new Mock(); + var mockServiceProvider = new Mock(); + mockServiceProvider.Setup(x => x.GetService(typeof(System.Collections.Generic.IEnumerable))) + .Returns(new System.Collections.Generic.List()); + var controller = new ProvidersController(_dbRepo, _dbRepo); + var dto = new SecretProviderDto + { + ProviderName = "Vault", + IsEnabled = true, + ConfigJson = "{\"url\":\"http://vault:8200\"}" + }; + + var result = await controller.SaveSecretProvider(dto, mockAudit.Object, mockServiceProvider.Object); + if (result is ObjectResult br && br.StatusCode != 200) + { + throw new Exception("ObjectResult: " + System.Text.Json.JsonSerializer.Serialize(br.Value)); + } + Assert.IsType(result); + } + + [Fact] + public async Task SaveSecretProvider_HttpUrl_RejectedForExternal() + { + var mockAudit = new Mock(); + var mockServiceProvider = new Mock(); + mockServiceProvider.Setup(x => x.GetService(typeof(System.Collections.Generic.IEnumerable))) + .Returns(new System.Collections.Generic.List()); + var controller = new ProvidersController(_dbRepo, _dbRepo); + var dto = new SecretProviderDto + { + ProviderName = "Vault", + IsEnabled = true, + ConfigJson = "{\"url\":\"http://vault.external.com:8200\"}" + }; + + var result = await controller.SaveSecretProvider(dto, mockAudit.Object, mockServiceProvider.Object); + var badRequest = Assert.IsType(result); + Assert.Contains("must use the HTTPS scheme", badRequest.Value!.ToString()); + } +} } \ No newline at end of file diff --git a/McpRouter.Tests/SessionManagerTests.cs b/McpRouter.Tests/SessionManagerTests.cs index b0e03e24..a2d746a1 100644 --- a/McpRouter.Tests/SessionManagerTests.cs +++ b/McpRouter.Tests/SessionManagerTests.cs @@ -5,11 +5,13 @@ using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; +using McpRouter.Tests.Attributes; namespace McpRouter.Tests { public class SessionManagerTests { + [Requirement("CORE-101", "Auto-added requirement tracking")] [Fact] public void PerformanceMetrics_And_TotalRequests_IncrementCorrectly() { @@ -33,6 +35,7 @@ public void PerformanceMetrics_And_TotalRequests_IncrementCorrectly() Assert.Equal(50, manager.TotalDurationMs); } + [Requirement("CORE-101", "Auto-added requirement tracking")] [Fact] public void UpdateBackendStatus_TracksBackendHealth() { diff --git a/McpRouter.Tests/ToolRoutingManagerTests.cs b/McpRouter.Tests/ToolRoutingManagerTests.cs index b32e8b9e..ca59b6b4 100644 --- a/McpRouter.Tests/ToolRoutingManagerTests.cs +++ b/McpRouter.Tests/ToolRoutingManagerTests.cs @@ -15,6 +15,7 @@ using McpRouter.Core.Routing; using Moq; using Xunit; +using McpRouter.Tests.Attributes; using Dapper; namespace McpRouter.Tests @@ -44,6 +45,7 @@ INTEGER DEFAULT 0 return (connection, mockDbFactory.Object); } + [Requirement("CORE-101", "Auto-added requirement tracking")] [Fact] public async Task ListToolsAsync_ReturnsMetaTools_InMetaMode() { @@ -64,6 +66,7 @@ public async Task ListToolsAsync_ReturnsMetaTools_InMetaMode() Assert.Equal(2, tools.Count); } + [Requirement("CORE-101", "Auto-added requirement tracking")] [Fact] public void InvalidateCache_ClearsPopulatedState() { @@ -72,6 +75,7 @@ public void InvalidateCache_ClearsPopulatedState() Assert.Empty(manager.GetCachedTools()); } + [Requirement("CORE-101", "Auto-added requirement tracking")] [Fact] public async Task CallToolAsync_SearchTools_ReturnsSemanticResults() { @@ -100,6 +104,7 @@ public async Task CallToolAsync_SearchTools_ReturnsSemanticResults() Assert.NotNull(result); } + [Requirement("CORE-101", "Auto-added requirement tracking")] [Fact] public async Task CallToolAsync_ExecuteTool_ReturnsError_WhenNameMissing() { @@ -126,6 +131,7 @@ public async Task CallToolAsync_ExecuteTool_ReturnsError_WhenNameMissing() Assert.NotNull(result); } + [Requirement("CORE-101", "Auto-added requirement tracking")] [Fact] public async Task CallToolAsync_ReturnsCancellationError_WhenCancelled() { @@ -154,6 +160,7 @@ public async Task CallToolAsync_ReturnsCancellationError_WhenCancelled() Assert.NotNull(result); } + [Requirement("CORE-101", "Auto-added requirement tracking")] [Fact] public async Task CallToolAsync_ThrowsKeyNotFound_WhenToolNotInRoutingTable() { @@ -177,6 +184,7 @@ await Assert.ThrowsAsync(() => manager.CallToolAsync( )); } + [Requirement("CORE-101", "Auto-added requirement tracking")] [Fact] [Requirement("AUTH-105", "Dynamic Auth Target Pass-Through", Type = RequirementType.Positive, Category = "AUTH")] public async Task ExecuteTargetToolAsync_Catches401_AndReturnsAuthPrompt() diff --git a/Middleware/AppKeyAuthenticationHandler.cs b/Middleware/AppKeyAuthenticationHandler.cs index f16259b4..86f694eb 100644 --- a/Middleware/AppKeyAuthenticationHandler.cs +++ b/Middleware/AppKeyAuthenticationHandler.cs @@ -57,18 +57,6 @@ protected override async Task HandleAuthenticateAsync() { token = xApiKey.Trim(); } - else if (Request.Query.TryGetValue("app_key", out var qAppKey) && !string.IsNullOrEmpty(qAppKey)) - { - token = qAppKey.ToString().Trim(); - } - else if (Request.Query.TryGetValue("api_key", out var qApiKey) && !string.IsNullOrEmpty(qApiKey)) - { - token = qApiKey.ToString().Trim(); - } - else if (Request.Query.TryGetValue("key", out var qKey) && !string.IsNullOrEmpty(qKey)) - { - token = qKey.ToString().Trim(); - } } } diff --git a/README.md b/README.md index a2168e8f..fcfcd56f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MCP Router Gateway & Semantic Proxy -![Version](https://img.shields.io/badge/version-v4.27.2-orange?style=for-the-badge) +![Version](https://img.shields.io/badge/version-v4.29.0-orange?style=for-the-badge) ![.NET 10.0](https://img.shields.io/badge/.NET-10.0-512BD4?style=for-the-badge&logo=dotnet&logoColor=white) ![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2026--07--28-0052CC?style=for-the-badge) ![Tests](https://img.shields.io/badge/tests-620%20passing-2ea44f?style=for-the-badge) @@ -207,12 +207,13 @@ For complete release history and version logs, see [**CHANGELOG.md**](CHANGELOG. | Version | Release Date | Summary of Key Changes | | :--- | :--- | :--- | +| **`v4.29.0`** | 2026-08-22 | Security fixes: resolved CodeQL alerts. | +| **`v4.28.0`** | 2026-08-22 | chore(repo): large repository health sweep refactoring backend, frontend, auth, secrets, CI, and rewriting all documentation guides | | **`v4.27.2`** | 2026-08-22 | refactor(reqs): normalize requirement taxonomy IDs across all C#, Vitest, and Playwright test suites to eliminate `REQ-` prefixes and strictly enforce standard category codes (`AUTH-`, `UI-`, `DB-`, `GUARD-`) | | **`v4.27.1`** | 2026-08-22 | test(e2e): add comprehensive Playwright E2E test suites for self-service personal AppKeys, personal quota limits, and admin custom user quota overrides | | **`v4.27.0`** | 2026-08-22 | feat(auth): self-service personal AppKeys, App-Level keys separation, UserQuotas table & management endpoints, and role-adaptive frontend UI | | **`v4.26.1`** | 2026-08-22 | feat(ui): implement accessible dark-mode ConfirmModal with useConfirmStore and migrate all destructive window.confirm dialogs across settings and server management to custom modal with toast notifications | | **`v4.26.0`** | 2026-08-22 | feat(testing): containerized multi-service E2E testing stack (OpenLDAP, Vault, MySQL, Mock MCP), comprehensive Playwright E2E suites (24 proofs, 100% pass), OAuth consent SPA routing fixes, and automated live user guide screenshots | - --- ## 🧪 Code Coverage & Quality Gates diff --git a/docs/admin-guide.md b/docs/admin-guide.md new file mode 100644 index 00000000..a5537dc7 --- /dev/null +++ b/docs/admin-guide.md @@ -0,0 +1,37 @@ +# MCP Router Admin Guide + +Welcome to the MCP Router Administration Guide. This document details the administrative procedures for managing backend server connections, configuring Role-Based Access Control (RBAC) policies, and managing secret and authentication providers. + +## Server Connections + +The MCP Router acts as a gateway connecting to multiple backend MCP servers. As an administrator, you configure and maintain these connections. + +### Managing Servers +Servers can be managed via the Admin Dashboard or directly via the Admin MCP Server's tools. + +- **Adding a Server**: Specify a Display Name, connection URL, and the transport protocol type (`sse`, `http`, `streamable`, `stdio`, or `custom`). +- **Authentication**: You can attach API keys, define custom headers, or integrate with Secret Providers (like Vault) to securely inject credentials at runtime without exposing them in the configuration. +- **Enabling/Disabling**: Servers can be temporarily disabled via the `toggle` feature, completely suspending routing to that backend. +- **Reconnecting**: If a backend server becomes unresponsive, you can trigger a manual reconnection. The Router will probe the server's health and refresh active sessions. + +## Access Control & Policies + +The MCP Router supports granular Role-Based Access Control (RBAC) to ensure that only authorized clients and users can invoke specific tools or access specific servers. + +### Managing Policies +Policies determine whether a specific identity is allowed or denied access to a target. + +- **Targets**: A policy targets a specific resource, which can be an entire backend Server ID, a specific Tool name, or a wildcard (`*`) to match any target. +- **Required Group**: Specify the required role, group name, or Security Identifier (SID) that the caller must possess. +- **Allow/Deny**: Policies can explicitly allow or deny access. A wildcard deny (`*`) cannot be created through standard tools as it would trigger a global lockout. + +### Group Mappings +If you integrate with an External Identity Provider (like Azure AD or Okta), you can use Group Mappings to map external groups or SIDs to internal Router roles. + +## Settings & Diagnostics + +Administrators have access to a suite of system tools to monitor the Router's health and configure global settings. + +- **Diagnostics**: View runtime metrics, active session counts, memory usage, and handle counts. +- **Audit Logs**: The router maintains an audit trail of all administrative actions and policy evaluations. You can query these logs by user, server, or timeframe. +- **Settings**: Configure global parameters, such as the maximum number of active AppKeys globally or per user, and manage the active Embedding Model configurations for semantic routing. diff --git a/docs/admin-mcp-features.md b/docs/admin-mcp-features.md new file mode 100644 index 00000000..78e80022 --- /dev/null +++ b/docs/admin-mcp-features.md @@ -0,0 +1,57 @@ +# Admin MCP Server Features + +The MCP Router embeds a virtual, in-process **Admin MCP Server**. This allows administrators to manage the router's configuration, servers, clients, policies, and diagnostics using the standard Model Context Protocol (MCP), effectively treating the router administration interface as just another MCP server. + +This server exposes 10 consolidated tools that cover 100% of the router's gateway administration and diagnostics flows. + +## Admin Tools Reference + +### 1. `manage_servers` +Manage backend MCP server configurations and connectivity in the router gateway. +- **Actions**: `list`, `get`, `create`, `update`, `delete`, `toggle`, `reconnect`, `reconnect_all` +- **Use Cases**: Provisioning new backend servers, updating connection settings (URLs, auth shapes, headers), toggling enabled states, and manually forcing health checks or reconnections. + +### 2. `manage_appkeys` +Manage user and application API keys, quotas, and expiration. +- **Actions**: `list`, `get_limits`, `create`, `revoke` +- **Use Cases**: Enforcing limits on API keys, provisioning temporary or permanent AppKeys for scripts or developers, and revoking compromised keys. + +### 3. `manage_clients` +Manage OAuth2 / dynamic client credentials. +- **Actions**: `list`, `register`, `delete` +- **Use Cases**: Registering new machine-to-machine OAuth2 clients, assigning display names and scopes, and rotating or deleting client credentials. + +### 4. `manage_policies` +Manage role-based access control (RBAC) policies for MCP servers, tools, and resources. +- **Actions**: `list`, `save`, `delete` +- **Use Cases**: Controlling which users or groups can access specific backend servers or execute specific tools. + +### 5. `manage_group_mappings` +Manage external identity provider group to internal group mappings. +- **Actions**: `list`, `save`, `delete` +- **Use Cases**: Mapping an external Active Directory SID or Okta Group to an internal router role (e.g., mapping `S-1-5-21-...` to `admins`). + +### 6. `manage_providers` +Manage secret providers (Vault, etc.) and authentication providers (LDAP/AD, OIDC). +- **Actions**: `list`, `save_secret`, `test_vault`, `save_auth`, `test_ldap` +- **Use Cases**: Configuring HashiCorp Vault integrations, saving LDAP/AD configuration details, and securely testing connections (e.g., LDAP bind or Vault AppRole auth) before enabling them. + +### 7. `manage_settings` +Manage global router settings, embedding models, and UI configuration. +- **Actions**: `get`, `update` +- **Use Cases**: Modifying the dashboard title/icon, configuring semantic routing embedding providers (like ONNX or API-based embeddings), and enforcing global AppKey limits. + +### 8. `manage_custom_files` +Manage custom prompt templates and local resource files in the router data directory. +- **Actions**: `list`, `get`, `save`, `delete` +- **Use Cases**: Creating or updating custom system prompts, uploading specialized `.json` prompt definitions, and managing raw resource text files used by the router. + +### 9. `manage_system` +Router system diagnostics, runtime metrics, logs, and audit trail. +- **Actions**: `diagnostics`, `get_logs`, `clear_logs`, `query_audit` +- **Use Cases**: Querying memory and handle usage, retrieving recent system logs, checking active session counts, and querying the comprehensive audit trail by user, server, or timestamp. + +### 10. `test_tool_call` +Test execution of a backend MCP tool directly via the router test bench. +- **Actions**: Execution (implied) +- **Use Cases**: Dispatching a raw JSON-RPC `tools/call` request to any connected backend server to verify its connectivity, protocol compatibility, and output formatting. diff --git a/docs/database-providers.md b/docs/database-providers.md index fa8e76cd..113844fc 100644 --- a/docs/database-providers.md +++ b/docs/database-providers.md @@ -631,7 +631,7 @@ volumes: ## 🔗 Related Documentation & References -* [Production Deployment & Database Migration Guide](../DEPLOY.md) +* [Production Deployment & Database Migration Guide](deployment-guide.md) * [System Architecture & Dependency Injection](../ARCHITECTURE.md) * [Pairwise Testing Matrix & Integration Suites](testing-matrix.md) * [Pluggable Secret Providers & Server Management](user-guide/02-server-management-and-secrets.md) diff --git a/DEPLOY.md b/docs/deployment-guide.md similarity index 95% rename from DEPLOY.md rename to docs/deployment-guide.md index 7a90708f..72f30fa7 100644 --- a/DEPLOY.md +++ b/docs/deployment-guide.md @@ -89,7 +89,7 @@ docker run --env-file .env -v mcprouter-data:/data -p 8080:8080 ## 📂 Database Providers & Schema Initialization -The MCP Router supports SQL Server, MySQL/MariaDB, and SQLite. For comprehensive dialect specifications, envelope encryption details, fail-closed validation contracts, and Docker Compose configurations, see the [**Database Provider Support & Deployment Matrix Guide**](docs/database-providers.md). +The MCP Router supports SQL Server, MySQL/MariaDB, and SQLite. For comprehensive dialect specifications, envelope encryption details, fail-closed validation contracts, and Docker Compose configurations, see the [**Database Provider Support & Deployment Matrix Guide**](database-providers.md). When configuring MS SQL Server or MySQL, execute the database scripts in the exact sequence described below to initialize the database and stored procedures. @@ -127,4 +127,7 @@ Run the corresponding delta migration script against your database using standar ## 📦 Deployment Configuration Templates -For quick integration, copy `.env.example` to `.env` or copy `appsettings.Production.json.example` to `appsettings.Production.json` directly into your container/server configuration. \ No newline at end of file +For quick integration, copy `.env.example` to `.env` or copy `appsettings.Production.json.example` to `appsettings.Production.json` directly into your container/server configuration. +## 🧩 Support Matrix + +For detailed information on supported combinations of hosting environments, authentication providers, and downstream delegation methods (e.g., Docker vs IIS, OIDC vs AppKey), please refer to the [**Deployment & Authentication Support Matrix**](support-matrix.md). diff --git a/docs/features-guide.md b/docs/features-guide.md index 16be9c65..3080c8ed 100644 --- a/docs/features-guide.md +++ b/docs/features-guide.md @@ -359,3 +359,46 @@ The skill includes pre-tested scaffold templates under `skills/mcp-router-setup/ - `appsettings.Production.json.example`: Production ASP.NET Core configuration snippet. + +--- + +## 👁️ 11. Observability & PII Audit Logging + +The MCP Router includes built-in observability features with a strong focus on privacy and security. + +### PII Sanitization +The `PiiSanitizer` automatically scrubs sensitive information before it reaches the logs: +- **Redacted Items**: Bearer tokens, API keys, passwords, and authorization headers are replaced with `[REDACTED]`. +- **Safe Logging**: Ensures that developer consoles and persistent log aggregators never leak downstream credentials. + +### Audit Logging +Administrative actions and tool executions are persistently recorded via stored procedures (e.g., `sp_InsertAuditLog`): +- **Traceability**: Records the caller's identity (user, group mapping, or AppKey), target server, tool invoked, and timestamp. +- **Auditing Tool**: The Admin MCP Server provides the `query_audit` action via the `manage_system` tool, allowing administrators and autonomous agents to query historical gateway logs. + +--- + +## 🏢 12. Enterprise Identity Delegation + +For complex enterprise networks, the router manages downstream identity flow dynamically, allowing backend services to enforce Row-Level Security (RLS) based on the user's origin identity. + +### Delegation Strategies +1. **X-Forwarded-User Propagation (Trusted Gateway Pattern)**: + The gateway injects the authenticated caller's username (extracted from the OIDC proxy or Active Directory) into the downstream HTTP/SSE backend requests. +2. **Kerberos / NTLM Impersonation**: + In native Windows IIS deployments, the router uses `S4U2Proxy` to assume the inbound caller's Active Directory identity when making downstream enterprise endpoint calls. +3. **OAuth2 / OIDC On-Behalf-Of**: + The router can act as a Confidential Client to dynamically mint or exchange tokens with Identity Providers (Azure AD, Okta, Authentik) on behalf of the user. +4. **Dynamic Auth Pass-Through**: + When downstream services issue interactive challenges, the router can proxy these `dynamic_auth` prompts directly back to the client IDE or LLM to complete the challenge. + +--- + +## 🐳 13. Batteries-Included Docker Environment + +The official Docker image simplifies deployment of STDIO-based subprocess servers without requiring complex sidecar networking. + +- **Image Tag**: `ghcr.io/org/mcp-router:latest-full` +- **Embedded Runtimes**: The image ships with pre-installed Node.js, Python 3, `uv` package manager, and `bun`. +- **Use Case**: Natively execute STDIO backend scripts (e.g., `npx -y @modelcontextprotocol/server-postgres`) directly from within the router container, minimizing network configuration overhead. + diff --git a/docs/requirements-catalog.json b/docs/requirements-catalog.json index 6387f6c3..685a3eba 100644 --- a/docs/requirements-catalog.json +++ b/docs/requirements-catalog.json @@ -1,10 +1,10 @@ { "metadata": { - "generatedAt": "2026-08-22T15:52:22.5663680Z", - "totalRequirements": 94, - "positiveCount": 71, + "generatedAt": "2026-08-22T16:41:38.5335548Z", + "totalRequirements": 101, + "positiveCount": 78, "guardrailCount": 23, - "totalProofs": 234 + "totalProofs": 243 }, "requirements": [ { @@ -16,7 +16,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/UserSecretStoreTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserSecretStoreTests.cs", "lineNumber": 13, "testName": "DatabaseUserSecretStore_SavesAndRetrieves_Secret", "details": null @@ -32,7 +32,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/UserCredentialsControllerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserCredentialsControllerTests.cs", "lineNumber": 18, "testName": "GetUserCredentials_ReturnsServerIds", "details": null @@ -44,119 +44,112 @@ "category": "AUTH", "type": "Negative", "description": "AdminPolicy allows principal with configured Admin Group Name (e.g., full_admin)", - "proofCount": 16, + "proofCount": 15, "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs", "lineNumber": 18, "testName": "AdminPolicy_Allows_Principal_With_AdminGroupName", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs", "lineNumber": 52, "testName": "AdminPolicy_Allows_Principal_With_AdminSid", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs", "lineNumber": 86, "testName": "AdminPolicy_Allows_Principal_With_ConfiguredAdminGroups", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs", "lineNumber": 121, "testName": "AdminPolicy_Denies_StandardRole_WithoutAdminSidOrGroup", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicySidOnlyTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicySidOnlyTests.cs", "lineNumber": 21, "testName": "AdminPolicy_Denies_StandardRole_Without_AdminSid", "details": "Verifies that users with standard/unconfigured role names lacking explicit Admin SID claim or Admin Group are denied by AdminPolicy." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicySidOnlyTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicySidOnlyTests.cs", "lineNumber": 63, "testName": "AdminPolicy_Allows_Principal_With_AdminSid", "details": "Verifies that principals with the configured Admin SID are granted administrative policy access." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 274, "testName": "AppKeysController_CreateAppKey_UnknownCategory_Admin_Succeeds", "details": "Verifies that admin callers can create forward-looking AppKeys for unconfigured categories." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs", "lineNumber": 226, "testName": "SecurityValidationHelper_IsAdmin_RequiresAdminGroupSid", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs", "lineNumber": 244, "testName": "SecurityValidationHelper_IsAdmin_AllowsAdminGroupName", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs", "lineNumber": 258, "testName": "SecurityValidationHelper_IsAdmin_RejectsNonAdminGroups", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs", "lineNumber": 275, "testName": "SecurityValidationHelper_IsAdmin_AllowsCustomAdminGroupsArray", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs", "lineNumber": 290, "testName": "SecurityValidationHelper_IsAdmin_AllowsMappedGroups", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs", "lineNumber": 305, "testName": "OidcIdentityProvider_DoesNotGrantAdminSid_FromGroupOrUserNames", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/IdentityAuthTab.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/IdentityAuthTab.test.tsx", "lineNumber": 12, "testName": "renders Active Directory disabled initially, toggles on and exposes fields", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/IdentityAuthTab.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/IdentityAuthTab.test.tsx", "lineNumber": 46, "testName": "fills LDAP parameters and executes test connection", "details": null - }, - { - "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/multi-user-matrix.spec.ts", - "lineNumber": 5, - "testName": "Admin Context: renders full administrator view and privileged controls", - "details": null } ] }, @@ -169,85 +162,85 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", "lineNumber": 255, "testName": "Pairwise_AppKeyScopes_RestrictsAccessPrecisely", "details": "Verifies pairwise combination of AppKey scopes across all MCP capabilities and backend targets." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs", "lineNumber": 151, "testName": "AppKeyRepository_SaveAndGet_PersistsKeyTypeAndFilters", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 213, "testName": "AppKeysController_CreateAppKey_ValidCategory_Succeeds", "details": "Verifies that AppKeys can be created with valid category scopes." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 297, "testName": "ClientsController_CreateClient_ValidCategory_Succeeds", "details": "Verifies that OAuth client credentials can be created with category-scoped access rules." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 380, "testName": "ClientSession_CategoryScope_AuthorizesMatchingServerTools_AndDeniesOthers", "details": "Verifies that category-scoped AppKey authorizes access exclusively to servers belonging to the permitted category." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 404, "testName": "ClientSession_GroupAliasScope_AuthorizesIdenticallyToCategory", "details": "Verifies that group-alias scopes evaluate identically to category scopes during tool authorization." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 426, "testName": "ClientSession_CategoryScope_IsCaseInsensitive", "details": "Verifies that category scope matching is case-insensitive across server categories." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 476, "testName": "ClientSession_ResourcesAndTemplates_FilteredByCategoryScope", "details": "Verifies that resources and templates are filtered according to category-scoped AppKey permissions." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 504, "testName": "ClientSession_DynamicServerMembership_UpdatesAccessDynamically", "details": "Verifies that dynamic updates to server categories immediately update tool access without re-authenticating." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 540, "testName": "ClientSession_MixedScopes_CombinesCategoryAndSpecificToolScopes", "details": "Verifies that AppKeys can combine category scopes and granular tool-level scopes additively." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 567, "testName": "ClientSession_Complete_FiltersServerNamesByCategoryScope", "details": "Verifies that autocomplete and completion requests filter suggestions to servers within category scopes." }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/multi-user-matrix.spec.ts", - "lineNumber": 68, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts", + "lineNumber": 70, "testName": "AppKey Direct Context: connects with API key header identity", "details": null } @@ -258,26 +251,19 @@ "category": "AUTH", "type": "Positive", "description": "SSO identity and group mappings resolve Windows SIDs and OIDC claims to internal access roles", - "proofCount": 3, + "proofCount": 2, "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", "lineNumber": 332, "testName": "Pairwise_SsoIdentityAndGroupMappings_EvaluateCorrectly", "details": "Verifies pairwise combination of SSO identities, SIDs, and group mappings across all targets." }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/rbac-enforcement-flow.spec.ts", - "lineNumber": 4, - "testName": "should create, verify, and delete RBAC policy and SID mapping", - "details": null - }, - { - "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/multi-user-matrix.spec.ts", - "lineNumber": 34, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts", + "lineNumber": 36, "testName": "Operator Context: allows overview and testbench navigation with operator identity", "details": null } @@ -292,21 +278,21 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs", "lineNumber": 17, "testName": "ResolveIdentityAsync_ExtractsWindowsIdentitySids_ViaAccessor", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs", "lineNumber": 55, "testName": "ResolveIdentityAsync_AugmentsWithLdapSids_WhenLdapServiceProvided", "details": null }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/ldap-identity-and-auth-flow.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/ldap-identity-and-auth-flow.spec.ts", "lineNumber": 5, "testName": "should configure LDAP identity provider, test connection, and save settings", "details": null @@ -322,14 +308,14 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/McpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/McpServerTests.cs", "lineNumber": 9, "testName": "McpServer_Should_Have_AllowPassThroughAuth", "details": null }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/my-mcp-servers.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/my-mcp-servers.spec.ts", "lineNumber": 7, "testName": "should render user provided servers and allow editing credentials with SQLite schema", "details": null @@ -345,7 +331,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/TransportsAuthShapeTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/TransportsAuthShapeTests.cs", "lineNumber": 208, "testName": "Transports_Use_PassThroughToken_If_Allowed", "details": null @@ -361,7 +347,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityHeaderTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityHeaderTests.cs", "lineNumber": 16, "testName": "HttpTransport_InjectsXForwardedUserHeader", "details": null @@ -377,8 +363,8 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/ToolRoutingManagerTests.cs", - "lineNumber": 180, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs", + "lineNumber": 187, "testName": "ExecuteTargetToolAsync_Catches401_AndReturnsAuthPrompt", "details": null } @@ -393,14 +379,14 @@ "proofs": [ { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/pages/ConsentView.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/pages/ConsentView.test.tsx", "lineNumber": 16, "testName": "renders client name from query string and sets form action", "details": null }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/oauth-consent-flow.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/oauth-consent-flow.spec.ts", "lineNumber": 5, "testName": "should render interactive OAuth consent screen and display requesting client name", "details": null @@ -416,7 +402,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs", "lineNumber": 347, "testName": "CreateAppKey_AllowsUnlimited_WhenLimitsAreZero", "details": null @@ -432,7 +418,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs", "lineNumber": 87, "testName": "AppKey_WithAdminScope_GrantsAdminAccess", "details": null @@ -448,7 +434,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs", "lineNumber": 263, "testName": "IsAdmin_AppKeyScopes_InHttpContextItems_ReturnsTrue", "details": null @@ -464,7 +450,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs", "lineNumber": 148, "testName": "AppKey_WithWildcardScope_GrantsAdminAccess", "details": null @@ -480,63 +466,63 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs", "lineNumber": 199, "testName": "CreateAppKey_NonAdmin_CreatesPersonalKey_UpToDefaultQuota", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts", "lineNumber": 290, "testName": "creates category-scoped key, captures one-time plaintext key, and refreshes", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx", "lineNumber": 19, "testName": "renders nothing when isCreateModalOpen is false", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx", "lineNumber": 61, "testName": "locks key type to personal key for non-admin and shows quota feedback", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx", "lineNumber": 115, "testName": "handles scope serialization for server scope and target username for admin", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx", "lineNumber": 154, "testName": "handles scope serialization for category scope and expiration days", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx", "lineNumber": 192, "testName": "disables submit button when quota limit is reached", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx", "lineNumber": 217, "testName": "displays one-time secret result and copies plaintext key to clipboard", "details": null }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/personal-appkeys-and-quotas.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/personal-appkeys-and-quotas.spec.ts", "lineNumber": 33, "testName": "Non-Admin Context: mints personal key, views snippet, and revokes key", "details": null @@ -552,42 +538,42 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs", "lineNumber": 133, "testName": "GetAppKeys_NonAdmin_ReturnsOnlyPersonalKeys_ForCurrentUser", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts", "lineNumber": 232, "testName": "loads app keys and updates store", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/App.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/App.test.tsx", "lineNumber": 79, "testName": "renders role-adaptive UI for non-admin user", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeysCard.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeysCard.test.tsx", "lineNumber": 34, "testName": "renders role-adapted My App Keys view for non-admin user", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeysCard.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeysCard.test.tsx", "lineNumber": 79, "testName": "renders keys list, copies config snippet, and revokes key", "details": null }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/personal-appkeys-and-quotas.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/personal-appkeys-and-quotas.spec.ts", "lineNumber": 5, "testName": "Non-Admin Context: displays My App Keys navigation and personal quota indicator", "details": null @@ -603,42 +589,42 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs", "lineNumber": 231, "testName": "CreateAppKey_CustomQuotaOverride_AllowsHigherLimit", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts", "lineNumber": 439, "testName": "sets user quota override and refreshes quota list", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/GeneralTab.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/GeneralTab.test.tsx", "lineNumber": 6, "testName": "renders GeneralTab with security default quota inputs and triggers save", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/GeneralTab.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/GeneralTab.test.tsx", "lineNumber": 71, "testName": "updates form state when settings prop changes", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeysCard.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeysCard.test.tsx", "lineNumber": 222, "testName": "manages custom user quotas in admin quotas tab", "details": null }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/personal-appkeys-and-quotas.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/personal-appkeys-and-quotas.spec.ts", "lineNumber": 135, "testName": "Admin Context: configures custom user quota override", "details": null @@ -654,7 +640,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs", "lineNumber": 184, "testName": "AdminPolicy_StandaloneMode_LoopbackIp_PassesAdminPolicy", "details": null @@ -670,7 +656,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs", "lineNumber": 43, "testName": "IsAdmin_StandaloneMode_CustomCidr_ReturnsTrue", "details": null @@ -686,7 +672,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs", "lineNumber": 22, "testName": "IsAdmin_StandaloneMode_LoopbackIp_ReturnsTrue", "details": null @@ -702,76 +688,180 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs", "lineNumber": 159, "testName": "SystemAppKeys_RequireAdmin_AndSeparateFromPersonalKeys", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeyAuthenticationTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeyAuthenticationTests.cs", "lineNumber": 307, "testName": "PersonalAppKey_WithAllScope_DoesNotGrantAdministratorRole", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeyAuthenticationTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeyAuthenticationTests.cs", "lineNumber": 360, "testName": "SystemAppKey_WithAdminScope_GrantsAdministratorRole", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts", "lineNumber": 216, "testName": "switches keyTypeTab between personal and system", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts", "lineNumber": 250, "testName": "fetches system-filtered app keys via query parameters", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/App.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/App.test.tsx", "lineNumber": 15, "testName": "renders header, navigation tabs, and default overview dashboard for admin user", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/App.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/App.test.tsx", "lineNumber": 35, "testName": "switches between tabs on navigation click", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx", "lineNumber": 31, "testName": "allows admin to select key type and create system app key", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeysCard.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeysCard.test.tsx", "lineNumber": 166, "testName": "handles admin tab switching and username filtering", "details": null }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/personal-appkeys-and-quotas.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/personal-appkeys-and-quotas.spec.ts", "lineNumber": 87, "testName": "Admin Context: manages segmented App-Level Keys and User Personal Keys", "details": null } ] }, + { + "id": "UI-120", + "category": "AUTH", + "type": "Positive", + "description": "RBAC and SID mapping administration UI allows configuring role policies and SID associations", + "proofCount": 1, + "proofs": [ + { + "suite": "Playwright E2E", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/rbac-enforcement-flow.spec.ts", + "lineNumber": 1, + "testName": "should create, verify, and delete RBAC policy and SID mapping", + "details": null + } + ] + }, + { + "id": "UI-125", + "category": "AUTH", + "type": "Positive", + "description": "Admin role renders full administrative dashboard and server management controls", + "proofCount": 1, + "proofs": [ + { + "suite": "Playwright E2E", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts", + "lineNumber": 1, + "testName": "Admin Context: renders full administrator view and privileged controls", + "details": null + } + ] + }, + { + "id": "CORE-101", + "category": "CORE", + "type": "Positive", + "description": "Auto-added requirement tracking", + "proofCount": 9, + "proofs": [ + { + "suite": "Backend xUnit", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SessionManagerTests.cs", + "lineNumber": 14, + "testName": "PerformanceMetrics_And_TotalRequests_IncrementCorrectly", + "details": null + }, + { + "suite": "Backend xUnit", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SessionManagerTests.cs", + "lineNumber": 38, + "testName": "UpdateBackendStatus_TracksBackendHealth", + "details": null + }, + { + "suite": "Backend xUnit", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs", + "lineNumber": 48, + "testName": "ListToolsAsync_ReturnsMetaTools_InMetaMode", + "details": null + }, + { + "suite": "Backend xUnit", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs", + "lineNumber": 69, + "testName": "InvalidateCache_ClearsPopulatedState", + "details": null + }, + { + "suite": "Backend xUnit", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs", + "lineNumber": 78, + "testName": "CallToolAsync_SearchTools_ReturnsSemanticResults", + "details": null + }, + { + "suite": "Backend xUnit", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs", + "lineNumber": 107, + "testName": "CallToolAsync_ExecuteTool_ReturnsError_WhenNameMissing", + "details": null + }, + { + "suite": "Backend xUnit", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs", + "lineNumber": 134, + "testName": "CallToolAsync_ReturnsCancellationError_WhenCancelled", + "details": null + }, + { + "suite": "Backend xUnit", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs", + "lineNumber": 163, + "testName": "CallToolAsync_ThrowsKeyNotFound_WhenToolNotInRoutingTable", + "details": null + }, + { + "suite": "Backend xUnit", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs", + "lineNumber": 187, + "testName": "ExecuteTargetToolAsync_Catches401_AndReturnsAuthPrompt", + "details": null + } + ] + }, { "id": "DB-01", "category": "DB", @@ -781,42 +871,42 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs", "lineNumber": 43, "testName": "Sqlite_UpgradeMigration_FromLegacySchema_PreservesDataAndPassesValidation", "details": "Verifies that SQLite upgrade migration from legacy schema preserves data, encrypts configs, and passes schema validation." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs", "lineNumber": 90, "testName": "UserQuotaRepository_SetAndGet_ReturnsPersistedQuota", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs", "lineNumber": 103, "testName": "UserQuotaRepository_GetAll_ReturnsAllUserQuotas", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs", "lineNumber": 122, "testName": "UserQuotaRepository_Update_UpdatesExistingQuota", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs", "lineNumber": 137, "testName": "UserQuotaRepository_Delete_RemovesQuota", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs", "lineNumber": 204, "testName": "DependencyInjection_RegistersIUserQuotaRepository", "details": null @@ -832,28 +922,28 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs", "lineNumber": 325, "testName": "Mssql_Scripts_DeclareAllProceduresAndExpectedParameters", "details": "Verifies that MSSQL stored procedure scripts declare all required procedures and parameter contracts correctly." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs", "lineNumber": 370, "testName": "MySql_Scripts_DeclareAllProceduresWithP_PrefixParameters", "details": "Verifies that MySQL stored procedure scripts declare all required procedures with p_ parameter conventions." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs", "lineNumber": 420, "testName": "Repositories_MySQL_AppKeyOperations_UseP_PrefixParameters", "details": "Verifies that Dapper repository mappings for MySQL correctly bind stored procedure p_ parameters." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/MySqlLiveIntegrationTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/MySqlLiveIntegrationTests.cs", "lineNumber": 36, "testName": "MySql_LiveRepository_AppKeyAndSecretProviderLifecycle_Succeeds", "details": null @@ -869,7 +959,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs", "lineNumber": 22, "testName": "Skill_Frontmatter_IsValidAndWithinCharacterLimit", "details": null @@ -885,7 +975,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs", "lineNumber": 156, "testName": "Skill_MirroredInAgentsDirectory", "details": null @@ -901,7 +991,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs", "lineNumber": 102, "testName": "Templates_AreValidAndContainRequiredDirectives", "details": null @@ -917,7 +1007,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs", "lineNumber": 48, "testName": "Skill_ContainsAllRequiredPhasesAndComparisons", "details": null @@ -933,7 +1023,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs", "lineNumber": 232, "testName": "AdminPolicy_ExternalIdpConfigured_LoopbackIp_RequiresCredentials", "details": null @@ -949,7 +1039,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs", "lineNumber": 208, "testName": "AdminPolicy_StandaloneMode_ExternalUntrustedIp_FailsAdminPolicy", "details": null @@ -965,7 +1055,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs", "lineNumber": 65, "testName": "IsAdmin_StandaloneMode_UntrustedIp_ReturnsFalse", "details": null @@ -981,35 +1071,35 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", "lineNumber": 481, "testName": "Pairwise_NullOrEmptyTarget_FailsClosed_ReturnsFalse", "details": "Ensures null or whitespace capability targets fail closed immediately." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", "lineNumber": 503, "testName": "Pairwise_CorruptedAppKeyScopesJson_FailsClosed_ReturnsFalse", "details": "Ensures corrupted AppKey scopes JSON fails closed safely and rejects execution." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 236, "testName": "AppKeysController_CreateAppKey_UnknownCategory_NonAdmin_FailsWithBadRequest", "details": "Ensures non-admin callers cannot create AppKeys with unconfigured categories." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 255, "testName": "AppKeysController_CreateAppKey_EmptyCategory_FailsWithBadRequest", "details": "Ensures AppKey creation with empty or whitespace category fails closed with BadRequest." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 320, "testName": "ClientsController_CreateClient_EmptyCategory_ReturnsBadRequest", "details": "Ensures client creation with empty category scope fails closed with BadRequest." @@ -1025,49 +1115,49 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/SseTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SseTransportTests.cs", "lineNumber": 39, "testName": "ResolveTokenAsync_ThrowsSecurityException_WhenSecretProviderFails", "details": "Ensures SSE transport fails closed with SecurityException when secret retriever fails." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/SseTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SseTransportTests.cs", "lineNumber": 60, "testName": "ResolveTokenAsync_ThrowsInvalidOperationException_WhenNoRetrieverRegistered", "details": "Ensures SSE transport fails closed when no secret retriever is registered." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 404, "testName": "StdioTransport_ShouldFailClosed_WhenSecretResolutionFails", "details": "Ensures STDIO transport fails closed without spawning subprocess if secret resolution fails." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/HttpTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/HttpTransportTests.cs", "lineNumber": 39, "testName": "ResolveTokenAsync_ThrowsSecurityException_WhenSecretProviderFails", "details": "Ensures HTTP transport fails closed with SecurityException when secret retriever fails." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/HttpTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/HttpTransportTests.cs", "lineNumber": 62, "testName": "ResolveTokenAsync_ThrowsInvalidOperationException_WhenNoRetrieverRegistered", "details": "Ensures HTTP transport fails closed when no secret retriever is registered." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs", "lineNumber": 70, "testName": "EnsureVaultClientAsync_ReturnsNull_WhenVaultProviderDisabledInRepo", "details": "Ensures Vault client returns null when Vault provider is disabled in repository." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs", "lineNumber": 122, "testName": "GetSecretAsync_ThrowsSecurityException_OnVaultException", "details": "Ensures Vault secret retrieval failures throw SecurityException and fail closed." @@ -1083,43 +1173,43 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 114, "testName": "StdioTransport_ShouldThrowSecurityExceptionForUnsafeExecutable", "details": "Ensures STDIO transport rejects commands with shell metacharacters or dangerous commands." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 136, "testName": "StdioTransport_ShouldThrowSecurityExceptionForShellExecutable", "details": "Ensures STDIO transport rejects shell wrappers and script interpreters lacking explicit safe paths." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 158, "testName": "StdioTransport_ShouldThrowOnInvalidExecutable", "details": "Ensures STDIO transport fails cleanly with InvalidOperationException when target binary is not found." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 220, "testName": "StdioTransport_ShouldTimeoutOnSlowRequests", "details": "Ensures STDIO transport times out requests exceeding configured execution duration limits." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 299, "testName": "StdioTransport_ShouldHandleUnexpectedExit", "details": "Ensures STDIO transport clears pending requests and fails closed upon unexpected child process termination." }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/multi-user-matrix.spec.ts", - "lineNumber": 53, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts", + "lineNumber": 55, "testName": "Guest / Denied Context: restricted user session renders safely", "details": null } @@ -1134,28 +1224,28 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", "lineNumber": 521, "testName": "Pairwise_CompleteAsync_MalformedOrMissingBackends_ThrowsOrFailsClosed", "details": "Ensures malformed completion payloads or unmapped backends throw or fail closed." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", "lineNumber": 544, "testName": "Pairwise_DatabaseDisconnection_FailsClosedSafely", "details": "Ensures database disconnection or failure fails closed safely without leaking access." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs", "lineNumber": 177, "testName": "SchemaValidation_FailsClosed_WhenRequiredColumnOrTableMissing", "details": "Ensures database schema validation fails closed when required columns or tables are missing." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs", "lineNumber": 203, "testName": "SchemaValidation_FailsClosed_WhenUserQuotasOrKeyTypeMissing", "details": "Ensures database schema validation fails closed when KeyType column or UserQuotas table is missing." @@ -1171,7 +1261,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/ProvidersControllerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ProvidersControllerTests.cs", "lineNumber": 324, "testName": "SaveAuthProvidersBatch_ReturnsBadRequest_WhenAllProvidersDisabled", "details": null @@ -1187,7 +1277,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/PermissionsControllerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PermissionsControllerTests.cs", "lineNumber": 236, "testName": "SavePolicy_ReturnsBadRequest_WhenWildcardDenyPolicy", "details": null @@ -1203,7 +1293,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 790, "testName": "ManageCustomFiles_ValidationGuardrails", "details": null @@ -1219,7 +1309,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs", "lineNumber": 196, "testName": "AdminEndpoint_UnauthorizedCaller_Returns403", "details": null @@ -1235,7 +1325,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 524, "testName": "ManagePolicies_WildcardDenyGuardrail", "details": null @@ -1251,7 +1341,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 661, "testName": "ManageProviders_LdapPlaintextGuardrail", "details": null @@ -1262,12 +1352,12 @@ "id": "GUARD-ADMIN-SERVERS-VALIDATION", "category": "GUARD", "type": "Negative", - "description": "manage_servers rejects invalid transport types, non-existent servers, and missing required parameters.", + "description": "Verifies that the manage_servers tool accurately enforces validation by rejecting malformed transport types, missing required parameters, and requests for non-existent servers.", "proofCount": 1, "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 335, "testName": "ManageServers_ValidationGuardrails", "details": null @@ -1283,7 +1373,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 602, "testName": "CallToolAsync_UnknownToolOrAction_ReturnsErrorResponse", "details": null @@ -1299,7 +1389,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 643, "testName": "CallToolAsync_TestToolCall_MissingServer_ReturnsError", "details": null @@ -1315,14 +1405,14 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", "lineNumber": 580, "testName": "Pairwise_MetaMode_ExecuteTool_EnforcesTargetAuthorization", "details": "Verifies that router meta-mode execute_tool strictly enforces target tool authorization policies." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs", "lineNumber": 444, "testName": "ClientSession_ExecuteTool_EnforcesCategoryScopeOnInnerTarget", "details": "Verifies that router meta-mode execute_tool validates and enforces category scopes on target tool calls." @@ -1338,7 +1428,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs", "lineNumber": 398, "testName": "Pairwise_AllCapabilities_UnderCallerRoles_EvaluateCorrectly", "details": "Verifies that all 5 MCP capability methods enforce caller role authorizations consistently." @@ -1354,7 +1444,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs", "lineNumber": 289, "testName": "AdminEndpoint_SseSession_CallTool_ManageSystemDiagnostics", "details": null @@ -1370,7 +1460,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs", "lineNumber": 215, "testName": "AdminEndpoint_HeadRequest_ReturnsEventStreamHeaders", "details": null @@ -1386,7 +1476,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs", "lineNumber": 227, "testName": "AdminEndpoint_SseSession_ListTools", "details": null @@ -1402,7 +1492,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs", "lineNumber": 152, "testName": "TargetProxy_RouterAdmin_RoutesToAdminServer", "details": null @@ -1418,7 +1508,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs", "lineNumber": 73, "testName": "AdminEndpoint_SseHandshake_NegotiatesProtocol", "details": null @@ -1434,7 +1524,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 205, "testName": "HandleInitializeAsync_NegotiatesProtocolVersion", "details": null @@ -1450,7 +1540,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 381, "testName": "ManageAppKeys_Parity_AllActions", "details": null @@ -1466,7 +1556,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 434, "testName": "ManageClients_Parity_AllActions", "details": null @@ -1482,7 +1572,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 727, "testName": "ManageCustomFiles_Parity_AllActions", "details": null @@ -1498,7 +1588,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 541, "testName": "ManageGroupMappings_Parity_AllActions", "details": null @@ -1514,7 +1604,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 884, "testName": "AdminTools_ProcessRequest_JsonRpcProtocol", "details": null @@ -1530,7 +1620,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 475, "testName": "ManagePolicies_Parity_AllActions", "details": null @@ -1546,7 +1636,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 588, "testName": "ManageProviders_Parity_AllActions", "details": null @@ -1557,12 +1647,12 @@ "id": "MCP-ADMIN-PARITY-SERVERS", "category": "MCP", "type": "Positive", - "description": "manage_servers supports full parity for list, get, create, update, toggle, delete, reconnect, and reconnect_all actions.", + "description": "Validates that the manage_servers tool provides comprehensive administrative capabilities including listing, retrieving, creating, updating, toggling, deleting, and reconnecting servers.", "proofCount": 1, "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 245, "testName": "ManageServers_Parity_AllActions", "details": null @@ -1578,7 +1668,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 678, "testName": "ManageSettings_Parity_AllActions", "details": null @@ -1594,7 +1684,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 827, "testName": "ManageSystem_Parity_AllActions", "details": null @@ -1610,7 +1700,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 857, "testName": "TestToolCall_Execution_Parity", "details": null @@ -1621,12 +1711,12 @@ "id": "MCP-ADMIN-PARITY-TOOLS-COVERAGE", "category": "MCP", "type": "Positive", - "description": "Every UI management flow has a corresponding verified action in the consolidated Admin MCP tools.", + "description": "Ensures every UI management workflow is backed by a verified, equivalent action within the consolidated Admin MCP tools.", "proofCount": 1, "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs", "lineNumber": 202, "testName": "AdminTools_ExecuteSuccessfully", "details": null @@ -1642,7 +1732,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 276, "testName": "CallToolAsync_RecordsAuditLog", "details": null @@ -1658,7 +1748,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 293, "testName": "CallToolAsync_ManageAppKeys_Lifecycle", "details": null @@ -1674,7 +1764,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 340, "testName": "CallToolAsync_ManageClients_Lifecycle", "details": null @@ -1690,7 +1780,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 514, "testName": "CallToolAsync_ManageCustomFiles_Lifecycle", "details": null @@ -1706,7 +1796,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 412, "testName": "CallToolAsync_ManageGroupMappings_Lifecycle", "details": null @@ -1722,7 +1812,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 378, "testName": "CallToolAsync_ManagePolicies_Lifecycle", "details": null @@ -1738,7 +1828,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 445, "testName": "CallToolAsync_ManageProviders_Lifecycle", "details": null @@ -1754,7 +1844,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 224, "testName": "CallToolAsync_ManageServers_ListAndCreate", "details": null @@ -1770,7 +1860,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 488, "testName": "CallToolAsync_ManageSettings_Lifecycle", "details": null @@ -1786,7 +1876,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 568, "testName": "CallToolAsync_ManageSystem_Lifecycle", "details": null @@ -1802,7 +1892,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 157, "testName": "ListToolsAsync_ReturnsTenConsolidatedTools", "details": null @@ -1818,7 +1908,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AuthorizationControllerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AuthorizationControllerTests.cs", "lineNumber": 23, "testName": "Exchange_ThrowsInvalidOperationException_WhenRequestNull", "details": null @@ -1834,7 +1924,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AuthorizationControllerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AuthorizationControllerTests.cs", "lineNumber": 41, "testName": "RegisterClient_CreatesApplicationAndReturnsOk", "details": null @@ -1850,7 +1940,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AuthorizationControllerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AuthorizationControllerTests.cs", "lineNumber": 79, "testName": "Authorize_ThrowsInvalidOperationException_WhenRequestNull", "details": null @@ -1866,42 +1956,42 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs", "lineNumber": 23, "testName": "EnsureVaultClientAsync_CreatesClient_WithAppRoleCredentials", "details": "Verifies that VaultSecretRetriever authenticates with HashiCorp Vault using AppRole credentials." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs", "lineNumber": 44, "testName": "EnsureVaultClientAsync_LoadsFromSecretRepo_WhenConfigJsonHasAppRole", "details": "Verifies that VaultSecretRetriever loads AppRole credentials dynamically from repository." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs", "lineNumber": 96, "testName": "ReloadConfigAsync_ClearsClient_ForcesRecreation", "details": "Verifies that reloading config flushes cached Vault client and forces recreation." }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/SecretProvidersTab.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/SecretProvidersTab.test.tsx", "lineNumber": 19, "testName": "renders provider inputs and submits updated configuration", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/SecretProvidersTab.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/SecretProvidersTab.test.tsx", "lineNumber": 90, "testName": "handles Test Vault connection button with success and failure responses", "details": null }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/full-ui-flow-sse-vault.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/full-ui-flow-sse-vault.spec.ts", "lineNumber": 8, "testName": "should register SSE server with Vault provider (Mount/Path/Field), verify badge, and run semantic search", "details": null @@ -1917,21 +2007,21 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 354, "testName": "StdioTransport_ShouldPassSecretViaEnvironmentVariables_AndNotCommandLine", "details": "Verifies that STDIO transport securely injects secret credentials via environment variables rather than command-line arguments." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 439, "testName": "StdioTransport_ShouldSanitizeAndMaskSecretsInLogs", "details": "Verifies that subprocess logs and stderr streams are actively sanitized to mask sensitive tokens and credentials." }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/vault-approle-config-flow.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/vault-approle-config-flow.spec.ts", "lineNumber": 5, "testName": "should configure Vault AppRole credentials and test connection in settings", "details": null @@ -1947,14 +2037,14 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs", "lineNumber": 366, "testName": "TrustedProxyHelper_AllowsXForwardedFor_WhenChainIsFullyTrusted", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs", "lineNumber": 425, "testName": "TrustedProxyHelper_ConfiguredProxyTrusted_CIDR", "details": null @@ -1970,14 +2060,14 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs", "lineNumber": 16, "testName": "GetSecretAsync_ReturnsPlainString_WhenRegistryValueIsString", "details": null }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs", "lineNumber": 31, "testName": "GetSecretAsync_DecryptsDpapiBytes_WhenRegistryValueIsByteArray", "details": null @@ -1993,7 +2083,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/ProviderSettingsEncryptionTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ProviderSettingsEncryptionTests.cs", "lineNumber": 395, "testName": "SaveSecretProvider_WhenDecryptionFailed_DoesNotOverwriteCorruptPayload", "details": null @@ -2009,7 +2099,7 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs", "lineNumber": 619, "testName": "CallToolAsync_AuditLog_RedactsSensitivePayloadData", "details": null @@ -2025,14 +2115,14 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/SseTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SseTransportTests.cs", "lineNumber": 18, "testName": "ResolveTokenAsync_ReturnsApiKey_WhenProviderNone", "details": "Verifies that SSE transport resolves plaintext API key when secret provider is None." }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/full-ui-flow-http-direct.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/full-ui-flow-http-direct.spec.ts", "lineNumber": 8, "testName": "should register HTTP server with Direct Key, verify status badge, and execute tool in Test Bench", "details": null @@ -2048,14 +2138,14 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/HttpTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/HttpTransportTests.cs", "lineNumber": 19, "testName": "ResolveTokenAsync_ReturnsApiKey_WhenProviderNone", "details": "Verifies that HTTP transport resolves plaintext API key when secret provider is None." }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/full-ui-flow-stdio-env.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/full-ui-flow-stdio-env.spec.ts", "lineNumber": 8, "testName": "should register STDIO server, verify card, and execute echo tool via Test Bench", "details": null @@ -2071,35 +2161,35 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 59, "testName": "StdioTransport_ShouldInitializeAndCallToolSuccessfully", "details": "Verifies that STDIO transport spawns subprocess, handles JSON-RPC initialization and executes tool calls." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 180, "testName": "StdioTransport_ShouldRouteStderrToLogs", "details": "Verifies that STDIO transport streams subprocess stderr asynchronously to logs." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 252, "testName": "StdioTransport_ShouldSupportCancellationAndProcessTreeTermination", "details": "Verifies that STDIO transport terminates subprocess tree cleanly upon disposal or cancellation." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 337, "testName": "StdioTransport_ParseCommandLine_Handles_Quotes_And_Spaces", "details": "Verifies that STDIO command-line tokenizer preserves quoted arguments and space escaping." }, { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs", "lineNumber": 482, "testName": "StdioTransport_ShouldDrainReaderStreamsToEOF_WhenProcessExitsImmediately", "details": "Verifies that STDIO transport drains buffered stdout/stderr streams to EOF when process exits rapidly." @@ -2110,48 +2200,34 @@ "id": "UI-01", "category": "UI", "type": "Positive", - "description": "Dashboard renders stats card, connected server list, and setup instructions", - "proofCount": 6, + "description": "Dashboard shows empty filter state when no servers match search term", + "proofCount": 4, "proofs": [ { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx", - "lineNumber": 38, - "testName": "renders stats card, server list, and client setup guide", - "details": null - }, - { - "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx", - "lineNumber": 113, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx", + "lineNumber": 115, "testName": "renders empty state when no servers match search", "details": null }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/prompts-resources-customfiles.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/prompts-resources-customfiles.spec.ts", "lineNumber": 42, "testName": "should navigate to Custom Files and Prompts in Settings view", "details": null }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/dashboard.spec.ts", - "lineNumber": 5, - "testName": "should render the dashboard layout and header components", - "details": null - }, - { - "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/dashboard.spec.ts", - "lineNumber": 21, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/dashboard.spec.ts", + "lineNumber": 23, "testName": "should display aggregate statistics cards", "details": null }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/dashboard.spec.ts", - "lineNumber": 35, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/dashboard.spec.ts", + "lineNumber": 37, "testName": "should filter servers using search input", "details": null } @@ -2161,55 +2237,48 @@ "id": "UI-02", "category": "UI", "type": "Positive", - "description": "Modal remains hidden when isInspectOpen is false", - "proofCount": 7, + "description": "Inspect modal displays spinner loading state while querying server capabilities", + "proofCount": 6, "proofs": [ { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx", - "lineNumber": 47, - "testName": "renders nothing when isInspectOpen is false", - "details": null - }, - { - "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx", - "lineNumber": 59, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx", + "lineNumber": 61, "testName": "renders loading state when inspectLoading is true", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx", - "lineNumber": 77, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx", + "lineNumber": 79, "testName": "renders tools tab with schema and handles tab switching", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx", - "lineNumber": 114, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx", + "lineNumber": 116, "testName": "renders resources tab items and handles search filtering", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx", - "lineNumber": 142, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx", + "lineNumber": 144, "testName": "renders prompts tab with arguments and empty state when filtered out", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx", - "lineNumber": 164, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx", + "lineNumber": 166, "testName": "renders empty states for tabs when data is empty", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx", - "lineNumber": 192, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx", + "lineNumber": 194, "testName": "closes modal when close button is clicked", "details": null } @@ -2224,15 +2293,15 @@ "proofs": [ { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx", - "lineNumber": 61, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx", + "lineNumber": 63, "testName": "renders grouped server view by category and allows collapsing", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx", - "lineNumber": 88, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx", + "lineNumber": 90, "testName": "renders grouped server view by status and type", "details": null } @@ -2242,61 +2311,54 @@ "id": "UI-04", "category": "UI", "type": "Positive", - "description": "Interactive tool tester renders server and tool selection dropdowns", - "proofCount": 8, + "description": "Tool selector filters available tools by selected backend server", + "proofCount": 7, "proofs": [ { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx", - "lineNumber": 41, - "testName": "renders initial server and tool selection options", - "details": null - }, - { - "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx", - "lineNumber": 75, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx", + "lineNumber": 77, "testName": "filters tools by selected server and handles tool change", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx", - "lineNumber": 104, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx", + "lineNumber": 106, "testName": "filters custom tools with no namespace prefix when selectedServer is custom", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx", - "lineNumber": 129, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx", + "lineNumber": 131, "testName": "renders dynamic fields for boolean, number, string, array, and object types", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx", - "lineNumber": 176, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx", + "lineNumber": 178, "testName": "renders empty state when selected tool takes no arguments", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx", - "lineNumber": 201, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx", + "lineNumber": 203, "testName": "switches to raw JSON tab and handles raw JSON editing", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx", - "lineNumber": 240, + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx", + "lineNumber": 242, "testName": "handles form submission", "details": null }, { "suite": "Playwright E2E", - "filePath": "/containers/dev/csharp-mcp-router/frontend/e2e/prompts-resources-customfiles.spec.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/prompts-resources-customfiles.spec.ts", "lineNumber": 5, "testName": "should interact with Prompt Tester and Resource Tester cards in Test Bench", "details": null @@ -2312,13 +2374,77 @@ "proofs": [ { "suite": "Backend xUnit", - "filePath": "/containers/dev/csharp-mcp-router/McpRouter.Tests/PipelineIntegrationTests.cs", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PipelineIntegrationTests.cs", "lineNumber": 242, "testName": "Pipeline_Settings_Branding_ReadWrite", "details": null } ] }, + { + "id": "UI-102", + "category": "UI", + "type": "Positive", + "description": "Dashboard renders stats card, connected server list, and setup instructions", + "proofCount": 1, + "proofs": [ + { + "suite": "Frontend Vitest", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx", + "lineNumber": 1, + "testName": "renders stats card, server list, and client setup guide", + "details": null + } + ] + }, + { + "id": "UI-103", + "category": "UI", + "type": "Positive", + "description": "Interactive tool tester renders server and tool selection dropdowns", + "proofCount": 1, + "proofs": [ + { + "suite": "Frontend Vitest", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx", + "lineNumber": 1, + "testName": "renders initial server and tool selection options", + "details": null + } + ] + }, + { + "id": "UI-116", + "category": "UI", + "type": "Positive", + "description": "Modal remains hidden when isInspectOpen is false", + "proofCount": 1, + "proofs": [ + { + "suite": "Frontend Vitest", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx", + "lineNumber": 1, + "testName": "renders nothing when isInspectOpen is false", + "details": null + } + ] + }, + { + "id": "UI-124", + "category": "UI", + "type": "Positive", + "description": "Renders main dashboard navigation tabs and layout headers", + "proofCount": 1, + "proofs": [ + { + "suite": "Playwright E2E", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/dashboard.spec.ts", + "lineNumber": 1, + "testName": "should render the dashboard layout and header components", + "details": null + } + ] + }, { "id": "UI-CONFIRM-MODAL", "category": "UI", @@ -2328,98 +2454,98 @@ "proofs": [ { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts", "lineNumber": 76, "testName": "deletes a policy when confirmed", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts", "lineNumber": 105, "testName": "does not delete policy when confirm is cancelled", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts", "lineNumber": 166, "testName": "deletes a group mapping when confirmed", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts", "lineNumber": 195, "testName": "does not delete group mapping when confirm is cancelled", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts", "lineNumber": 222, "testName": "deletes a custom file when confirmed", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts", "lineNumber": 251, "testName": "does not delete custom file when confirm is cancelled", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts", "lineNumber": 88, "testName": "prompts confirmation and deletes client when confirmed", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts", "lineNumber": 117, "testName": "cancels deletion when user denies confirmation", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts", "lineNumber": 350, "testName": "confirms and revokes AppKey and refreshes list", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts", "lineNumber": 379, "testName": "cancels revocation when confirm is rejected", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts", "lineNumber": 471, "testName": "prompts confirmation modal and resets user quota when confirmed", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts", "lineNumber": 501, "testName": "cancels quota reset when user denies confirmation", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useServerStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useServerStore.test.ts", "lineNumber": 203, "testName": "prompts window.confirm and deletes server when confirmed", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/stores/useServerStore.test.ts", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useServerStore.test.ts", "lineNumber": 232, "testName": "does not send delete request when confirm is cancelled", "details": null @@ -2435,56 +2561,56 @@ "proofs": [ { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/pages/MyMcpServers.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/pages/MyMcpServers.test.tsx", "lineNumber": 23, "testName": "shows error toast when saving invalid JSON credentials", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/pages/MyMcpServers.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/pages/MyMcpServers.test.tsx", "lineNumber": 64, "testName": "saves valid credentials successfully and closes modal", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/CustomFileModal.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/CustomFileModal.test.tsx", "lineNumber": 88, "testName": "shows error toast when switching from invalid JSON to Visual Prompt Builder", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/CustomFileModal.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/CustomFileModal.test.tsx", "lineNumber": 109, "testName": "shows error toast when saving without a file name", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/CustomFileModal.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/CustomFileModal.test.tsx", "lineNumber": 129, "testName": "shows error toast when saving prompt with invalid JSON content", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/IdentityAuthTab.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/IdentityAuthTab.test.tsx", "lineNumber": 99, "testName": "saves updated Active Directory configuration JSON", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/IdentityAuthTab.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/IdentityAuthTab.test.tsx", "lineNumber": 138, "testName": "displays error toast when saving auth providers fails", "details": null }, { "suite": "Frontend Vitest", - "filePath": "/containers/dev/csharp-mcp-router/frontend/src/test/components/SecretProvidersTab.test.tsx", + "filePath": "/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/SecretProvidersTab.test.tsx", "lineNumber": 63, "testName": "displays error toast when saving secret providers fails", "details": null diff --git a/docs/software-requirements-and-test-catalog.md b/docs/software-requirements-and-test-catalog.md index 27237708..52f4565d 100644 --- a/docs/software-requirements-and-test-catalog.md +++ b/docs/software-requirements-and-test-catalog.md @@ -1,7 +1,7 @@ # Software Requirements Specification (SRS) & Test Verification Catalog > **Automated Verification Document:** Generated via `dotnet run --project scripts/CatalogGenerator` -> **Catalog Statistics:** **94 Requirements Verified** across **234 Test Proofs** (71 Functional Capabilities, 23 Safety Guardrails). +> **Catalog Statistics:** **101 Requirements Verified** across **243 Test Proofs** (78 Functional Capabilities, 23 Safety Guardrails). --- @@ -9,14 +9,15 @@ | Category | Domain | Total Requirements | Positive Features | Guardrails / Fail-Closed | Verification Proofs | | :--- | :--- | :---: | :---: | :---: | :---: | -| **`AUTH`** | Authentication, RBAC & Identity | **22** | 20 | 2 | 81 proofs | +| **`AUTH`** | Authentication, RBAC & Identity | **24** | 22 | 2 | 81 proofs | +| **`CORE`** | CORE | **1** | 1 | 0 | 9 proofs | | **`DB`** | Multi-Database Persistence & Migrations | **2** | 2 | 0 | 10 proofs | | **`DOC`** | DOC | **4** | 4 | 0 | 4 proofs | | **`GUARD`** | Universal Safety & Fail-Closed Guardrails | **16** | 0 | 16 | 34 proofs | | **`MCP`** | Model Context Protocol Engine & Tool Routing | **31** | 31 | 0 | 32 proofs | | **`SEC`** | Secrets Providers & Encryption | **9** | 6 | 3 | 18 proofs | | **`TRANS`** | Transports (SSE, HTTP, STDIO, Proxy) | **3** | 3 | 0 | 9 proofs | -| **`UI`** | Dashboard, Test Bench & Settings UI | **7** | 5 | 2 | 46 proofs | +| **`UI`** | Dashboard, Test Bench & Settings UI | **11** | 9 | 2 | 46 proofs | --- @@ -26,506 +27,551 @@ * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/UserSecretStoreTests.cs#L13`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/UserSecretStoreTests.cs#L13) (`DatabaseUserSecretStore_SavesAndRetrieves_Secret`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserSecretStoreTests.cs#L13`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserSecretStoreTests.cs#L13) (`DatabaseUserSecretStore_SavesAndRetrieves_Secret`) ### `[AUTH-002]` Verify UserCredentialsController returns configured server IDs. * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/UserCredentialsControllerTests.cs#L18`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/UserCredentialsControllerTests.cs#L18) (`GetUserCredentials_ReturnsServerIds`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserCredentialsControllerTests.cs#L18`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserCredentialsControllerTests.cs#L18) (`GetUserCredentials_ReturnsServerIds`) ### `[AUTH-02]` AppKey scopes restrict access precisely across all MCP capabilities and backend targets * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (12):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L255`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L255) (`Pairwise_AppKeyScopes_RestrictsAccessPrecisely`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L151`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L151) (`AppKeyRepository_SaveAndGet_PersistsKeyTypeAndFilters`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L213`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L213) (`AppKeysController_CreateAppKey_ValidCategory_Succeeds`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L297`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L297) (`ClientsController_CreateClient_ValidCategory_Succeeds`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L380`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L380) (`ClientSession_CategoryScope_AuthorizesMatchingServerTools_AndDeniesOthers`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L404`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L404) (`ClientSession_GroupAliasScope_AuthorizesIdenticallyToCategory`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L426`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L426) (`ClientSession_CategoryScope_IsCaseInsensitive`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L476`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L476) (`ClientSession_ResourcesAndTemplates_FilteredByCategoryScope`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L504`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L504) (`ClientSession_DynamicServerMembership_UpdatesAccessDynamically`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L540`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L540) (`ClientSession_MixedScopes_CombinesCategoryAndSpecificToolScopes`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L567`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L567) (`ClientSession_Complete_FiltersServerNamesByCategoryScope`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/multi-user-matrix.spec.ts#L68`](file:////containers/dev/csharp-mcp-router/frontend/e2e/multi-user-matrix.spec.ts#L68) (`AppKey Direct Context: connects with API key header identity`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L255`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L255) (`Pairwise_AppKeyScopes_RestrictsAccessPrecisely`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L151`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L151) (`AppKeyRepository_SaveAndGet_PersistsKeyTypeAndFilters`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L213`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L213) (`AppKeysController_CreateAppKey_ValidCategory_Succeeds`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L297`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L297) (`ClientsController_CreateClient_ValidCategory_Succeeds`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L380`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L380) (`ClientSession_CategoryScope_AuthorizesMatchingServerTools_AndDeniesOthers`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L404`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L404) (`ClientSession_GroupAliasScope_AuthorizesIdenticallyToCategory`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L426`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L426) (`ClientSession_CategoryScope_IsCaseInsensitive`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L476`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L476) (`ClientSession_ResourcesAndTemplates_FilteredByCategoryScope`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L504`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L504) (`ClientSession_DynamicServerMembership_UpdatesAccessDynamically`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L540`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L540) (`ClientSession_MixedScopes_CombinesCategoryAndSpecificToolScopes`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L567`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L567) (`ClientSession_Complete_FiltersServerNamesByCategoryScope`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts#L70`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts#L70) (`AppKey Direct Context: connects with API key header identity`) ### `[AUTH-03]` SSO identity and group mappings resolve Windows SIDs and OIDC claims to internal access roles * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability -* **Verification Proofs (3):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L332`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L332) (`Pairwise_SsoIdentityAndGroupMappings_EvaluateCorrectly`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/rbac-enforcement-flow.spec.ts#L4`](file:////containers/dev/csharp-mcp-router/frontend/e2e/rbac-enforcement-flow.spec.ts#L4) (`should create, verify, and delete RBAC policy and SID mapping`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/multi-user-matrix.spec.ts#L34`](file:////containers/dev/csharp-mcp-router/frontend/e2e/multi-user-matrix.spec.ts#L34) (`Operator Context: allows overview and testbench navigation with operator identity`) +* **Verification Proofs (2):** + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L332`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L332) (`Pairwise_SsoIdentityAndGroupMappings_EvaluateCorrectly`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts#L36`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts#L36) (`Operator Context: allows overview and testbench navigation with operator identity`) ### `[AUTH-04]` ActiveDirectoryIdentityProvider extracts Windows caller SIDs and security groups via IWindowsIdentityAccessor and augments with LDAP * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (3):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs#L17`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs#L17) (`ResolveIdentityAsync_ExtractsWindowsIdentitySids_ViaAccessor`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs#L55`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs#L55) (`ResolveIdentityAsync_AugmentsWithLdapSids_WhenLdapServiceProvided`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/ldap-identity-and-auth-flow.spec.ts#L5`](file:////containers/dev/csharp-mcp-router/frontend/e2e/ldap-identity-and-auth-flow.spec.ts#L5) (`should configure LDAP identity provider, test connection, and save settings`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs#L17`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs#L17) (`ResolveIdentityAsync_ExtractsWindowsIdentitySids_ViaAccessor`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs#L55`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs#L55) (`ResolveIdentityAsync_AugmentsWithLdapSids_WhenLdapServiceProvided`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/ldap-identity-and-auth-flow.spec.ts#L5`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/ldap-identity-and-auth-flow.spec.ts#L5) (`should configure LDAP identity provider, test connection, and save settings`) ### `[AUTH-05]` McpServer supports AllowPassThroughAuth flag * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (2):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/McpServerTests.cs#L9`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/McpServerTests.cs#L9) (`McpServer_Should_Have_AllowPassThroughAuth`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/my-mcp-servers.spec.ts#L7`](file:////containers/dev/csharp-mcp-router/frontend/e2e/my-mcp-servers.spec.ts#L7) (`should render user provided servers and allow editing credentials with SQLite schema`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/McpServerTests.cs#L9`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/McpServerTests.cs#L9) (`McpServer_Should_Have_AllowPassThroughAuth`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/my-mcp-servers.spec.ts#L7`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/my-mcp-servers.spec.ts#L7) (`should render user provided servers and allow editing credentials with SQLite schema`) ### `[AUTH-06]` Transports use passThroughToken when AllowPassThroughAuth is true * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/TransportsAuthShapeTests.cs#L208`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/TransportsAuthShapeTests.cs#L208) (`Transports_Use_PassThroughToken_If_Allowed`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/TransportsAuthShapeTests.cs#L208`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/TransportsAuthShapeTests.cs#L208) (`Transports_Use_PassThroughToken_If_Allowed`) ### `[AUTH-101]` HTTP transport injects X-Forwarded-User header based on connected user identity. * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityHeaderTests.cs#L16`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityHeaderTests.cs#L16) (`HttpTransport_InjectsXForwardedUserHeader`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityHeaderTests.cs#L16`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityHeaderTests.cs#L16) (`HttpTransport_InjectsXForwardedUserHeader`) ### `[AUTH-105]` Dynamic Auth Target Pass-Through * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/ToolRoutingManagerTests.cs#L180`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/ToolRoutingManagerTests.cs#L180) (`ExecuteTargetToolAsync_Catches401_AndReturnsAuthPrompt`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L187`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L187) (`ExecuteTargetToolAsync_Catches401_AndReturnsAuthPrompt`) ### `[AUTH-109]` ConsentView properly renders the client name from query string and builds correct form action. * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (2):** - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/pages/ConsentView.test.tsx#L16`](file:////containers/dev/csharp-mcp-router/frontend/src/test/pages/ConsentView.test.tsx#L16) (`renders client name from query string and sets form action`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/oauth-consent-flow.spec.ts#L5`](file:////containers/dev/csharp-mcp-router/frontend/e2e/oauth-consent-flow.spec.ts#L5) (`should render interactive OAuth consent screen and display requesting client name`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/pages/ConsentView.test.tsx#L16`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/pages/ConsentView.test.tsx#L16) (`renders client name from query string and sets form action`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/oauth-consent-flow.spec.ts#L5`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/oauth-consent-flow.spec.ts#L5) (`should render interactive OAuth consent screen and display requesting client name`) ### `[AUTH-110]` CreateAppKey allows creating unlimited AppKeys when UserMaxKeys is set to 0. * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L347`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L347) (`CreateAppKey_AllowsUnlimited_WhenLimitsAreZero`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L347`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L347) (`CreateAppKey_AllowsUnlimited_WhenLimitsAreZero`) ### `[AUTH-APPKEY-ADMIN-SCOPE-ALLOW]` AppKeys with admin scope grant Administrator role and pass AdminPolicy. * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L87`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L87) (`AppKey_WithAdminScope_GrantsAdminAccess`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L87`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L87) (`AppKey_WithAdminScope_GrantsAdminAccess`) ### `[AUTH-APPKEY-ITEMS-SCOPE-ALLOW]` SecurityValidationHelper recognizes admin scopes in HttpContext.Items. * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L263`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L263) (`IsAdmin_AppKeyScopes_InHttpContextItems_ReturnsTrue`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L263`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L263) (`IsAdmin_AppKeyScopes_InHttpContextItems_ReturnsTrue`) ### `[AUTH-APPKEY-WILDCARD-SCOPE-ALLOW]` AppKeys with wildcard scope '*' grant Administrator role and pass AdminPolicy. * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L148`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L148) (`AppKey_WithWildcardScope_GrantsAdminAccess`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L148`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L148) (`AppKey_WithWildcardScope_GrantsAdminAccess`) ### `[AUTH-PERSONAL-APPKEY-LIST]` Non-admin users can view their personal App Keys * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (6):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L133`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L133) (`GetAppKeys_NonAdmin_ReturnsOnlyPersonalKeys_ForCurrentUser`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L232`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L232) (`loads app keys and updates store`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/App.test.tsx#L79`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/App.test.tsx#L79) (`renders role-adaptive UI for non-admin user`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeysCard.test.tsx#L34`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeysCard.test.tsx#L34) (`renders role-adapted My App Keys view for non-admin user`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeysCard.test.tsx#L79`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeysCard.test.tsx#L79) (`renders keys list, copies config snippet, and revokes key`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L5`](file:////containers/dev/csharp-mcp-router/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L5) (`Non-Admin Context: displays My App Keys navigation and personal quota indicator`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L133`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L133) (`GetAppKeys_NonAdmin_ReturnsOnlyPersonalKeys_ForCurrentUser`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L232`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L232) (`loads app keys and updates store`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/App.test.tsx#L79`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/App.test.tsx#L79) (`renders role-adaptive UI for non-admin user`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeysCard.test.tsx#L34`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeysCard.test.tsx#L34) (`renders role-adapted My App Keys view for non-admin user`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeysCard.test.tsx#L79`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeysCard.test.tsx#L79) (`renders keys list, copies config snippet, and revokes key`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L5`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L5) (`Non-Admin Context: displays My App Keys navigation and personal quota indicator`) ### `[AUTH-PERSONAL-APPKEY-QUOTA-OVERRIDE]` Custom user quotas override default limit * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (6):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L231`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L231) (`CreateAppKey_CustomQuotaOverride_AllowsHigherLimit`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L439`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L439) (`sets user quota override and refreshes quota list`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/GeneralTab.test.tsx#L6`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/GeneralTab.test.tsx#L6) (`renders GeneralTab with security default quota inputs and triggers save`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/GeneralTab.test.tsx#L71`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/GeneralTab.test.tsx#L71) (`updates form state when settings prop changes`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeysCard.test.tsx#L222`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeysCard.test.tsx#L222) (`manages custom user quotas in admin quotas tab`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L135`](file:////containers/dev/csharp-mcp-router/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L135) (`Admin Context: configures custom user quota override`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L231`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L231) (`CreateAppKey_CustomQuotaOverride_AllowsHigherLimit`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L439`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L439) (`sets user quota override and refreshes quota list`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/GeneralTab.test.tsx#L6`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/GeneralTab.test.tsx#L6) (`renders GeneralTab with security default quota inputs and triggers save`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/GeneralTab.test.tsx#L71`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/GeneralTab.test.tsx#L71) (`updates form state when settings prop changes`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeysCard.test.tsx#L222`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeysCard.test.tsx#L222) (`manages custom user quotas in admin quotas tab`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L135`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L135) (`Admin Context: configures custom user quota override`) ### `[AUTH-STANDALONE-ADMINPOLICY-LOOPBACK-ALLOW]` AdminPolicy succeeds in standalone mode for unauthenticated loopback requests. * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L184`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L184) (`AdminPolicy_StandaloneMode_LoopbackIp_PassesAdminPolicy`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L184`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L184) (`AdminPolicy_StandaloneMode_LoopbackIp_PassesAdminPolicy`) ### `[AUTH-STANDALONE-CUSTOM-CIDR-ALLOW]` Standalone mode grants admin access to client IPs matching Admin:StandaloneAllowedNetworks CIDR ranges. * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L43`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L43) (`IsAdmin_StandaloneMode_CustomCidr_ReturnsTrue`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L43`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L43) (`IsAdmin_StandaloneMode_CustomCidr_ReturnsTrue`) ### `[AUTH-STANDALONE-LOOPBACK-ALLOW]` Standalone mode without external IDP grants admin access to loopback IP addresses. * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L22`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L22) (`IsAdmin_StandaloneMode_LoopbackIp_ReturnsTrue`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L22`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L22) (`IsAdmin_StandaloneMode_LoopbackIp_ReturnsTrue`) ### `[AUTH-SYSTEM-APPKEY-SEPARATION]` System keys are distinct and require admin permissions * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Positive Feature Capability * **Verification Proofs (10):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L159`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L159) (`SystemAppKeys_RequireAdmin_AndSeparateFromPersonalKeys`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeyAuthenticationTests.cs#L307`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeyAuthenticationTests.cs#L307) (`PersonalAppKey_WithAllScope_DoesNotGrantAdministratorRole`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeyAuthenticationTests.cs#L360`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeyAuthenticationTests.cs#L360) (`SystemAppKey_WithAdminScope_GrantsAdministratorRole`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L216`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L216) (`switches keyTypeTab between personal and system`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L250`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L250) (`fetches system-filtered app keys via query parameters`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/App.test.tsx#L15`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/App.test.tsx#L15) (`renders header, navigation tabs, and default overview dashboard for admin user`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/App.test.tsx#L35`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/App.test.tsx#L35) (`switches between tabs on navigation click`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L31`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L31) (`allows admin to select key type and create system app key`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeysCard.test.tsx#L166`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeysCard.test.tsx#L166) (`handles admin tab switching and username filtering`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L87`](file:////containers/dev/csharp-mcp-router/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L87) (`Admin Context: manages segmented App-Level Keys and User Personal Keys`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L159`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L159) (`SystemAppKeys_RequireAdmin_AndSeparateFromPersonalKeys`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeyAuthenticationTests.cs#L307`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeyAuthenticationTests.cs#L307) (`PersonalAppKey_WithAllScope_DoesNotGrantAdministratorRole`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeyAuthenticationTests.cs#L360`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeyAuthenticationTests.cs#L360) (`SystemAppKey_WithAdminScope_GrantsAdministratorRole`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L216`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L216) (`switches keyTypeTab between personal and system`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L250`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L250) (`fetches system-filtered app keys via query parameters`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/App.test.tsx#L15`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/App.test.tsx#L15) (`renders header, navigation tabs, and default overview dashboard for admin user`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/App.test.tsx#L35`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/App.test.tsx#L35) (`switches between tabs on navigation click`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L31`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L31) (`allows admin to select key type and create system app key`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeysCard.test.tsx#L166`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeysCard.test.tsx#L166) (`handles admin tab switching and username filtering`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L87`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L87) (`Admin Context: manages segmented App-Level Keys and User Personal Keys`) + +### `[UI-120]` RBAC and SID mapping administration UI allows configuring role policies and SID associations +* **Category:** `AUTH` (Authentication, RBAC & Identity) +* **Type:** Positive Feature Capability +* **Verification Proofs (1):** + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/rbac-enforcement-flow.spec.ts#L1`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/rbac-enforcement-flow.spec.ts#L1) (`should create, verify, and delete RBAC policy and SID mapping`) + +### `[UI-125]` Admin role renders full administrative dashboard and server management controls +* **Category:** `AUTH` (Authentication, RBAC & Identity) +* **Type:** Positive Feature Capability +* **Verification Proofs (1):** + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts#L1`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts#L1) (`Admin Context: renders full administrator view and privileged controls`) + +### `[CORE-101]` Auto-added requirement tracking +* **Category:** `CORE` (CORE) +* **Type:** Positive Feature Capability +* **Verification Proofs (9):** + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SessionManagerTests.cs#L14`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SessionManagerTests.cs#L14) (`PerformanceMetrics_And_TotalRequests_IncrementCorrectly`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SessionManagerTests.cs#L38`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SessionManagerTests.cs#L38) (`UpdateBackendStatus_TracksBackendHealth`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L48`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L48) (`ListToolsAsync_ReturnsMetaTools_InMetaMode`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L69`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L69) (`InvalidateCache_ClearsPopulatedState`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L78`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L78) (`CallToolAsync_SearchTools_ReturnsSemanticResults`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L107`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L107) (`CallToolAsync_ExecuteTool_ReturnsError_WhenNameMissing`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L134`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L134) (`CallToolAsync_ReturnsCancellationError_WhenCancelled`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L163`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L163) (`CallToolAsync_ThrowsKeyNotFound_WhenToolNotInRoutingTable`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L187`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L187) (`ExecuteTargetToolAsync_Catches401_AndReturnsAuthPrompt`) ### `[DB-01]` SQLite auto-migration seamlessly upgrades legacy schema, encrypts plaintext secrets, and preserves data * **Category:** `DB` (Multi-Database Persistence & Migrations) * **Type:** Positive Feature Capability * **Verification Proofs (6):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L43`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L43) (`Sqlite_UpgradeMigration_FromLegacySchema_PreservesDataAndPassesValidation`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L90`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L90) (`UserQuotaRepository_SetAndGet_ReturnsPersistedQuota`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L103`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L103) (`UserQuotaRepository_GetAll_ReturnsAllUserQuotas`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L122`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L122) (`UserQuotaRepository_Update_UpdatesExistingQuota`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L137`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L137) (`UserQuotaRepository_Delete_RemovesQuota`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L204`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L204) (`DependencyInjection_RegistersIUserQuotaRepository`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L43`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L43) (`Sqlite_UpgradeMigration_FromLegacySchema_PreservesDataAndPassesValidation`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L90`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L90) (`UserQuotaRepository_SetAndGet_ReturnsPersistedQuota`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L103`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L103) (`UserQuotaRepository_GetAll_ReturnsAllUserQuotas`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L122`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L122) (`UserQuotaRepository_Update_UpdatesExistingQuota`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L137`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L137) (`UserQuotaRepository_Delete_RemovesQuota`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L204`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserQuotaAndAppKeyRepositoryTests.cs#L204) (`DependencyInjection_RegistersIUserQuotaRepository`) ### `[DB-02]` MSSQL stored procedure scripts declare all required procedures and parameter contracts correctly * **Category:** `DB` (Multi-Database Persistence & Migrations) * **Type:** Positive Feature Capability * **Verification Proofs (4):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L325`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L325) (`Mssql_Scripts_DeclareAllProceduresAndExpectedParameters`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L370`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L370) (`MySql_Scripts_DeclareAllProceduresWithP_PrefixParameters`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L420`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L420) (`Repositories_MySQL_AppKeyOperations_UseP_PrefixParameters`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/MySqlLiveIntegrationTests.cs#L36`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/MySqlLiveIntegrationTests.cs#L36) (`MySql_LiveRepository_AppKeyAndSecretProviderLifecycle_Succeeds`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L325`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L325) (`Mssql_Scripts_DeclareAllProceduresAndExpectedParameters`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L370`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L370) (`MySql_Scripts_DeclareAllProceduresWithP_PrefixParameters`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L420`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L420) (`Repositories_MySQL_AppKeyOperations_UseP_PrefixParameters`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/MySqlLiveIntegrationTests.cs#L36`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/MySqlLiveIntegrationTests.cs#L36) (`MySql_LiveRepository_AppKeyAndSecretProviderLifecycle_Succeeds`) ### `[DOC-SETUP-SKILL-FRONTMATTER]` mcp-router-setup skill frontmatter is valid YAML, specifies name, description starting with 'Use when...', and length is under 1024 characters * **Category:** `DOC` (DOC) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs#L22`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs#L22) (`Skill_Frontmatter_IsValidAndWithinCharacterLimit`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs#L22`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs#L22) (`Skill_Frontmatter_IsValidAndWithinCharacterLimit`) ### `[DOC-SETUP-SKILL-MIRROR]` The mcp-router-setup skill and templates are mirrored 1:1 in .agents/skills/mcp-router-setup/ * **Category:** `DOC` (DOC) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs#L156`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs#L156) (`Skill_MirroredInAgentsDirectory`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs#L156`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs#L156) (`Skill_MirroredInAgentsDirectory`) ### `[DOC-SETUP-SKILL-TEMPLATES]` All scaffold templates exist, are non-empty, and contain required directives such as responseBufferLimit, ROUTER_MASTER_KEY, and ghcr.io/spelech/mcp-router * **Category:** `DOC` (DOC) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs#L102`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs#L102) (`Templates_AreValidAndContainRequiredDirectives`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs#L102`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs#L102) (`Templates_AreValidAndContainRequiredDirectives`) ### `[DOC-SETUP-SKILL-WORKFLOW]` mcp-router-setup skill contains all 6 required setup phases including environment probing, hosting platforms, env vs UI trade-offs, identity/network topology, artifact generation, and health/client configuration * **Category:** `DOC` (DOC) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs#L48`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs#L48) (`Skill_ContainsAllRequiredPhasesAndComparisons`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs#L48`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs#L48) (`Skill_ContainsAllRequiredPhasesAndComparisons`) ### `[MCP-01]` Meta-mode execute_tool strictly enforces target tool authorization policies * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (2):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L580`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L580) (`Pairwise_MetaMode_ExecuteTool_EnforcesTargetAuthorization`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L444`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L444) (`ClientSession_ExecuteTool_EnforcesCategoryScopeOnInnerTarget`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L580`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L580) (`Pairwise_MetaMode_ExecuteTool_EnforcesTargetAuthorization`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L444`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L444) (`ClientSession_ExecuteTool_EnforcesCategoryScopeOnInnerTarget`) ### `[MCP-02]` All MCP protocol capabilities enforce caller role authorizations consistently * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L398`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L398) (`Pairwise_AllCapabilities_UnderCallerRoles_EvaluateCorrectly`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L398`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L398) (`Pairwise_AllCapabilities_UnderCallerRoles_EvaluateCorrectly`) ### `[MCP-ADMIN-ENDPOINT-CALL-TOOL]` Admin endpoint /admin/message executes tools/call for manage_system diagnostics. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L289`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L289) (`AdminEndpoint_SseSession_CallTool_ManageSystemDiagnostics`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L289`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L289) (`AdminEndpoint_SseSession_CallTool_ManageSystemDiagnostics`) ### `[MCP-ADMIN-ENDPOINT-HEAD-REQUEST]` Admin endpoint /admin handles HEAD request returning text/event-stream headers. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L215`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L215) (`AdminEndpoint_HeadRequest_ReturnsEventStreamHeaders`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L215`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L215) (`AdminEndpoint_HeadRequest_ReturnsEventStreamHeaders`) ### `[MCP-ADMIN-ENDPOINT-LIST-TOOLS]` Admin endpoint /admin/message executes tools/list over active SSE session and returns 10 admin tools. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L227`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L227) (`AdminEndpoint_SseSession_ListTools`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L227`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L227) (`AdminEndpoint_SseSession_ListTools`) ### `[MCP-ADMIN-ENDPOINT-ROUTER-ADMIN-TARGET]` Target proxy endpoint /router-admin routes directly to the Admin MCP server. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L152`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L152) (`TargetProxy_RouterAdmin_RoutesToAdminServer`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L152`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L152) (`TargetProxy_RouterAdmin_RoutesToAdminServer`) ### `[MCP-ADMIN-ENDPOINT-SSE-HANDSHAKE]` Admin endpoint /admin/sse performs initialize handshake with 2026-07-28 protocol version. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L73`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L73) (`AdminEndpoint_SseHandshake_NegotiatesProtocol`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L73`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L73) (`AdminEndpoint_SseHandshake_NegotiatesProtocol`) ### `[MCP-ADMIN-INITIALIZE-HANDSHAKE]` AdminMcpServer initialize handles protocol negotiation for 2026-07-28 and 2024-11-05. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L205`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L205) (`HandleInitializeAsync_NegotiatesProtocolVersion`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L205`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L205) (`HandleInitializeAsync_NegotiatesProtocolVersion`) ### `[MCP-ADMIN-PARITY-APPKEYS]` manage_appkeys supports full parity for list, get_limits, create, and revoke actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L381`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L381) (`ManageAppKeys_Parity_AllActions`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L381`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L381) (`ManageAppKeys_Parity_AllActions`) ### `[MCP-ADMIN-PARITY-CLIENTS]` manage_clients supports full parity for register, list, and delete actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L434`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L434) (`ManageClients_Parity_AllActions`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L434`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L434) (`ManageClients_Parity_AllActions`) ### `[MCP-ADMIN-PARITY-CUSTOM-FILES]` manage_custom_files supports full parity for list, get, save, and delete prompt and resource files. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L727`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L727) (`ManageCustomFiles_Parity_AllActions`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L727`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L727) (`ManageCustomFiles_Parity_AllActions`) ### `[MCP-ADMIN-PARITY-GROUP-MAPPINGS]` manage_group_mappings supports full parity for list, save, and delete external-to-internal group mappings. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L541`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L541) (`ManageGroupMappings_Parity_AllActions`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L541`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L541) (`ManageGroupMappings_Parity_AllActions`) ### `[MCP-ADMIN-PARITY-JSONRPC-DISPATCH]` AdminMcpServer processes standard JSON-RPC 2.0 requests (tools/list, tools/call, ping). * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L884`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L884) (`AdminTools_ProcessRequest_JsonRpcProtocol`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L884`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L884) (`AdminTools_ProcessRequest_JsonRpcProtocol`) ### `[MCP-ADMIN-PARITY-POLICIES]` manage_policies supports full parity for list, save, and delete access control policies. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L475`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L475) (`ManagePolicies_Parity_AllActions`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L475`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L475) (`ManagePolicies_Parity_AllActions`) ### `[MCP-ADMIN-PARITY-PROVIDERS]` manage_providers supports full parity for list, save_secret, test_vault, save_auth, and test_ldap actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L588`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L588) (`ManageProviders_Parity_AllActions`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L588`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L588) (`ManageProviders_Parity_AllActions`) -### `[MCP-ADMIN-PARITY-SERVERS]` manage_servers supports full parity for list, get, create, update, toggle, delete, reconnect, and reconnect_all actions. +### `[MCP-ADMIN-PARITY-SERVERS]` Validates that the manage_servers tool provides comprehensive administrative capabilities including listing, retrieving, creating, updating, toggling, deleting, and reconnecting servers. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L245`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L245) (`ManageServers_Parity_AllActions`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L245`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L245) (`ManageServers_Parity_AllActions`) ### `[MCP-ADMIN-PARITY-SETTINGS]` manage_settings supports full parity for get and update global router configurations. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L678`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L678) (`ManageSettings_Parity_AllActions`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L678`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L678) (`ManageSettings_Parity_AllActions`) ### `[MCP-ADMIN-PARITY-SYSTEM]` manage_system supports full parity for diagnostics, get_logs, clear_logs, and query_audit actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L827`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L827) (`ManageSystem_Parity_AllActions`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L827`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L827) (`ManageSystem_Parity_AllActions`) ### `[MCP-ADMIN-PARITY-TEST-TOOL-CALL]` test_tool_call executes test bench backend tool calls and formats responses. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L857`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L857) (`TestToolCall_Execution_Parity`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L857`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L857) (`TestToolCall_Execution_Parity`) -### `[MCP-ADMIN-PARITY-TOOLS-COVERAGE]` Every UI management flow has a corresponding verified action in the consolidated Admin MCP tools. +### `[MCP-ADMIN-PARITY-TOOLS-COVERAGE]` Ensures every UI management workflow is backed by a verified, equivalent action within the consolidated Admin MCP tools. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L202`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L202) (`AdminTools_ExecuteSuccessfully`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L202`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L202) (`AdminTools_ExecuteSuccessfully`) ### `[MCP-ADMIN-TOOL-AUDIT-LOG]` AdminMcpServer tool calls record audit log entries with caller and tool name. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L276`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L276) (`CallToolAsync_RecordsAuditLog`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L276`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L276) (`CallToolAsync_RecordsAuditLog`) ### `[MCP-ADMIN-TOOL-MANAGE-APPKEYS]` AdminMcpServer executes manage_appkeys create, list, limits, and revoke actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L293`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L293) (`CallToolAsync_ManageAppKeys_Lifecycle`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L293`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L293) (`CallToolAsync_ManageAppKeys_Lifecycle`) ### `[MCP-ADMIN-TOOL-MANAGE-CLIENTS]` AdminMcpServer executes manage_clients register, list, and delete actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L340`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L340) (`CallToolAsync_ManageClients_Lifecycle`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L340`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L340) (`CallToolAsync_ManageClients_Lifecycle`) ### `[MCP-ADMIN-TOOL-MANAGE-CUSTOM-FILES]` AdminMcpServer executes manage_custom_files save, get, list, and delete actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L514`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L514) (`CallToolAsync_ManageCustomFiles_Lifecycle`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L514`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L514) (`CallToolAsync_ManageCustomFiles_Lifecycle`) ### `[MCP-ADMIN-TOOL-MANAGE-GROUP-MAPPINGS]` AdminMcpServer executes manage_group_mappings save, list, and delete actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L412`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L412) (`CallToolAsync_ManageGroupMappings_Lifecycle`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L412`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L412) (`CallToolAsync_ManageGroupMappings_Lifecycle`) ### `[MCP-ADMIN-TOOL-MANAGE-POLICIES]` AdminMcpServer executes manage_policies save, list, and delete actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L378`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L378) (`CallToolAsync_ManagePolicies_Lifecycle`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L378`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L378) (`CallToolAsync_ManagePolicies_Lifecycle`) ### `[MCP-ADMIN-TOOL-MANAGE-PROVIDERS]` AdminMcpServer executes manage_providers list, save_secret, and save_auth actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L445`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L445) (`CallToolAsync_ManageProviders_Lifecycle`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L445`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L445) (`CallToolAsync_ManageProviders_Lifecycle`) ### `[MCP-ADMIN-TOOL-MANAGE-SERVERS]` AdminMcpServer executes manage_servers list, get, create, update, toggle, and delete actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L224`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L224) (`CallToolAsync_ManageServers_ListAndCreate`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L224`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L224) (`CallToolAsync_ManageServers_ListAndCreate`) ### `[MCP-ADMIN-TOOL-MANAGE-SETTINGS]` AdminMcpServer executes manage_settings get and update actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L488`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L488) (`CallToolAsync_ManageSettings_Lifecycle`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L488`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L488) (`CallToolAsync_ManageSettings_Lifecycle`) ### `[MCP-ADMIN-TOOL-MANAGE-SYSTEM]` AdminMcpServer executes manage_system diagnostics, get_logs, clear_logs, and query_audit actions. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L568`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L568) (`CallToolAsync_ManageSystem_Lifecycle`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L568`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L568) (`CallToolAsync_ManageSystem_Lifecycle`) ### `[MCP-ADMIN-TOOLS-LIST-COUNT]` AdminMcpServer tools/list returns all 10 consolidated tools with complete JSON schemas. * **Category:** `MCP` (Model Context Protocol Engine & Tool Routing) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L157`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L157) (`ListToolsAsync_ReturnsTenConsolidatedTools`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L157`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L157) (`ListToolsAsync_ReturnsTenConsolidatedTools`) ### `[AUTH-107]` RegisterClient successfully handles DCR requests when open DCR is enabled. * **Category:** `SEC` (Secrets Providers & Encryption) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AuthorizationControllerTests.cs#L41`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AuthorizationControllerTests.cs#L41) (`RegisterClient_CreatesApplicationAndReturnsOk`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AuthorizationControllerTests.cs#L41`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AuthorizationControllerTests.cs#L41) (`RegisterClient_CreatesApplicationAndReturnsOk`) ### `[SEC-01]` VaultSecretRetriever authenticates with HashiCorp Vault using AppRole RoleID and SecretID credentials * **Category:** `SEC` (Secrets Providers & Encryption) * **Type:** Positive Feature Capability * **Verification Proofs (6):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L23`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L23) (`EnsureVaultClientAsync_CreatesClient_WithAppRoleCredentials`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L44`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L44) (`EnsureVaultClientAsync_LoadsFromSecretRepo_WhenConfigJsonHasAppRole`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L96`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L96) (`ReloadConfigAsync_ClearsClient_ForcesRecreation`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/SecretProvidersTab.test.tsx#L19`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/SecretProvidersTab.test.tsx#L19) (`renders provider inputs and submits updated configuration`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/SecretProvidersTab.test.tsx#L90`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/SecretProvidersTab.test.tsx#L90) (`handles Test Vault connection button with success and failure responses`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/full-ui-flow-sse-vault.spec.ts#L8`](file:////containers/dev/csharp-mcp-router/frontend/e2e/full-ui-flow-sse-vault.spec.ts#L8) (`should register SSE server with Vault provider (Mount/Path/Field), verify badge, and run semantic search`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L23`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L23) (`EnsureVaultClientAsync_CreatesClient_WithAppRoleCredentials`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L44`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L44) (`EnsureVaultClientAsync_LoadsFromSecretRepo_WhenConfigJsonHasAppRole`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L96`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L96) (`ReloadConfigAsync_ClearsClient_ForcesRecreation`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/SecretProvidersTab.test.tsx#L19`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/SecretProvidersTab.test.tsx#L19) (`renders provider inputs and submits updated configuration`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/SecretProvidersTab.test.tsx#L90`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/SecretProvidersTab.test.tsx#L90) (`handles Test Vault connection button with success and failure responses`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/full-ui-flow-sse-vault.spec.ts#L8`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/full-ui-flow-sse-vault.spec.ts#L8) (`should register SSE server with Vault provider (Mount/Path/Field), verify badge, and run semantic search`) ### `[SEC-02]` STDIO transport securely injects secret credentials via environment variables rather than command-line arguments * **Category:** `SEC` (Secrets Providers & Encryption) * **Type:** Positive Feature Capability * **Verification Proofs (3):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L354`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L354) (`StdioTransport_ShouldPassSecretViaEnvironmentVariables_AndNotCommandLine`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L439`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L439) (`StdioTransport_ShouldSanitizeAndMaskSecretsInLogs`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/vault-approle-config-flow.spec.ts#L5`](file:////containers/dev/csharp-mcp-router/frontend/e2e/vault-approle-config-flow.spec.ts#L5) (`should configure Vault AppRole credentials and test connection in settings`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L354`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L354) (`StdioTransport_ShouldPassSecretViaEnvironmentVariables_AndNotCommandLine`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L439`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L439) (`StdioTransport_ShouldSanitizeAndMaskSecretsInLogs`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/vault-approle-config-flow.spec.ts#L5`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/vault-approle-config-flow.spec.ts#L5) (`should configure Vault AppRole credentials and test connection in settings`) ### `[SEC-03]` Ensure TrustedProxyHelper supports CIDR ranges in XFF validation * **Category:** `SEC` (Secrets Providers & Encryption) * **Type:** Positive Feature Capability * **Verification Proofs (2):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L366`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L366) (`TrustedProxyHelper_AllowsXForwardedFor_WhenChainIsFullyTrusted`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L425`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L425) (`TrustedProxyHelper_ConfiguredProxyTrusted_CIDR`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L366`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L366) (`TrustedProxyHelper_AllowsXForwardedFor_WhenChainIsFullyTrusted`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L425`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L425) (`TrustedProxyHelper_ConfiguredProxyTrusted_CIDR`) ### `[SEC-04]` WindowsRegistrySecretRetriever securely decrypts DPAPI LocalMachine machine-level encrypted binary values and retrieves plaintext registry strings * **Category:** `SEC` (Secrets Providers & Encryption) * **Type:** Positive Feature Capability * **Verification Proofs (2):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs#L16`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs#L16) (`GetSecretAsync_ReturnsPlainString_WhenRegistryValueIsString`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs#L31`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs#L31) (`GetSecretAsync_DecryptsDpapiBytes_WhenRegistryValueIsByteArray`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs#L16`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs#L16) (`GetSecretAsync_ReturnsPlainString_WhenRegistryValueIsString`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs#L31`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs#L31) (`GetSecretAsync_DecryptsDpapiBytes_WhenRegistryValueIsByteArray`) ### `[SEC-ADMIN-AUDIT-REDACTION]` AdminMcpServer redacts sensitive secrets from argument payloads before recording audit logs. * **Category:** `SEC` (Secrets Providers & Encryption) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L619`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L619) (`CallToolAsync_AuditLog_RedactsSensitivePayloadData`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L619`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L619) (`CallToolAsync_AuditLog_RedactsSensitivePayloadData`) ### `[TRANS-01]` SSE transport resolves static plaintext API keys when provider is None * **Category:** `TRANS` (Transports (SSE, HTTP, STDIO, Proxy)) * **Type:** Positive Feature Capability * **Verification Proofs (2):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/SseTransportTests.cs#L18`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SseTransportTests.cs#L18) (`ResolveTokenAsync_ReturnsApiKey_WhenProviderNone`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/full-ui-flow-http-direct.spec.ts#L8`](file:////containers/dev/csharp-mcp-router/frontend/e2e/full-ui-flow-http-direct.spec.ts#L8) (`should register HTTP server with Direct Key, verify status badge, and execute tool in Test Bench`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SseTransportTests.cs#L18`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SseTransportTests.cs#L18) (`ResolveTokenAsync_ReturnsApiKey_WhenProviderNone`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/full-ui-flow-http-direct.spec.ts#L8`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/full-ui-flow-http-direct.spec.ts#L8) (`should register HTTP server with Direct Key, verify status badge, and execute tool in Test Bench`) ### `[TRANS-02]` HTTP stateless transport resolves static API keys when secret provider is None * **Category:** `TRANS` (Transports (SSE, HTTP, STDIO, Proxy)) * **Type:** Positive Feature Capability * **Verification Proofs (2):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/HttpTransportTests.cs#L19`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/HttpTransportTests.cs#L19) (`ResolveTokenAsync_ReturnsApiKey_WhenProviderNone`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/full-ui-flow-stdio-env.spec.ts#L8`](file:////containers/dev/csharp-mcp-router/frontend/e2e/full-ui-flow-stdio-env.spec.ts#L8) (`should register STDIO server, verify card, and execute echo tool via Test Bench`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/HttpTransportTests.cs#L19`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/HttpTransportTests.cs#L19) (`ResolveTokenAsync_ReturnsApiKey_WhenProviderNone`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/full-ui-flow-stdio-env.spec.ts#L8`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/full-ui-flow-stdio-env.spec.ts#L8) (`should register STDIO server, verify card, and execute echo tool via Test Bench`) ### `[TRANS-03]` STDIO transport spawns subprocess, handles JSON-RPC initialization and executes tool calls * **Category:** `TRANS` (Transports (SSE, HTTP, STDIO, Proxy)) * **Type:** Positive Feature Capability * **Verification Proofs (5):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L59`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L59) (`StdioTransport_ShouldInitializeAndCallToolSuccessfully`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L180`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L180) (`StdioTransport_ShouldRouteStderrToLogs`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L252`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L252) (`StdioTransport_ShouldSupportCancellationAndProcessTreeTermination`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L337`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L337) (`StdioTransport_ParseCommandLine_Handles_Quotes_And_Spaces`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L482`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L482) (`StdioTransport_ShouldDrainReaderStreamsToEOF_WhenProcessExitsImmediately`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L59`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L59) (`StdioTransport_ShouldInitializeAndCallToolSuccessfully`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L180`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L180) (`StdioTransport_ShouldRouteStderrToLogs`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L252`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L252) (`StdioTransport_ShouldSupportCancellationAndProcessTreeTermination`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L337`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L337) (`StdioTransport_ParseCommandLine_Handles_Quotes_And_Spaces`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L482`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L482) (`StdioTransport_ShouldDrainReaderStreamsToEOF_WhenProcessExitsImmediately`) -### `[UI-01]` Dashboard renders stats card, connected server list, and setup instructions +### `[UI-01]` Dashboard shows empty filter state when no servers match search term * **Category:** `UI` (Dashboard, Test Bench & Settings UI) * **Type:** Positive Feature Capability -* **Verification Proofs (6):** - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx#L38`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx#L38) (`renders stats card, server list, and client setup guide`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx#L113`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx#L113) (`renders empty state when no servers match search`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/prompts-resources-customfiles.spec.ts#L42`](file:////containers/dev/csharp-mcp-router/frontend/e2e/prompts-resources-customfiles.spec.ts#L42) (`should navigate to Custom Files and Prompts in Settings view`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/dashboard.spec.ts#L5`](file:////containers/dev/csharp-mcp-router/frontend/e2e/dashboard.spec.ts#L5) (`should render the dashboard layout and header components`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/dashboard.spec.ts#L21`](file:////containers/dev/csharp-mcp-router/frontend/e2e/dashboard.spec.ts#L21) (`should display aggregate statistics cards`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/dashboard.spec.ts#L35`](file:////containers/dev/csharp-mcp-router/frontend/e2e/dashboard.spec.ts#L35) (`should filter servers using search input`) - -### `[UI-02]` Modal remains hidden when isInspectOpen is false +* **Verification Proofs (4):** + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx#L115`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx#L115) (`renders empty state when no servers match search`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/prompts-resources-customfiles.spec.ts#L42`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/prompts-resources-customfiles.spec.ts#L42) (`should navigate to Custom Files and Prompts in Settings view`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/dashboard.spec.ts#L23`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/dashboard.spec.ts#L23) (`should display aggregate statistics cards`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/dashboard.spec.ts#L37`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/dashboard.spec.ts#L37) (`should filter servers using search input`) + +### `[UI-02]` Inspect modal displays spinner loading state while querying server capabilities * **Category:** `UI` (Dashboard, Test Bench & Settings UI) * **Type:** Positive Feature Capability -* **Verification Proofs (7):** - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L47`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L47) (`renders nothing when isInspectOpen is false`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L59`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L59) (`renders loading state when inspectLoading is true`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L77`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L77) (`renders tools tab with schema and handles tab switching`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L114`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L114) (`renders resources tab items and handles search filtering`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L142`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L142) (`renders prompts tab with arguments and empty state when filtered out`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L164`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L164) (`renders empty states for tabs when data is empty`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L192`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L192) (`closes modal when close button is clicked`) +* **Verification Proofs (6):** + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L61`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L61) (`renders loading state when inspectLoading is true`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L79`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L79) (`renders tools tab with schema and handles tab switching`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L116`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L116) (`renders resources tab items and handles search filtering`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L144`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L144) (`renders prompts tab with arguments and empty state when filtered out`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L166`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L166) (`renders empty states for tabs when data is empty`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L194`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L194) (`closes modal when close button is clicked`) ### `[UI-03]` Grouped server view renders category sections and supports collapsible groups * **Category:** `UI` (Dashboard, Test Bench & Settings UI) * **Type:** Positive Feature Capability * **Verification Proofs (2):** - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx#L61`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx#L61) (`renders grouped server view by category and allows collapsing`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx#L88`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx#L88) (`renders grouped server view by status and type`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx#L63`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx#L63) (`renders grouped server view by category and allows collapsing`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx#L90`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx#L90) (`renders grouped server view by status and type`) -### `[UI-04]` Interactive tool tester renders server and tool selection dropdowns +### `[UI-04]` Tool selector filters available tools by selected backend server * **Category:** `UI` (Dashboard, Test Bench & Settings UI) * **Type:** Positive Feature Capability -* **Verification Proofs (8):** - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L41`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L41) (`renders initial server and tool selection options`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L75`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L75) (`filters tools by selected server and handles tool change`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L104`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L104) (`filters custom tools with no namespace prefix when selectedServer is custom`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L129`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L129) (`renders dynamic fields for boolean, number, string, array, and object types`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L176`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L176) (`renders empty state when selected tool takes no arguments`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L201`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L201) (`switches to raw JSON tab and handles raw JSON editing`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L240`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L240) (`handles form submission`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/prompts-resources-customfiles.spec.ts#L5`](file:////containers/dev/csharp-mcp-router/frontend/e2e/prompts-resources-customfiles.spec.ts#L5) (`should interact with Prompt Tester and Resource Tester cards in Test Bench`) +* **Verification Proofs (7):** + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L77`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L77) (`filters tools by selected server and handles tool change`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L106`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L106) (`filters custom tools with no namespace prefix when selectedServer is custom`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L131`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L131) (`renders dynamic fields for boolean, number, string, array, and object types`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L178`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L178) (`renders empty state when selected tool takes no arguments`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L203`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L203) (`switches to raw JSON tab and handles raw JSON editing`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L242`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L242) (`handles form submission`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/prompts-resources-customfiles.spec.ts#L5`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/prompts-resources-customfiles.spec.ts#L5) (`should interact with Prompt Tester and Resource Tester cards in Test Bench`) ### `[UI-05]` Router allows customized branding parameters (DashboardTitle, DashboardIcon) to be saved and retrieved via the API. * **Category:** `UI` (Dashboard, Test Bench & Settings UI) * **Type:** Positive Feature Capability * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/PipelineIntegrationTests.cs#L242`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PipelineIntegrationTests.cs#L242) (`Pipeline_Settings_Branding_ReadWrite`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PipelineIntegrationTests.cs#L242`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PipelineIntegrationTests.cs#L242) (`Pipeline_Settings_Branding_ReadWrite`) + +### `[UI-102]` Dashboard renders stats card, connected server list, and setup instructions +* **Category:** `UI` (Dashboard, Test Bench & Settings UI) +* **Type:** Positive Feature Capability +* **Verification Proofs (1):** + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx#L1`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx#L1) (`renders stats card, server list, and client setup guide`) + +### `[UI-103]` Interactive tool tester renders server and tool selection dropdowns +* **Category:** `UI` (Dashboard, Test Bench & Settings UI) +* **Type:** Positive Feature Capability +* **Verification Proofs (1):** + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L1`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L1) (`renders initial server and tool selection options`) + +### `[UI-116]` Modal remains hidden when isInspectOpen is false +* **Category:** `UI` (Dashboard, Test Bench & Settings UI) +* **Type:** Positive Feature Capability +* **Verification Proofs (1):** + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L1`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L1) (`renders nothing when isInspectOpen is false`) + +### `[UI-124]` Renders main dashboard navigation tabs and layout headers +* **Category:** `UI` (Dashboard, Test Bench & Settings UI) +* **Type:** Positive Feature Capability +* **Verification Proofs (1):** + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/dashboard.spec.ts#L1`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/dashboard.spec.ts#L1) (`should render the dashboard layout and header components`) --- @@ -537,201 +583,200 @@ ### `[AUTH-01]` AdminPolicy allows principal with configured Admin Group Name (e.g., full_admin) * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Negative / Safety Guardrail (Fail-Closed) -* **Verification Proofs (16):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L18`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L18) (`AdminPolicy_Allows_Principal_With_AdminGroupName`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L52`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L52) (`AdminPolicy_Allows_Principal_With_AdminSid`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L86`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L86) (`AdminPolicy_Allows_Principal_With_ConfiguredAdminGroups`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L121`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L121) (`AdminPolicy_Denies_StandardRole_WithoutAdminSidOrGroup`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicySidOnlyTests.cs#L21`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicySidOnlyTests.cs#L21) (`AdminPolicy_Denies_StandardRole_Without_AdminSid`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicySidOnlyTests.cs#L63`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicySidOnlyTests.cs#L63) (`AdminPolicy_Allows_Principal_With_AdminSid`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L274`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L274) (`AppKeysController_CreateAppKey_UnknownCategory_Admin_Succeeds`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L226`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L226) (`SecurityValidationHelper_IsAdmin_RequiresAdminGroupSid`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L244`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L244) (`SecurityValidationHelper_IsAdmin_AllowsAdminGroupName`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L258`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L258) (`SecurityValidationHelper_IsAdmin_RejectsNonAdminGroups`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L275`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L275) (`SecurityValidationHelper_IsAdmin_AllowsCustomAdminGroupsArray`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L290`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L290) (`SecurityValidationHelper_IsAdmin_AllowsMappedGroups`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L305`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L305) (`OidcIdentityProvider_DoesNotGrantAdminSid_FromGroupOrUserNames`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/IdentityAuthTab.test.tsx#L12`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/IdentityAuthTab.test.tsx#L12) (`renders Active Directory disabled initially, toggles on and exposes fields`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/IdentityAuthTab.test.tsx#L46`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/IdentityAuthTab.test.tsx#L46) (`fills LDAP parameters and executes test connection`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/multi-user-matrix.spec.ts#L5`](file:////containers/dev/csharp-mcp-router/frontend/e2e/multi-user-matrix.spec.ts#L5) (`Admin Context: renders full administrator view and privileged controls`) +* **Verification Proofs (15):** + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L18`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L18) (`AdminPolicy_Allows_Principal_With_AdminGroupName`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L52`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L52) (`AdminPolicy_Allows_Principal_With_AdminSid`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L86`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L86) (`AdminPolicy_Allows_Principal_With_ConfiguredAdminGroups`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L121`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L121) (`AdminPolicy_Denies_StandardRole_WithoutAdminSidOrGroup`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicySidOnlyTests.cs#L21`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicySidOnlyTests.cs#L21) (`AdminPolicy_Denies_StandardRole_Without_AdminSid`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicySidOnlyTests.cs#L63`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicySidOnlyTests.cs#L63) (`AdminPolicy_Allows_Principal_With_AdminSid`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L274`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L274) (`AppKeysController_CreateAppKey_UnknownCategory_Admin_Succeeds`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L226`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L226) (`SecurityValidationHelper_IsAdmin_RequiresAdminGroupSid`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L244`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L244) (`SecurityValidationHelper_IsAdmin_AllowsAdminGroupName`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L258`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L258) (`SecurityValidationHelper_IsAdmin_RejectsNonAdminGroups`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L275`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L275) (`SecurityValidationHelper_IsAdmin_AllowsCustomAdminGroupsArray`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L290`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L290) (`SecurityValidationHelper_IsAdmin_AllowsMappedGroups`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L305`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L305) (`OidcIdentityProvider_DoesNotGrantAdminSid_FromGroupOrUserNames`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/IdentityAuthTab.test.tsx#L12`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/IdentityAuthTab.test.tsx#L12) (`renders Active Directory disabled initially, toggles on and exposes fields`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/IdentityAuthTab.test.tsx#L46`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/IdentityAuthTab.test.tsx#L46) (`fills LDAP parameters and executes test connection`) ### `[AUTH-PERSONAL-APPKEY-CREATE]` Non-admin users can create personal App Keys up to quota * **Category:** `AUTH` (Authentication, RBAC & Identity) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (9):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L199`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L199) (`CreateAppKey_NonAdmin_CreatesPersonalKey_UpToDefaultQuota`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L290`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L290) (`creates category-scoped key, captures one-time plaintext key, and refreshes`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L19`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L19) (`renders nothing when isCreateModalOpen is false`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L61`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L61) (`locks key type to personal key for non-admin and shows quota feedback`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L115`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L115) (`handles scope serialization for server scope and target username for admin`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L154`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L154) (`handles scope serialization for category scope and expiration days`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L192`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L192) (`disables submit button when quota limit is reached`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L217`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/AppKeyModal.test.tsx#L217) (`displays one-time secret result and copies plaintext key to clipboard`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L33`](file:////containers/dev/csharp-mcp-router/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L33) (`Non-Admin Context: mints personal key, views snippet, and revokes key`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L199`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L199) (`CreateAppKey_NonAdmin_CreatesPersonalKey_UpToDefaultQuota`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L290`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L290) (`creates category-scoped key, captures one-time plaintext key, and refreshes`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L19`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L19) (`renders nothing when isCreateModalOpen is false`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L61`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L61) (`locks key type to personal key for non-admin and shows quota feedback`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L115`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L115) (`handles scope serialization for server scope and target username for admin`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L154`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L154) (`handles scope serialization for category scope and expiration days`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L192`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L192) (`disables submit button when quota limit is reached`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L217`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/AppKeyModal.test.tsx#L217) (`displays one-time secret result and copies plaintext key to clipboard`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L33`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/personal-appkeys-and-quotas.spec.ts#L33) (`Non-Admin Context: mints personal key, views snippet, and revokes key`) ### `[AUTH-EXTERNAL-IDP-DENIES-ANONYMOUS-LOOPBACK]` When an external IDP is configured, anonymous loopback requests do not bypass authentication. * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L232`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L232) (`AdminPolicy_ExternalIdpConfigured_LoopbackIp_RequiresCredentials`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L232`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L232) (`AdminPolicy_ExternalIdpConfigured_LoopbackIp_RequiresCredentials`) ### `[AUTH-STANDALONE-ADMINPOLICY-EXTERNAL-DENY]` AdminPolicy rejects unauthenticated requests from non-whitelisted external IPs in standalone mode. * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L208`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L208) (`AdminPolicy_StandaloneMode_ExternalUntrustedIp_FailsAdminPolicy`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L208`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L208) (`AdminPolicy_StandaloneMode_ExternalUntrustedIp_FailsAdminPolicy`) ### `[AUTH-STANDALONE-EXTERNAL-DENY]` Standalone mode denies admin access to non-whitelisted external IPs without an Admin AppKey. * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L65`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L65) (`IsAdmin_StandaloneMode_UntrustedIp_ReturnsFalse`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L65`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L65) (`IsAdmin_StandaloneMode_UntrustedIp_ReturnsFalse`) ### `[GUARD-01]` Null or empty capability targets must immediately fail closed and return unauthorized * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (5):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L481`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L481) (`Pairwise_NullOrEmptyTarget_FailsClosed_ReturnsFalse`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L503`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L503) (`Pairwise_CorruptedAppKeyScopesJson_FailsClosed_ReturnsFalse`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L236`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L236) (`AppKeysController_CreateAppKey_UnknownCategory_NonAdmin_FailsWithBadRequest`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L255`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L255) (`AppKeysController_CreateAppKey_EmptyCategory_FailsWithBadRequest`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L320`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L320) (`ClientsController_CreateClient_EmptyCategory_ReturnsBadRequest`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L481`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L481) (`Pairwise_NullOrEmptyTarget_FailsClosed_ReturnsFalse`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L503`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L503) (`Pairwise_CorruptedAppKeyScopesJson_FailsClosed_ReturnsFalse`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L236`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L236) (`AppKeysController_CreateAppKey_UnknownCategory_NonAdmin_FailsWithBadRequest`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L255`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L255) (`AppKeysController_CreateAppKey_EmptyCategory_FailsWithBadRequest`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L320`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/CategoryScopedAppKeysTests.cs#L320) (`ClientsController_CreateClient_EmptyCategory_ReturnsBadRequest`) ### `[GUARD-02]` SSE transport fails closed with SecurityException when secret provider resolution fails * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (7):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/SseTransportTests.cs#L39`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SseTransportTests.cs#L39) (`ResolveTokenAsync_ThrowsSecurityException_WhenSecretProviderFails`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/SseTransportTests.cs#L60`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SseTransportTests.cs#L60) (`ResolveTokenAsync_ThrowsInvalidOperationException_WhenNoRetrieverRegistered`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L404`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L404) (`StdioTransport_ShouldFailClosed_WhenSecretResolutionFails`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/HttpTransportTests.cs#L39`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/HttpTransportTests.cs#L39) (`ResolveTokenAsync_ThrowsSecurityException_WhenSecretProviderFails`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/HttpTransportTests.cs#L62`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/HttpTransportTests.cs#L62) (`ResolveTokenAsync_ThrowsInvalidOperationException_WhenNoRetrieverRegistered`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L70`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L70) (`EnsureVaultClientAsync_ReturnsNull_WhenVaultProviderDisabledInRepo`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L122`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L122) (`GetSecretAsync_ThrowsSecurityException_OnVaultException`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SseTransportTests.cs#L39`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SseTransportTests.cs#L39) (`ResolveTokenAsync_ThrowsSecurityException_WhenSecretProviderFails`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SseTransportTests.cs#L60`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SseTransportTests.cs#L60) (`ResolveTokenAsync_ThrowsInvalidOperationException_WhenNoRetrieverRegistered`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L404`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L404) (`StdioTransport_ShouldFailClosed_WhenSecretResolutionFails`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/HttpTransportTests.cs#L39`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/HttpTransportTests.cs#L39) (`ResolveTokenAsync_ThrowsSecurityException_WhenSecretProviderFails`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/HttpTransportTests.cs#L62`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/HttpTransportTests.cs#L62) (`ResolveTokenAsync_ThrowsInvalidOperationException_WhenNoRetrieverRegistered`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L70`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L70) (`EnsureVaultClientAsync_ReturnsNull_WhenVaultProviderDisabledInRepo`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L122`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L122) (`GetSecretAsync_ThrowsSecurityException_OnVaultException`) ### `[GUARD-03]` STDIO transport rejects commands with shell metacharacters or dangerous commands * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (6):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L114`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L114) (`StdioTransport_ShouldThrowSecurityExceptionForUnsafeExecutable`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L136`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L136) (`StdioTransport_ShouldThrowSecurityExceptionForShellExecutable`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L158`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L158) (`StdioTransport_ShouldThrowOnInvalidExecutable`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L220`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L220) (`StdioTransport_ShouldTimeoutOnSlowRequests`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L299`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L299) (`StdioTransport_ShouldHandleUnexpectedExit`) - - [Playwright E2E] [`/containers/dev/csharp-mcp-router/frontend/e2e/multi-user-matrix.spec.ts#L53`](file:////containers/dev/csharp-mcp-router/frontend/e2e/multi-user-matrix.spec.ts#L53) (`Guest / Denied Context: restricted user session renders safely`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L114`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L114) (`StdioTransport_ShouldThrowSecurityExceptionForUnsafeExecutable`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L136`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L136) (`StdioTransport_ShouldThrowSecurityExceptionForShellExecutable`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L158`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L158) (`StdioTransport_ShouldThrowOnInvalidExecutable`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L220`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L220) (`StdioTransport_ShouldTimeoutOnSlowRequests`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L299`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L299) (`StdioTransport_ShouldHandleUnexpectedExit`) + - [Playwright E2E] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts#L55`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts#L55) (`Guest / Denied Context: restricted user session renders safely`) ### `[GUARD-04]` Malformed completion payloads or unmapped backends must fail closed safely * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (4):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L521`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L521) (`Pairwise_CompleteAsync_MalformedOrMissingBackends_ThrowsOrFailsClosed`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L544`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L544) (`Pairwise_DatabaseDisconnection_FailsClosedSafely`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L177`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L177) (`SchemaValidation_FailsClosed_WhenRequiredColumnOrTableMissing`) - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L203`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L203) (`SchemaValidation_FailsClosed_WhenUserQuotasOrKeyTypeMissing`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L521`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L521) (`Pairwise_CompleteAsync_MalformedOrMissingBackends_ThrowsOrFailsClosed`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L544`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L544) (`Pairwise_DatabaseDisconnection_FailsClosedSafely`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L177`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L177) (`SchemaValidation_FailsClosed_WhenRequiredColumnOrTableMissing`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L203`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L203) (`SchemaValidation_FailsClosed_WhenUserQuotasOrKeyTypeMissing`) ### `[GUARD-05]` Batch save of authentication providers must fail closed if all providers are disabled * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/ProvidersControllerTests.cs#L324`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/ProvidersControllerTests.cs#L324) (`SaveAuthProvidersBatch_ReturnsBadRequest_WhenAllProvidersDisabled`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ProvidersControllerTests.cs#L324`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ProvidersControllerTests.cs#L324) (`SaveAuthProvidersBatch_ReturnsBadRequest_WhenAllProvidersDisabled`) ### `[GUARD-06]` Global deny policies with TargetId '*' and IsAllowed false must fail closed * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/PermissionsControllerTests.cs#L236`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PermissionsControllerTests.cs#L236) (`SavePolicy_ReturnsBadRequest_WhenWildcardDenyPolicy`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PermissionsControllerTests.cs#L236`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PermissionsControllerTests.cs#L236) (`SavePolicy_ReturnsBadRequest_WhenWildcardDenyPolicy`) ### `[GUARD-ADMIN-CUSTOM-FILES-VALIDATION]` manage_custom_files rejects invalid prompt JSON syntax and unsupported file categories. * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L790`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L790) (`ManageCustomFiles_ValidationGuardrails`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L790`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L790) (`ManageCustomFiles_ValidationGuardrails`) ### `[GUARD-ADMIN-ENDPOINT-UNAUTHORIZED]` Unauthenticated / non-admin client request to /admin receives 403 Forbidden. * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L196`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L196) (`AdminEndpoint_UnauthorizedCaller_Returns403`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L196`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L196) (`AdminEndpoint_UnauthorizedCaller_Returns403`) ### `[GUARD-ADMIN-POLICIES-WILDCARD-DENY]` manage_policies rejects wildcard deny policies to prevent global lockout. * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L524`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L524) (`ManagePolicies_WildcardDenyGuardrail`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L524`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L524) (`ManagePolicies_WildcardDenyGuardrail`) ### `[GUARD-ADMIN-PROVIDERS-LDAP-PLAINTEXT]` manage_providers rejects unencrypted LDAP connections on port 389. * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L661`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L661) (`ManageProviders_LdapPlaintextGuardrail`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L661`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L661) (`ManageProviders_LdapPlaintextGuardrail`) -### `[GUARD-ADMIN-SERVERS-VALIDATION]` manage_servers rejects invalid transport types, non-existent servers, and missing required parameters. +### `[GUARD-ADMIN-SERVERS-VALIDATION]` Verifies that the manage_servers tool accurately enforces validation by rejecting malformed transport types, missing required parameters, and requests for non-existent servers. * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L335`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L335) (`ManageServers_ValidationGuardrails`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L335`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L335) (`ManageServers_ValidationGuardrails`) ### `[GUARD-ADMIN-UNKNOWN-TOOL]` AdminMcpServer returns an error response for unknown tool or action invocations. * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L602`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L602) (`CallToolAsync_UnknownToolOrAction_ReturnsErrorResponse`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L602`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L602) (`CallToolAsync_UnknownToolOrAction_ReturnsErrorResponse`) ### `[MCP-ADMIN-TOOL-TEST-CALL-ERROR]` AdminMcpServer test_tool_call propagates downstream backend errors with visibility. * **Category:** `GUARD` (Universal Safety & Fail-Closed Guardrails) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L643`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L643) (`CallToolAsync_TestToolCall_MissingServer_ReturnsError`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L643`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L643) (`CallToolAsync_TestToolCall_MissingServer_ReturnsError`) ### `[AUTH-106]` Exchange throws InvalidOperationException when request is null. * **Category:** `SEC` (Secrets Providers & Encryption) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AuthorizationControllerTests.cs#L23`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AuthorizationControllerTests.cs#L23) (`Exchange_ThrowsInvalidOperationException_WhenRequestNull`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AuthorizationControllerTests.cs#L23`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AuthorizationControllerTests.cs#L23) (`Exchange_ThrowsInvalidOperationException_WhenRequestNull`) ### `[AUTH-108]` Authorize throws InvalidOperationException when OIDC request is null. * **Category:** `SEC` (Secrets Providers & Encryption) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/AuthorizationControllerTests.cs#L79`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AuthorizationControllerTests.cs#L79) (`Authorize_ThrowsInvalidOperationException_WhenRequestNull`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AuthorizationControllerTests.cs#L79`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AuthorizationControllerTests.cs#L79) (`Authorize_ThrowsInvalidOperationException_WhenRequestNull`) ### `[SEC-05]` Router must not overwrite corrupt encrypted database fields if an update occurs without user reset. * **Category:** `SEC` (Secrets Providers & Encryption) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (1):** - - [Backend xUnit] [`/containers/dev/csharp-mcp-router/McpRouter.Tests/ProviderSettingsEncryptionTests.cs#L395`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/ProviderSettingsEncryptionTests.cs#L395) (`SaveSecretProvider_WhenDecryptionFailed_DoesNotOverwriteCorruptPayload`) + - [Backend xUnit] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ProviderSettingsEncryptionTests.cs#L395`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ProviderSettingsEncryptionTests.cs#L395) (`SaveSecretProvider_WhenDecryptionFailed_DoesNotOverwriteCorruptPayload`) ### `[UI-CONFIRM-MODAL]` Deletes an access policy when confirmed via confirm modal. * **Category:** `UI` (Dashboard, Test Bench & Settings UI) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (14):** - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L76`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L76) (`deletes a policy when confirmed`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L105`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L105) (`does not delete policy when confirm is cancelled`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L166`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L166) (`deletes a group mapping when confirmed`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L195`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L195) (`does not delete group mapping when confirm is cancelled`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L222`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L222) (`deletes a custom file when confirmed`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L251`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L251) (`does not delete custom file when confirm is cancelled`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L88`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L88) (`prompts confirmation and deletes client when confirmed`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L117`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L117) (`cancels deletion when user denies confirmation`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L350`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L350) (`confirms and revokes AppKey and refreshes list`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L379`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L379) (`cancels revocation when confirm is rejected`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L471`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L471) (`prompts confirmation modal and resets user quota when confirmed`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L501`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useClientStore.test.ts#L501) (`cancels quota reset when user denies confirmation`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useServerStore.test.ts#L203`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useServerStore.test.ts#L203) (`prompts window.confirm and deletes server when confirmed`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/stores/useServerStore.test.ts#L232`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/useServerStore.test.ts#L232) (`does not send delete request when confirm is cancelled`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L76`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L76) (`deletes a policy when confirmed`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L105`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L105) (`does not delete policy when confirm is cancelled`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L166`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L166) (`deletes a group mapping when confirmed`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L195`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L195) (`does not delete group mapping when confirm is cancelled`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L222`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L222) (`deletes a custom file when confirmed`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L251`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L251) (`does not delete custom file when confirm is cancelled`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L88`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L88) (`prompts confirmation and deletes client when confirmed`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L117`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L117) (`cancels deletion when user denies confirmation`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L350`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L350) (`confirms and revokes AppKey and refreshes list`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L379`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L379) (`cancels revocation when confirm is rejected`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L471`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L471) (`prompts confirmation modal and resets user quota when confirmed`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L501`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useClientStore.test.ts#L501) (`cancels quota reset when user denies confirmation`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useServerStore.test.ts#L203`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useServerStore.test.ts#L203) (`prompts window.confirm and deletes server when confirmed`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useServerStore.test.ts#L232`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/useServerStore.test.ts#L232) (`does not send delete request when confirm is cancelled`) ### `[UI-TOAST-TRANSITION]` Displays error toast notification when saving invalid JSON credentials for user-provided server. * **Category:** `UI` (Dashboard, Test Bench & Settings UI) * **Type:** Negative / Safety Guardrail (Fail-Closed) * **Verification Proofs (8):** - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/pages/MyMcpServers.test.tsx#L23`](file:////containers/dev/csharp-mcp-router/frontend/src/test/pages/MyMcpServers.test.tsx#L23) (`shows error toast when saving invalid JSON credentials`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/pages/MyMcpServers.test.tsx#L64`](file:////containers/dev/csharp-mcp-router/frontend/src/test/pages/MyMcpServers.test.tsx#L64) (`saves valid credentials successfully and closes modal`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/CustomFileModal.test.tsx#L88`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/CustomFileModal.test.tsx#L88) (`shows error toast when switching from invalid JSON to Visual Prompt Builder`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/CustomFileModal.test.tsx#L109`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/CustomFileModal.test.tsx#L109) (`shows error toast when saving without a file name`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/CustomFileModal.test.tsx#L129`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/CustomFileModal.test.tsx#L129) (`shows error toast when saving prompt with invalid JSON content`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/IdentityAuthTab.test.tsx#L99`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/IdentityAuthTab.test.tsx#L99) (`saves updated Active Directory configuration JSON`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/IdentityAuthTab.test.tsx#L138`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/IdentityAuthTab.test.tsx#L138) (`displays error toast when saving auth providers fails`) - - [Frontend Vitest] [`/containers/dev/csharp-mcp-router/frontend/src/test/components/SecretProvidersTab.test.tsx#L63`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/SecretProvidersTab.test.tsx#L63) (`displays error toast when saving secret providers fails`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/pages/MyMcpServers.test.tsx#L23`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/pages/MyMcpServers.test.tsx#L23) (`shows error toast when saving invalid JSON credentials`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/pages/MyMcpServers.test.tsx#L64`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/pages/MyMcpServers.test.tsx#L64) (`saves valid credentials successfully and closes modal`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/CustomFileModal.test.tsx#L88`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/CustomFileModal.test.tsx#L88) (`shows error toast when switching from invalid JSON to Visual Prompt Builder`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/CustomFileModal.test.tsx#L109`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/CustomFileModal.test.tsx#L109) (`shows error toast when saving without a file name`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/CustomFileModal.test.tsx#L129`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/CustomFileModal.test.tsx#L129) (`shows error toast when saving prompt with invalid JSON content`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/IdentityAuthTab.test.tsx#L99`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/IdentityAuthTab.test.tsx#L99) (`saves updated Active Directory configuration JSON`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/IdentityAuthTab.test.tsx#L138`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/IdentityAuthTab.test.tsx#L138) (`displays error toast when saving auth providers fails`) + - [Frontend Vitest] [`/home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/SecretProvidersTab.test.tsx#L63`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/SecretProvidersTab.test.tsx#L63) (`displays error toast when saving secret providers fails`) --- @@ -739,97 +784,104 @@ | Requirement ID | Type | Category | Description | Primary Proof | Suite | | :--- | :---: | :--- | :--- | :--- | :--- | -| `AUTH-001` | Positive | `AUTH` | Verify DatabaseUserSecretStore encrypts and decrypts secret correctly. | [`UserSecretStoreTests.cs:L13`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/UserSecretStoreTests.cs#L13) | Backend xUnit | -| `AUTH-002` | Positive | `AUTH` | Verify UserCredentialsController returns configured server IDs. | [`UserCredentialsControllerTests.cs:L18`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/UserCredentialsControllerTests.cs#L18) | Backend xUnit | -| `AUTH-01` | **Guardrail** | `AUTH` | AdminPolicy allows principal with configured Admin Group Name (e.g., full_admin) | [`AdminPolicyHybridAuthTests.cs:L18`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L18) | Backend xUnit | -| `AUTH-02` | Positive | `AUTH` | AppKey scopes restrict access precisely across all MCP capabilities and backend targets | [`PairwiseIntegrationMatrixTests.cs:L255`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L255) | Backend xUnit | -| `AUTH-03` | Positive | `AUTH` | SSO identity and group mappings resolve Windows SIDs and OIDC claims to internal access roles | [`PairwiseIntegrationMatrixTests.cs:L332`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L332) | Backend xUnit | -| `AUTH-04` | Positive | `AUTH` | ActiveDirectoryIdentityProvider extracts Windows caller SIDs and security groups via IWindowsIdentityAccessor and augments with LDAP | [`ActiveDirectoryWindowsIdentityTests.cs:L17`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs#L17) | Backend xUnit | -| `AUTH-05` | Positive | `AUTH` | McpServer supports AllowPassThroughAuth flag | [`McpServerTests.cs:L9`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/McpServerTests.cs#L9) | Backend xUnit | -| `AUTH-06` | Positive | `AUTH` | Transports use passThroughToken when AllowPassThroughAuth is true | [`TransportsAuthShapeTests.cs:L208`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/TransportsAuthShapeTests.cs#L208) | Backend xUnit | -| `AUTH-101` | Positive | `AUTH` | HTTP transport injects X-Forwarded-User header based on connected user identity. | [`IdentityHeaderTests.cs:L16`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityHeaderTests.cs#L16) | Backend xUnit | -| `AUTH-105` | Positive | `AUTH` | Dynamic Auth Target Pass-Through | [`ToolRoutingManagerTests.cs:L180`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/ToolRoutingManagerTests.cs#L180) | Backend xUnit | -| `AUTH-109` | Positive | `AUTH` | ConsentView properly renders the client name from query string and builds correct form action. | [`ConsentView.test.tsx:L16`](file:////containers/dev/csharp-mcp-router/frontend/src/test/pages/ConsentView.test.tsx#L16) | Frontend Vitest | -| `AUTH-110` | Positive | `AUTH` | CreateAppKey allows creating unlimited AppKeys when UserMaxKeys is set to 0. | [`AppKeysControllerTests.cs:L347`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L347) | Backend xUnit | -| `AUTH-APPKEY-ADMIN-SCOPE-ALLOW` | Positive | `AUTH` | AppKeys with admin scope grant Administrator role and pass AdminPolicy. | [`StandaloneAdminAuthTests.cs:L87`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L87) | Backend xUnit | -| `AUTH-APPKEY-ITEMS-SCOPE-ALLOW` | Positive | `AUTH` | SecurityValidationHelper recognizes admin scopes in HttpContext.Items. | [`StandaloneAdminAuthTests.cs:L263`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L263) | Backend xUnit | -| `AUTH-APPKEY-WILDCARD-SCOPE-ALLOW` | Positive | `AUTH` | AppKeys with wildcard scope '*' grant Administrator role and pass AdminPolicy. | [`StandaloneAdminAuthTests.cs:L148`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L148) | Backend xUnit | -| `AUTH-PERSONAL-APPKEY-CREATE` | **Guardrail** | `AUTH` | Non-admin users can create personal App Keys up to quota | [`AppKeysControllerTests.cs:L199`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L199) | Backend xUnit | -| `AUTH-PERSONAL-APPKEY-LIST` | Positive | `AUTH` | Non-admin users can view their personal App Keys | [`AppKeysControllerTests.cs:L133`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L133) | Backend xUnit | -| `AUTH-PERSONAL-APPKEY-QUOTA-OVERRIDE` | Positive | `AUTH` | Custom user quotas override default limit | [`AppKeysControllerTests.cs:L231`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L231) | Backend xUnit | -| `AUTH-STANDALONE-ADMINPOLICY-LOOPBACK-ALLOW` | Positive | `AUTH` | AdminPolicy succeeds in standalone mode for unauthenticated loopback requests. | [`StandaloneAdminAuthTests.cs:L184`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L184) | Backend xUnit | -| `AUTH-STANDALONE-CUSTOM-CIDR-ALLOW` | Positive | `AUTH` | Standalone mode grants admin access to client IPs matching Admin:StandaloneAllowedNetworks CIDR ranges. | [`StandaloneAdminAuthTests.cs:L43`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L43) | Backend xUnit | -| `AUTH-STANDALONE-LOOPBACK-ALLOW` | Positive | `AUTH` | Standalone mode without external IDP grants admin access to loopback IP addresses. | [`StandaloneAdminAuthTests.cs:L22`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L22) | Backend xUnit | -| `AUTH-SYSTEM-APPKEY-SEPARATION` | Positive | `AUTH` | System keys are distinct and require admin permissions | [`AppKeysControllerTests.cs:L159`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AppKeysControllerTests.cs#L159) | Backend xUnit | -| `DB-01` | Positive | `DB` | SQLite auto-migration seamlessly upgrades legacy schema, encrypts plaintext secrets, and preserves data | [`DatabaseSchemaUpgradeAndContractTests.cs:L43`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L43) | Backend xUnit | -| `DB-02` | Positive | `DB` | MSSQL stored procedure scripts declare all required procedures and parameter contracts correctly | [`DatabaseSchemaUpgradeAndContractTests.cs:L325`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L325) | Backend xUnit | -| `DOC-SETUP-SKILL-FRONTMATTER` | Positive | `DOC` | mcp-router-setup skill frontmatter is valid YAML, specifies name, description starting with 'Use when...', and length is under 1024 characters | [`SetupSkillTests.cs:L22`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs#L22) | Backend xUnit | -| `DOC-SETUP-SKILL-MIRROR` | Positive | `DOC` | The mcp-router-setup skill and templates are mirrored 1:1 in .agents/skills/mcp-router-setup/ | [`SetupSkillTests.cs:L156`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs#L156) | Backend xUnit | -| `DOC-SETUP-SKILL-TEMPLATES` | Positive | `DOC` | All scaffold templates exist, are non-empty, and contain required directives such as responseBufferLimit, ROUTER_MASTER_KEY, and ghcr.io/spelech/mcp-router | [`SetupSkillTests.cs:L102`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs#L102) | Backend xUnit | -| `DOC-SETUP-SKILL-WORKFLOW` | Positive | `DOC` | mcp-router-setup skill contains all 6 required setup phases including environment probing, hosting platforms, env vs UI trade-offs, identity/network topology, artifact generation, and health/client configuration | [`SetupSkillTests.cs:L48`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SetupSkillTests.cs#L48) | Backend xUnit | -| `AUTH-EXTERNAL-IDP-DENIES-ANONYMOUS-LOOPBACK` | **Guardrail** | `GUARD` | When an external IDP is configured, anonymous loopback requests do not bypass authentication. | [`StandaloneAdminAuthTests.cs:L232`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L232) | Backend xUnit | -| `AUTH-STANDALONE-ADMINPOLICY-EXTERNAL-DENY` | **Guardrail** | `GUARD` | AdminPolicy rejects unauthenticated requests from non-whitelisted external IPs in standalone mode. | [`StandaloneAdminAuthTests.cs:L208`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L208) | Backend xUnit | -| `AUTH-STANDALONE-EXTERNAL-DENY` | **Guardrail** | `GUARD` | Standalone mode denies admin access to non-whitelisted external IPs without an Admin AppKey. | [`StandaloneAdminAuthTests.cs:L65`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StandaloneAdminAuthTests.cs#L65) | Backend xUnit | -| `GUARD-01` | **Guardrail** | `GUARD` | Null or empty capability targets must immediately fail closed and return unauthorized | [`PairwiseIntegrationMatrixTests.cs:L481`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L481) | Backend xUnit | -| `GUARD-02` | **Guardrail** | `GUARD` | SSE transport fails closed with SecurityException when secret provider resolution fails | [`SseTransportTests.cs:L39`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SseTransportTests.cs#L39) | Backend xUnit | -| `GUARD-03` | **Guardrail** | `GUARD` | STDIO transport rejects commands with shell metacharacters or dangerous commands | [`StdioTransportTests.cs:L114`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L114) | Backend xUnit | -| `GUARD-04` | **Guardrail** | `GUARD` | Malformed completion payloads or unmapped backends must fail closed safely | [`PairwiseIntegrationMatrixTests.cs:L521`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L521) | Backend xUnit | -| `GUARD-05` | **Guardrail** | `GUARD` | Batch save of authentication providers must fail closed if all providers are disabled | [`ProvidersControllerTests.cs:L324`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/ProvidersControllerTests.cs#L324) | Backend xUnit | -| `GUARD-06` | **Guardrail** | `GUARD` | Global deny policies with TargetId '*' and IsAllowed false must fail closed | [`PermissionsControllerTests.cs:L236`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PermissionsControllerTests.cs#L236) | Backend xUnit | -| `GUARD-ADMIN-CUSTOM-FILES-VALIDATION` | **Guardrail** | `GUARD` | manage_custom_files rejects invalid prompt JSON syntax and unsupported file categories. | [`AdminToolsParityTests.cs:L790`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L790) | Backend xUnit | -| `GUARD-ADMIN-ENDPOINT-UNAUTHORIZED` | **Guardrail** | `GUARD` | Unauthenticated / non-admin client request to /admin receives 403 Forbidden. | [`AdminEndpointsTests.cs:L196`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L196) | Backend xUnit | -| `GUARD-ADMIN-POLICIES-WILDCARD-DENY` | **Guardrail** | `GUARD` | manage_policies rejects wildcard deny policies to prevent global lockout. | [`AdminToolsParityTests.cs:L524`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L524) | Backend xUnit | -| `GUARD-ADMIN-PROVIDERS-LDAP-PLAINTEXT` | **Guardrail** | `GUARD` | manage_providers rejects unencrypted LDAP connections on port 389. | [`AdminToolsParityTests.cs:L661`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L661) | Backend xUnit | -| `GUARD-ADMIN-SERVERS-VALIDATION` | **Guardrail** | `GUARD` | manage_servers rejects invalid transport types, non-existent servers, and missing required parameters. | [`AdminToolsParityTests.cs:L335`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L335) | Backend xUnit | -| `GUARD-ADMIN-UNKNOWN-TOOL` | **Guardrail** | `GUARD` | AdminMcpServer returns an error response for unknown tool or action invocations. | [`AdminMcpServerTests.cs:L602`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L602) | Backend xUnit | -| `MCP-ADMIN-TOOL-TEST-CALL-ERROR` | **Guardrail** | `GUARD` | AdminMcpServer test_tool_call propagates downstream backend errors with visibility. | [`AdminMcpServerTests.cs:L643`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L643) | Backend xUnit | -| `MCP-01` | Positive | `MCP` | Meta-mode execute_tool strictly enforces target tool authorization policies | [`PairwiseIntegrationMatrixTests.cs:L580`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L580) | Backend xUnit | -| `MCP-02` | Positive | `MCP` | All MCP protocol capabilities enforce caller role authorizations consistently | [`PairwiseIntegrationMatrixTests.cs:L398`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L398) | Backend xUnit | -| `MCP-ADMIN-ENDPOINT-CALL-TOOL` | Positive | `MCP` | Admin endpoint /admin/message executes tools/call for manage_system diagnostics. | [`AdminEndpointsTests.cs:L289`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L289) | Backend xUnit | -| `MCP-ADMIN-ENDPOINT-HEAD-REQUEST` | Positive | `MCP` | Admin endpoint /admin handles HEAD request returning text/event-stream headers. | [`AdminEndpointsTests.cs:L215`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L215) | Backend xUnit | -| `MCP-ADMIN-ENDPOINT-LIST-TOOLS` | Positive | `MCP` | Admin endpoint /admin/message executes tools/list over active SSE session and returns 10 admin tools. | [`AdminEndpointsTests.cs:L227`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L227) | Backend xUnit | -| `MCP-ADMIN-ENDPOINT-ROUTER-ADMIN-TARGET` | Positive | `MCP` | Target proxy endpoint /router-admin routes directly to the Admin MCP server. | [`AdminEndpointsTests.cs:L152`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L152) | Backend xUnit | -| `MCP-ADMIN-ENDPOINT-SSE-HANDSHAKE` | Positive | `MCP` | Admin endpoint /admin/sse performs initialize handshake with 2026-07-28 protocol version. | [`AdminEndpointsTests.cs:L73`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminEndpointsTests.cs#L73) | Backend xUnit | -| `MCP-ADMIN-INITIALIZE-HANDSHAKE` | Positive | `MCP` | AdminMcpServer initialize handles protocol negotiation for 2026-07-28 and 2024-11-05. | [`AdminMcpServerTests.cs:L205`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L205) | Backend xUnit | -| `MCP-ADMIN-PARITY-APPKEYS` | Positive | `MCP` | manage_appkeys supports full parity for list, get_limits, create, and revoke actions. | [`AdminToolsParityTests.cs:L381`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L381) | Backend xUnit | -| `MCP-ADMIN-PARITY-CLIENTS` | Positive | `MCP` | manage_clients supports full parity for register, list, and delete actions. | [`AdminToolsParityTests.cs:L434`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L434) | Backend xUnit | -| `MCP-ADMIN-PARITY-CUSTOM-FILES` | Positive | `MCP` | manage_custom_files supports full parity for list, get, save, and delete prompt and resource files. | [`AdminToolsParityTests.cs:L727`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L727) | Backend xUnit | -| `MCP-ADMIN-PARITY-GROUP-MAPPINGS` | Positive | `MCP` | manage_group_mappings supports full parity for list, save, and delete external-to-internal group mappings. | [`AdminToolsParityTests.cs:L541`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L541) | Backend xUnit | -| `MCP-ADMIN-PARITY-JSONRPC-DISPATCH` | Positive | `MCP` | AdminMcpServer processes standard JSON-RPC 2.0 requests (tools/list, tools/call, ping). | [`AdminToolsParityTests.cs:L884`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L884) | Backend xUnit | -| `MCP-ADMIN-PARITY-POLICIES` | Positive | `MCP` | manage_policies supports full parity for list, save, and delete access control policies. | [`AdminToolsParityTests.cs:L475`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L475) | Backend xUnit | -| `MCP-ADMIN-PARITY-PROVIDERS` | Positive | `MCP` | manage_providers supports full parity for list, save_secret, test_vault, save_auth, and test_ldap actions. | [`AdminToolsParityTests.cs:L588`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L588) | Backend xUnit | -| `MCP-ADMIN-PARITY-SERVERS` | Positive | `MCP` | manage_servers supports full parity for list, get, create, update, toggle, delete, reconnect, and reconnect_all actions. | [`AdminToolsParityTests.cs:L245`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L245) | Backend xUnit | -| `MCP-ADMIN-PARITY-SETTINGS` | Positive | `MCP` | manage_settings supports full parity for get and update global router configurations. | [`AdminToolsParityTests.cs:L678`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L678) | Backend xUnit | -| `MCP-ADMIN-PARITY-SYSTEM` | Positive | `MCP` | manage_system supports full parity for diagnostics, get_logs, clear_logs, and query_audit actions. | [`AdminToolsParityTests.cs:L827`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L827) | Backend xUnit | -| `MCP-ADMIN-PARITY-TEST-TOOL-CALL` | Positive | `MCP` | test_tool_call executes test bench backend tool calls and formats responses. | [`AdminToolsParityTests.cs:L857`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L857) | Backend xUnit | -| `MCP-ADMIN-PARITY-TOOLS-COVERAGE` | Positive | `MCP` | Every UI management flow has a corresponding verified action in the consolidated Admin MCP tools. | [`AdminToolsParityTests.cs:L202`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminToolsParityTests.cs#L202) | Backend xUnit | -| `MCP-ADMIN-TOOL-AUDIT-LOG` | Positive | `MCP` | AdminMcpServer tool calls record audit log entries with caller and tool name. | [`AdminMcpServerTests.cs:L276`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L276) | Backend xUnit | -| `MCP-ADMIN-TOOL-MANAGE-APPKEYS` | Positive | `MCP` | AdminMcpServer executes manage_appkeys create, list, limits, and revoke actions. | [`AdminMcpServerTests.cs:L293`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L293) | Backend xUnit | -| `MCP-ADMIN-TOOL-MANAGE-CLIENTS` | Positive | `MCP` | AdminMcpServer executes manage_clients register, list, and delete actions. | [`AdminMcpServerTests.cs:L340`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L340) | Backend xUnit | -| `MCP-ADMIN-TOOL-MANAGE-CUSTOM-FILES` | Positive | `MCP` | AdminMcpServer executes manage_custom_files save, get, list, and delete actions. | [`AdminMcpServerTests.cs:L514`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L514) | Backend xUnit | -| `MCP-ADMIN-TOOL-MANAGE-GROUP-MAPPINGS` | Positive | `MCP` | AdminMcpServer executes manage_group_mappings save, list, and delete actions. | [`AdminMcpServerTests.cs:L412`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L412) | Backend xUnit | -| `MCP-ADMIN-TOOL-MANAGE-POLICIES` | Positive | `MCP` | AdminMcpServer executes manage_policies save, list, and delete actions. | [`AdminMcpServerTests.cs:L378`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L378) | Backend xUnit | -| `MCP-ADMIN-TOOL-MANAGE-PROVIDERS` | Positive | `MCP` | AdminMcpServer executes manage_providers list, save_secret, and save_auth actions. | [`AdminMcpServerTests.cs:L445`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L445) | Backend xUnit | -| `MCP-ADMIN-TOOL-MANAGE-SERVERS` | Positive | `MCP` | AdminMcpServer executes manage_servers list, get, create, update, toggle, and delete actions. | [`AdminMcpServerTests.cs:L224`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L224) | Backend xUnit | -| `MCP-ADMIN-TOOL-MANAGE-SETTINGS` | Positive | `MCP` | AdminMcpServer executes manage_settings get and update actions. | [`AdminMcpServerTests.cs:L488`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L488) | Backend xUnit | -| `MCP-ADMIN-TOOL-MANAGE-SYSTEM` | Positive | `MCP` | AdminMcpServer executes manage_system diagnostics, get_logs, clear_logs, and query_audit actions. | [`AdminMcpServerTests.cs:L568`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L568) | Backend xUnit | -| `MCP-ADMIN-TOOLS-LIST-COUNT` | Positive | `MCP` | AdminMcpServer tools/list returns all 10 consolidated tools with complete JSON schemas. | [`AdminMcpServerTests.cs:L157`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L157) | Backend xUnit | -| `AUTH-106` | **Guardrail** | `SEC` | Exchange throws InvalidOperationException when request is null. | [`AuthorizationControllerTests.cs:L23`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AuthorizationControllerTests.cs#L23) | Backend xUnit | -| `AUTH-107` | Positive | `SEC` | RegisterClient successfully handles DCR requests when open DCR is enabled. | [`AuthorizationControllerTests.cs:L41`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AuthorizationControllerTests.cs#L41) | Backend xUnit | -| `AUTH-108` | **Guardrail** | `SEC` | Authorize throws InvalidOperationException when OIDC request is null. | [`AuthorizationControllerTests.cs:L79`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AuthorizationControllerTests.cs#L79) | Backend xUnit | -| `SEC-01` | Positive | `SEC` | VaultSecretRetriever authenticates with HashiCorp Vault using AppRole RoleID and SecretID credentials | [`VaultAppRoleAndRenewalTests.cs:L23`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L23) | Backend xUnit | -| `SEC-02` | Positive | `SEC` | STDIO transport securely injects secret credentials via environment variables rather than command-line arguments | [`StdioTransportTests.cs:L354`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L354) | Backend xUnit | -| `SEC-03` | Positive | `SEC` | Ensure TrustedProxyHelper supports CIDR ranges in XFF validation | [`IdentityProviderTests.cs:L366`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/IdentityProviderTests.cs#L366) | Backend xUnit | -| `SEC-04` | Positive | `SEC` | WindowsRegistrySecretRetriever securely decrypts DPAPI LocalMachine machine-level encrypted binary values and retrieves plaintext registry strings | [`WindowsRegistrySecretRetrieverTests.cs:L16`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs#L16) | Backend xUnit | -| `SEC-05` | **Guardrail** | `SEC` | Router must not overwrite corrupt encrypted database fields if an update occurs without user reset. | [`ProviderSettingsEncryptionTests.cs:L395`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/ProviderSettingsEncryptionTests.cs#L395) | Backend xUnit | -| `SEC-ADMIN-AUDIT-REDACTION` | Positive | `SEC` | AdminMcpServer redacts sensitive secrets from argument payloads before recording audit logs. | [`AdminMcpServerTests.cs:L619`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/AdminMcpServerTests.cs#L619) | Backend xUnit | -| `TRANS-01` | Positive | `TRANS` | SSE transport resolves static plaintext API keys when provider is None | [`SseTransportTests.cs:L18`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/SseTransportTests.cs#L18) | Backend xUnit | -| `TRANS-02` | Positive | `TRANS` | HTTP stateless transport resolves static API keys when secret provider is None | [`HttpTransportTests.cs:L19`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/HttpTransportTests.cs#L19) | Backend xUnit | -| `TRANS-03` | Positive | `TRANS` | STDIO transport spawns subprocess, handles JSON-RPC initialization and executes tool calls | [`StdioTransportTests.cs:L59`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/StdioTransportTests.cs#L59) | Backend xUnit | -| `UI-01` | Positive | `UI` | Dashboard renders stats card, connected server list, and setup instructions | [`DashboardView.test.tsx:L38`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx#L38) | Frontend Vitest | -| `UI-02` | Positive | `UI` | Modal remains hidden when isInspectOpen is false | [`ServerInspectModal.test.tsx:L47`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ServerInspectModal.test.tsx#L47) | Frontend Vitest | -| `UI-03` | Positive | `UI` | Grouped server view renders category sections and supports collapsible groups | [`DashboardView.test.tsx:L61`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/DashboardView.test.tsx#L61) | Frontend Vitest | -| `UI-04` | Positive | `UI` | Interactive tool tester renders server and tool selection dropdowns | [`ToolTesterCard.test.tsx:L41`](file:////containers/dev/csharp-mcp-router/frontend/src/test/components/ToolTesterCard.test.tsx#L41) | Frontend Vitest | -| `UI-05` | Positive | `UI` | Router allows customized branding parameters (DashboardTitle, DashboardIcon) to be saved and retrieved via the API. | [`PipelineIntegrationTests.cs:L242`](file:////containers/dev/csharp-mcp-router/McpRouter.Tests/PipelineIntegrationTests.cs#L242) | Backend xUnit | -| `UI-CONFIRM-MODAL` | **Guardrail** | `UI` | Deletes an access policy when confirmed via confirm modal. | [`usePolicyStore.test.ts:L76`](file:////containers/dev/csharp-mcp-router/frontend/src/test/stores/usePolicyStore.test.ts#L76) | Frontend Vitest | -| `UI-TOAST-TRANSITION` | **Guardrail** | `UI` | Displays error toast notification when saving invalid JSON credentials for user-provided server. | [`MyMcpServers.test.tsx:L23`](file:////containers/dev/csharp-mcp-router/frontend/src/test/pages/MyMcpServers.test.tsx#L23) | Frontend Vitest | +| `AUTH-001` | Positive | `AUTH` | Verify DatabaseUserSecretStore encrypts and decrypts secret correctly. | [`UserSecretStoreTests.cs:L13`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserSecretStoreTests.cs#L13) | Backend xUnit | +| `AUTH-002` | Positive | `AUTH` | Verify UserCredentialsController returns configured server IDs. | [`UserCredentialsControllerTests.cs:L18`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/UserCredentialsControllerTests.cs#L18) | Backend xUnit | +| `AUTH-01` | **Guardrail** | `AUTH` | AdminPolicy allows principal with configured Admin Group Name (e.g., full_admin) | [`AdminPolicyHybridAuthTests.cs:L18`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminPolicyHybridAuthTests.cs#L18) | Backend xUnit | +| `AUTH-02` | Positive | `AUTH` | AppKey scopes restrict access precisely across all MCP capabilities and backend targets | [`PairwiseIntegrationMatrixTests.cs:L255`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L255) | Backend xUnit | +| `AUTH-03` | Positive | `AUTH` | SSO identity and group mappings resolve Windows SIDs and OIDC claims to internal access roles | [`PairwiseIntegrationMatrixTests.cs:L332`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L332) | Backend xUnit | +| `AUTH-04` | Positive | `AUTH` | ActiveDirectoryIdentityProvider extracts Windows caller SIDs and security groups via IWindowsIdentityAccessor and augments with LDAP | [`ActiveDirectoryWindowsIdentityTests.cs:L17`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ActiveDirectoryWindowsIdentityTests.cs#L17) | Backend xUnit | +| `AUTH-05` | Positive | `AUTH` | McpServer supports AllowPassThroughAuth flag | [`McpServerTests.cs:L9`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/McpServerTests.cs#L9) | Backend xUnit | +| `AUTH-06` | Positive | `AUTH` | Transports use passThroughToken when AllowPassThroughAuth is true | [`TransportsAuthShapeTests.cs:L208`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/TransportsAuthShapeTests.cs#L208) | Backend xUnit | +| `AUTH-101` | Positive | `AUTH` | HTTP transport injects X-Forwarded-User header based on connected user identity. | [`IdentityHeaderTests.cs:L16`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityHeaderTests.cs#L16) | Backend xUnit | +| `AUTH-105` | Positive | `AUTH` | Dynamic Auth Target Pass-Through | [`ToolRoutingManagerTests.cs:L187`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ToolRoutingManagerTests.cs#L187) | Backend xUnit | +| `AUTH-109` | Positive | `AUTH` | ConsentView properly renders the client name from query string and builds correct form action. | [`ConsentView.test.tsx:L16`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/pages/ConsentView.test.tsx#L16) | Frontend Vitest | +| `AUTH-110` | Positive | `AUTH` | CreateAppKey allows creating unlimited AppKeys when UserMaxKeys is set to 0. | [`AppKeysControllerTests.cs:L347`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L347) | Backend xUnit | +| `AUTH-APPKEY-ADMIN-SCOPE-ALLOW` | Positive | `AUTH` | AppKeys with admin scope grant Administrator role and pass AdminPolicy. | [`StandaloneAdminAuthTests.cs:L87`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L87) | Backend xUnit | +| `AUTH-APPKEY-ITEMS-SCOPE-ALLOW` | Positive | `AUTH` | SecurityValidationHelper recognizes admin scopes in HttpContext.Items. | [`StandaloneAdminAuthTests.cs:L263`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L263) | Backend xUnit | +| `AUTH-APPKEY-WILDCARD-SCOPE-ALLOW` | Positive | `AUTH` | AppKeys with wildcard scope '*' grant Administrator role and pass AdminPolicy. | [`StandaloneAdminAuthTests.cs:L148`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L148) | Backend xUnit | +| `AUTH-PERSONAL-APPKEY-CREATE` | **Guardrail** | `AUTH` | Non-admin users can create personal App Keys up to quota | [`AppKeysControllerTests.cs:L199`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L199) | Backend xUnit | +| `AUTH-PERSONAL-APPKEY-LIST` | Positive | `AUTH` | Non-admin users can view their personal App Keys | [`AppKeysControllerTests.cs:L133`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L133) | Backend xUnit | +| `AUTH-PERSONAL-APPKEY-QUOTA-OVERRIDE` | Positive | `AUTH` | Custom user quotas override default limit | [`AppKeysControllerTests.cs:L231`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L231) | Backend xUnit | +| `AUTH-STANDALONE-ADMINPOLICY-LOOPBACK-ALLOW` | Positive | `AUTH` | AdminPolicy succeeds in standalone mode for unauthenticated loopback requests. | [`StandaloneAdminAuthTests.cs:L184`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L184) | Backend xUnit | +| `AUTH-STANDALONE-CUSTOM-CIDR-ALLOW` | Positive | `AUTH` | Standalone mode grants admin access to client IPs matching Admin:StandaloneAllowedNetworks CIDR ranges. | [`StandaloneAdminAuthTests.cs:L43`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L43) | Backend xUnit | +| `AUTH-STANDALONE-LOOPBACK-ALLOW` | Positive | `AUTH` | Standalone mode without external IDP grants admin access to loopback IP addresses. | [`StandaloneAdminAuthTests.cs:L22`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L22) | Backend xUnit | +| `AUTH-SYSTEM-APPKEY-SEPARATION` | Positive | `AUTH` | System keys are distinct and require admin permissions | [`AppKeysControllerTests.cs:L159`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AppKeysControllerTests.cs#L159) | Backend xUnit | +| `UI-120` | Positive | `AUTH` | RBAC and SID mapping administration UI allows configuring role policies and SID associations | [`rbac-enforcement-flow.spec.ts:L1`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/rbac-enforcement-flow.spec.ts#L1) | Playwright E2E | +| `UI-125` | Positive | `AUTH` | Admin role renders full administrative dashboard and server management controls | [`multi-user-matrix.spec.ts:L1`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/multi-user-matrix.spec.ts#L1) | Playwright E2E | +| `CORE-101` | Positive | `CORE` | Auto-added requirement tracking | [`SessionManagerTests.cs:L14`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SessionManagerTests.cs#L14) | Backend xUnit | +| `DB-01` | Positive | `DB` | SQLite auto-migration seamlessly upgrades legacy schema, encrypts plaintext secrets, and preserves data | [`DatabaseSchemaUpgradeAndContractTests.cs:L43`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L43) | Backend xUnit | +| `DB-02` | Positive | `DB` | MSSQL stored procedure scripts declare all required procedures and parameter contracts correctly | [`DatabaseSchemaUpgradeAndContractTests.cs:L325`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/DatabaseSchemaUpgradeAndContractTests.cs#L325) | Backend xUnit | +| `DOC-SETUP-SKILL-FRONTMATTER` | Positive | `DOC` | mcp-router-setup skill frontmatter is valid YAML, specifies name, description starting with 'Use when...', and length is under 1024 characters | [`SetupSkillTests.cs:L22`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs#L22) | Backend xUnit | +| `DOC-SETUP-SKILL-MIRROR` | Positive | `DOC` | The mcp-router-setup skill and templates are mirrored 1:1 in .agents/skills/mcp-router-setup/ | [`SetupSkillTests.cs:L156`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs#L156) | Backend xUnit | +| `DOC-SETUP-SKILL-TEMPLATES` | Positive | `DOC` | All scaffold templates exist, are non-empty, and contain required directives such as responseBufferLimit, ROUTER_MASTER_KEY, and ghcr.io/spelech/mcp-router | [`SetupSkillTests.cs:L102`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs#L102) | Backend xUnit | +| `DOC-SETUP-SKILL-WORKFLOW` | Positive | `DOC` | mcp-router-setup skill contains all 6 required setup phases including environment probing, hosting platforms, env vs UI trade-offs, identity/network topology, artifact generation, and health/client configuration | [`SetupSkillTests.cs:L48`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SetupSkillTests.cs#L48) | Backend xUnit | +| `AUTH-EXTERNAL-IDP-DENIES-ANONYMOUS-LOOPBACK` | **Guardrail** | `GUARD` | When an external IDP is configured, anonymous loopback requests do not bypass authentication. | [`StandaloneAdminAuthTests.cs:L232`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L232) | Backend xUnit | +| `AUTH-STANDALONE-ADMINPOLICY-EXTERNAL-DENY` | **Guardrail** | `GUARD` | AdminPolicy rejects unauthenticated requests from non-whitelisted external IPs in standalone mode. | [`StandaloneAdminAuthTests.cs:L208`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L208) | Backend xUnit | +| `AUTH-STANDALONE-EXTERNAL-DENY` | **Guardrail** | `GUARD` | Standalone mode denies admin access to non-whitelisted external IPs without an Admin AppKey. | [`StandaloneAdminAuthTests.cs:L65`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StandaloneAdminAuthTests.cs#L65) | Backend xUnit | +| `GUARD-01` | **Guardrail** | `GUARD` | Null or empty capability targets must immediately fail closed and return unauthorized | [`PairwiseIntegrationMatrixTests.cs:L481`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L481) | Backend xUnit | +| `GUARD-02` | **Guardrail** | `GUARD` | SSE transport fails closed with SecurityException when secret provider resolution fails | [`SseTransportTests.cs:L39`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SseTransportTests.cs#L39) | Backend xUnit | +| `GUARD-03` | **Guardrail** | `GUARD` | STDIO transport rejects commands with shell metacharacters or dangerous commands | [`StdioTransportTests.cs:L114`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L114) | Backend xUnit | +| `GUARD-04` | **Guardrail** | `GUARD` | Malformed completion payloads or unmapped backends must fail closed safely | [`PairwiseIntegrationMatrixTests.cs:L521`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L521) | Backend xUnit | +| `GUARD-05` | **Guardrail** | `GUARD` | Batch save of authentication providers must fail closed if all providers are disabled | [`ProvidersControllerTests.cs:L324`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ProvidersControllerTests.cs#L324) | Backend xUnit | +| `GUARD-06` | **Guardrail** | `GUARD` | Global deny policies with TargetId '*' and IsAllowed false must fail closed | [`PermissionsControllerTests.cs:L236`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PermissionsControllerTests.cs#L236) | Backend xUnit | +| `GUARD-ADMIN-CUSTOM-FILES-VALIDATION` | **Guardrail** | `GUARD` | manage_custom_files rejects invalid prompt JSON syntax and unsupported file categories. | [`AdminToolsParityTests.cs:L790`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L790) | Backend xUnit | +| `GUARD-ADMIN-ENDPOINT-UNAUTHORIZED` | **Guardrail** | `GUARD` | Unauthenticated / non-admin client request to /admin receives 403 Forbidden. | [`AdminEndpointsTests.cs:L196`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L196) | Backend xUnit | +| `GUARD-ADMIN-POLICIES-WILDCARD-DENY` | **Guardrail** | `GUARD` | manage_policies rejects wildcard deny policies to prevent global lockout. | [`AdminToolsParityTests.cs:L524`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L524) | Backend xUnit | +| `GUARD-ADMIN-PROVIDERS-LDAP-PLAINTEXT` | **Guardrail** | `GUARD` | manage_providers rejects unencrypted LDAP connections on port 389. | [`AdminToolsParityTests.cs:L661`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L661) | Backend xUnit | +| `GUARD-ADMIN-SERVERS-VALIDATION` | **Guardrail** | `GUARD` | Verifies that the manage_servers tool accurately enforces validation by rejecting malformed transport types, missing required parameters, and requests for non-existent servers. | [`AdminToolsParityTests.cs:L335`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L335) | Backend xUnit | +| `GUARD-ADMIN-UNKNOWN-TOOL` | **Guardrail** | `GUARD` | AdminMcpServer returns an error response for unknown tool or action invocations. | [`AdminMcpServerTests.cs:L602`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L602) | Backend xUnit | +| `MCP-ADMIN-TOOL-TEST-CALL-ERROR` | **Guardrail** | `GUARD` | AdminMcpServer test_tool_call propagates downstream backend errors with visibility. | [`AdminMcpServerTests.cs:L643`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L643) | Backend xUnit | +| `MCP-01` | Positive | `MCP` | Meta-mode execute_tool strictly enforces target tool authorization policies | [`PairwiseIntegrationMatrixTests.cs:L580`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L580) | Backend xUnit | +| `MCP-02` | Positive | `MCP` | All MCP protocol capabilities enforce caller role authorizations consistently | [`PairwiseIntegrationMatrixTests.cs:L398`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PairwiseIntegrationMatrixTests.cs#L398) | Backend xUnit | +| `MCP-ADMIN-ENDPOINT-CALL-TOOL` | Positive | `MCP` | Admin endpoint /admin/message executes tools/call for manage_system diagnostics. | [`AdminEndpointsTests.cs:L289`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L289) | Backend xUnit | +| `MCP-ADMIN-ENDPOINT-HEAD-REQUEST` | Positive | `MCP` | Admin endpoint /admin handles HEAD request returning text/event-stream headers. | [`AdminEndpointsTests.cs:L215`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L215) | Backend xUnit | +| `MCP-ADMIN-ENDPOINT-LIST-TOOLS` | Positive | `MCP` | Admin endpoint /admin/message executes tools/list over active SSE session and returns 10 admin tools. | [`AdminEndpointsTests.cs:L227`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L227) | Backend xUnit | +| `MCP-ADMIN-ENDPOINT-ROUTER-ADMIN-TARGET` | Positive | `MCP` | Target proxy endpoint /router-admin routes directly to the Admin MCP server. | [`AdminEndpointsTests.cs:L152`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L152) | Backend xUnit | +| `MCP-ADMIN-ENDPOINT-SSE-HANDSHAKE` | Positive | `MCP` | Admin endpoint /admin/sse performs initialize handshake with 2026-07-28 protocol version. | [`AdminEndpointsTests.cs:L73`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminEndpointsTests.cs#L73) | Backend xUnit | +| `MCP-ADMIN-INITIALIZE-HANDSHAKE` | Positive | `MCP` | AdminMcpServer initialize handles protocol negotiation for 2026-07-28 and 2024-11-05. | [`AdminMcpServerTests.cs:L205`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L205) | Backend xUnit | +| `MCP-ADMIN-PARITY-APPKEYS` | Positive | `MCP` | manage_appkeys supports full parity for list, get_limits, create, and revoke actions. | [`AdminToolsParityTests.cs:L381`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L381) | Backend xUnit | +| `MCP-ADMIN-PARITY-CLIENTS` | Positive | `MCP` | manage_clients supports full parity for register, list, and delete actions. | [`AdminToolsParityTests.cs:L434`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L434) | Backend xUnit | +| `MCP-ADMIN-PARITY-CUSTOM-FILES` | Positive | `MCP` | manage_custom_files supports full parity for list, get, save, and delete prompt and resource files. | [`AdminToolsParityTests.cs:L727`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L727) | Backend xUnit | +| `MCP-ADMIN-PARITY-GROUP-MAPPINGS` | Positive | `MCP` | manage_group_mappings supports full parity for list, save, and delete external-to-internal group mappings. | [`AdminToolsParityTests.cs:L541`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L541) | Backend xUnit | +| `MCP-ADMIN-PARITY-JSONRPC-DISPATCH` | Positive | `MCP` | AdminMcpServer processes standard JSON-RPC 2.0 requests (tools/list, tools/call, ping). | [`AdminToolsParityTests.cs:L884`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L884) | Backend xUnit | +| `MCP-ADMIN-PARITY-POLICIES` | Positive | `MCP` | manage_policies supports full parity for list, save, and delete access control policies. | [`AdminToolsParityTests.cs:L475`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L475) | Backend xUnit | +| `MCP-ADMIN-PARITY-PROVIDERS` | Positive | `MCP` | manage_providers supports full parity for list, save_secret, test_vault, save_auth, and test_ldap actions. | [`AdminToolsParityTests.cs:L588`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L588) | Backend xUnit | +| `MCP-ADMIN-PARITY-SERVERS` | Positive | `MCP` | Validates that the manage_servers tool provides comprehensive administrative capabilities including listing, retrieving, creating, updating, toggling, deleting, and reconnecting servers. | [`AdminToolsParityTests.cs:L245`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L245) | Backend xUnit | +| `MCP-ADMIN-PARITY-SETTINGS` | Positive | `MCP` | manage_settings supports full parity for get and update global router configurations. | [`AdminToolsParityTests.cs:L678`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L678) | Backend xUnit | +| `MCP-ADMIN-PARITY-SYSTEM` | Positive | `MCP` | manage_system supports full parity for diagnostics, get_logs, clear_logs, and query_audit actions. | [`AdminToolsParityTests.cs:L827`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L827) | Backend xUnit | +| `MCP-ADMIN-PARITY-TEST-TOOL-CALL` | Positive | `MCP` | test_tool_call executes test bench backend tool calls and formats responses. | [`AdminToolsParityTests.cs:L857`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L857) | Backend xUnit | +| `MCP-ADMIN-PARITY-TOOLS-COVERAGE` | Positive | `MCP` | Ensures every UI management workflow is backed by a verified, equivalent action within the consolidated Admin MCP tools. | [`AdminToolsParityTests.cs:L202`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminToolsParityTests.cs#L202) | Backend xUnit | +| `MCP-ADMIN-TOOL-AUDIT-LOG` | Positive | `MCP` | AdminMcpServer tool calls record audit log entries with caller and tool name. | [`AdminMcpServerTests.cs:L276`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L276) | Backend xUnit | +| `MCP-ADMIN-TOOL-MANAGE-APPKEYS` | Positive | `MCP` | AdminMcpServer executes manage_appkeys create, list, limits, and revoke actions. | [`AdminMcpServerTests.cs:L293`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L293) | Backend xUnit | +| `MCP-ADMIN-TOOL-MANAGE-CLIENTS` | Positive | `MCP` | AdminMcpServer executes manage_clients register, list, and delete actions. | [`AdminMcpServerTests.cs:L340`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L340) | Backend xUnit | +| `MCP-ADMIN-TOOL-MANAGE-CUSTOM-FILES` | Positive | `MCP` | AdminMcpServer executes manage_custom_files save, get, list, and delete actions. | [`AdminMcpServerTests.cs:L514`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L514) | Backend xUnit | +| `MCP-ADMIN-TOOL-MANAGE-GROUP-MAPPINGS` | Positive | `MCP` | AdminMcpServer executes manage_group_mappings save, list, and delete actions. | [`AdminMcpServerTests.cs:L412`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L412) | Backend xUnit | +| `MCP-ADMIN-TOOL-MANAGE-POLICIES` | Positive | `MCP` | AdminMcpServer executes manage_policies save, list, and delete actions. | [`AdminMcpServerTests.cs:L378`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L378) | Backend xUnit | +| `MCP-ADMIN-TOOL-MANAGE-PROVIDERS` | Positive | `MCP` | AdminMcpServer executes manage_providers list, save_secret, and save_auth actions. | [`AdminMcpServerTests.cs:L445`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L445) | Backend xUnit | +| `MCP-ADMIN-TOOL-MANAGE-SERVERS` | Positive | `MCP` | AdminMcpServer executes manage_servers list, get, create, update, toggle, and delete actions. | [`AdminMcpServerTests.cs:L224`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L224) | Backend xUnit | +| `MCP-ADMIN-TOOL-MANAGE-SETTINGS` | Positive | `MCP` | AdminMcpServer executes manage_settings get and update actions. | [`AdminMcpServerTests.cs:L488`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L488) | Backend xUnit | +| `MCP-ADMIN-TOOL-MANAGE-SYSTEM` | Positive | `MCP` | AdminMcpServer executes manage_system diagnostics, get_logs, clear_logs, and query_audit actions. | [`AdminMcpServerTests.cs:L568`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L568) | Backend xUnit | +| `MCP-ADMIN-TOOLS-LIST-COUNT` | Positive | `MCP` | AdminMcpServer tools/list returns all 10 consolidated tools with complete JSON schemas. | [`AdminMcpServerTests.cs:L157`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L157) | Backend xUnit | +| `AUTH-106` | **Guardrail** | `SEC` | Exchange throws InvalidOperationException when request is null. | [`AuthorizationControllerTests.cs:L23`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AuthorizationControllerTests.cs#L23) | Backend xUnit | +| `AUTH-107` | Positive | `SEC` | RegisterClient successfully handles DCR requests when open DCR is enabled. | [`AuthorizationControllerTests.cs:L41`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AuthorizationControllerTests.cs#L41) | Backend xUnit | +| `AUTH-108` | **Guardrail** | `SEC` | Authorize throws InvalidOperationException when OIDC request is null. | [`AuthorizationControllerTests.cs:L79`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AuthorizationControllerTests.cs#L79) | Backend xUnit | +| `SEC-01` | Positive | `SEC` | VaultSecretRetriever authenticates with HashiCorp Vault using AppRole RoleID and SecretID credentials | [`VaultAppRoleAndRenewalTests.cs:L23`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/VaultAppRoleAndRenewalTests.cs#L23) | Backend xUnit | +| `SEC-02` | Positive | `SEC` | STDIO transport securely injects secret credentials via environment variables rather than command-line arguments | [`StdioTransportTests.cs:L354`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L354) | Backend xUnit | +| `SEC-03` | Positive | `SEC` | Ensure TrustedProxyHelper supports CIDR ranges in XFF validation | [`IdentityProviderTests.cs:L366`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/IdentityProviderTests.cs#L366) | Backend xUnit | +| `SEC-04` | Positive | `SEC` | WindowsRegistrySecretRetriever securely decrypts DPAPI LocalMachine machine-level encrypted binary values and retrieves plaintext registry strings | [`WindowsRegistrySecretRetrieverTests.cs:L16`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/WindowsRegistrySecretRetrieverTests.cs#L16) | Backend xUnit | +| `SEC-05` | **Guardrail** | `SEC` | Router must not overwrite corrupt encrypted database fields if an update occurs without user reset. | [`ProviderSettingsEncryptionTests.cs:L395`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/ProviderSettingsEncryptionTests.cs#L395) | Backend xUnit | +| `SEC-ADMIN-AUDIT-REDACTION` | Positive | `SEC` | AdminMcpServer redacts sensitive secrets from argument payloads before recording audit logs. | [`AdminMcpServerTests.cs:L619`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/AdminMcpServerTests.cs#L619) | Backend xUnit | +| `TRANS-01` | Positive | `TRANS` | SSE transport resolves static plaintext API keys when provider is None | [`SseTransportTests.cs:L18`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/SseTransportTests.cs#L18) | Backend xUnit | +| `TRANS-02` | Positive | `TRANS` | HTTP stateless transport resolves static API keys when secret provider is None | [`HttpTransportTests.cs:L19`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/HttpTransportTests.cs#L19) | Backend xUnit | +| `TRANS-03` | Positive | `TRANS` | STDIO transport spawns subprocess, handles JSON-RPC initialization and executes tool calls | [`StdioTransportTests.cs:L59`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/StdioTransportTests.cs#L59) | Backend xUnit | +| `UI-01` | Positive | `UI` | Dashboard shows empty filter state when no servers match search term | [`DashboardView.test.tsx:L115`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx#L115) | Frontend Vitest | +| `UI-02` | Positive | `UI` | Inspect modal displays spinner loading state while querying server capabilities | [`ServerInspectModal.test.tsx:L61`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L61) | Frontend Vitest | +| `UI-03` | Positive | `UI` | Grouped server view renders category sections and supports collapsible groups | [`DashboardView.test.tsx:L63`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx#L63) | Frontend Vitest | +| `UI-04` | Positive | `UI` | Tool selector filters available tools by selected backend server | [`ToolTesterCard.test.tsx:L77`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L77) | Frontend Vitest | +| `UI-05` | Positive | `UI` | Router allows customized branding parameters (DashboardTitle, DashboardIcon) to be saved and retrieved via the API. | [`PipelineIntegrationTests.cs:L242`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/McpRouter.Tests/PipelineIntegrationTests.cs#L242) | Backend xUnit | +| `UI-102` | Positive | `UI` | Dashboard renders stats card, connected server list, and setup instructions | [`DashboardView.test.tsx:L1`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/DashboardView.test.tsx#L1) | Frontend Vitest | +| `UI-103` | Positive | `UI` | Interactive tool tester renders server and tool selection dropdowns | [`ToolTesterCard.test.tsx:L1`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ToolTesterCard.test.tsx#L1) | Frontend Vitest | +| `UI-116` | Positive | `UI` | Modal remains hidden when isInspectOpen is false | [`ServerInspectModal.test.tsx:L1`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/components/ServerInspectModal.test.tsx#L1) | Frontend Vitest | +| `UI-124` | Positive | `UI` | Renders main dashboard navigation tabs and layout headers | [`dashboard.spec.ts:L1`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/e2e/dashboard.spec.ts#L1) | Playwright E2E | +| `UI-CONFIRM-MODAL` | **Guardrail** | `UI` | Deletes an access policy when confirmed via confirm modal. | [`usePolicyStore.test.ts:L76`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/stores/usePolicyStore.test.ts#L76) | Frontend Vitest | +| `UI-TOAST-TRANSITION` | **Guardrail** | `UI` | Displays error toast notification when saving invalid JSON credentials for user-provided server. | [`MyMcpServers.test.tsx:L23`](file:////home/steve/.gemini/antigravity-cli/brain/1da572ff-d460-4160-9d3d-a451cae51f49/.system_generated/worktrees/subagent-Arch---Reqs-Writer-doc-writer-f82a384f/frontend/src/test/pages/MyMcpServers.test.tsx#L23) | Frontend Vitest | diff --git a/docs/support-matrix.md b/docs/support-matrix.md new file mode 100644 index 00000000..d4b50b35 --- /dev/null +++ b/docs/support-matrix.md @@ -0,0 +1,59 @@ +# 🧩 Deployment & Authentication Support Matrix + +This matrix outlines the supported combinations of hosting environments, authentication providers, and downstream delegation methods for the MCP Router. + +## 🏗️ Hosting & Authentication Matrix + +| Hosting Environment | Identity / Auth Mechanism | AppKey Support | Downstream Identity Delegation | Notes & Limitations | +| :--- | :--- | :--- | :--- | :--- | +| **Linux Container (Docker)** | **Reverse Proxy Headers** (OIDC, SAML, etc.) | ✅ Supported | **OAuth2 On-Behalf-Of JWT** or **Header Propagation** (`X-Forwarded-User`) | **Requires** configuring `Oidc:TrustedProxies`. Kerberos/NTLM impersonation (`S4U2Proxy`) is **not supported** in Linux containers. | +| **Windows Native (IIS)** | **Active Directory / Windows Auth** | ✅ Supported | **Kerberos/NTLM Impersonation** (`S4U2Proxy`) or **Header Propagation** | Application pool must run as a domain account with constrained delegation rights if using `S4U2Proxy`. | +| **Standalone (Kestrel)** | **AppKey Only** (Machine-to-Machine) | ✅ Supported | **None** (Executes in Router context) | Typically used for automated agents or internal microservices where user context is not applicable. | +| **Any Environment** | **Dynamic Auth Pass-Through** | ✅ Supported | **Direct Target Auth** (`X-Target-Auth`) | Router intercepts 401s from tools and prompts the client/IDE for credentials dynamically. | + +--- + +## 🔑 Authentication Context Limitations + +Depending on how a client authenticates to the MCP Router, different capabilities and downstream restrictions apply. + +### 1. Reverse Proxy (OIDC / Header Auth) +* **Bound Context:** Human User. +* **Capabilities:** + * Provides granular user identity to the router. + * Can be mapped to internal Admin roles via `manage_group_mappings`. + * Fully supports auditing user activity. +* **Limitations:** + * Only works if the proxy IP is explicitly whitelisted in `Oidc:TrustedProxies`. + * If the proxy is bypassed, headers are stripped and access degrades to Guest/Anonymous. + +### 2. AppKey Authentication +* **Bound Context:** Machine / Autonomous Agent. +* **Capabilities:** + * Highly granular scoping (`server:*`, `category:*`, `tool:*`). + * Can act on behalf of a specific owner (via `OwnerSid`). +* **Limitations:** + * Bypasses reverse proxy SSO headers. + * Does not seamlessly map to downstream interactive SSO flows without exchanging for an On-Behalf-Of token. + +### 3. Windows / Active Directory (IIS) +* **Bound Context:** Enterprise Domain User. +* **Capabilities:** + * True Zero-Trust without external identity providers. + * Router natively parses `WindowsIdentity` for rapid access evaluation. +* **Limitations:** + * Tied to Windows Server hosting. Cannot easily port to Linux-based Kubernetes deployments without complex LDAP sidecars or gMSA setups. + +--- + +## 🛡️ Downstream Delegation Support + +When the Router forwards a request to a remote MCP Server, it must authenticate itself. The matrix below shows which outbound delegation strategies work based on your inbound authentication. + +| Inbound Auth Method | Outbound: Header Propagation | Outbound: OAuth2 On-Behalf-Of | Outbound: Kerberos Impersonation | +| :--- | :---: | :---: | :---: | +| **Proxy SSO Header** | ✅ Supported | ✅ Supported | ❌ Not Supported | +| **AppKey** | ⚠️ AppKey Owner Context | ❌ Not Supported (App is not a user) | ❌ Not Supported | +| **Windows Auth (AD)** | ✅ Supported | ❌ Not Supported (No JWT) | ✅ Supported (Windows Only) | + +> 💡 **Recommendation:** For modern containerized deployments, utilize a **Reverse Proxy (OIDC)** and rely on **Header Propagation** (`X-Forwarded-User`) or **OAuth2 Token Exchange** for backend security. Avoid Windows Auth unless migrating legacy domain-bound enterprise systems. diff --git a/docs/user-guide.md b/docs/user-guide.md new file mode 100644 index 00000000..319b585f --- /dev/null +++ b/docs/user-guide.md @@ -0,0 +1,46 @@ +# MCP Router User Guide + +Welcome to the user guide for the MCP Router. This guide helps you manage your user profile, create and manage application keys (AppKeys), and utilize the Interactive Test Bench for testing backend MCP tools. + +## Managing Your Profile + +Users can view and manage their personal details through the dashboard. + +### Viewing Quotas +Your profile defines limits on the number of Application Keys you are allowed to create. You can check your quotas in the dashboard, which displays: +- **Global Maximum Keys**: The absolute limit for active AppKeys across the entire router instance. +- **User Maximum Keys**: The limit for active AppKeys per individual user. +- **Total Active Keys**: The number of active AppKeys currently in the system. +- **Your Active Keys**: The number of AppKeys you have currently provisioned. + +## Managing App Keys + +Application Keys (AppKeys) act as credentials for authenticating with the MCP Router. You can manage your AppKeys under the credentials section of your dashboard. + +### Creating an App Key +1. Click on **Create App Key**. +2. **Name**: Provide a descriptive name for the key. +3. **Scopes**: Define what the key is allowed to access (e.g., `all` or specific categories like `category:database`). +4. **Expiration**: Optionally define the number of days until the key expires. +5. The generated **Client ID** and **Client Secret** (Plaintext Key) will be displayed. + > [!WARNING] + > The Plaintext Key is only shown once at creation. Store it securely! + +### Revoking an App Key +If an AppKey is compromised or no longer needed, you should revoke it immediately. +1. Locate the AppKey in your list. +2. Click **Revoke**. The key will be immediately invalidated and can no longer be used for authentication. + +## Interactive Test Bench + +The Interactive Test Bench allows you to test tool calls against backend MCP servers directly through the router's UI or by calling the router's test endpoints. + +### Using the Test Bench +1. Select a connected backend server from the server list. +2. The UI will automatically fetch and display the available tools provided by the backend server. +3. Select a tool to test. +4. Input the required JSON argument payload for the tool. +5. Click **Execute** or **Call Tool**. The router will handle the protocol handshake and forward your request to the backend server, displaying the result directly in the test bench interface. + +> [!TIP] +> The test bench automatically handles protocol version negotiation (falling back to legacy versions if necessary) and handles the lifecycle of the connection for the duration of the test. diff --git a/frontend/e2e/appkey-and-client-lifecycle.spec.ts b/frontend/e2e/appkey-and-client-lifecycle.spec.ts index 2519da80..802ffa7a 100644 --- a/frontend/e2e/appkey-and-client-lifecycle.spec.ts +++ b/frontend/e2e/appkey-and-client-lifecycle.spec.ts @@ -1,3 +1,5 @@ +/** @requirement UI-129 */ + import { test, expect } from '@playwright/test'; test.describe('AppKey and Client Lifecycle Flow', () => { diff --git a/frontend/e2e/client-setup-and-appkeys.spec.ts b/frontend/e2e/client-setup-and-appkeys.spec.ts index 923abf25..f6fc73e6 100644 --- a/frontend/e2e/client-setup-and-appkeys.spec.ts +++ b/frontend/e2e/client-setup-and-appkeys.spec.ts @@ -1,3 +1,5 @@ +/** @requirement UI-123 */ + import { test, expect } from '@playwright/test'; test.describe('Client Setup & App Key Management Flow', () => { diff --git a/frontend/e2e/dashboard.spec.ts b/frontend/e2e/dashboard.spec.ts index de66b23b..3bfe1418 100644 --- a/frontend/e2e/dashboard.spec.ts +++ b/frontend/e2e/dashboard.spec.ts @@ -1,3 +1,5 @@ +/** @requirement UI-124 */ + import { test, expect } from '@playwright/test'; test.describe('Dashboard & Navigation Flow', () => { diff --git a/frontend/e2e/multi-user-matrix.spec.ts b/frontend/e2e/multi-user-matrix.spec.ts index 93b6cdf8..92828178 100644 --- a/frontend/e2e/multi-user-matrix.spec.ts +++ b/frontend/e2e/multi-user-matrix.spec.ts @@ -1,3 +1,5 @@ +/** @requirement UI-125 */ + import { test, expect } from './fixtures/userContexts'; test.describe('Multi-User Context Matrix Flow (Issue #50)', () => { diff --git a/frontend/e2e/rbac-and-permissions.spec.ts b/frontend/e2e/rbac-and-permissions.spec.ts index 79b233b4..cab5be3b 100644 --- a/frontend/e2e/rbac-and-permissions.spec.ts +++ b/frontend/e2e/rbac-and-permissions.spec.ts @@ -1,3 +1,5 @@ +/** @requirement UI-127 */ + import { test, expect } from '@playwright/test'; test.describe('RBAC Access Control & Policy Modal Flow', () => { diff --git a/frontend/e2e/rbac-enforcement-flow.spec.ts b/frontend/e2e/rbac-enforcement-flow.spec.ts index a61aafaf..c6d07edb 100644 --- a/frontend/e2e/rbac-enforcement-flow.spec.ts +++ b/frontend/e2e/rbac-enforcement-flow.spec.ts @@ -1,3 +1,5 @@ +/** @requirement UI-120 */ + import { test, expect } from '@playwright/test'; test.describe('RBAC Policy and Group/SID Mapping Lifecycle Flow', () => { diff --git a/frontend/e2e/server-inspector.spec.ts b/frontend/e2e/server-inspector.spec.ts index 33c86424..13a9d84c 100644 --- a/frontend/e2e/server-inspector.spec.ts +++ b/frontend/e2e/server-inspector.spec.ts @@ -1,3 +1,5 @@ +/** @requirement UI-126 */ + import { test, expect } from '@playwright/test'; test.describe('Server Inspector Modal Flow', () => { diff --git a/frontend/e2e/server-management.spec.ts b/frontend/e2e/server-management.spec.ts index 2446b564..ac44a7c6 100644 --- a/frontend/e2e/server-management.spec.ts +++ b/frontend/e2e/server-management.spec.ts @@ -1,3 +1,5 @@ +/** @requirement UI-121 */ + import { test, expect } from '@playwright/test'; test.describe('Server Registration & Secret Providers Flow', () => { diff --git a/frontend/e2e/settings.spec.ts b/frontend/e2e/settings.spec.ts index 89e97981..16474a1e 100644 --- a/frontend/e2e/settings.spec.ts +++ b/frontend/e2e/settings.spec.ts @@ -1,3 +1,5 @@ +/** @requirement UI-122 */ + import { test, expect } from '@playwright/test'; test.describe('Settings View Flow', () => { diff --git a/frontend/e2e/testbench.spec.ts b/frontend/e2e/testbench.spec.ts index 352321cf..5623dc27 100644 --- a/frontend/e2e/testbench.spec.ts +++ b/frontend/e2e/testbench.spec.ts @@ -1,3 +1,5 @@ +/** @requirement UI-128 */ + import { test, expect } from '@playwright/test'; test.describe('Test Bench View Flow', () => { diff --git a/frontend/src/components/settings/CustomFileModal.tsx b/frontend/src/components/settings/CustomFileModal.tsx index 5e4d6772..c5777ddb 100644 --- a/frontend/src/components/settings/CustomFileModal.tsx +++ b/frontend/src/components/settings/CustomFileModal.tsx @@ -1,19 +1,8 @@ import React, { useState } from 'react'; import { useSettingsStore } from '../../stores/useSettingsStore'; import { showToast } from '../../stores/useToastStore'; +import { VisualPromptBuilder, ArgBuilderItem, MsgBuilderItem } from './VisualPromptBuilder'; -interface ArgBuilderItem { - id: string; - name: string; - description: string; - required: boolean; -} - -interface MsgBuilderItem { - id: string; - role: 'user' | 'assistant'; - text: string; -} const initializePromptBuilder = (meta: { type: 'prompts' | 'resources'; name: string } | null, content: string) => { if (meta) { @@ -135,30 +124,6 @@ const CustomFileModalDialog: React.FC = () => { setActiveFileModalTab(tab); }; - const addArgument = () => { - setBuilderArgs([...builderArgs, { id: Math.random().toString(), name: '', description: '', required: false }]); - }; - - const removeArgument = (id: string) => { - setBuilderArgs(builderArgs.filter((a) => a.id !== id)); - }; - - const updateArgument = (id: string, field: keyof ArgBuilderItem, val: any) => { - setBuilderArgs(builderArgs.map((a) => (a.id === id ? { ...a, [field]: val } : a))); - }; - - const addMessage = (role: 'user' | 'assistant') => { - setBuilderMsgs([...builderMsgs, { id: Math.random().toString(), role, text: '' }]); - }; - - const removeMessage = (id: string) => { - setBuilderMsgs(builderMsgs.filter((m) => m.id !== id)); - }; - - const updateMessageText = (id: string, text: string) => { - setBuilderMsgs(builderMsgs.map((m) => (m.id === id ? { ...m, text } : m))); - }; - const handleSave = async (e: React.FormEvent) => { e.preventDefault(); if (!fileName.trim()) { @@ -277,138 +242,14 @@ const CustomFileModalDialog: React.FC = () => { /> ) : ( -
-
- - setPromptDesc(e.target.value)} - /> -
- -
-

- Prompt Arguments / Variables -

- -
- - {builderArgs.length === 0 ? ( -
- No arguments defined. -
- ) : ( -
- {builderArgs.map((arg) => ( -
- updateArgument(arg.id, 'name', e.target.value)} - style={{ fontSize: '12px' }} - /> - updateArgument(arg.id, 'description', e.target.value)} - style={{ fontSize: '12px' }} - /> - - -
- ))} -
- )} - -
-

- Messages Sequence -

-
- - -
-
- -
- {builderMsgs.map((msg, index) => ( -
-
- - #{index + 1} {msg.role.toUpperCase()} - - -
-