A distributed platform for managing, executing, and proxying MCP (Model Context Protocol) servers. It handles tool discovery, authentication, and deployment of MCP server instances across containerized environments, and serves as the gateway for clients like Cursor, LibreChat, and Claude Desktop.
High-Level Design
The platform is a set of cooperating Go microservices behind a single MCP endpoint, coordinated through an event-driven architecture:
- Proxy: the MCP protocol handler. Speaks pure JSON-RPC 2.0 over HTTP (protocol versions 2024-11-05 and 2025-03-26), validates JWT bearer tokens via JWKS, discovers and filters tools per user, and routes
tools/callrequests to the right running server instance. - Registry: the source of truth. Catalogs server metadata, group-based access rules, secret templates, and tenant quotas in PostgreSQL; publishes lifecycle events when servers are created, updated, or deleted.
- Execution: the orchestrator. Consumes registry events and provisions MCP server instances on a pluggable backend (Nomad or Kubernetes, selected per server), with Consul handling service registration and health checks.
- Identity: the federation gateway. Fronts an IDP federation layer (Ory Polis) so each customer tenant can bring their own identity provider (Keycloak, Azure AD, Okta, Google Workspace) over SAML or OIDC, and runs a SCIM-based directory sync worker that provisions users and groups automatically.
State changes propagate through Redis Streams events (registry.server.approved, execution.server.started, …) rather than direct service-to-service calls. Server and tool caches are persistent, with no expiry-based invalidation, and updated purely by events, with a periodic Consul reconciliation pass as a safety net against missed events.
Architecture
flowchart TB
clients["MCP Clients<br/>Cursor · Claude Desktop · LibreChat"]
web["Web App (React)<br/>role-based UI: users · tenant admins · operators"]
proxy["Proxy Service<br/>MCP protocol · auth · routing"]
registry["Registry Service<br/>metadata · groups · tenants"]
redis[("Redis<br/>Streams + cache")]
pg[("PostgreSQL<br/>tenant-scoped")]
vault[("Vault<br/>tenant-isolated secrets")]
exec["Execution Service<br/>multi-backend dispatch"]
orch["Nomad / Kubernetes"]
mcp["MCP Server Instances<br/>HTTP-native & stdio-wrapped"]
consul["Consul<br/>discovery + health checks"]
identity["Identity Service<br/>directory sync worker"]
polis["IDP Federation<br/>SAML · OIDC · SCIM"]
idp["Customer IDPs<br/>Keycloak · Azure AD · Okta"]
clients -- "JSON-RPC 2.0 / HTTP" --> proxy
web -- REST --> registry
web -- REST --> proxy
registry --> pg
registry -. events .-> redis
redis -. events .-> exec
redis -. events .-> proxy
proxy -- "tool calls" --> mcp
proxy --> vault
exec --> orch
orch -- schedules --> mcp
mcp -- registers --> consul
consul -. reconciliation .-> exec
identity -- polls --> polis
identity --> pg
idp -- "SCIM 2.0 push" --> polis
idp <-- "SAML / OIDC login" --> polis
Data Flows
Server provisioning: an admin imports a server from the official MCP registry; the registry stores it and, on approval, publishes registry.server.approved; the execution service picks the configured backend (Nomad job or Kubernetes Deployment + Service), starts the container, and registers it in Consul; execution.server.started then flows back through Redis so the proxy adds the new tools to its cache. The next tools/list from any client sees them: no polling, no restarts.
sequenceDiagram
participant W as Web App
participant R as Registry
participant E as Execution
participant O as Nomad / K8s
participant C as Consul
participant P as Proxy
W->>R: import server
R--)E: registry.server.approved (Redis Streams)
E->>O: schedule instance
O->>C: register service + health check
E--)P: execution.server.started (Redis Streams)
Note over P: cache updated, tools visible<br/>on next tools/list
Tool execution: a client POSTs a JSON-RPC tools/call to /mcp; the proxy validates the JWT, resolves the tool name to a server instance from its cache, checks group permissions and tenant isolation, injects user secrets from Vault if the server requires them, and forwards the call to the instance’s registered endpoint.
User-specific discovery: servers requiring credentials (e.g. a GitHub server needing a personal token) are excluded from the global tool list. When a user authenticates, the proxy checks Vault for that user’s secrets and merges matching servers into their personal tool list, with secrets injected per-request from templates. Two users of the same GitHub server see their own repositories.
Identity & directory sync: customer IDPs push users and groups over SCIM 2.0 to the federation layer; a sync worker polls and reconciles them into PostgreSQL per tenant, so group-based tool access stays current as people join and leave the organization.
Multi-Tenancy & Security
Single shared deployment serves all tenants. Isolation is enforced at every layer: the tenant ID is derived from the user’s email domain and carried as a JWT claim, every database query filters on tenant_id, Redis cache keys are tenant-scoped, and Vault policy templates confine each tenant to secret/{tenant}/* paths; cross-tenant secret reads fail at the policy level, not in application code. Tenant onboarding includes DNS TXT-record domain verification before SSO can be configured.
A React web app fronts the platform, with features shown or hidden based on the roles and groups carried in the user’s JWT. End users get a self-service area to manage their profile, enable or disable servers, supply their own authentication secrets (stored in Vault), and browse the tools, prompts, and resources their groups grant them. Tenant admins land in a management console for servers, users, SSO connections, and directory sync, and platform operators get system-level administration: tenant onboarding with the DNS verification flow, quotas, and a fleet-wide view of servers and users across all tenants. The platform supports protocol versioning so multiple MCP server implementations, HTTP-native and stdio-based (wrapped via an HTTP↔stdio gateway), coexist behind a single endpoint.
Freelance/contract project, March 2025 – June 2026.