Skip to content

Updated SEP#6

Closed
Degiorgio wants to merge 9 commits intomodelcontextprotocol:mainfrom
Degiorgio:sep/update-spec
Closed

Updated SEP#6
Degiorgio wants to merge 9 commits intomodelcontextprotocol:mainfrom
Degiorgio:sep/update-spec

Conversation

@Degiorgio
Copy link
Copy Markdown
Contributor

@Degiorgio Degiorgio commented Apr 13, 2026

Summary

  • Eliminated the "observability" interceptor type -- the original had three types (validation, mutation, observability); the updated SEP reduces this to two types (validation, mutation), with observability handled via a new audit mode on validators and mutators
  • Introduced mode field ("enforce" | "audit") on the Interceptor interface, replacing the dedicated observability type; audit-mode validators log violations without blocking, audit-mode mutators compute shadow mutations without applying them
  • Introduced failOpen field (boolean, default false) on the Interceptor interface for fail-open/fail-closed behavior.
  • Restructured the Interceptor interface -- replaced flat events and phase fields with a nested hook object containing hook.events and hook.phase
  • Renamed type InterceptorEvent to InterceptionEvent and added | string to make the union extensible for custom events
  • Added InterceptionPhase type alias ("request" | "response" | "both") as a standalone type
  • Removed BaseInterceptorResult / ObservabilityResult / unified result envelope -- the original had a shared base type and three result subtypes; the updated SEP defines ValidationResult and MutationResult inline within their respective Validator/Mutator sections
  • Removed interceptor/executeChain as a JSON-RPC protocol method -- chain execution is now a client-side SDK convenience utility, not a protocol-level method
  • Added formal definitions for "Context Operation" and "Lifecycle Event" as blockquoted callouts in the Specification section
  • Added a "How do Interceptors Differ from Tools?" comparison table contrasting invocation model, result handling, and purpose
  • Added explicit Validator and Mutator subsections with formal definitions, use-case lists, and dedicated result interfaces.
  • Added detailed wildcard documentation -- clarified semantics of "*", "*/request", "*/response", interaction with hook.phase, and extensibility guidance for namespace wildcards
  • Added timeout and cancellation semantics for individual interceptor invocations (timeoutMs field on InterceptorInvocationParams)
  • Introduced RFC 2119 keyword usage -- the specification section now uses MUST/SHOULD/MAY language throughout
  • Restructured the Execution Model section -- moved from a numbered subsection (3.x) to a standalone section with a summary callout box, consolidated trust-boundary pattern, and explicit audit-mode/failOpen semantics
  • Restructured sequence diagrams -- removed the separate Observability participant from all diagrams; simplified to show only Mutation and Validation participants
  • Removed the Payload Formats section (Section 4 in the original) -- all detailed payload format definitions for server features, client features, and LLM interactions were removed
  • Moved Configuration and Context from a standalone section (Section 6) into the Interceptor Invocation subsection under JSON-RPC Methods
  • Updated the Rationale section -- changed from "Separation of Validation, Mutation, and Observability" to "Separation of Validation and Mutation" with new "Audit Mode for Both Types" subsection explaining the design rationale
  • Updated Backward Compatibility section to use RFC 2119 language (MUST, SHOULD, MAY, etc.)
  • Updated Security Best Practices to use RFC 2119 language and added explicit failOpen: false default guidance
  • Updated Reference Implementation SDK description to mention audit mode support and removed mention of observability interceptors
  • Updated the audit-logger example in interceptors/list response from type: "observability" to type: "validation" with mode: "audit" and failOpen: true
  • Removed "Observability" from Technical Gaps -- item 8 ("Observability: No standardized way to collect metrics...") was removed

Changes

1. Type System: Two Types Instead of Three

The original SEP defined three interceptor types: validation, mutation, and observability. The updated SEP collapses this to two types: validation and mutation.

The observability type is replaced by a cross-cutting mode field:

  • mode: "enforce" (default) -- normal blocking/transforming behavior
  • mode: "audit" -- non-blocking operation; validators log without blocking, mutators compute without applying

A new failOpen boolean (default false) controls failure routing: whether a crashed/timed-out interceptor blocks or allows the message to proceed.

2. Interceptor Interface Restructuring

Original: Flat structure with events: InterceptorEvent[] and phase: "request" | "response" | "both" at the top level.

Updated: Nested hook object:

hook: {
  events: InterceptionEvent[];
  phase: InterceptionPhase;
};

The type name changed from InterceptorEvent to InterceptionEvent and gained a | string extension point. A new InterceptionPhase type alias was added.

3. Result Types Simplified

Original: A BaseInterceptorResult base interface with three subtypes (ValidationResult, MutationResult, ObservabilityResult) and a union type InterceptorResult. These were defined in a "Unified Result Envelope" section.

Updated: ValidationResult and MutationResult are defined inline within their respective Validator and Mutator subsections. ObservabilityResult and BaseInterceptorResult are removed entirely.

4. Chain Execution: Protocol Method to SDK Utility

Original: interceptor/executeChain was a JSON-RPC method with full request/response schemas, making it part of the MCP protocol.

Updated: Chain execution is described as a "convenience utility provided by SDKs." It uses client-side InterceptorChain, ChainEntry, ChainExecutionParams, and ChainExecutionResult interfaces. The orchestration pattern is documented as a 5-step process (Discover, Merge & Sort, Order by Trust Boundary, Execute, Aggregate) that calls interceptor/invoke on individual servers. The ChainEntry type includes an MCPServerConnection reference (implementation-specific) for routing invocations to the correct server.

5. New Formal Definitions and Callouts

The updated SEP adds:

  • Blockquoted definition of "Context Operation"
  • Blockquoted definition of "Lifecycle Event"
  • A comparison table: "How do Interceptors Differ from Tools?"

6. Wildcard and Extensibility Documentation

The updated SEP adds detailed documentation on wildcard behavior ("*", "*/request", "*/response"), their interaction with hook.phase, guidance on namespace wildcards (e.g., "tools/*"), and how implementations MAY extend the event set with custom events.

7. Payload Formats Section Removed

The original Section 4 ("Payload Formats") containing detailed request/response schemas for all interceptable events across Server Features, Client Features, and LLM Interactions was removed.

8. Execution Model Restructuring

The updated Execution Model section adds:

  • A summary callout box with key execution rules
  • Explicit audit-mode and failOpen semantics in the execution table
  • A dedicated "Audit Mode" column in the type behaviors table
  • Stronger RFC 2119 language throughout (MUST, MUST NOT, etc.)
  • Priority Resolution, Timeout, and Sequence Diagrams moved into the Execution Model
  • Chain Execution placed last as a convenience utility

9. Sequence Diagrams Simplified

All three sequence diagrams (Client-Side, Server-Side, Cross-Boundary) updated to remove the separate Observability participant, reflecting the two-type model.

10. Backward Compatibility and Security Hardened

Both sections updated to use RFC 2119 normative language. Security Best Practices now explicitly state that interceptors "MUST default to failOpen: false."

@Degiorgio Degiorgio marked this pull request as ready for review April 15, 2026 12:38
@Degiorgio Degiorgio requested a review from sambhav April 16, 2026 11:36
Degiorgio and others added 2 commits April 16, 2026 20:40
…n model

- Remove `interceptor/executeChain` JSON-RPC method and replace with
  client-side chain execution convenience utility provided by SDKs
- Restructure Execution Model section: summary box, trust boundary
  pattern, type behaviors, priority resolution, sequence diagrams,
  then chain execution
- Move Timeout and Cancellation to Interceptor Invocation section
  (per-invocation concern, not execution model)
- Move Configuration and Context (InterceptorInvocationParams) to
  Interceptor Invocation section
- Move Short-Circuit Semantics into Execution Model summary box
- Move Sequence Diagrams above Chain Execution (illustrate the model)
- Add Chain Execution interfaces: InterceptorChain, ChainEntry,
  ChainExecutionParams, ChainExecutionResult
- MCPServerConnection left as implementation-specific type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kurt Degiorgio <kdegiorgio@bloomberg.net>
Comment thread sep.md Outdated

**Created:** 2025-11-04

**Authors:** @sambhav
Copy link
Copy Markdown
Member

@sambhav sambhav Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Degiorgio please add yourself as well along with @PederHP 🙏

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

@PederHP
Copy link
Copy Markdown
Member

PederHP commented Apr 19, 2026

I must admit I would prefer to keep the observability type. It mirrors kubernetes interceptors, and also conceptually a validator or mutator is something where the result matters. An observability interceptor is a sink. I think the semantics of a validator or mutator in audit mode is strange. Adding this makes it less intuitive and more complex than simply having three types.

Validator is a blocking gate.
Mutator is a pipeline element.
Observer is a non-blocking sink.

It also makes it tricky to handle ordering of observers if they're expressed as the other types in a special mode.

With these three anything except control flows (which are probably not a good idea for interceptors anyway, at this stage at least) can be expressed.

@Degiorgio Degiorgio force-pushed the sep/update-spec branch 2 times, most recently from 62d3f68 to 9869442 Compare April 20, 2026 09:54
@Degiorgio
Copy link
Copy Markdown
Contributor Author

I must admit I would prefer to keep the observability type. It mirrors kubernetes interceptors, and also conceptually a validator or mutator is something where the result matters. An observability interceptor is a sink. I think the semantics of a validator or mutator in audit mode is strange. Adding this makes it less intuitive and more complex than simply having three types.

Validator is a blocking gate. Mutator is a pipeline element. Observer is a non-blocking sink.

It also makes it tricky to handle ordering of observers if they're expressed as the other types in a special mode.

With these three anything except control flows (which are probably not a good idea for interceptors anyway, at this stage at least) can be expressed.

The move from a dedicated observer type to audit mode was driven by feedback on the original SEP. The core concern raised was that observability is already converging around OpenTelemetry, and baking a parallel observability envelope into the interceptor spec risks fragmenting what is an established standard.

That said, at least in my view audit mode actually preserves the semantic clarity you're describing, it just expresses it differently. A validator in audit mode is still a sink: it runs, it logs, its result doesn't gate the chain. The difference is that the logic is the same as the enforcing version, which enables a workflow that comes up a lot in practice: deploy in audit mode first (observe what would be blocked), then promote to enforce with a single field change. With a separate observer type, that promotion requires changing the interceptor even though the logic hasn't changed.

It's also worth noting that two types with audit mode is strictly more expressive than three types. A dedicated observer type can only be a sink. An audit-mode mutator gives you shadow mutations, compute a transformation without applying it, useful for canary testing new mutation logic in production.

With regards to ordering, audit mode doesn't change the execution model:

  • Validators run in parallel regardless of mode, an audit-mode validator simply won't gate the result.
  • Mutators still run at their priorityHint position in the chain, audit mode only controls whether the transformation is applied, not when it runs.

Happy to discuss further, I would agree that it would be good to make sure the spec is clear on the relevant semantics here!

@Degiorgio Degiorgio requested a review from sambhav April 20, 2026 10:31
@Degiorgio Degiorgio force-pushed the sep/update-spec branch 2 times, most recently from 98555af to 18d30b0 Compare April 21, 2026 13:44
Signed-off-by: Kurt Degiorgio <kdegiorgio@bloomberg.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants