Skip to content

Implement Go interceptor chain#4

Open
Degiorgio wants to merge 1 commit intomainfrom
interceptors/go
Open

Implement Go interceptor chain#4
Degiorgio wants to merge 1 commit intomainfrom
interceptors/go

Conversation

@Degiorgio
Copy link
Collaborator

@Degiorgio Degiorgio commented Mar 11, 2026

Summary

Implements the server-side interceptor chain for Go, enabling validation and mutation middleware on
any server that processes request/response pairs.

The core interceptor framework (chain engine, validators, mutators, invocations, results) is fully
protocol-agnostic with zero MCP dependencies. The MCP-specific server integration lives in a
separate mcpserver sub-package, wiring interceptors into go-sdk's middleware for all transports
(stdio, HTTP).

Protocol-Level Methods — Current Limitation

The SEP defines interceptors as a first-class resource type, like tools, prompts, and resources.
The server should expose them via interceptors/list and interceptor/executeChain, and
consumers — including the middleware integration — should call them as clients through the
transport, the same way a client calls tools/call.

Where we want to be:

  • The interceptor server exposes interceptors/list and interceptor/executeChain as JSON-RPC methods
  • The middleware integration is just another client — it calls interceptor/executeChain via
    mcp.Client to run the chain, rather than calling the Chain engine directly
  • This enables both in-process and sidecar/remote interceptor deployments through the same interface

Where we are right now:

  • The middleware calls the Chain engine directly (in-process only)
  • The go-sdk (v1.4.0) does not support custom JSON-RPC method registration —
    checkRequest() rejects unknown methods before middleware runs, and there is no AddMethod API
  • Once the go-sdk supports custom methods, the middleware can be refactored to go through the protocol

Blocked on go-sdk:

  • Upstream AddMethod (or equivalent) to go-sdk
  • Register interceptors/list and interceptor/executeChain as protocol methods
  • Refactor middleware to use mcp.Client as the entry point

Architecture

  • Protocol-agnostic core — the interceptors package exposes a public Chain type that manages
    interceptor registration and execution with no transport coupling. Any server can use
    Chain.ExecuteForReceiving / Chain.ExecuteForSending directly.
  • Typed payloadInvocation.Payload carries the live Go value (any) directly through the chain, the
    same pattern as gRPC-Go interceptors. Handlers type-assert to the concrete type and mutate in place. Zero JSON
    operations in the normal path.
  • Trust-boundary ordering — request phase runs validate → mutate (validators gate before mutation);
    response phase runs mutate → validate (mutations prepare before validation).
  • Audit-mode isolation — audit-mode mutators receive a deep copy via JSON round-trip so their
    modifications don't affect the real payload. Only audit mutators pay this cost.
  • Chain cachingchainExecutors are cached per event|phase in a sync.Map, invalidated implicitly on
    Add via atomic snapshot swap.
  • Concurrency — validators run in parallel (goroutines + mutex); mutators run sequentially in priority
    order.

Package Structure

Package File Responsibility
interceptors chain.go Public Chain type, NewChain, Add, ExecuteForReceiving/Sending, IsEmpty
interceptors chain_executor.go Internal chainExecutor, filtering, sorting, snapshot caching
interceptors chain_validate.go Parallel validator dispatch
interceptors chain_mutate.go Sequential mutator execution
interceptors interceptor.go Types, Metadata, Validator/Mutator structs
interceptors invocation.go Invocation with audit-mode payload cloning
interceptors result.go ValidationResult, MutationResult, ChainResult, AbortInfo
interceptors doc.go Package documentation with examples
interceptors/mcpserver server.go MCP Server wrapper, middleware, capability declaration
interceptors/mcpserver events.go MCP event name constants
interceptors/mcpserver doc.go Sub-package documentation

Documentation

  • doc/DESIGN.md — architecture, execution model, integration with go-sdk
  • doc/CONFORMANCE.md — SEP conformance status
  • doc/PERFORMANCE.md — per-request cost model
  • doc.go — detailed go docs

TODO

  • More integration tests
  • Unit tests
  • Implement SEP's protocol-level methods (interceptors/*) — blocked on go-sdk custom method support
  • Support wildcards for event interception (separate PR)

strategy:
matrix:
go: ["1.23", "1.24", "1.25"]
go: ["1.24", "1.25"]

Choose a reason for hiding this comment

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

Can you include go version 1.26 too

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes, will add!

// work across all transports (stdio, HTTP) and compose with other extensions
// like variants.
type Server struct {
inner *mcp.Server

Choose a reason for hiding this comment

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

Will it be better to embed the mcp.Server direclty?

type Server struct {
    *mcp.Server
    // ...
}

Copy link
Collaborator Author

@Degiorgio Degiorgio Mar 11, 2026

Choose a reason for hiding this comment

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

I opted not to embed *mcp.Server to avoid leaking AddReceivingMiddleware (or the sending equiv). The interceptor server installs its own middleware during construction, and exposing AddReceivingMiddleware on the wrapper would let callers unknowingly affect middleware ordering. Wrapping keeps the API surface explicit.

@Degiorgio Degiorgio force-pushed the interceptors/go branch 2 times, most recently from 22dec21 to d836df8 Compare March 11, 2026 12:21

// interceptResponse runs the response-phase interceptor chain. Mutators
// modify the typed result in place, so no marshal/unmarshal is needed.
func (s *Server) interceptResponse(

Choose a reason for hiding this comment

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

interceptResponse may need access to the request payload too.
Can you include it as one of the argument or part of InvocationContext?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I agree this will be useful, added a getter, see tests

// InterceptorType identifies the category of an interceptor.
type InterceptorType string

const (

Choose a reason for hiding this comment

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

Is there a plan to support the third interceptor type from the SEP, observability?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, check the comments on the SEP, in short, we added an audit mode for validators and mutators instead

@Degiorgio Degiorgio force-pushed the interceptors/go branch 5 times, most recently from 0a4611f to 84baa99 Compare March 11, 2026 19:36
@Degiorgio Degiorgio requested a review from sambhav March 12, 2026 10:50
Add the interceptor implementation for servers built with the go-sdk.
Interceptors sit between the transport and method handlers, enabling
policy enforcement, data sanitization, and traffic auditing without
modifying handler code.

The core interceptor framework (chain engine, validators, mutators,
invocations, results) is fully protocol-agnostic with zero MCP
dependencies, so it can be used with any server that processes
request/response pairs (gRPC, custom HTTP, etc.).

The MCP-specific server integration lives in a separate mcpserver
sub-package, wiring interceptors into go-sdk's middleware for all
transports (stdio, HTTP).

This covers the server-side chain execution model only. The SEP's
protocol-level methods (interceptors/register, interceptors/update,
interceptors/list, interceptors/execute) are not yet implemented.

Package structure:

  interceptors/
    chain.go          — public Chain type, protocol-agnostic API
    chain_executor.go — chainExecutor, filtering, sorting, snapshots
    chain_validate.go — parallel validator dispatch
    chain_mutate.go   — sequential mutator execution
    interceptor.go    — types, Metadata, Validator/Mutator structs
    invocation.go     — Invocation with audit-mode payload cloning
    result.go         — ValidationResult, MutationResult, ChainResult
    doc.go            — package documentation with examples

  interceptors/mcpserver/
    server.go         — Server wrapper, middleware, capability declaration
    events.go         — MCP event name constants
    doc.go            — sub-package documentation

Also includes:
- examples/validator and examples/mutator
- HTTP integration tests (httptest + StreamableHTTPHandler)
- doc/DESIGN.md, doc/CONFORMANCE.md, doc/PERFORMANCE.md

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.

2 participants