The open source, self-hosted MCP gateway
Octelium is a free and open source, self-hosted zero trust access platform. Its MCP mode turns a Service into an identity-aware Model Context Protocol gateway: it parses JSON-RPC messages, authorizes each tool call against the identity behind it, injects the upstream credential, and records what was called with which arguments.
- Free and open source
- Designed for self-hosting
- Streamable HTTP MCP servers
kind: Service
metadata:
name: tools-mcp
spec:
mode: MCP
isPublic: true
config:
upstream:
url: http://mcp.internal:8080
mcp:
endpoint: /mcp
protocol:
versions: ["2026-07-28"]
requireVersion: truekind: Policy
metadata:
name: finance-tools
spec:
rules:
- effect: ALLOW
condition:
all:
of:
- match: ctx.request.mcp.method == "tools/call"
- match: ctx.request.mcp.name == "transfer"
- match: '"finance" in ctx.user.spec.groups'
- effect: ALLOW
condition:
match: ctx.request.mcp.method == "tools/list"kind: Service
metadata:
name: tools-mcp
spec:
mode: MCP
isPublic: true
config:
upstream:
container:
port: 8080
image: ghcr.io/acme/mcp:1.4.0
replicas: 2
mcp:
endpoint: /mcpA unified secure access platform
An MCP gateway is one mode of the same platform. Each capability is described in detail below, in the same order.
- Unified access platform
One architecture for client-based zero-config VPN-like access over WireGuard and QUIC tunnels and clientless access over browsers for humans and OAuth2 access for workloads.
Read more - L7-aware access control
Identity-based, application-layer aware ABAC access control with policy-as-code via CEL and OPA, applied on a per-request basis.
Read more - Secretless access
Dynamic secretless access where teams and AI agents access protected infrastructure without ever holding a credential.
Read more - Unified identity management
Teams, workloads and AI agents share the same model for identity management, access control, and auditing.
Read more - Real-time visibility and auditing
OpenTelemetry-native, real-time, application-layer-aware visibility and structured logging.
Read more - GitOps-friendly management
Octelium is administered like Kubernetes. Define your resources in YAML files, store them in a Git repository.
Read more
One architecture for every MCP server you run
Each MCP server is represented by a Service, served by an identity-aware proxy that abstracts away the network details of the upstream behind it. The same Service is reachable both ways, whichever environment the server happens to live in.
- Published clientlessly over HTTPS, the Service URL is what an agent points at, authenticating with standard OAuth2 client credentials or a bearer token and no client, SDK or agent of any kind.
- Reached over a client-based WireGuard or QUIC tunnel, the same Service has a stable private address and DNS name, which keeps an internal MCP server entirely off the public internet.
- Upstreams can be MCP servers behind NAT in any environment, publicly hosted servers protected by a credential, or containers that the Cluster deploys and scales itself.
kind: Service
metadata:
name: internal-mcp
spec:
mode: MCP
config:
upstream:
url: http://10.0.6.20:8080
mcp:
endpoint: /mcpkind: Service
metadata:
name: internal-mcp
spec:
mode: MCP
isPublic: true
config:
upstream:
url: http://10.0.6.20:8080
mcp:
endpoint: /mcpkind: Service
metadata:
name: tools-mcp
spec:
mode: MCP
isPublic: true
config:
upstream:
url: https://mcp.example.com
mcp:
endpoint: /mcp
cors:
allowOriginStringMatch:
- https://client.example.comLayer-7 aware access control with policy-as-code
Access is authorized on a per-request basis, using CEL or Open Policy Agent, against the identity behind the request and the JSON-RPC message itself rather than the network path it arrived on.
- The method and the tool, prompt or resource name are separate normalized fields, so discovery can be open to everyone while individual tools are restricted to the identities that should call them.
- The parsed JSON-RPC body is part of the request context, including tool arguments with no normalized field, which lets a policy bound an argument rather than allow a tool outright.
- Protocol validation runs independently of policy. Accepted versions are an allowlist, a declared version can be required, and methods the running Cluster does not recognize can be rejected.
kind: Policy
metadata:
name: search-only
spec:
rules:
- effect: ALLOW
condition:
all:
of:
- match: ctx.request.mcp.method == "tools/call"
- match: ctx.request.mcp.name == "search_docs"
- match: '"agents" in ctx.user.spec.groups'kind: Policy
metadata:
name: bounded-transfer
spec:
rules:
- effect: ALLOW
condition:
match: 'ctx.request.mcp.method == "tools/call" &&
ctx.request.mcp.name == "transfer" &&
ctx.request.mcp.http.bodyMap.params.arguments.amount <= 1000'kind: Service
metadata:
name: tools-mcp
spec:
mode: MCP
config:
upstream:
url: http://mcp.internal:8080
mcp:
endpoint: /mcp
protocol:
versions:
- "2025-11-25"
- "2026-07-28"
requireVersion: true
rejectUnknownMethods: trueUpstream credentials that are never distributed
An MCP server protected by a bearer token, API key, basic authentication or OAuth2 client credentials is reached without that credential ever leaving the Cluster. Agents hold an Octelium identity instead of a copy of the token.
- The upstream credential is stored as a Secret and injected only after a request has been authenticated and authorized. The downstream authorization credential is not forwarded to the MCP upstream.
- Which upstream and which credential are used can be selected per request, so a sensitive tool routes to its own MCP server while everything else stays on the general one.
- Deployed MCP servers pull from private registries with registry credentials held as Secrets, and receive their own environment variables from Secrets rather than from a checked-in file.
kind: Service
metadata:
name: tools-mcp
spec:
mode: MCP
isPublic: true
config:
upstream:
url: https://mcp.example.com
mcp:
endpoint: /mcp
auth:
bearer:
fromSecret: mcp-upstream-tokenkind: Service
metadata:
name: tools-mcp
spec:
mode: MCP
config:
upstream:
url: http://general-mcp:8080
mcp:
endpoint: /mcp
dynamicConfig:
configs:
- name: sensitive
upstream:
url: http://sensitive-mcp:8080
mcp:
auth:
bearer:
fromSecret: sensitive-mcp-token
rules:
- condition:
match: ctx.request.mcp.name == "deploy"
configName: sensitivekind: Service
metadata:
name: tools-mcp
spec:
mode: MCP
isPublic: true
config:
upstream:
container:
port: 8080
image: ghcr.io/acme/mcp:1.4.0
replicas: 2
credentials:
usernamePassword:
username: ghcr-user
password:
fromSecret: ghcr-token
mcp:
endpoint: /mcpA unified identity model for humans and workloads
Human and workload Users share the same identity management, authentication, access control and visibility model. For agentic systems this matters more than it once did, because the number of non-human identities holding credentials grows faster than the number of people.
- Agents authenticate with authentication tokens, with the OAuth2 client credentials flow, or secretlessly with federated OIDC assertions from GitHub Actions, cloud providers and Kubernetes clusters.
- Client-reported information such as the client name, declared capabilities and session identifier is recorded as context, but identity decisions use the Octelium User, Session and Device instead.
- The LLM mode gives the model an agent talks to the same treatment as the tools it calls, authorizing the operation, model, declared tools and token limits of each inference request.
- Cordium, built on Octelium, provides isolated sandboxes for agents and developers, where processes reach authorized Services through the Workspace identity with no secrets inside the sandbox.
kind: IdentityProvider
metadata:
name: github-actions
spec:
oidcIdentityToken:
issuerURL: https://token.actions.githubusercontent.com
audience: https://example.comkind: User
metadata:
name: release-agent
spec:
type: WORKLOAD
groups:
- agentskind: Service
metadata:
name: anthropic
spec:
mode: LLM
isPublic: true
config:
upstream:
url: https://api.anthropic.com
llm:
protocol: ANTHROPIC
auth:
custom:
header: x-api-key
value:
fromSecret: anthropic-api-keyEvery tool call, with the identity that made it
Every request produces an AccessLog identifying the User, Session and Device alongside the JSON-RPC detail of the message itself, emitted in real time to your OpenTelemetry OTLP receivers.
- Entries include the protocol version, method, target name, request identifier, notification status, client information, result type, JSON-RPC error details and tool error status.
- Streamed responses produce a start entry and a final entry carrying the stream duration, event and notification counts, and any protocol metadata observed during the stream.
- Body capture is enabled by default for this mode because inspecting JSON-RPC messages is usually the point of the audit, and it can be disabled per Service when tool arguments are sensitive.
A scalable platform that also deploys your MCP servers
A Cluster runs on Kubernetes and uses it to scale its own data plane, place Service proxies across Gateways, and run containerized applications that the Cluster itself deploys.
- Managed containers deploy, scale and serve a containerized MCP server as the Service upstream, from public or private registries, with resource limits, volumes and health probes.
- The Cluster is managed declaratively. Resources are defined in YAML, kept in a Git repository, and applied with a single octeliumctl apply that reproduces the entire Cluster state.
- Management is centralized over the Cluster's gRPC APIs, so the Cluster is fully programmable and no configuration change requires SSH access to a server.
$ octeliumctl apply ./cluster
Secret: ghcr-token Updated
Secret: mcp-upstream-token Updated
IdentityProvider: github-actions Created
Policy: finance-tools Created
Group: agents Created
User: release-agent Created
Service: tools-mcp.ai Created
Service: internal-mcp.ai Created
Service: anthropic.ai Updated
Namespace: ai Created
Cluster Core resources successfully applied
7 resources created
3 resources updated
ClusterConfig updatedEverything an MCP request carries, and what checks it
An MCP call is not a single decision. These are the parts of a request that matter for access control and auditing, next to the mechanism in an Octelium Cluster that handles each one.
- 01
The caller
Every request carries an Octelium identity. Agents and workloads authenticate with the OAuth2 client credentials flow, access tokens or federated OIDC assertions, and a browser-based client authenticates through your own identity provider.
Read more - 02
The protocol version
The declared version is read from the MCP-Protocol-Version header, the request metadata or an initialize request, and checked against an optional allowlist. An empty allowlist accepts any valid version, so a Service can accept newer revisions without a Cluster upgrade.
Read more - 03
The JSON-RPC method
The method, such as tools/call, tools/list, prompts/get or resources/read, is a normalized field available to Policies. Unknown but otherwise valid methods are accepted by default and can be rejected explicitly.
Read more - 04
The tool being called
The tool name, prompt name or resource URI is normalized separately from the method, so discovery can be allowed for everyone while individual tools are restricted to the identities that should call them.
Read more - 05
The tool arguments
The parsed JSON-RPC body is part of the request context, including arguments that have no normalized field, so a policy can bound a numeric argument or require a particular target rather than allowing a tool outright.
Read more - 06
The browser origin
Browser-based clients send an Origin header, which is validated by default to protect MCP servers from DNS rebinding attacks. Additional origins are an explicit allowlist, and requests without the header are accepted for non-browser clients.
Read more - 07
The upstream credential
A bearer token, API key, basic authentication or OAuth2 client credential protecting the MCP server is stored as a Secret and injected only after a request is authorized. The downstream credential is never forwarded upstream.
Read more - 08
The server itself
The upstream can be an existing MCP server anywhere, or a container the Cluster deploys, scales and serves itself from a public or private registry with resource limits, volumes and health probes.
Read more
Client-reported names, capabilities and session identifiers are useful context and useful audit evidence, but they are supplied by the caller. Identity decisions use the Octelium User, Session and Device.
The same platform capabilities ship with every deployment, whichever way you use it.
Frequently asked
- What does the MCP mode actually inspect?
- An MCP request must use POST, carry an application/json media type and contain a single valid JSON-RPC 2.0 request or notification. The mode normalizes the protocol version, method, target name, request identifier, notification status, reported client information, declared capabilities and transport session identifier, and it also exposes the parsed request body. JSON-RPC batch requests are not supported, and a streamed SSE response is proxied without waiting for the stream to end.
- Can access control reach individual tool calls?
- Yes. The JSON-RPC method and the tool name are separate normalized fields, so tools/list can be allowed for every authenticated User while tools/call is restricted per tool and per group. Tool arguments are available through the parsed request body, which lets a policy bound a numeric argument or require a specific target rather than allowing a tool unconditionally.
- Can the Cluster host the MCP server as well as protect it?
- Yes. Managed containers deploy, scale and serve a containerized MCP server as the Service upstream, using the Kubernetes infrastructure of the Cluster itself. The server must listen on 0.0.0.0 and its port must match the configured container port. Environment variables and Secrets, custom commands, replica counts, private registries, resource limits, security contexts, volumes and health probes are all supported.
- Is the client information reported by an MCP client trustworthy?
- No, and it should not be treated as identity. The client name and version, declared capabilities, JSON-RPC request identifier and MCP session identifier are all controlled by the downstream client. They are useful as request context and audit information, but access control decisions should use the Octelium User, Session, Device and group information instead.
- How do agents authenticate without a client or SDK?
- Workload Users share the same identity model as human Users. They authenticate with authentication tokens, with the standard OAuth2 client credentials flow against the Cluster's own token endpoint, or secretlessly with federated OpenID Connect assertions issued by the platform hosting them, such as GitHub Actions, a cloud provider or a Kubernetes cluster. No special SDK is required.
- Are MCP messages recorded?
- Yes. Each request produces an identity-aware AccessLog streamed in real time over OpenTelemetry, including the protocol version, method, target name, request identifier, client information, result type, JSON-RPC error details and tool error status. Unlike the LLM mode, request and response body visibility is enabled by default here because inspecting JSON-RPC messages is commonly required for auditing, and it can be disabled per Service.
Related solutions
AI gateway
An identity-aware gateway for OpenAI and Anthropic APIs, with per-request control over models, tools and token limits.
Read moreZero trust infrastructure for AI agents
Identity, access control and auditing for AI agents, MCP servers and LLM providers at scale.
Read moreSelf-hosted PaaS
Deploy, scale and serve containerized applications that the Cluster itself runs, with or without public access.
Read moreDeploy Octelium on your own infrastructure in minutes
Free and open source. Self-hosted. No vendor lock-in.