MCP GOVERNANCE. ONE REGISTRY, ONE SCOPE, ONE VALIDATOR.
Each MCP server is its own OAuth2 resource server with its own audience, scope list, and JWT secret. A boot-time validator refuses misconfigured servers. One executor writes every audit row.
Per-Server OAuth Scoping
When one agent compromises its credentials, the rest of the fleet should stay shut. Each MCP server in systemprompt.io is a separate OAuth2 resource server with its own audience, its own scope list, and its own JWT secret. Rotating the database server's client secret leaves the file server untouched. One agent losing its token does not give the attacker the whole tool surface.
At request time, every bearer token is checked against the server it is calling. The middleware in front of every MCP tool dispatch loads that server's OAuth config from the registry, validates the JWT signature and expiry, checks the audience claim, and compares required scopes against the caller's permission set before any handler runs. Scope elevation is blocked by a hierarchy check on the permission model, so a caller holding a narrower permission cannot satisfy a higher-privileged scope. Unauthenticated requests to a server that requires OAuth return a structured MCP error. The middleware never silently downgrades to anonymous.
Proxy-verified traffic takes a second, explicit path. A trusted upstream proxy that has already authenticated the user passes identity through x-proxy-verified, x-user-id, and x-user-permissions headers, and the middleware validates the proxy signature before accepting those headers. The authentication result is authenticated or anonymous, never an ambiguous middle state. An auditor running one query over the execution log lists every request that reached a given MCP server and the exact scopes that let it through.
- Independent Audience per Server — Each server defines its own OAuth audience and scope list, so revoking access to one MCP server leaves every other server untouched. Without this, one stolen token exposes the entire MCP fleet.
- Scope Elevation Blocked at the Door — A hierarchy check on the permission model means a caller holding a narrower permission cannot satisfy a higher-privileged scope, so an agent with read access cannot quietly invoke a write tool.
- Per-Server Credential Rotation — JWT secret, issuer, and audience list are configured per server, so rotating one MCP server's client secret on Monday does not require coordinating a fleet-wide deploy.
- rbac.rs enforce_rbac_from_registry loads per-server OAuth config and validates JWT audience and scope.
- auth.rs validate_jwt_token with HS256 decoding, per-server issuer check, and audience validation.
- validator.rs#L96-L114 validate_oauth_configs rejects any enabled server requiring OAuth with an empty scope list.
- permission.rs#L93 Permission::implies, the hierarchy check that prevents scope elevation.
- session_manager.rs Database-backed session manager persisting per-session identity and activity across restarts.
Subprocess Tool Execution
If one tool call path bypasses the audit writer, the audit trail is a lie. Every MCP tool invocation in systemprompt.io passes through a single executor that serialises the input, generates a unique execution id, calls the handler, records status (success or failure), and persists the full execution record before returning. There is no second path. The audit table is the whole truth because the executor is the only caller that writes to it.
Tool contracts are validated before the binary ships. Input and output types for every MCP tool must implement schema-generating traits at compile time, so a mismatched schema fails the build, not a customer call. The output schema tags each result with a typed artifact variant. Text, table, chart, dashboard, and more each carry their own validation. A CISO asking "what are the possible output shapes of our MCP fleet?" gets a real answer from the compiled binary, not a wiki.
Sessions survive restarts by design. The MCP session manager writes session state to PostgreSQL, so a process restart does not lose identity. If a client reconnects after an in-memory session has been wiped, the session manager sees the database record, detects the gap, and signals the client to reconnect cleanly instead of silently dropping the tool call. Activity tracking on every session gives idle timeout a reliable source of truth. An auditor querying the execution table can trace every tool call from a session that started three days ago.
- One Executor, One Audit Row — Every tool call is wrapped by the executor, which writes the execution id, tool name, server name, and status before the response returns. Any tool path that dodges the wrapper is a gap in the audit trail.
- Compile-Time Schema Contracts — Input and output types implement schema-generating traits at compile time, so a broken tool contract fails the build instead of reaching a customer. Invalid outputs cannot become runtime incidents the auditor only sees later.
- Sessions Survive Restarts — Session state is persisted to PostgreSQL, so a rolling restart does not lose identity or force clients into anonymous retries. Every deploy avoids a coordinated logout for every connected agent.
- tool.rs#L18-L48 McpToolExecutor::execute, the single dispatch path for every MCP tool call.
- schema.rs McpOutputSchema trait with artifact variants and validated_schema() generation.
- rbac.rs RBAC enforcement with proxy-verified auth and scope validation before handler dispatch.
- session_manager.rs DatabaseSessionManager with persist_create, persist_close, and resume semantics.
- hooks.rs PreToolUse, PostToolUse, and PostToolUseFailure extension points called around every execution.
- mcp_tool_executions.sql The tool execution audit table schema that every call writes to via the executor.
MCP Server Registry
When a security lead asks how many MCP servers are running in production and what each one requires, nobody should be grepping config files. systemprompt.io exposes the full fleet from a single registry service. One call returns every enabled MCP server with its name, port, OAuth requirements, schema definitions, tool configuration, and environment variables. Internal servers (compiled binaries) and external servers (remote endpoints) sit in the same list. Dev-only servers are filtered out in cloud mode so a staging binary cannot accidentally appear in a production inventory.
A misconfigured server never boots. The registry validator runs checks before any server starts. A port-conflict pass uses a hash set to catch duplicate port assignments across enabled servers so two internal servers cannot silently fight over the same socket. Internal servers must have ports above the privileged range (an MCP binary has no business binding a privileged port) and the crate path must exist on disk. OAuth-required servers with an empty scope list are rejected, because an OAuth server without scopes accepts every token. Internal servers must have binaries and external servers must have remote endpoints, catching copy-paste errors in the registry file before they reach users. All checks must pass or no servers start.
The registry is not a read-only catalogue. It implements three traits that the rest of the binary calls. Server discovery (list, find, exists), tool enumeration (list tools, load tools for a given caller's servers), and external-consumer lookup for the admin surface. The MCP port range defaults to a dedicated window wide enough for a fleet that grows without colliding with common development services. A staff engineer wanting to verify a specific claim opens the validator file at the line numbers cited below.
- One Call, Full Fleet — The registry returns every enabled MCP server with name, port, OAuth config, schemas, tools, and environment variables in a single lookup. An auditor enumerating the MCP surface no longer pieces it together from config files.
- Boot-Time Validation — Port conflicts, server configuration, OAuth scope presence, and server type consistency all pass, or the fleet refuses to start. Each pass maps to a specific failure mode the validator has seen in practice. A broken registry entry cannot ship to production and first present as a 500 to a live caller.
- Port Isolation With Room to Grow — The MCP port range is sized so the fleet adds servers without renegotiating ports, with the conflict detector enforcing one server per port. Port collisions stop becoming intermittent outages that tail-follow one service at a time.
- manager.rs#L13-L72 RegistryService::get_enabled_servers_as_config builds the full McpServerConfig list from global configuration.
- settings.rs#L59-L61 default_mcp_port_range returns the MCP fleet port window.
- validator.rs Validator for port conflicts, server configs, OAuth scopes, and server types.
- trait_impl.rs McpRegistry, McpToolProvider, and McpRegistryProvider trait implementations.
- deployment.rs McpServerType (Internal/External) and deployment configuration.
- port_manager.rs Port preparation, cleanup, and wait_for_port_release_with_retry.
- proxy_health.rs Health check that reports which servers can route traffic and which cannot.
Fail-Loud Boot Gate
A YAML typo should not become a production incident. When an operator edits the MCP registry, the server-level validator checks every structural rule before anything starts. Internal servers must have ports above the privileged range and crate paths that exist on disk. Display names and descriptions must be non-empty so the fleet inventory is readable. External servers must have remote endpoints and no binary references. A server that fails any check does not ship, and the operator sees the specific line that failed.
Tool schemas are validated against the database before the servers accept a call. The schema sync step creates a schema validator (strict, warn, skip), runs it against every server's declared schemas, creates missing tables when configured to, and collects error messages into a structured validation report. Validated count, created count, and error messages all land in one place. If any server fails schema validation in strict mode, orchestration halts so a broken schema does not quietly accept writes to the wrong table.
Agent-tool mappings are declared, not inferred. Every tool an agent can reach is named in the plugin manifest by the plugin variable definition, with a secret flag and a required flag per variable. If a tool is not declared in the manifest, it does not exist for that agent. No implicit permissions, no discovery surprises. A staff engineer checking "what tools can this plugin call?" reads the manifest and knows the answer is complete. Runtime service status (health, pid, uptime, tool count, latency) is captured per server so declared configuration can be compared against what is actually running.
- Structural Rules Enforced at Boot — Port above the privileged range, crate path exists, display name present, external endpoint set. A server failing any check refuses to start and names the specific failure. A YAML typo cannot become a late-night page.
- Schema Validation With Three Modes — Strict halts on any schema error, warn logs and continues, skip is for tests. Validated count, created count, and error list all land in one structured report. A schema drift can no longer silently accept writes against the wrong table.
- Declared Mappings Only — Agent-tool access is named in the plugin manifest, with secret and required flags per variable. An undeclared tool is not reachable for that agent, so discoverable tools never become accidental permissions.
- validator.rs Server-level validator covering port range, crate path, display name, and server-type constraints.
- schema_sync.rs validate_and_migrate_schemas with SchemaValidator (strict/warn/skip modes) and SchemaValidationReport.
- plugin.rs#L49-L59 PluginVariableDef declares per-plugin scoped variables with name, secret flag, and required flag.
- service_validation.rs validate_service with connection testing and status reporting.
- status.rs ServiceStatus struct with health, PID, uptime, tools count, and latency.
- trait_impl.rs McpDeploymentProviderImpl exposing the MCP protocol version for version-aware validation.
Managed MCP Server Lifecycle
A server that half-starts is worse than a server that refuses to start. The lifecycle manager coordinates a strict startup sequence. Verify the binary exists on disk, prepare the port (retrying with backoff so a stuck process from the last deploy does not block the new one), spawn the process, then run health checks. Port cleanup retries with backoff to give the OS time to release a socket without turning into an infinite loop. Health checks run with capped attempts and bounded delay, budgeted to catch a slow-starting MCP server without pinning a deploy for minutes. Only after a healthy check does the server register in the database with its pid and start time. A failed health sequence never reports "running" to the fleet.
Shutdown follows the inverse path. The stop routine finds the process (checking both the recorded pid and the bound port so a restarted pid cannot hide a live socket), sets status to stopping, sends SIGTERM for graceful exit, waits for the process to quit, then sends SIGKILL if anything survives. The finalise step updates database status, clears the pid, and releases port resources. A stale entry cleanup path handles servers that are already stopped but have lingering rows, so a crashed server does not leave phantom state behind.
Reconciliation runs on every boot. The orchestrator clears stale and crashed service entries, deletes servers that are no longer in configuration, validates schemas, kills orphaned processes with no matching registry record, kills processes running an outdated binary, stops every running server, and then starts the pending fleet fresh. Continuous health monitoring tracks failure counts and downtime duration per server so an operator paged at 3am sees "this server has been unhealthy, three checks in a row" instead of a raw 500. The control plane shows one process owning startup, shutdown, and recovery for every MCP server.
- Controlled Startup — Binary check, port preparation with bounded cleanup retries, process spawn, then health attempts with capped delay. Database registration only follows a healthy status, so a server never reports running before it can serve.
- Graceful Shutdown — SIGTERM first, grace period, SIGKILL if the process survives, then port and state cleanup. A stuck process cannot hold its port and block the next deploy.
- Boot-Time Reconciliation — Stale entries cleared, orphaned processes killed, outdated binaries killed, schemas validated, fleet restarted fresh. Continuous health tracking records failure counts and downtime. Zombie state from the last deploy cannot silently degrade the current one.
- startup.rs#L53-L97 Start-server health loop with bounded attempt count and capped delay.
- shutdown.rs stop_server with graceful SIGTERM, wait, then SIGKILL, followed by finalize_shutdown.
- health.rs (lifecycle) check_server_health with process detection and error marking.
- restart.rs restart_server with verify_clean_state preventing overlapping lifecycles.
- reconciliation.rs Boot-time reconciliation for orphan detection, stale-binary cleanup, and fleet restart.
- health.rs (monitoring) HealthStatus, HealthCheckResult, and continuous monitoring with failure-count tracking.
- port_manager.rs#L4-L6 Port cleanup constants sized to let the OS release a socket without an infinite retry.
- proxy_health.rs Proxy health with can_route_traffic and get_routable_services for per-server routing.
Founder-led. Self-service first.
No sales team. No demo theatre. The template is free to evaluate — if it solves your problem, we talk.
Who we are
One founder, one binary, full IP ownership. Every line of Rust, every governance rule, every MCP integration — written in-house. Two years of building AI governance infrastructure from first principles. No venture capital dictating roadmap. No advisory board approving features.
How to engage
Evaluate
Clone the template from GitHub. Run it locally with Docker or compile from source. Full governance pipeline.
Talk
Once you have seen the governance pipeline running, book a meeting to discuss your specific requirements — technical implementation, enterprise licensing, or custom integrations.
Deploy
The binary and extension code run on your infrastructure. Perpetual licence, source-available under BSL-1.1, with support and update agreements tailored to your compliance requirements.