# Visibility

> Octelium documentation. Canonical page: <https://octelium.com/docs/octelium/latest/management/core/service/mcp/visibility>.

An `MCP` *Service* emits identity-aware, protocol-specific *AccessLogs* in real time. The common part identifies the *User*, *Session*, *Device*, *Service*, *Namespace*, authorization decision and policy match. The `entry.info.mcp` part records the normalized JSON-RPC exchange (read more about visibility [here](https://octelium.com/docs/octelium/latest/management/core/visibility.md)).

## Body and Header Visibility

MCP request and response bodies are recorded by default because tool arguments and results are often required for auditing. They can also contain secrets, personal information, prompts and proprietary data. Disable body capture when the audit backend must not store that content:

```yaml
spec:
  mode: MCP
  config:
    upstream:
      url: https://mcp.example.com/mcp
    mcp:
      visibility:
        disableRequestBody: true
        disableResponseBody: true
```

Individual HTTP headers can be recorded as follows:

```yaml
mcp:
  visibility:
    includeRequestHeaders:
      - User-Agent
      - X-Request-ID
    includeResponseHeaders:
      - Content-Type
      - Mcp-Session-Id
```

All headers can be enabled with explicit exclusions:

```yaml
mcp:
  visibility:
    includeAllRequestHeaders: true
    includeAllResponseHeaders: true
    excludeRequestHeaders:
      - Cookie
      - X-Internal-Token
    excludeResponseHeaders:
      - Set-Cookie
```

Sensitive authentication and session headers are excluded even when all headers are enabled. Body and event capture is bounded, so large values can be omitted from an *AccessLog*.

## Entry Types

The `type` field describes the response lifecycle:

- `COMPLETE` is a request with a finite response.
- `STREAM_START` is emitted when an SSE response starts.
- `STREAM_END` is the final record and carries accumulated stream details.

## Tool Call

The following is the MCP-specific part of an example `tools/call` *AccessLog*. Common identity and policy fields are omitted for brevity:

```json
{
  "entry": {
    "info": {
      "mcp": {
        "type": "COMPLETE",
        "protocolVersion": "2026-07-28",
        "method": "tools/call",
        "name": "transfer",
        "requestID": "req-42",
        "resultType": "complete",
        "client": {
          "name": "example-agent",
          "version": "2.4.0",
          "title": "Example Agent"
        },
        "sessionID": "mcp-session-01",
        "http": {
          "request": {
            "method": "POST",
            "path": "/mcp",
            "bodyBytes": "184",
            "bodyMap": {
              "jsonrpc": "2.0",
              "method": "tools/call",
              "params": {
                "name": "transfer",
                "arguments": {
                  "amount": 100,
                  "currency": "USD"
                }
              },
              "id": "req-42"
            }
          },
          "response": {
            "code": 200,
            "bodyBytes": "162",
            "contentType": "application/json"
          },
          "httpVersion": "HTTP2"
        }
      }
    }
  }
}
```

`method` is the JSON-RPC method. `name` is its logical target: a tool name for `tools/call`, a prompt name for `prompts/get`, or a resource URI for `resources/read`. It is empty for methods without a target, such as `tools/list`.

`requestID` is normalized into a string whether the downstream used a string or number. `isNotification` is true when no identifier was supplied and no response is expected.

## Client and Session Information

`client` contains the name, version and title reported by the downstream. `sessionID` is the `Mcp-Session-Id` transport identifier used by applicable MCP protocol revisions.

Neither value is authenticated identity. The client can choose them, and an MCP session is unrelated to an Octelium *Session*. Use the *AccessLog*'s common *User*, *Session* and *Device* references for identity and security decisions.

Client capabilities reported by the downstream are available in request policy context. They remain self-reported context rather than identity.

## Streamed Response

The final entry of an SSE response records event and notification counts as well as any result metadata observed during the stream:

```json
{
  "entry": {
    "info": {
      "mcp": {
        "type": "STREAM_END",
        "protocolVersion": "2026-07-28",
        "method": "resources/read",
        "name": "docs://security/handbook",
        "requestID": "73",
        "resultType": "complete",
        "eventCount": "14",
        "notificationCount": "3",
        "ttlMs": "60000",
        "cacheScope": "public",
        "http": {
          "request": {
            "method": "POST",
            "path": "/mcp"
          },
          "response": {
            "code": 200,
            "contentType": "text/event-stream"
          },
          "httpVersion": "HTTP2"
        }
      }
    }
  }
}
```

`eventCount` counts SSE events and `notificationCount` counts JSON-RPC notifications observed within them. `ttlMs` and `cacheScope` record cache freshness metadata returned by the MCP server when present. Integer values whose protobuf type is 64-bit are represented as JSON strings.

A response guardrail buffers the complete stream before it is released, but the resulting successful exchange still uses the MCP stream entry types.

## Protocol and Tool Errors

A JSON-RPC error is recorded independently of the HTTP status code:

```json
{
  "entry": {
    "info": {
      "mcp": {
        "type": "COMPLETE",
        "method": "tools/call",
        "name": "transfer",
        "requestID": "req-43",
        "isProtocolError": true,
        "errorCode": -32602,
        "errorMessage": "Invalid parameters",
        "http": {
          "response": {
            "code": 200,
            "contentType": "application/json"
          }
        }
      }
    }
  }
}
```

`isProtocolError`, `errorCode` and `errorMessage` describe a JSON-RPC `error` object. A provider can return it with HTTP status `200`, so dashboards should not infer MCP success from the HTTP status alone.

A successful JSON-RPC result from `tools/call` can still contain `isError: true`. Octelium records that separately:

```json
{
  "entry": {
    "info": {
      "mcp": {
        "type": "COMPLETE",
        "method": "tools/call",
        "name": "production_deploy",
        "requestID": "req-44",
        "isToolError": true,
        "http": {
          "response": {
            "code": 200
          }
        }
      }
    }
  }
}
```

Track protocol errors and tool errors independently.

## Notifications

A JSON-RPC notification has no request identifier and expects no response:

```json
{
  "entry": {
    "info": {
      "mcp": {
        "type": "COMPLETE",
        "protocolVersion": "2026-07-28",
        "method": "notifications/initialized",
        "isNotification": true,
        "http": {
          "request": {
            "method": "POST",
            "path": "/mcp"
          }
        }
      }
    }
  }
}
```

## Metrics and Export

*AccessLogs* are exported through OpenTelemetry and can be sent to an observability or SIEM backend. Useful dimensions include *Service*, *User*, protocol version, method, target name, result type, protocol error, tool error and stream type. Avoid using downstream-controlled client, request or MCP session identifiers as trusted security dimensions.

Read more about the [MCP configuration](https://octelium.com/docs/octelium/latest/management/core/service/mcp/overview.md) and [MCP plugins](https://octelium.com/docs/octelium/latest/management/core/service/mcp/plugins.md).
