# Overview

> Octelium documentation. Canonical page: <https://octelium.com/docs/octelium/latest/management/core/service/mcp/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](https://octelium.com/docs/octelium/latest/management/core/service/mcp/plugins.md#guardrail) for tool arguments and server-supplied content.

Here is a simple streamable HTTP MCP gateway:

```yaml
kind: Service
metadata:
  name: my-mcp
spec:
  mode: MCP
  isPublic: true
  config:
    upstream:
      url: http://my-mcp-server.example.com:8080/mcp
    # !mark(1:7)
    mcp:
      endpoint: /mcp
      protocol:
        versions:
          - "2026-07-28"
        requireVersion: true
```

The `isPublic` field enables public clientless access (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/clientless.md)). 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:

```yaml
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.

```yaml
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`:

```yaml
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:

```yaml
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

```yaml
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](https://octelium.com/docs/octelium/latest/management/core/service/secretless.md)). Downstream authorization credentials are never forwarded.

### Bearer Token

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

### Custom API Key

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

### Basic Authentication

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

### OAuth2 Client Credentials

```yaml
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

```yaml
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](https://octelium.com/docs/octelium/latest/management/core/service/http.md#header-manipulation)):

```yaml
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:

```yaml
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](https://octelium.com/docs/octelium/latest/management/core/service/mcp/plugins.md#http-plugins).

## 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:

```yaml
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](https://octelium.com/docs/octelium/latest/management/core/service/managed-containers.md)):

```yaml
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](https://octelium.com/docs/octelium/latest/management/core/service/dynamic-config.md)):

```yaml
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](https://octelium.com/docs/octelium/latest/management/core/service/mcp/plugins.md) and [MCP visibility](https://octelium.com/docs/octelium/latest/management/core/service/mcp/visibility.md) references.
