Skip to content

SEP-1763: Interceptors for Model Context Protocol #1763

@sambhav

Description

@sambhav

Interceptor Framework for Model Context Protocol

Preamble

Title: Interceptor Framework for Model Context Protocol

Status: Draft

Created: 2025-11-04

Authors: @sambhav

Abstract

This SEP proposes an interceptor framework for the Model Context Protocol (MCP) that enables intercepting, validating, and transforming messages at various extension points within the protocol lifecycle. The interceptor system introduces a new resource type that can hook into MCP events including server features (tool discovery, tool invocation, prompt handling, resource access), client features (sampling requests, elicitation requests, roots access), as well as LLM interactions (chat completions). Interceptors can be deployed on both client and server sides, supporting validation with severity levels (info, warn, error), mutation operations that replace message content, and observability for auditing, logging, and metrics collection. The framework follows MCP's JSON-RPC patterns and includes a discovery mechanism similar to existing tools, prompts, and resources.

Motivation

The Problem: A Sprawling Ecosystem Without Reusability

The MCP ecosystem is rapidly developing a sprawling landscape of sidecars, proxies, and gateways to address cross-cutting concerns like validation, security, observability, and rate limiting. However, these implementations are largely:

  • Non-reusable: Each solution is tightly coupled to specific deployment patterns or languages
  • Non-interoperable: Different approaches don't work together, creating vendor lock-in
  • Duplicative: Similar functionality is reimplemented across different tools and servers
  • Fragmented: No common interface or discovery mechanism exists

This leads to an M × N problem: Each of M clients must integrate with each of N middleware solutions, resulting in M × N integration points and configurations.

The Solution: Standardized Plug-and-Play Interceptors

This SEP proposes standardizing interceptors in the same plug-and-play fashion that made MCP tools successful. The core goal is to transform the M × N problem into an M + N problem:

  • Clients implement the interceptor call pattern once
  • Servers expose interceptors through a standardized interface once
  • Platform teams can deploy interceptors across all compatible clients and servers

Key Advantages

1. Plug-and-Play Simplicity

Just as tools made MCP powerful through discoverability and standardization, interceptors bring the same benefits to cross-cutting concerns:

Add gateway-level interceptor → Plug in configuration → All tools and servers gain new capabilities

2. Language-Agnostic Deployment

Unlike framework-specific middleware (e.g., FastAPI middleware, Express.js middleware), interceptors enable platform teams to:

  • Introduce new capabilities company-wide regardless of implementation language
  • Guarantee consistent behavior across Python, TypeScript, Go, Rust, or any other server implementation
  • Deploy guardrails and policies without requiring code changes to each service

3. Sidecar and Service-Based Architecture

While traditional library-based middleware requires code integration, interceptors support:

  • First-party interceptors: Deployed as code running locally within the server process
  • Third-party interceptors: Deployed as separate services that servers call out to
  • Hybrid approaches: Mix local and remote interceptors based on security, performance, and organizational requirements

This flexibility is critical for:

  • Security: Sensitive validation logic can run in isolated, hardened services
  • Compliance: Audit logging can be centralized and immutable
  • Performance: High-throughput operations can use in-process interceptors while complex policies use dedicated services

4. Well-Defined Invocation Lifecycle

Interceptors provide a standardized execution model with clear semantics for:

  • Event subscription and filtering
  • Request/response phase handling
  • Priority-based ordering for mutations
  • Validation severity levels (info, warn, error)
  • Error handling and propagation

Technical Gaps Addressed

The current MCP specification lacks a standardized mechanism for:

  1. Input Validation: No way to validate tool parameters, prompt inputs, resource requests, sampling requests, or elicitation requests before processing
  2. Output Transformation: No capability to transform or sanitize responses before they reach clients or servers
  3. Cross-cutting Concerns: No support for logging, rate limiting, or content filtering that applies across multiple protocol operations (both server and client features)
  4. Policy Enforcement: No mechanism to enforce organizational policies on what prompts can be sent to LLMs, what tool results are returned, what sampling models can be used, or what data can be elicited from users
  5. Security Boundaries: When MCP servers and clients cross trust boundaries, there's no way to inspect or validate messages in either direction
  6. LLM Interaction Control: While MCP handles tool/resource/prompt orchestration and sampling requests, there's no hook into these interactions for content filtering or prompt injection detection
  7. Client Feature Protection: No way to prevent servers from requesting excessive sampling tokens, asking for sensitive information through elicitation, or accessing unauthorized filesystem roots
  8. Observability: No standardized way to collect metrics, traces, and audit logs across MCP operations including client-provided capabilities

These limitations become critical in enterprise deployments where:

  • Multiple MCP servers with varying trust levels need coordination
  • Compliance requirements mandate audit trails and content filtering
  • Security policies require validation of inputs and outputs
  • Performance monitoring requires instrumentation across protocol operations
  • Debugging complex interactions requires detailed tracing

Ecosystem Benefits

For Platform Teams

  • Deploy interceptors once, gain coverage across all compatible servers and clients
  • Enforce company-wide policies without modifying individual services
  • Centralize security, compliance, and observability concerns

For Server Developers

  • Inherit rich ecosystem of interceptors without custom integration work
  • Focus on core business logic rather than cross-cutting concerns
  • Support both local and remote interceptor patterns

For Client Developers

  • Discover and utilize interceptors through standard MCP mechanisms
  • Consistent behavior across different servers
  • Reduced integration complexity

For the Community

  • Build reusable interceptors that work across the entire ecosystem
  • Share best practices through standardized implementations
  • Foster innovation in security, observability, and policy enforcement

An interceptor framework addresses these needs by providing standardized extension points that maintain MCP's design principles while enabling powerful cross-cutting functionality and solving the ecosystem's reusability challenge.
f

Specification

1. Interceptor Resource Type

Interceptors are introduced as a new first-class resource type in MCP, similar to tools, prompts, and resources.

1.1 Interceptor Definition

interface Interceptor {
  /**
   * Unique identifier for the interceptor
   */
  name: string;
  
  /**
   * Semantic version of this interceptor
   */
  version?: string;
  
  /**
   * Human-readable description
   */
  description?: string;
  
  /**
   * Events this interceptor subscribes to
   */
  events: InterceptorEvent[];
  
  /**
   * Interceptor operation type
   */
  type: "validation" | "mutation" | "observability";
  
  /**
   * Execution phase
   */
  phase: "request" | "response" | "both";
  
  /**
   * Priority hint for mutation interceptor ordering (lower executes first).
   * 
   * Can be specified as:
   * - A single number: applies to both request and response phases
   * - An object: specify different priorities per phase
   * 
   * Range: 32-bit signed integer (-2,147,483,648 to 2,147,483,647)
   * Default: 0 if omitted
   * 
   * Tie-breaker: Interceptors with equal priorityHint are ordered alphabetically by name.
   * For validation and observability interceptors, this field is ignored.
   * 
   * Examples:
   *   priorityHint: -1000                           // Same priority for both phases
   *   priorityHint: { request: -1000 }              // Request: -1000, Response: 0 (default)
   *   priorityHint: { response: 1000 }              // Request: 0 (default), Response: 1000
   *   priorityHint: { request: -1000, response: 1000 }  // Different per phase
   */
  priorityHint?: number | {
    request?: number;
    response?: number;
  };
  
  /**
   * Protocol version compatibility
   */
  compat?: {
    /**
     * Minimum MCP protocol version required
     */
    minProtocol: string;
    /**
     * Maximum MCP protocol version supported (optional)
     */
    maxProtocol?: string;
  };
  
  /**
   * Optional JSON Schema for interceptor configuration
   * Documents the expected configuration format
   */
  configSchema?: {
    type: "object";
    properties?: Record<string, unknown>;
    required?: string[];
    additionalProperties?: boolean;
  };
}

1.2 Interceptor Events

type InterceptorEvent =
  // MCP Server Features
  | "tools/list"
  | "tools/call"
  | "prompts/list"
  | "prompts/get"
  | "resources/list"
  | "resources/read"
  | "resources/subscribe"
  
  // MCP Client Features
  | "sampling/createMessage"
  | "elicitation/create"
  | "roots/list"
  
  // LLM Interaction Events (using common format)
  | "llm/completion"
  
  // Wildcard
  | "*/request"
  | "*/response"
  | "*";

2. JSON-RPC Methods

2.1 Interceptor Discovery

Request:

{
  jsonrpc: "2.0",
  id: 1,
  method: "interceptors/list",
  params?: {
    // Optional filter by event type
    event?: InterceptorEvent;
  }
}

Response:

{
  jsonrpc: "2.0",
  id: 1,
  result: {
    interceptors: [
      {
        name: "content-filter",
        version: "1.2.0",
        description: "Filters sensitive content from prompts and responses",
        events: ["llm/completion", "prompts/get"],
        type: "mutation",
        phase: "both",
        priorityHint: -500,  // Same priority for both request and response
        compat: {
          minProtocol: "2024-11-05"
        },
        configSchema: {
          type: "object",
          properties: {
            sensitivityLevel: {
              type: "string",
              enum: ["low", "medium", "high"]
            },
            redactPatterns: {
              type: "array",
              items: { type: "string" }
            }
          }
        }
      },
      {
        name: "parameter-validator",
        version: "2.0.1",
        description: "Validates tool call parameters",
        events: ["tools/call"],
        type: "validation",
        phase: "request",
        compat: {
          minProtocol: "2024-11-05",
          maxProtocol: "2025-12-31"
        },
        configSchema: {
          type: "object",
          properties: {
            strictMode: { type: "boolean" }
          }
        }
      },
      {
        name: "pii-redactor",
        version: "2.1.0",
        description: "Redacts PII from requests and responses",
        events: ["tools/call", "llm/completion"],
        type: "mutation",
        phase: "both",
        // Different priorities for request vs response
        priorityHint: {
          request: -1000,  // Redact early when sending
          response: 1000   // Redact late when receiving (after decryption)
        },
        compat: {
          minProtocol: "2024-11-05"
        },
        configSchema: {
          type: "object",
          properties: {
            patterns: {
              type: "array",
              items: { type: "string" }
            }
          }
        }
      },
      {
        name: "sampling-guard",
        version: "1.0.0",
        description: "Enforces token limits and model policies for sampling requests",
        events: ["sampling/createMessage"],
        type: "validation",
        phase: "request",
        compat: {
          minProtocol: "2025-06-18"
        },
        configSchema: {
          type: "object",
          properties: {
            maxTokens: { type: "number" },
            allowedModels: {
              type: "array",
              items: { type: "string" }
            }
          }
        }
      },
      {
        name: "elicitation-pii-blocker",
        version: "1.0.0",
        description: "Prevents elicitation requests from asking for sensitive information",
        events: ["elicitation/create"],
        type: "validation",
        phase: "request",
        compat: {
          minProtocol: "2025-06-18"
        },
        configSchema: {
          type: "object",
          properties: {
            blockedPatterns: {
              type: "array",
              items: { type: "string" }
            },
            sensitiveFields: {
              type: "array",
              items: { type: "string" }
            }
          }
        }
      },
      {
        name: "audit-logger",
        version: "1.0.0",
        description: "Logs all MCP operations for compliance",
        events: ["*"],
        type: "observability",
        phase: "both",
        configSchema: {
          type: "object",
          properties: {
            destination: {
              type: "string",
              enum: ["local", "remote", "both"]
            },
            includePayloads: { type: "boolean" }
          },
          required: ["destination"]
        }
      }
    ]
  }
}

2.2 Unified Result Envelope

All interceptor invocations return results conforming to a unified envelope structure:

/**
 * Base result shared by all interceptor types
 */
interface BaseInterceptorResult {
  /**
   * Name of the interceptor that produced this result
   */
  interceptor: string;
  
  /**
   * Type of interceptor
   */
  type: "validation" | "mutation" | "observability";
  
  /**
   * Phase when this interceptor executed
   */
  phase: "request" | "response";
  
  /**
   * Execution duration in milliseconds
   */
  durationMs?: number;
  
  /**
   * Additional interceptor-specific information
   */
  info?: Record<string, unknown>;
}

/**
 * Validation interceptor result
 */
interface ValidationResult extends BaseInterceptorResult {
  type: "validation";
  
  /**
   * Overall validation outcome
   */
  valid: boolean;
  
  /**
   * Validation severity (error blocks execution)
   */
  severity?: "info" | "warn" | "error";
  
  /**
   * Detailed validation messages
   */
  messages?: Array<{
    path?: string;
    message: string;
    severity: "info" | "warn" | "error";
  }>;
  
  /**
   * Optional suggested corrections
   */
  suggestions?: Array<{
    path: string;
    value: unknown;
  }>;
  
  /**
   * Future: Cryptographic signature for validation result
   */
  signature?: {
    algorithm: "ed25519";
    publicKey: string;
    value: string;
  };
}

/**
 * Mutation interceptor result
 */
interface MutationResult extends BaseInterceptorResult {
  type: "mutation";
  
  /**
   * Whether the payload was modified
   */
  modified: boolean;
  
  /**
   * The mutated payload (or original if not modified)
   */
  payload: unknown;
}

/**
 * Observability interceptor result
 */
interface ObservabilityResult extends BaseInterceptorResult {
  type: "observability";
  
  /**
   * Whether observation was recorded
   */
  observed: boolean;
  
  /**
   * Optional metrics collected
   */
  metrics?: Record<string, number>;
}

type InterceptorResult = ValidationResult | MutationResult | ObservabilityResult;

2.3 Interceptor Invocation

Request for Validation:

{
  jsonrpc: "2.0",
  id: 2,
  method: "interceptor/invoke",
  params: {
    name: "parameter-validator",
    event: "tools/call",
    phase: "request",
    payload: {
      // Original request content
      method: "tools/call",
      params: {
        name: "get_weather",
        arguments: {
          location: "malicious_sql_injection"
        }
      }
    },
    config?: {
      // Optional interceptor-specific configuration
    }
  }
}

Response for Validation:

{
  jsonrpc: "2.0",
  id: 2,
  result: {
    valid: false,
    severity: "error",
    messages: [
      {
        path: "params.arguments.location",
        message: "Input contains potentially malicious content",
        severity: "error"
      }
    ],
    // Optional: suggested corrections
    suggestions?: [
      {
        path: "params.arguments.location",
        value: "[REDACTED]"
      }
    ],
    // Future: Cryptographic signature for validation result
    // This enables verification that validation occurred at trust boundary
    signature?: {
      algorithm: "ed25519",
      publicKey: "...",
      value: "..."
    }
  }
}

Note: The signature field is reserved for future use to enable cryptographic verification of validation results at trust boundaries. This will allow recipients to verify that validations were performed by authorized interceptor before accepting data.

Request for Mutation:

{
  jsonrpc: "2.0",
  id: 3,
  method: "interceptor/invoke",
  params: {
    name: "content-filter",
    event: "llm/completion",
    phase: "request",
    payload: {
      // LLM completion request (following common format)
      messages: [
        {
          role: "user",
          content: "What is the password for admin@example.com?"
        }
      ],
      model: "gpt-4"
    }
  }
}

Response for Mutation:

{
  jsonrpc: "2.0",
  id: 3,
  result: {
    modified: true,
    payload: {
      messages: [
        {
          role: "user",
          content: "What is the password for [REDACTED_EMAIL]?"
        }
      ],
      model: "gpt-4"
    },
    // Additional information about the mutation
    info: {
      redactions: 1,
      reason: "PII detected and redacted"
    }
  }
}

Request for Observability:

{
  jsonrpc: "2.0",
  id: 4,
  method: "interceptor/invoke",
  params: {
    name: "audit-logger",
    event: "tools/call",
    phase: "request",
    payload: {
      method: "tools/call",
      params: {
        name: "execute_command",
        arguments: {
          command: "ls -la"
        }
      }
    },
    context: {
      principal: {
        type: "user",
        id: "user-123"
      },
      traceId: "trace-abc-123",
      timestamp: "2024-01-15T10:30:00Z"
    }
  }
}

Response for Observability:

{
  jsonrpc: "2.0",
  id: 4,
  result: {
    observed: true,
    // Optional metrics collected
    metrics: {
      payloadSize: 156,
      processingTime: 2
    },
    // Additional information about what was observed
    info: {
      logId: "log-xyz-789",
      destination: "audit-system",
      // Optional alerts or notifications triggered
      alerts?: [
        {
          level: "info",
          message: "High-privilege command executed",
          tags: ["security", "audit"]
        }
      ]
    }
  }
}

2.4 Interceptor Chain Execution

Request:

{
  jsonrpc: "2.0",
  id: 5,
  method: "interceptor/executeChain",
  params: {
    event: "tools/call",
    phase: "request",
    payload: {
      method: "tools/call",
      params: {
        name: "get_weather",
        arguments: {
          location: "San Francisco"
        }
      }
    },
    // Optional: specify which interceptor to include
    interceptor?: string[];
    // Optional: per-invocation configuration
    config?: Record<string, Record<string, unknown>>;
    // Optional: timeout for entire chain
    timeoutMs?: number;
    // Optional: context passed to all interceptor
    context?: {
      principal?: {
        type: "user" | "service" | "anonymous";
        id?: string;
      };
      traceId?: string;
      timestamp: string;
    };
  }
}

Response:

{
  jsonrpc: "2.0",
  id: 5,
  result: {
    /**
     * Overall chain execution status
     */
    status: "success" | "validation_failed" | "mutation_failed" | "timeout",
    
    /**
     * Event and phase for this chain
     */
    event: "tools/call",
    phase: "request",
    
    /**
     * Results from all executed interceptor
     */
    results: [
      {
        interceptor: "pii-redactor",
        type: "mutation",
        phase: "request",
        modified: true,
        payload: { /* mutated payload */ },
        durationMs: 5
      },
      {
        interceptor: "content-filter",
        type: "mutation",
        phase: "request",
        modified: false,
        payload: { /* unchanged */ },
        durationMs: 3
      },
      {
        interceptor: "parameter-validator",
        type: "validation",
        phase: "request",
        valid: true,
        durationMs: 2
      },
      {
        interceptor: "audit-logger",
        type: "observability",
        phase: "request",
        observed: true,
        metrics: { payloadSize: 156 },
        durationMs: 1
      }
    ],
    
    /**
     * Final payload after all mutations (if chain completed)
     */
    finalPayload?: unknown;
    
    /**
     * Validation summary
     */
    validationSummary: {
      errors: 0,
      warnings: 0,
      infos: 1
    },
    
    /**
     * Total execution time
     */
    totalDurationMs: 11,
    
    /**
     * If chain was aborted, details about where and why
     */
    abortedAt?: {
      interceptor: string;
      reason: string;
      type: "validation" | "mutation" | "timeout";
    }
  }
}

The interceptor/executeChain method executes the full interceptor chain for an event and phase, handling ordering, parallel execution, and aggregation automatically. This is an optional convenience helper, not required by MCP. Implementations can choose to use individual interceptor/invoke calls if they prefer explicit control. The helper method obeys negotiated capabilities and protocol versions per the MCP Lifecycle initialization.

This is the recommended way to invoke interceptor as it:

  • Automatically discovers and orders applicable interceptor
  • Handles mutations sequentially by priorityHint with alphabetical tie-breaking
  • Executes validations and observability in parallel
  • Aggregates results into a single response
  • Provides detailed execution metrics and validation summaries
  • Simplifies client/server implementation

2.5 Error Handling

Interceptor invocation errors follow standard JSON-RPC error format:

{
  jsonrpc: "2.0",
  id: 4,
  error: {
    code: -32603,
    message: "Interceptor execution failed",
    data: {
      interceptor: "content-filter",
      reason: "Configuration invalid"
    }
  }
}

3. Execution Model

3.1 Interceptor Chain

Multiple interceptors can be chained for a single event. The execution model follows a trust-boundary-aware pattern where validation acts as a security gate.

Discovery and Ordering

The interceptor/list method returns all available interceptors. The invoker is responsible for:

  • Collecting relevant interceptors for an event
  • Resolving phase-specific priorities (see Section 3.1.3)
  • Ordering mutations by priority (lower executes first), then alphabetically by name for ties
  • Executing validations and observability in parallel

Trust Boundary Execution Pattern

Execution order depends on data flow direction:

Sending Data (Client → Server or Server → Client):

Mutate (sequential) → Validate & Observe (parallel) → Send
  • Mutations prepare/sanitize data
  • Validations verify before crossing boundary (can block)
  • Observability logs (fire-and-forget, never blocks)

Receiving Data (Server ← Client or Client ← Server):

Receive → Validate & Observe (parallel) → Mutate (sequential)
  • Validations act as security barrier (can block)
  • Observability logs (fire-and-forget, never blocks)
  • Mutations process validated data

This pattern ensures validation guards every trust boundary crossing, enabling defense-in-depth and future cryptographic verification.

Interceptor Type Behaviors

Type Execution Failure Behavior Ordering
Mutation Sequential Chain halts, returns last valid state By priorityHint (low→high), then alphabetically
Validation Parallel severity: "error" rejects request None (parallel)
Observability Parallel, fire-and-forget Never blocks None (parallel)

Key semantics:

  • Mutations are atomic: entire chain succeeds or none apply
  • Validations with severity: "warn"/"info" don't block execution
  • Observability failures are logged internally but never affect request flow

4. Example Interceptor Chain

For a tools/call event, assume the following interceptor are available:

// From interceptor/list response:
[
  {
    name: "pii-redactor",
    type: "mutation",
    phase: "both",
    priorityHint: {
      request: -1000,   // Run first when sending
      response: 1000    // Run last when receiving
    }
  },
  {
    name: "content-filter",
    type: "mutation",
    phase: "both",
    priorityHint: -500  // Same priority for both phases
  },
  {
    name: "format-normalizer",
    type: "mutation",
    phase: "both",
    priorityHint: { request: 100 }  // Only applies to requests, response uses default (0)
  },
  {
    name: "schema-validator",
    type: "validation",
    phase: "request"
    // No priorityHint - runs in parallel with other validators
  },
  {
    name: "parameter-validator",
    type: "validation",
    phase: "request"
    // No priorityHint - runs in parallel
  },
  {
    name: "audit-logger",
    type: "observability",
    phase: "both"
    // No priorityHint - fire-and-forget
  }
]

Full Request/Response Cycle

CLIENT REQUEST (Sending):
  Original: { email: "john@example.com", ssn: "123-45-6789" }
  → Mutate: pii-redactor (-1000), content-filter (-500), format-normalizer (100)
  → Result: { email: "[EMAIL]", ssn: "[SSN]" }
  → Validate & Observe: client-validator ✓, audit-logger ✓
  → Send across TRUST BOUNDARY

SERVER REQUEST (Receiving):
  Received: { email: "[EMAIL]", ssn: "[SSN]" }
  → Validate & Observe: schema-validator ✓, parameter-validator ✓, audit-logger ✓
  → Mutate: sanitizer (10)
  → Execute Tool → { result: { name: "John", ... } }

SERVER RESPONSE (Sending):
  Original: { name: "John", ... }
  → Mutate: pii-redactor (1000), content-filter (-500)
  → Validate & Observe: response-validator ✓, audit-logger ✓
  → Send across TRUST BOUNDARY

CLIENT RESPONSE (Receiving):
  Received: { name: "John", ... }
  → Validate & Observe: response-validator ✓, audit-logger ✓
  → Mutate: response-enhancer (10)
  → Deliver to Application

Key Points:

  • Mutations run in priority order: pii-redactor (-1000) → content-filter (-500) → format-normalizer (100)
  • Validations and observability run in parallel at each boundary
  • Trust boundaries are guarded by validation on both sides

Error Handling

Interceptor chains handle errors based on type:

// Mutation failure: Chain halts immediately
{
  error: {
    code: -32603,
    message: "Interceptor mutation failed",
    data: { failedInterceptor: "content-filter", lastValidPayload: {...} }
  }
}

// Validation failure: All validations complete, then reject if severity: "error"
{
error: {
code: -32602,
message: "Interceptor validation failed",
data: {
validationErrors: [
{ interceptor: "schema-validator", severity: "error", message: "Required field missing" }
]
}
}
}


**Short-Circuit Semantics:**
- **Sending**: Mutations → Validate → Send (mutation errors halt before validation)
- **Receiving**: Validate → Mutations → Process (validation errors prevent mutations)
- **Observability**: Fire-and-forget, failures never block execution

**9. Recommended Priority Ranges**

To promote consistency across interceptor implementations, the following priority ranges are recommended:

| Priority Range | Purpose | Example Use Cases |
|----------------|---------|-------------------|
| **-2,000,000,000 to -1,000,000** | System-critical security | Anti-malware scanning, SQL injection prevention |
| **-999,999 to -10,000** | Security sanitization | PII redaction, credential scrubbing, XSS filtering |
| **-9,999 to -1,000** | Input/output normalization | Character encoding, format standardization |
| **-999 to -1** | Content transformation | Language translation, markdown rendering |
| **0** | Default (no priority specified) | General-purpose interceptor |
| **1 to 999** | Enrichment | Metadata injection, tagging, formatting |
| **1,000 to 9,999** | Optimization | Compression, caching, batching |
| **10,000 to 999,999** | Observability | Detailed logging, tracing, metrics |
| **1,000,000 to 2,000,000,000** | Low-priority finalization | Audit stamps, response wrapping |

**Note on Phase-Specific Priorities:**

Different interceptor may need different priorities for request vs response phases:

```typescript
// PII Redactor: High priority on both phases, but different positioning
{
  name: "pii-redactor",
  priorityHint: {
    request: -50000,   // Redact early when sending (before other transforms)
    response: 50000    // Redact late when receiving (after decompression, decryption)
  }
}

// Compression: Opposite pattern
{
  name: "compressor",
  priorityHint: {
    request: 5000,     // Compress late when sending (after all transforms)
    response: -5000    // Decompress early when receiving (before other processing)
  }
}

3.2 Priority Resolution

When ordering mutations, the invoker resolves phase-specific priorities:

function resolvePriority(interceptor: Interceptor, phase: "request" | "response"): number {
  if (interceptor.priorityHint === undefined) return 0;
  if (typeof interceptor.priorityHint === "number") return interceptor.priorityHint;
  return interceptor.priorityHint[phase] ?? 0;
}

Recommended Priority Ranges:

Priority Range Purpose Examples
< -1,000,000 System-critical security Anti-malware, SQL injection prevention
-999,999 to -10,000 Security sanitization PII redaction, credential scrubbing
-9,999 to -1 Normalization & transformation Encoding, format standardization
0 Default General-purpose interceptors
1 to 9,999 Enrichment & optimization Metadata injection, compression
≥ 10,000 Observability & finalization Logging, audit stamps

3.3 Sequence Diagrams

Client-Side Interceptor Invocation:

sequenceDiagram
    participant App as Application
    participant Client as MCP Client
    participant MW1 as Mutation 1<br/>(PII Redactor)
    participant MW2 as Mutation 2<br/>(Content Filter)
    participant MW3 as Validation<br/>(Validator)
    participant MW4 as Observability<br/>(Audit Logger)
    participant Server as MCP Server

    Note over App,Server: CLIENT REQUEST (Sending)
    App->>Client: tools/call request
    Client->>Client: Discover interceptor via interceptor/list<br/>Order mutations by suggestedPriority
    
    Note over Client,MW2: STEP 1: Mutations (Sequential)
    Client->>MW1: interceptor/invoke<br/>(pii-redactor, priority=10)
    MW1-->>Client: { modified: true, payload: {...} }
    
    Client->>MW2: interceptor/invoke<br/>(content-filter, priority=20)
    MW2-->>Client: { modified: true, payload: {...} }
    
    Note over Client,MW4: STEP 2: Validation & Observability (Parallel)<br/>Trust Boundary Check
    par Validation before sending
        Client->>MW3: interceptor/invoke<br/>(validator)
        MW3-->>Client: { valid: true }
    and Observability (fire-and-forget)
        Client->>MW4: interceptor/invoke<br/>(audit-logger)
        MW4-->>Client: { observed: true }
    end
    
    Client->>Server: Modified & validated request
    Note over Client,Server: ═══ TRUST BOUNDARY ═══
    Server-->>Client: tools/call response
    
    Note over App,Server: CLIENT RESPONSE (Receiving)
    
    Note over Client,MW4: STEP 1: Validation & Observability (Parallel)<br/>Trust Boundary Check
    par Validation after receiving
        Client->>MW3: interceptor/invoke<br/>(response-validator)
        MW3-->>Client: { valid: true }
    and Observability (fire-and-forget)
        Client->>MW4: interceptor/invoke<br/>(audit-logger)
        MW4-->>Client: { observed: true }
    end
    
    Note over Client,MW2: STEP 2: Mutations (Sequential)
    Client->>MW1: interceptor/invoke<br/>(response-enhancer, priority=10)
    MW1-->>Client: { modified: false, payload: {...} }
    
    Client->>App: Final validated & processed response
Loading

Server-Side Interceptor Invocation:

sequenceDiagram
    participant Client as MCP Client
    participant Server as MCP Server
    participant MW1 as Validation<br/>(Schema Validator)
    participant MW2 as Validation<br/>(Rate Limiter)
    participant MW3 as Observability<br/>(Audit Logger)
    participant MW4 as Mutation<br/>(Sanitizer)
    participant Tool as Tool Implementation

    Note over Client,Tool: SERVER REQUEST (Receiving)
    Client->>Server: tools/call request
    Note over Client,Server: ═══ TRUST BOUNDARY ═══
    Server->>Server: Discover interceptor via interceptor/list<br/>Order mutations by suggestedPriority
    
    Note over Server,MW3: STEP 1: Validation & Observability (Parallel)<br/>Trust Boundary Check
    par Validation after receiving
        Server->>MW1: interceptor/invoke<br/>(schema-validator)
        MW1-->>Server: { valid: true }
    and Validation
        Server->>MW2: interceptor/invoke<br/>(rate-limiter)
        MW2-->>Server: { valid: true }
    and Observability (fire-and-forget)
        Server->>MW3: interceptor/invoke<br/>(audit-logger)
        MW3-->>Server: { observed: true }
    end
    
    alt Any validation failed
        Server-->>Client: Error: Validation failed
    else All validations passed
        Note over Server,MW4: STEP 2: Mutations (Sequential)
        Server->>MW4: interceptor/invoke<br/>(sanitizer, priority=10)
        MW4-->>Server: { modified: true, payload: {...} }
        
        Server->>Tool: Execute tool
        Tool-->>Server: Tool result
        
        Note over Client,Tool: SERVER RESPONSE (Sending)
        
        Note over Server,MW4: STEP 1: Mutations (Sequential)
        Server->>MW4: interceptor/invoke<br/>(response-sanitizer, priority=10)
        MW4-->>Server: { modified: true, payload: {...} }
        
        Note over Server,MW3: STEP 2: Validation & Observability (Parallel)<br/>Trust Boundary Check
        par Validation before sending
            Server->>MW1: interceptor/invoke<br/>(schema-validator)
            MW1-->>Server: { valid: true }
        and Observability (fire-and-forget)
            Server->>MW3: interceptor/invoke<br/>(audit-logger)
            MW3-->>Server: { observed: true }
        end
        
        Note over Client,Server: ═══ TRUST BOUNDARY ═══
        Server-->>Client: Mutated & validated response
    end
Loading

Cross-Boundary Interceptor (Client + Server):

sequenceDiagram
    participant App as Application
    participant Client as MCP Client
    participant CMut as Client Mutation<br/>(PII Redactor)
    participant CVal as Client Validation<br/>(Validator)
    participant CObs as Client Observability<br/>(Logger)
    participant Server as MCP Server
    participant SVal as Server Validation<br/>(Schema/PII Validator)
    participant SMut as Server Mutation<br/>(Sanitizer)
    participant SObs as Server Observability<br/>(Audit Logger)
    participant Tool as Tool

    Note over App,Tool: CLIENT REQUEST (Sending - Trust Boundary)
    App->>Client: tools/call with PII
    
    Note over Client,CMut: Client STEP 1: Mutations
    Client->>CMut: interceptor/invoke<br/>(pii-redactor)
    CMut-->>Client: { modified: true, payload: "[REDACTED]" }
    
    Note over Client,CObs: Client STEP 2: Validation & Observability<br/>(Trust Boundary Check)
    par Client validates before sending
        Client->>CVal: interceptor/invoke<br/>(validator)
        CVal-->>Client: { valid: true }
    and Observability
        Client->>CObs: interceptor/invoke<br/>(logger)
        CObs-->>Client: { observed: true }
    end
    
    Client->>Server: Validated request
    Note over Client,Server: ═══════════════════════<br/>║ TRUST BOUNDARY ║<br/>═══════════════════════
    
    Note over Server,SObs: Server STEP 1: Validation & Observability<br/>(Trust Boundary Check)
    par Server validates after receiving
        Server->>SVal: interceptor/invoke<br/>(schema/pii-validator)
        SVal-->>Server: { valid: true }
    and Observability
        Server->>SObs: interceptor/invoke<br/>(audit-logger)
        SObs-->>Server: { observed: true }
    end
    
    Note over Server,SMut: Server STEP 2: Mutations
    Server->>SMut: interceptor/invoke<br/>(sanitizer)
    SMut-->>Server: { modified: true, payload: {...} }
    
    Server->>Tool: Execute tool safely
    Tool-->>Server: Result
    
    Note over App,Tool: SERVER RESPONSE (Sending - Trust Boundary)
    
    Note over Server,SMut: Server STEP 1: Mutations
    Server->>SMut: interceptor/invoke<br/>(response-sanitizer)
    SMut-->>Server: { modified: true, payload: {...} }
    
    Note over Server,SObs: Server STEP 2: Validation & Observability<br/>(Trust Boundary Check)
    par Server validates before sending
        Server->>SVal: interceptor/invoke<br/>(schema-validator)
        SVal-->>Server: { valid: true }
    and Observability
        Server->>SObs: interceptor/invoke<br/>(audit-logger)
        SObs-->>Server: { observed: true }
    end
    
    Server-->>Client: Validated response
    Note over Client,Server: ═══════════════════════<br/>║ TRUST BOUNDARY ║<br/>═══════════════════════
    
    Note over Client,CObs: Client STEP 1: Validation & Observability<br/>(Trust Boundary Check)
    par Client validates after receiving
        Client->>CVal: interceptor/invoke<br/>(response-validator)
        CVal-->>Client: { valid: true }
    and Observability
        Client->>CObs: interceptor/invoke<br/>(logger)
        CObs-->>Client: { observed: true }
    end
    
    Note over Client,CMut: Client STEP 2: Mutations
    Client->>CMut: interceptor/invoke<br/>(response-enhancer)
    CMut-->>Client: { modified: false, payload: {...} }
    
    Client->>App: Final validated result
    
    Note over App,Tool: Validation guards both sides of trust boundary
Loading

4. Payload Formats

This section describes the payload formats for all interceptable events. Payloads are organized into three categories: Server Features (tools, prompts, resources), Client Features (sampling, elicitation, roots), and LLM Interactions (completion requests). Each event type receives equal treatment with format specifications and example use cases.

4.1 Server Feature Events

Server feature events encompass tool discovery and invocation, prompt handling, and resource access. These events follow the standard MCP JSON-RPC format.

4.1.1 Tool Events

tools/call

// tools/call request
{
  method: "tools/call",
  params: {
    name: string;
    arguments?: Record<string, unknown>;
  }
}

// tools/call response
{
  result: {
    content: Array<TextContent | ImageContent | EmbeddedResource>;
    isError?: boolean;
  }
}

tools/list

// tools/list request
{
  method: "tools/list",
  params: {}
}

// tools/list response
{
  result: {
    tools: Array<{
      name: string;
      description?: string;
      inputSchema: {
        type: "object";
        properties?: Record<string, unknown>;
        required?: string[];
      };
    }>;
  }
}

Example Use Cases for Tool Interceptors:

  1. Validation: Verify tool arguments match schema, check for injection attacks
  2. Mutation: Transform arguments, sanitize outputs, inject context
  3. Observability: Track tool usage, measure execution time, audit access patterns
4.1.2 Prompt Events

prompts/get

// prompts/get request
{
  method: "prompts/get",
  params: {
    name: string;
    arguments?: Record<string, unknown>;
  }
}

// prompts/get response
{
  result: {
    description?: string;
    messages: Array<{
      role: "user" | "assistant";
      content: {
        type: "text" | "image" | "resource";
        text?: string;
        data?: string;
        mimeType?: string;
        resource?: {
          uri: string;
          text?: string;
          mimeType?: string;
        };
      };
    }>;
  }
}

prompts/list

// prompts/list request
{
  method: "prompts/list",
  params: {}
}

// prompts/list response
{
  result: {
    prompts: Array<{
      name: string;
      description?: string;
      arguments?: Array<{
        name: string;
        description?: string;
        required?: boolean;
      }>;
    }>;
  }
}

Example Use Cases for Prompt Interceptors:

  1. Validation: Verify prompt arguments, check for prohibited content patterns
  2. Mutation: Inject system instructions, apply prompt templates, sanitize outputs
  3. Observability: Log prompt usage, track argument patterns, monitor access frequency
4.1.3 Resource Events

resources/read

// resources/read request
{
  method: "resources/read",
  params: {
    uri: string;
  }
}

// resources/read response
{
  result: {
    contents: Array<{
      uri: string;
      mimeType?: string;
      text?: string;
      blob?: string;  // base64-encoded
    }>;
  }
}

resources/list

// resources/list request
{
  method: "resources/list",
  params: {}
}

// resources/list response
{
  result: {
    resources: Array<{
      uri: string;
      name: string;
      description?: string;
      mimeType?: string;
    }>;
  }
}

resources/subscribe

// resources/subscribe request
{
  method: "resources/subscribe",
  params: {
    uri: string;
  }
}

// resources/subscribe response
{
  result: {}
}

Example Use Cases for Resource Interceptors:

  1. Validation: Verify resource URIs are authorized, check content size limits
  2. Mutation: Transform resource content, apply access control filters, redact sensitive data
  3. Observability: Track resource access patterns, monitor bandwidth usage, audit data flow

4.2 Client Feature Events

Client feature events enable servers to request services from clients: sampling (LLM inference), elicitation (user input), and roots (filesystem access). These events follow MCP client capability formats.

4.2.1 Sampling Events

sampling/createMessage

// sampling/createMessage request
{
  method: "sampling/createMessage",
  params: {
    messages: Array<{
      role: "user" | "assistant";
      content: {
        type: "text" | "image" | "audio";
        text?: string;
        data?: string;  // base64-encoded for image/audio
        mimeType?: string;
      };
    }>;
    modelPreferences?: {
      hints?: Array<{ name: string }>;
      costPriority?: number;      // 0-1
      speedPriority?: number;     // 0-1
      intelligencePriority?: number;  // 0-1
    };
    systemPrompt?: string;
    includeContext?: "none" | "thisServer" | "allServers";
    temperature?: number;
    maxTokens: number;
    stopSequences?: string[];
    metadata?: Record<string, unknown>;
  }
}

// sampling/createMessage response
{
  result: {
    role: "assistant";
    content: {
      type: "text" | "image" | "audio";
      text?: string;
      data?: string;
      mimeType?: string;
    };
    model: string;
    stopReason?: "endTurn" | "stopSequence" | "maxTokens" | string;
  }
}

Example Use Cases for Sampling Interceptors:

  1. Validation: Verify sampling requests don't contain prohibited content or exceed token limits
  2. Mutation: Inject additional context or modify model preferences before sampling
  3. Observability: Track sampling usage, measure latency, and audit model selections
// Example: Intercepting a sampling request to enforce token limits
{
  jsonrpc: "2.0",
  id: 5,
  method: "interceptor/invoke",
  params: {
    name: "token-limit-validator",
    event: "sampling/createMessage",
    phase: "request",
    payload: {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: "Generate a very long response..."
          }
        }
      ],
      maxTokens: 100000  // Exceeds organizational limit
    }
  }
}

// Validation response blocking excessive token request
{
  jsonrpc: "2.0",
  id: 5,
  result: {
    valid: false,
    severity: "error",
    messages: [
      {
        path: "maxTokens",
        message: "Requested tokens (100000) exceeds organizational limit (4096)",
        severity: "error"
      }
    ]
  }
}
4.2.2 Elicitation Events

elicitation/create

// elicitation/create request
{
  method: "elicitation/create",
  params: {
    message: string;
    requestedSchema: {
      type: "object";
      properties: Record<string, {
        type: "string" | "number" | "integer" | "boolean";
        title?: string;
        description?: string;
        enum?: any[];
        enumNames?: string[];
        minimum?: number;
        maximum?: number;
        minLength?: number;
        maxLength?: number;
        format?: "email" | "uri" | "date" | "date-time";
        default?: any;
      }>;
      required?: string[];
    };
  }
}

// elicitation/create response
{
  result: {
    action: "accept" | "decline" | "cancel";
    content?: Record<string, unknown>;  // Present when action is "accept"
  }
}

Example Use Cases for Elicitation Interceptors:

  1. Validation: Verify elicitation requests don't ask for sensitive information (PII, credentials)
  2. Mutation: Modify or enhance the requested schema, add validation rules
  3. Observability: Log elicitation requests for compliance and audit trails
// Example: Intercepting an elicitation request to prevent sensitive data collection
{
  jsonrpc: "2.0",
  id: 6,
  method: "interceptor/invoke",
  params: {
    name: "pii-blocker",
    event: "elicitation/create",
    phase: "request",
    payload: {
      message: "Please provide your credit card number for verification",
      requestedSchema: {
        type: "object",
        properties: {
          creditCard: {
            type: "string",
            description: "Your credit card number"
          }
        },
        required: ["creditCard"]
      }
    }
  }
}

// Validation response blocking PII request
{
  jsonrpc: "2.0",
  id: 6,
  result: {
    valid: false,
    severity: "error",
    messages: [
      {
        path: "requestedSchema.properties.creditCard",
        message: "Elicitation requests MUST NOT request sensitive financial information",
        severity: "error"
      }
    ],
    suggestions: [
      {
        path: "message",
        value: "Please confirm your identity through the secure verification system"
      }
    ]
  }
}
4.2.3 Roots Events

roots/list

// roots/list request
{
  method: "roots/list",
  params: {}
}

// roots/list response
{
  result: {
    roots: Array<{
      uri: string;  // Must be file:// URI
      name?: string;
    }>;
  }
}

Example Use Cases for Roots Interceptors:

  1. Validation: Verify servers only request allowed root paths
  2. Mutation: Filter or transform root URIs based on security policies
  3. Observability: Audit which roots are exposed to which servers

4.3 LLM Interaction Events

For LLM interaction events, we use a common format inspired by OpenAI's API:

llm/completion

// llm/completion request
{
  messages: Array<{
    role: "system" | "user" | "assistant" | "tool";
    content: string | Array<ContentPart>;
    name?: string;
  }>;
  model: string;
  tools?: Array<ToolDefinition>;
  temperature?: number;
  max_tokens?: number;
  top_p?: number;
  frequency_penalty?: number;
  presence_penalty?: number;
  stop?: string | string[];
  // ...other parameters
}

// llm/completion response
{
  choices: Array<{
    message: {
      role: "assistant";
      content: string | null;
      tool_calls?: Array<ToolCall>;
    };
    finish_reason: string;
    index: number;
  }>;
  usage?: {
    prompt_tokens: number;
    completion_tokens: number;
    total_tokens: number;
  };
  model: string;
  id: string;
  created: number;
}

Example Use Cases for LLM Interceptors:

  1. Validation: Detect prompt injection attempts, verify model access permissions, enforce token limits
  2. Mutation: Apply prompt templates, inject safety instructions, sanitize tool call results
  3. Observability: Monitor LLM usage costs, track model performance metrics, audit sensitive queries

5. Interceptor Server Capabilities

During initialization, servers declare interceptor support:

{
  jsonrpc: "2.0",
  id: 1,
  result: {
    protocolVersion: "2024-11-05",
    capabilities: {
      interceptor?: {
        // Events this server's interceptor can handle
        supportedEvents: InterceptorEvent[];
      },
      // ...existing capabilities
    },
    serverInfo: {
      name: "example-server",
      version: "1.0.0"
    }
  }
}

6. Configuration and Context

Interceptor may receive configuration and context:

interface InterceptorInvocationParams {
  name: string;
  event: InterceptorEvent;
  phase: "request" | "response";
  payload: unknown;
  
  // Optional configuration for this invocation
  config?: Record<string, unknown>;
  
  // Optional timeout in milliseconds
  // If exceeded, interceptor execution is cancelled and returns timeout error
  timeoutMs?: number;
  
  // Optional context information
  context?: {
    // Identity information
    principal?: {
      type: "user" | "service" | "anonymous";
      id?: string;
      claims?: Record<string, unknown>;
    };
    
    // Tracing information
    traceId?: string;
    spanId?: string;
    
    // Timing information
    timestamp: string;  // ISO 8601
    
    // Session information
    sessionId?: string;
    
    // FUTURE: Interceptor state propagation
    // Allows interceptor to share state through the chain
    // This field is reserved for future enhancement
    interceptorState?: Record<string, unknown>;
  };
}

Timeout and Cancellation:

When timeoutMs is specified:

  • Interceptor execution is cancelled if it exceeds the timeout
  • A timeout error is returned with code -32000 (Server error)
  • For interceptor/executeChain, the entire chain is cancelled on timeout
  • Individual interceptor/invoke calls timeout independently
  • Observability interceptor timeouts are logged but don't affect the main flow (fire-and-forget)
// Timeout error response
{
  jsonrpc: "2.0",
  id: 1,
  error: {
    code: -32000,
    message: "Interceptor execution timeout",
    data: {
      interceptor: "slow-validator",
      timeoutMs: 5000,
      phase: "request"
    }
  }
}

7. Future Enhancements

7.1 Context Propagation and Interceptor State (Future)

Status: Reserved for future specification

Inspired by gRPC interceptors and OpenTelemetry context propagation, future versions may support enrichable interceptor state that flows through the entire chain. This would enable:

  • State Sharing: Early interceptor can enrich context for downstream interceptor
  • Cross-Cutting Data: Authentication, rate limiting, and audit data flows through chain
  • Trace Correlation: Distributed tracing baggage propagates automatically
  • Deadline Propagation: Timeout context flows through nested operations

Proposed Model:

// FUTURE: Enhanced context with state propagation
interface InterceptorChainContext {
  // Immutable request context (set by initiator, read-only)
  readonly request: {
    principal?: {
      type: "user" | "service" | "anonymous";
      id?: string;
      claims?: Record<string, unknown>;
    };
    traceId: string;
    spanId?: string;
    timestamp: string;
    sessionId?: string;
  };
  
  // Mutable interceptor state (enriched by interceptor during chain execution)
  // Each interceptor can read and write to this shared state
  interceptorState?: Record<string, unknown>;
  
  // Deadline for entire operation (inherited and propagated)
  deadline?: string;  // ISO 8601 timestamp
  
  // Distributed tracing baggage (key-value pairs for trace correlation)
  // Follows W3C Baggage specification
  baggage?: Record<string, string>;
}

Interceptor State Enrichment:

Mutation and validation interceptor could optionally return context updates:

// FUTURE: Interceptor result with context enrichment
interface MutationResult extends BaseInterceptorResult {
  type: "mutation";
  modified: boolean;
  payload: unknown;
  
  // Optional: Updates to propagate to downstream interceptor
  contextUpdates?: {
    interceptorState?: Record<string, unknown>;
    baggage?: Record<string, string>;
  };
}

Example Flow:

// Interceptor 1: Authentication enriches context
{
  interceptor: "authenticator",
  type: "mutation",
  modified: false,
  payload: { /* unchanged */ },
  contextUpdates: {
    interceptorState: {
      principal: { 
        id: "user-123", 
        roles: ["admin"],
        authMethod: "oauth2"
      }
    }
  }
}

// Interceptor 2: Rate limiter reads enriched context
// Has access to context.interceptorState.principal
{
  interceptor: "rate-limiter",
  type: "validation",
  valid: true,
  info: {
    rateLimitUsed: 45,
    rateLimitMax: 100,
    // Used principal.id from context for rate limiting
  },
  contextUpdates: {
    interceptorState: {
      rateLimit: { remaining: 55, resetAt: "2025-11-04T15:30:00Z" }
    }
  }
}

// Interceptor 3: Audit logger has full enriched context
// Logs with principal + rate limit info + original request
{
  interceptor: "audit-logger",
  type: "observability",
  observed: true,
  info: {
    logEntry: {
      user: "user-123",
      roles: ["admin"],
      rateLimitRemaining: 55,
      action: "tools/call",
      timestamp: "2025-11-04T15:29:00Z"
    }
  }
}

Benefits of State Propagation:

  1. Loose Coupling: Interceptor don't need direct dependencies
  2. Composability: New interceptor can leverage existing enriched state
  3. Observability: Full context available for logging and tracing
  4. Performance: Avoid redundant operations (e.g., auth check once, reuse result)

Design Considerations:

  • Immutability: Request context is read-only to prevent tampering
  • Namespace Conflicts: Interceptor should use namespaced keys (e.g., auth.principal, rateLimit.info)
  • Size Limits: Context should have reasonable size limits to prevent abuse
  • Serialization: Context must be JSON-serializable for transport

This feature would be opt-in and backward compatible, with interceptor that don't support context enrichment working unchanged.

Rationale

Design Decisions

1. Separation of Validation, Mutation, and Observability

We explicitly separate these three interceptor types rather than combining them because:

  • Clear intent: Users know immediately what a interceptor does
  • Simpler implementation: Each type has focused logic
  • Better error handling: Validation failures vs mutation failures have different semantics
  • Performance: Validation and observability can run in parallel, mutations run sequentially
  • Isolation: Observability failures never affect the request/response flow

Alternative considered: Combined interceptor that can both validate and mutate. Rejected because it creates ambiguity in execution order and error handling.

2. Validation as Trust Boundary Guard

Execution order is trust-boundary-aware:

  • Sending: Mutate → Validate → Send (prepare data, verify before crossing)
  • Receiving: Validate → Mutate → Process (security barrier, then process)

This ensures validation guards all boundary crossings, enabling zero-trust architecture and future cryptographic verification. Alternative (same order for all phases) rejected because validation must be first when receiving untrusted data.

3. Phase-Aware Priority Hints

Mutations declare priorityHint (default: 0) which can differ by phase. Benefits:

  • Flexibility: PII redaction runs early when sending (-50000), late when receiving (+50000)
  • Simplicity: Single number for common cases, object for phase-specific needs
  • Determinism: Alphabetical tie-breaking by name ensures consistency
  • Wide range: 32-bit integers align with Kubernetes standards

Alternative (global fixed ordering) rejected as too rigid for phase-aware operations like compression/decompression.

4. Other Design Choices

  • Event-Based Targeting: Interceptors declare specific events (vs. all traffic) for performance and security
  • Severity Levels (info/warn/error): Graduated response vs. binary pass/fail enables observability without blocking
  • Replace vs. Patch: Mutations replace entire payloads (vs. JSON Patch) for simplicity and atomicity
  • Method Names: interceptor/list and interceptor/invoke mirror MCP patterns (tools/list, etc.)
  • "info" Field: Used instead of "metadata" to avoid confusion with MCP's _meta protocol metadata
  • Cross-Boundary Support: Client and server interceptors enable defense-in-depth and zero-trust architecture

Related Work and Inspiration

Inspired By:

  • Kubernetes Admission Controllers: Validation/mutation webhooks (MCP uses replace vs. patch for simplicity)
  • gRPC Interceptors: Client/server-side interception (MCP adds discoverability and dynamic invocation)
  • HTTP Middleware: Express.js, ASP.NET patterns (MCP is event-specific vs. path-based)
  • Envoy Filters: Network-level request/response filtering

Complements Existing MCP:

  • Adds validation/mutation capabilities to sampling, tools, prompts, and resources
  • Enables parameter validation, access control, and content filtering across all MCP features

Backward Compatibility

This SEP is fully backward compatible:

  1. Optional Capability: Interceptor is an optional server capability. Servers without interceptor support continue to work.

  2. No Protocol Changes: Existing JSON-RPC methods are unchanged. Interceptor adds new methods (interceptor/list, interceptor/invoke) but doesn't modify existing ones.

  3. Client Compatibility: Clients that don't support interceptor can still communicate with interceptor-enabled servers. Interceptor runs transparently on the server side.

  4. Graceful Degradation: If a client requests interceptor from a server that doesn't support it, the server returns a standard JSON-RPC error, which clients already handle.

  5. Version Negotiation: The existing protocol version negotiation during initialization allows clients and servers to discover interceptor support.

Security Implications

Threat Model

1. Malicious Interceptor

A compromised interceptor could:

  • Exfiltrate sensitive data from requests/responses
  • Modify data to inject malicious content
  • Cause denial of service by rejecting all requests
  • Bypass security controls by approving invalid inputs

Mitigations:

  • Interceptor runs in the same trust domain as the server/client
  • Organizations must vet interceptor before deployment
  • Interceptor should be sandboxed where possible
  • Audit logging of all interceptor invocations
  • Cryptographic signing of interceptor for integrity

2. Interceptor Bypass

Attackers might try to bypass interceptor validation:

  • Send requests directly to underlying services
  • Exploit interceptor chain ordering

Mitigations:

  • Interceptor enforcement must be at the transport layer
  • Server-side interceptor is mandatory for requests entering the trust boundary
  • Interceptor chain order must be explicitly configured and immutable

3. Information Disclosure

Interceptor error messages might leak sensitive information:

  • Internal system details
  • Validation rules that could be reverse-engineered
  • PII in error messages

Mitigations:

  • Interceptor should use generic error messages
  • Detailed errors should only be logged, not returned to untrusted clients
  • Separate internal and external validation messages

4. Denial of Service

Interceptor could be exploited for DoS:

  • Computationally expensive validation
  • Infinite loops in mutation logic
  • Memory exhaustion from large payloads

Mitigations:

  • Timeouts for interceptor execution
  • Resource limits (CPU, memory)
  • Rate limiting on interceptor invocations
  • Circuit breakers for failing interceptor

5. Privilege Escalation

Interceptor mutation could be used to inject unauthorized operations:

  • Modifying request parameters to access unauthorized resources
  • Changing tool invocation targets
  • Injecting administrative commands

Mitigations:

  • Validation interceptor should verify request authorization at trust boundaries
  • Immutable fields should be enforced at protocol level
  • Audit all mutations that touch security-sensitive fields

Security Best Practices

  1. Principle of Least Privilege: Interceptor should only have access to the data it needs to process

  2. Defense in Depth: Deploy interceptor at both client and server boundaries

  3. Audit Everything: Log all interceptor invocations, including payloads (with PII redaction)

  4. Fail Secure: If interceptor fails, the default should be to reject the request

  5. Input Validation: Interceptor configuration and payloads should be validated

  6. Cryptographic Integrity: Consider signing interceptor responses to prevent tampering

  7. Isolation: Run untrusted interceptor in sandboxed environments

Compliance Considerations

Interceptor enables compliance with:

  • GDPR: PII detection and redaction
  • HIPAA: PHI filtering in healthcare contexts
  • SOC 2: Audit logging and access control
  • PCI DSS: Sensitive data masking

However, organizations must ensure:

  • Interceptor doesn't create new compliance violations (e.g., storing PII in logs)
  • Audit trails are tamper-proof
  • Data retention policies apply to interceptor logs

Reference Implementation

The reference implementation will provide:

Multi-Language SDKs

SDK libraries for writing and running interceptors in various languages, including Python, TypeScript/JavaScript, Go and other community-contributed languages. These SDKs will provide base classes, helpers, and patterns for building validation, mutation, and observability interceptors.

Common Interceptor Sidecar

A universal interceptor runtime that enables teams to deploy and manage interceptors without modifying individual MCP servers. The sidecar acts as an MCP proxy that loads custom interceptors (both local and remote) via configuration and injects interceptor invocations transparently.

Example Configuration:

# interceptor-config.yaml
interceptors:
  # Local interceptor: runs in-process
  - name: pii-redactor
    type: mutation
    transport: local
    command: ./interceptors/pii-redactor.py
    args: []
    config:
      patterns: ["email", "ssn", "phone"]
    
  # Remote interceptor: calls external service
  - name: security-scanner
    type: validation
    transport: remote
    endpoint: https://security.example.com/scan
    timeout: 5s
    headers:
      Authorization: "Bearer ${SECURITY_TOKEN}"
    
  # Local TypeScript interceptor
  - name: rate-limiter
    type: validation
    transport: local
    comamnd: ./interceptors/rate-limiter.js
    args: []
    config:
      requests_per_minute: 100

routing:
  # Apply interceptors to specific event types
  tools.call:
    request: [rate-limiter, security-scanner]
    response: [pii-redactor]
  
  llm.chatCompletion:
    request: [security-scanner]
    response: [pii-redactor]

This approach enables platform teams to deploy guardrails across all services from a central control plane without code changes to existing MCP servers.

Acknowledgments

This proposal draws inspiration from:

Metadata

Metadata

Assignees

No one assigned

    Labels

    SEPproposalSEP proposal without a sponsor.

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions