Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4c21334
chore: update GitHub Actions to latest versions and fix AGENTS.md link
Aug 22, 2026
0779cc1
chore: sweep auth vulnerabilities and add security warnings
Aug 22, 2026
acbfd99
chore(frontend): sweep frontend, split large components and add missi…
Aug 22, 2026
fe166ea
Refactor routing managers to partials and annotate tests
Aug 22, 2026
8172bb8
fix(frontend): remove REQ- prefix from requirement IDs and update cat…
Aug 22, 2026
01c29b1
fix(secrets): improve url validation and UI error handling for secret…
Aug 22, 2026
c7ec44c
Merge branch 'chore/sweep-frontend' into chore/repo-health-sweep-phase1
Aug 22, 2026
1f35c35
Merge branch 'chore/sweep-auth' into chore/repo-health-sweep-phase1
Aug 22, 2026
cfde152
Merge branch 'chore/sweep-secrets' into chore/repo-health-sweep-phase1
Aug 22, 2026
6440bb1
Merge branch 'chore/sweep-infra' into chore/repo-health-sweep-phase1
Aug 22, 2026
20d8be8
Fix CI failures in C# and React tests
Aug 22, 2026
d25d5c2
chore: refactor exception handling to prevent leaking sensitive info
Aug 22, 2026
63eb632
Fix NRE in unit tests and restore validation message actionability
Aug 22, 2026
62dd47d
docs: Update architecture flow diagrams and improve test requirements
Aug 22, 2026
f5c16ff
docs: add user, admin, and admin-mcp guides
Aug 22, 2026
5477893
docs: add missing features to features-guide.md
Aug 22, 2026
c20379b
docs: create deployment guide and support matrix
Aug 22, 2026
6a6aa78
Fix CodeQL Path Traversal warnings in CapabilityEndpoints
Aug 22, 2026
6421dff
Fix CodeQL Path Traversal warnings in CapabilityEndpoints
Aug 22, 2026
a2c2e0d
Fix CodeQL Path Traversal warnings with Path.GetFileName
Aug 22, 2026
aace86f
Fix CodeQL Path Traversal warnings with Path.GetFileName
Aug 22, 2026
8dba5c7
chore(release): bump version to 4.28.0
Aug 22, 2026
ad853fd
Merge pull request #111 from spelech/chore/repo-health-sweep-phase2
spelech Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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'

Expand All @@ -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: |
Expand All @@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 6 additions & 6 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@ 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 }}
password: ${{ secrets.GITHUB_TOKEN }}

- 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: |
Expand All @@ -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: |
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
19 changes: 19 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ For summary details and quick references, see [README.md](README.md).

| Version | Release Date | Summary of Key Changes |
| :--- | :--- | :--- |
| **`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 |
Expand Down
21 changes: 14 additions & 7 deletions Components/AppKeys/AppKeysController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ public async Task<IActionResult> GetAppKeys([FromQuery] string? keyType = null,
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -148,7 +149,8 @@ public async Task<IActionResult> GetAppKeysLimits()
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -311,7 +313,8 @@ await _auditLogger.LogAdminActionAsync(
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -351,7 +354,8 @@ await _auditLogger.LogAdminActionAsync(
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand All @@ -370,7 +374,8 @@ public async Task<IActionResult> GetUserQuotas()
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -405,7 +410,8 @@ await _auditLogger.LogAdminActionAsync(
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -435,7 +441,8 @@ await _auditLogger.LogAdminActionAsync(
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down
9 changes: 6 additions & 3 deletions Components/AppKeys/UserCredentialsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public async Task<IActionResult> GetConfiguredCredentials()
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -81,7 +82,8 @@ public async Task<IActionResult> SaveCredential(string serverId, [FromBody] Save
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand All @@ -103,7 +105,8 @@ public async Task<IActionResult> DeleteCredential(string serverId)
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}
}
Expand Down
18 changes: 12 additions & 6 deletions Components/Authorization/PermissionsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public async Task<IActionResult> GetPolicies()
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -98,8 +99,9 @@ WHEN NOT MATCHED THEN
}
catch (Exception ex)
{
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.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." });
}
}

Expand All @@ -117,8 +119,9 @@ public async Task<IActionResult> DeletePolicy(string id)
}
catch (Exception ex)
{
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.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." });
}
}

Expand All @@ -136,7 +139,8 @@ public async Task<IActionResult> GetMappings()
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.CreateLogger(GetType().Name)?.LogError(ex, "An unexpected error occurred.");
return StatusCode(500, new { error = "An unexpected error occurred." });
}
}

Expand Down Expand Up @@ -191,8 +195,9 @@ WHEN NOT MATCHED THEN
}
catch (Exception ex)
{
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.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." });
}
}

Expand All @@ -210,8 +215,9 @@ public async Task<IActionResult> DeleteMapping(string id)
}
catch (Exception ex)
{
HttpContext?.RequestServices?.GetService<Microsoft.Extensions.Logging.ILoggerFactory>()?.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." });
}
}
}
Expand Down
Loading
Loading