Skip to main content
Ultra’s pipeline is a chain of interceptors that process every MCP request and response. This is the core mechanism for observability — each interceptor can inspect, record, or transform messages passing through.

How It Works

Request  → [Interceptor Chain (by priority)] → Upstream Server
Response ← [Interceptor Chain (reverse)]     ←
Interceptors execute in priority order for requests and reverse priority order for responses, forming a stack pattern. Lower numbers run first on requests and last on responses.

Built-in Interceptors

InterceptorPriorityBeforeRequestAfterResponsePurpose
Trace0Fail openFail openCreates OpenTelemetry spans, stores trace records
Logging75Fail openFail openStructured request/response logging
Audit75Fail openFail closedRecords security audit events
Metrics100Fail openFail openCounters, histograms, latency

Priority Constants

PriorityFirst  = 0     # Authentication, trace setup
PriorityEarly  = 25    # Rate limiting, validation
PriorityNormal = 50    # Custom interceptors
PriorityLate   = 75    # Logging, audit
PriorityLast   = 100   # Metrics, final cleanup

Interceptor Interface

Each interceptor implements two methods:
  • BeforeRequest — Called before the request reaches the upstream server. Can inspect, modify, or block the request.
  • AfterResponse — Called after the response comes back from the upstream. Can record, transform, or block the response.

Failure Modes

Understanding failure modes is critical for security:

Request Phase (BeforeRequest)

Any error from any interceptor blocks the request entirely. The request never reaches the upstream server, and the client receives an error. Currently, all built-in interceptors return nil in BeforeRequest (fail open individually). Future interceptors like Policy will block requests that violate defined policies.

Response Phase (AfterResponse)

Behavior varies by interceptor:
  • Trace, Logging, Metrics — Absorb errors (fail open). A storage failure won’t block your MCP call.
  • Audit — Propagates errors (fail closed). If the audit event can’t be recorded, the call fails. This ensures complete audit trails.

Practical Implications

Scenario: SQLite disk full

Trace interceptor:    ⚠ Warning logged, request continues
Logging interceptor:  ⚠ Warning logged, request continues
Audit interceptor:    ✗ Request FAILS — audit trail must be complete
Metrics interceptor:  ⚠ Warning logged, request continues

Request/Response Types

Request

Each request carries:
FieldDescription
IDUnique request identifier
TraceIDOpenTelemetry trace ID
SpanIDOpenTelemetry span ID
TypeOperation type (tool_call, resource_read, etc.)
UpstreamTarget server name
MethodJSON-RPC method
ToolNameTool name (for tool calls)
ToolParamsTool arguments (JSON)
ResourceURIResource URI (for resource reads)
PromptNamePrompt name (for prompt gets)
PrincipalClient/user identifier
MetadataInter-interceptor communication map

Response

FieldDescription
SuccessWhether the operation succeeded
ErrorError (if failed)
DurationHow long the upstream call took
RawResponseRaw JSON response

Metadata Communication

Interceptors can share data through the request’s Metadata map. For example, the trace interceptor stores timing information that other interceptors can reference.
Auth and Policy interceptors are planned but not yet implemented. The policy interceptor will execute at PriorityNormal (50) and fail closed on policy evaluation errors. See Roadmap: Policy Engine.