Skip to content

feat: MCP Gateway — external server proxy with scoped management#29

Merged
christianromeni merged 6 commits intomainfrom
feat/mcp-gateway
Mar 28, 2026
Merged

feat: MCP Gateway — external server proxy with scoped management#29
christianromeni merged 6 commits intomainfrom
feat/mcp-gateway

Conversation

@christianromeni
Copy link
Copy Markdown
Contributor

Summary

Complete MCP Gateway: register, proxy, and manage external MCP servers with scoped access control, session management, usage tracking, and YAML config sync.

MCP Gateway

  • Proxy JSON-RPC to external MCP servers via /api/v1/mcp/:alias
  • Automatic session management (initialize + Mcp-Session-Id forwarding)
  • SSE + JSON response handling from upstream servers
  • Prometheus metrics for tool calls, duration, transport errors
  • Async usage logger for tool call tracking

Server Management

  • Scoped registration: global (system_admin), org (org_admin), team (team_admin)
  • Alias shadowing: team > org > global priority resolution
  • YAML config sync at startup (source=yaml preserved, source=api never overwritten)
  • Activate/deactivate endpoints
  • Test connection with session-aware probe
  • SSRF protection at registration + dial time (DNS rebinding safe)

UI

  • MCP Servers page under Manage (all roles can view)
  • Scope selector in Create dialog (role-dependent options)
  • Source badges (YAML/API), YAML servers read-only
  • Auth Type as TabSwitcher
  • Toast update() for test connection progress

Access Control

  • Org/team/key allowlists for global servers (most-restrictive-wins)
  • Scoped servers implicitly accessible within their scope
  • Separate read vs write permission checks

Test plan

  • go test ./... -race — 19 packages, 75+ MCP Gateway tests
  • npx tsc --noEmit && npm run lint
  • Create global MCP server via API + YAML
  • Proxy tool call → reaches external server with session
  • Test Connection → shows loading toast → success/error
  • YAML sync: restart preserves YAML servers, skips API-created
  • Scoped access: org server not visible to other orgs

Backend for proxying JSON-RPC requests to registered external MCP servers
with full access control, usage tracking, metrics, and SSRF protection.

Gateway:
- POST /api/v1/mcp/:alias routes to built-in voidllm or external servers
- HTTP transport with Bearer/Header auth (AES-256-GCM encrypted tokens)
- Access control: org/team/key allowlists (most-restrictive-wins)
- Prometheus metrics: tool_calls_total, duration, transport_errors
- Async usage logger: buffered channel, batch flush, drop-on-full

Server CRUD (system_admin only):
- POST/GET/PATCH/DELETE /api/v1/mcp-servers
- Test connection endpoint (tools/list probe)
- Alias validation (reserved "voidllm", URL-safe format)

Security:
- SSRF protection: blocks private IPs, localhost, cloud metadata by default
- Configurable: allow_private_urls for internal deployments (YAML only)
- auth_header blocklist (prevents Host/Content-Type override)
- Prometheus cardinality protection (unknown methods → "unknown" label)
- Response body limits on test connection (10MB)
- Error message sanitization

DB: migration 0004 — mcp_servers, org/team/key_mcp_access, mcp_tool_calls
Tests: transport, DB CRUD, admin API, proxy integration
MCP servers can now be registered at three scopes:
- Global (system_admin) — available to all orgs via access control
- Org-scoped (org_admin) — visible only within the org
- Team-scoped (team_admin) — visible only within the team

Alias shadowing: team > org > global priority in proxy resolution.
Scoped servers are implicitly accessible — no access control entries
needed. Global servers still use explicit org/team/key allowlists.

API:
- POST/GET /api/v1/orgs/:org_id/mcp-servers (org-scoped)
- POST/GET /api/v1/orgs/:org_id/teams/:team_id/mcp-servers (team-scoped)
- GET/PATCH/DELETE /api/v1/mcp-servers/:id (ownership-based RBAC)
- DNS rebinding protection at transport layer
- PostgreSQL-compatible migration (coalesce)
- MCP Servers page under Manage (all roles can view, team_admin+ can create)
- Scope selector: Global/Org/Team based on caller's role
- Create/Edit/Delete dialogs with Test Connection
- Scope badges, auth type badges, active toggle

UI improvements:
- Auth Type, Key Type, Invite Role use TabSwitcher instead of Select
  (all options visible without dropdown scroll)
- SA Create: Team selector moved to top (avoids scroll on dropdown)
- MCP Servers query only fires for caller's role (no 403 retries)
- MCP Servers page under Manage with scoped create (global/org/team)
- Create/Edit/Delete dialogs, Test Connection, scope/auth badges
- HTTP transport accepts both JSON and SSE responses (auto-detect)
- TabSwitcher className prop (replaces -mb-4 hack)
- Auth Type, Key Type, Invite Role use TabSwitcher instead of Select
- SA Create: Team selector moved to top
- Query enablement: no 403 retries for non-system-admin

Known limitation: MCP session management not yet implemented —
stateful MCP servers (most real-world servers) require Mcp-Session-Id
header forwarding after initialize. Next commit.
Session management:
- Auto-initialize upstream MCP sessions on first request
- Mcp-Session-Id header forwarded on all subsequent requests
- Session expired (404) triggers automatic re-initialize + retry
- One session per MCP server per VoidLLM instance (in-memory sync.Map)
- ListTools() now handles full initialize + notification + tools/list flow

UI:
- Toast update() function — replaces existing toast content by ID
- Test Connection shows loading toast that transitions to success/error
- Test timeout increased to 30s (some MCP servers are slow)
YAML sync:
- mcp_servers in voidllm.yaml synced to DB at startup (source=yaml)
- API-created servers (source=api) preserved, never overwritten
- Auth tokens encrypted with server-ID-bound AAD

Session management:
- Sessions keyed by alias+orgID (cross-org isolation)
- Concurrent re-init protected by global mutex with double-check
- Metrics use bounded method labels (cardinality protection)

API:
- PATCH /mcp-servers/:id/activate and /deactivate endpoints
- Separate read vs write permission checks (GetMCPServer relaxed)
- Source field in response + UI (YAML servers shown as read-only)

Config:
- MCPServerConfig type with validation (alias, URL, auth_type)
- mcp_servers YAML section documented in configuration.md

Tests: session forwarding, YAML sync (6), config validation (13),
source field (2), auto-initialize flow
@christianromeni christianromeni merged commit f33d654 into main Mar 28, 2026
5 checks passed
@christianromeni christianromeni deleted the feat/mcp-gateway branch March 28, 2026 01:31
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant