experimental-ext-interceptors / sdk
CLAUDE.md23 starsChanged 3 months agoApache-2.0View on GitHub
Why message filter, not handlers: McpServerHandlers and McpServerImpl are internal in the SDK. We can't register handlers for new JSON-RPC methods from outside. Instead we use McpServerOptions.Filters.Message.IncomingFilters to intercept interceptors/list and interceptor/invoke, handle them, send JsonRpcResponse via context.Server.SendMessageAsync(), and skip calling next. See InterceptorMessageFilter.cs. Chain execution is no longer a wire method — it's SDK orchestration in Client/InterceptorChainOrchestrator.cs. Why ServerCapabilities.Extensions: The SDK's intended mechanism for protocol extensions (stable since SDK 2.x — no experimental pragma needed). We advertise InterceptorsCapability {…
The file
We split this CLAUDE.md into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 78
Tool call error handling note: When an interceptor validation aborts a tools/call, the gateway throws McpInterceptorValidationException.
- Very specific: line 96
Not wired into InterceptingMcpClient — these are for custom gateway use
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
MCP Interceptors - C# SDK
What this is
C# implementation of gateway-level interceptors from SEP-2624. NuGet package additive to the official C# MCP SDK (v2.1.0, MCP spec 2026-07-28). Focus is on the protocol-level extension (client → interceptor server → server), NOT in-process middleware.
Build & test
dotnet build # from csharp/sdk/
dotnet test # 105 tests across the interceptor test projectKey architectural constraints
Why message filter, not handlers: McpServerHandlers and McpServerImpl are internal in the SDK. We can't register handlers for new JSON-RPC methods from outside. Instead we use McpServerOptions.Filters.Message.IncomingFilters to intercept interceptors/list and interceptor/invoke, handle them, send JsonRpcResponse via context.Server.SendMessageAsync(), and skip calling next. See InterceptorMessageFilter.cs. Chain execution is no longer a wire method — it's SDK orchestration in Client/InterceptorChainOrchestrator.cs.
Why ServerCapabilities.Extensions: The SDK's intended mechanism for protocol extensions (stable since SDK 2.x — no experimental pragma needed). We advertise InterceptorsCapability { SupportedEvents } under Extensions["io.modelcontextprotocol/interceptors"].
Client SendRequestAsync: The public SendRequestAsync<TParameters, TResult> endpoint extension takes JsonSerializerOptions (call it with named cancellationToken: — 2.x inserted an optional RequestId parameter before it). We pass InterceptorJsonUtilities.DefaultOptions which chains McpJsonUtilities.DefaultOptions + our InterceptorJsonContext. The internal overload takes JsonTypeInfo<T> — we can't use it.
SDK 2.x / spec 2026-07-28 notes:
- Tasks moved out of SDK core into a separate, wire-incompatible
ModelContextProtocol.Extensions.Taskspackage; the gateway no longer needs to suppress a backendTaskscapability. - Logging (
logging/setLevel) andresources/subscribeare deprecated by the spec (SEP-2577 / SEP-2575) and rejected on2026-07-28connections, but the gateway intentionally keeps proxying them for down-level clients/backends (#pragma warning disable MCP9005at those sites; tests pinprotocolVersion: "2025-06-18"). Tool.inputSchemais required and validated on deserialization; the gateway fetchestools/listas raw JSON and coerces missing/invalid schemas to{"type":"object"}(GatewayProxyConfigurator.EnsureToolInputSchemas) so down-level backends and schema-stripping interceptors can't fail the request.- MRTR (multi-round-trip requests) is not forwarded; the gateway's server reports
IsMrtrSupported == false.
InterceptingMcpClient is composition: McpClient has an internal constructor; subclassing is [Experimental]. We wrap it as a concrete class exposing .Inner for direct access.
Chain execution order (SEP-2624)
- Request phase (sending): Mutations (sequential by priority ↑) → Validations (parallel) → Sinks (fire-and-forget)
- Response phase (receiving): Validations (parallel) → Sinks (fire-and-forget) → Mutations (sequential by priority ↑)
- Lower
PriorityHintexecutes first; scalar (both phases) or per-phase{request, response}form, unset phase resolves to 0; ties broken alphabetically by name
JSON-RPC methods
| Method | Params → Result |
interceptors/list | ListInterceptorsRequestParams → ListInterceptorsResult |
interceptor/invoke | InvokeInterceptorRequestParams → InterceptorResult (polymorphic) |
InterceptorResult polymorphism
Uses [JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] with "validation", "mutation", "sink" discriminators. Serialization/deserialization handles this automatically via STJ source-gen in InterceptorJsonContext.
Parameter binding (ReflectionMcpServerInterceptor)
Interceptor methods auto-bind from InvokeInterceptorRequestParams:
JsonNode payload→.PayloadJsonNode config→.Configstring event/string eventName→.EventInterceptorPhase phase→.PhaseInvokeInterceptorContext→.ContextCancellationToken,McpServer,IServiceProvider→ framework- Return
bool→ wrapped asValidationInterceptorResult { Valid = result }
SDK reference paths (local at /mnt/d/code/ai/mcp/csharp-sdk)
src/ModelContextProtocol.Core/Server/McpServerTool.cs— pattern we followsrc/ModelContextProtocol.Core/Server/McpMessageFilter.cs— our hook pointsrc/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs— Extensions dictsrc/ModelContextProtocol.Core/McpSession.Methods.cs— public SendRequestAsyncsrc/ModelContextProtocol/McpServerBuilderExtensions.cs— builder patternsrc/ModelContextProtocol.Core/McpJsonUtilities.cs— JSON context chaining pattern
Transparent gateway/proxy (Gateway/)
McpInterceptorGateway: Configures an McpServer as a transparent proxy. Reads backend ServerCapabilities, registers handler delegates (CallToolHandler, ListToolsHandler, etc.) that route through interceptor chains before forwarding to the backend. By default the gateway is transparent-only; SEP passthrough is opt-in via ExposeInterceptorProtocol = true. To connecting clients, the proxy appears to be the backend server.
InterceptorChainRunner (internal): Shared interception logic used by both InterceptingMcpClient and McpInterceptorGateway. Multiple interceptor clients form a single merged chain per the SEP: interceptors discovered from every client (parallel interceptors/list, fail-closed) are sorted globally by priority hint (alphabetical tie-break) and each is invoked on the client hosting it. Public API: Client/InterceptorChain.cs (DiscoverAsync + ExecuteAsync over IEnumerable<McpClient>); duplicate names across servers are not deduplicated.
Gateway split: GatewayProxyConfigurator owns transparent MCP proxy wiring, GatewayInterceptorProtocolBridge owns optional SEP passthrough wiring, and GatewayConnectionForwardingRegistrar owns connection-bound notification forwarding registration.
McpInterceptorGatewayOptions: Configuration — BackendClient, optional preconnected InterceptorClients, optional external InterceptorServerConnections, optional dynamic InterceptorServerConnectionResolver, Events filter, TimeoutMs, DefaultContext, optional ServerInfo override, and ExposeInterceptorProtocol.
External interceptor connections: McpInterceptorGateway.CreateAsync(...) can create interceptor clients from standard SDK transports (IClientTransport) via McpInterceptorServerConnectionOptions. Dynamic per-request transparent resolution is supported via InterceptorServerConnectionResolver using the SDK's MessageContext plus SEP event name.
Builder extension: IMcpServerBuilder.WithInterceptorGateway(options) and WithInterceptorGateway(Func<IServiceProvider, McpInterceptorGatewayOptions>) for DI/builder scenarios. Builder-based wiring still expects already-connected interceptor clients; async external transport setup should use McpInterceptorGateway.CreateAsync(...).
Notification forwarding: gateway.RegisterNotificationForwarding(proxyServer) subscribes to backend tools/list_changed, prompts/list_changed, resources/list_changed and re-sends through the proxy.
Why handler delegates (not message filters) for the proxy: The SDK's With*Handler methods automatically set ServerCapabilities, are type-safe, and are the intended extension point. Message filters are still used for interceptor protocol passthrough (interceptors/list, interceptor/invoke).
Tool call error handling note: When an interceptor validation aborts a tools/call, the gateway throws McpInterceptorValidationException. The SDK catches this and returns CallToolResult { IsError = true } (not a JSON-RPC error), since tool execution errors are returned as results by design.
InterceptingMcpClient wrapped methods
CallToolAsync—tools/callListToolsAsync—tools/listListPromptsAsync—prompts/listGetPromptAsync—prompts/getListResourcesAsync—resources/listReadResourceAsync—resources/readSubscribeToResourceAsync—resources/subscribeListInterceptorsAsync— direct passthrough to interceptor client
LLM completion payloads (Protocol/LlmCompletionPayload.cs)
LlmCompletionRequestPayload— model, messages, maxTokens, temperature, metadataLlmCompletionResponsePayload— model, message, stopReason, usage, metadataLlmMessage— role + contentLlmUsage— inputTokens + outputTokens- Registered in
InterceptorJsonContextfor source-gen serialization - Not wired into
InterceptingMcpClient— these are for custom gateway use
Samples
InterceptorServerSample— stdio server hosting 3 interceptorsGatewaySample— single gateway: client → interceptor → everything serverInterceptorClientSample— client API: list, invoke, execute chain directlyGatewayChainSample— chained gateways: security layer → logging layer → serverTransparentProxySample— stdio proxy server: clients connect to it as if it were the backend, all requests routed through interceptorsConfigDrivenGatewaySample— sample-only config-driven host composed from the gateway primitives; demonstrates static and dynamic external interceptor resolution with transport-configured backend/interceptor clients
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

