Octelium documentation · Latest

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).

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:

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:

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

All headers can be enabled with explicit exclusions:

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:

{ "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:

{ "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:

{ "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:

{ "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:

{ "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 and MCP plugins.