Octelium documentation · Latest

Overview

The MCP mode turns an Octelium Service into an identity-aware Model Context Protocol gateway. It validates MCP JSON-RPC messages, exposes normalized protocol information to Policies and dynamic configuration, injects upstream credentials and emits MCP-specific access logs. It also provides protocol-aware guardrails for tool arguments and server-supplied content.

Here is a simple streamable HTTP MCP gateway:

kind: Service metadata: name: my-mcp spec: mode: MCP isPublic: true config: upstream: url: http://my-mcp-server.example.com:8080/mcp mcp: endpoint: /mcp protocol: versions: - "2026-07-28" requireVersion: true

The isPublic field enables public clientless access (read more here). An MCP client can use an Octelium bearer token instead of knowing the location or credential of the upstream server.

Requests and Responses

An MCP request must use POST, have an application/json media type and contain one valid JSON-RPC 2.0 request or notification. JSON-RPC batches are not supported. Invalid requests are rejected before reaching the upstream.

Finite JSON responses and text/event-stream responses are supported. SSE events are inspected and forwarded as they arrive unless a response guardrail must withhold the complete response before reaching a verdict.

Endpoints and Upstream Paths

endpoint is the exact canonical path exposed to downstream clients. A request on another path is rejected. When it is omitted, requests are accepted on any path.

The path in upstream.url independently controls the server endpoint:

spec: mode: MCP config: upstream: # Every accepted request is sent to this path url: https://mcp.example.com/api/v2/mcp mcp: # Clients use this path endpoint: /mcp

When upstream.url has no path, the downstream path is forwarded as is. Since MCP is a single-endpoint protocol, an upstream URL path entirely replaces rather than prefixes the downstream path. endpoint must be set in the default configuration to take effect.

Protocol Versions

protocol.versions is an optional allowlist. An empty list accepts any syntactically valid MCP version because Octelium does not keep a hardcoded version list.

mcp: protocol: versions: - "2025-11-25" - "2026-07-28" requireVersion: true rejectUnknownMethods: true

The protocol version can be declared in MCP-Protocol-Version, in the io.modelcontextprotocol/protocolVersion request metadata, or in params.protocolVersion for initialize. Values present in more than one location must match.

requireVersion rejects a request without a version. It is disabled by default for compatibility with revisions before the version header was introduced.

rejectUnknownMethods rejects JSON-RPC methods unknown to the running Octelium version. It is disabled by default so official extensions and newer protocol revisions can reach the upstream while remaining visible through ctx.request.mcp.method. An explicit method allowlist in a Policy is often a better balance between strict access and protocol compatibility.

The protocol configuration is global and only takes effect in the default configuration.

Origin Validation and CORS

The MCP mode validates the Origin header to protect servers from DNS rebinding. The Service's own origin is always accepted. Requests without an origin, which is typical for non-browser clients, are also accepted.

Allow browser applications from additional origins with cors:

mcp: cors: allowOriginStringMatch: - https://agent-console.example.com allowMethods: POST allowHeaders: Authorization, Content-Type, MCP-Protocol-Version exposeHeaders: Mcp-Session-Id maxAge: "3600" allowCredentials: true

allowOriginStringMatch accepts exact origins or *. Octelium responds with the request's origin rather than a literal wildcard when credentials are used.

The Cluster's domain and every Service subdomain can be allowed for web consoles and playgrounds as follows:

mcp: cors: allowClusterServices: true

This trusts browser applications served by every Service in the Cluster. disableOriginCheck: true disables origin validation entirely and is not recommended for a browser-reachable endpoint. Both fields are global.

Limits

mcp: limits: # 512 KiB JSON-RPC request maxRequestBytes: 524288 # 128 KiB per SSE event maxStreamEventBytes: 131072

maxRequestBytes bounds the request body buffered and parsed. maxStreamEventBytes bounds inspection of an individual SSE event; a larger event is still forwarded but is not inspected. Zero uses the Octelium default, and internal hard limits always apply. Limits are global.

Secretless Access

The MCP upstream credential is independent from the credential used by the client to access Octelium (read more here). Downstream authorization credentials are never forwarded.

Bearer Token

mcp: auth: bearer: fromSecret: mcp-upstream-token

Custom API Key

mcp: auth: custom: header: X-Api-Key value: fromSecret: mcp-api-key

Basic Authentication

mcp: auth: basic: username: mcp-client password: fromSecret: mcp-password

OAuth2 Client Credentials

mcp: auth: oauth2ClientCredentials: clientID: octelium-mcp clientSecret: fromSecret: mcp-oauth-client-secret tokenURL: https://identity.example.com/oauth2/token scopes: - mcp.invoke

AWS Signature Version 4

mcp: auth: sigv4: accessKeyID: AKIAEXAMPLE secretAccessKey: fromSecret: aws-secret-access-key region: us-east-1 service: execute-api

Header Manipulation

Request and response headers can be added or removed using the HTTP header configuration (read more here):

mcp: header: addRequestHeaders: - key: X-MCP-Gateway value: octelium - key: X-User-ID eval: ctx.user.metadata.uid removeRequestHeaders: - X-Untrusted-Header addResponseHeaders: - key: X-Content-Source value: mcp removeResponseHeaders: - Server forwardedMode: DROP authorizationMode: DELETE host: value: mcp.internal.example.com

MCP reserved headers such as MCP-Protocol-Version, Mcp-Method, Mcp-Name, Mcp-Param-* and Mcp-Session-Id can also be manipulated. Authorization always uses the validated JSON-RPC body rather than these headers. An upstream server can reject a message whose reserved headers disagree with its body.

The upstream host can instead be preserved or computed with eval. forwardedMode can be DROP, OBFUSCATE or TRANSPARENT, and authorizationMode controls whether a downstream Authorization header is passed or deleted. The default safely deletes it for non-anonymous Services.

Path and HTTP/2

Use path to transform the upstream path after endpoint validation:

mcp: path: removePrefix: /mcp addPrefix: /internal/mcp isUpstreamHTTP2: true listenHTTP2: true

isUpstreamHTTP2 enables HTTP/2 to the server. listenHTTP2 enables downstream HTTP/2 and is global; it is enabled by default for a Service using TLS.

Prefer the path in upstream.url for the ordinary case where every request uses one upstream endpoint. path is also available as a conditional plugin.

Access Control

MCP request information is stored in ctx.request.mcp. The underlying HTTP request and parsed JSON-RPC body are available in ctx.request.mcp.http and ctx.request.mcp.http.bodyMap.

The following Policy permits tool discovery and allows a group to invoke a bounded transfer:

authorization: inlinePolicies: - spec: rules: - effect: ALLOW condition: match: ctx.request.mcp.method == "tools/list" - effect: ALLOW condition: all: of: - match: ctx.request.mcp.method == "tools/call" - match: ctx.request.mcp.name == "transfer" - match: ctx.request.mcp.http.bodyMap.params.arguments.amount <= 1000 - match: '"finance-agents" in ctx.user.spec.groups'

The normalized fields are:

  • protocolVersion, method and name.

  • requestID and isNotification.

  • The self-reported client.name, client.version and client.title.

  • The self-reported capabilities list.

  • sessionID, which is the MCP transport session identifier and is unrelated to an Octelium Session.

Client information, capabilities, the JSON-RPC identifier and MCP session identifier are controlled by the downstream. Do not use them as authenticated identity. Use the Octelium User, Session, Device and groups.

Managed Containers

Octelium can deploy and scale a containerized MCP server as the Service upstream (read more here):

kind: Service metadata: name: managed-mcp spec: mode: MCP isPublic: true config: upstream: container: port: 8080 image: ghcr.io/org/my-mcp:1.2.3 replicas: 2 credentials: usernamePassword: username: ghcr-username password: fromSecret: ghcr-token resourceLimit: cpu: millicores: 1000 memory: megabytes: 2000 mcp: endpoint: /mcp

The server must listen on 0.0.0.0, and container.port must match its listening port. Environment variables, commands, arguments, security contexts, volumes and health probes are supported.

Dynamic Configuration

Dynamic configuration can select an upstream, credential and other per-request configuration using identity and normalized MCP context (read more here):

spec: mode: MCP config: upstream: url: http://general-mcp.example.com:8080/mcp mcp: endpoint: /mcp dynamicConfig: configs: - name: production upstream: url: https://production-mcp.example.com/mcp mcp: auth: bearer: fromSecret: production-mcp-token rules: - condition: all: of: - match: ctx.request.mcp.method == "tools/call" - match: ctx.request.mcp.name == "production_deploy" - match: '"platform-admins" in ctx.user.spec.groups' configName: production

endpoint, protocol, limits, cors, disableOriginCheck and listenHTTP2 are global and only take effect in the default configuration. Dynamic configurations can change the upstream, authentication, headers, path, plugins, upstream HTTP/2 and visibility. They can also deploy multiple managed MCP server containers and route among them.

Continue with the MCP plugins and MCP visibility references.