Skip to content

C# SDK: multi-server interceptor chain orchestration per SEP#18

Open
PederHP wants to merge 1 commit into
fix/issue-15-mode-priorityhintfrom
fix/issue-15-multi-server-chain
Open

C# SDK: multi-server interceptor chain orchestration per SEP#18
PederHP wants to merge 1 commit into
fix/issue-15-mode-priorityhintfrom
fix/issue-15-multi-server-chain

Conversation

@PederHP

@PederHP PederHP commented Jun 11, 2026

Copy link
Copy Markdown
Member

Addresses the third item of #15 ("Chain orchestrator only supports one MCP server").

Stacked on #17 — this PR targets that branch because it builds on the per-phase PriorityHint resolution introduced there. After #17 merges, the base will be switched to main (or it can be retargeted now and rebased).

What changed

The SEP's chain execution pattern requires: discover via interceptors/list on one or more servers → merge & sort all interceptors into a single chain by priorityHint (ascending, alphabetical tie-break) → invoke each interceptor on the server that hosts it (mutations sequential with payload chaining, validations parallel) → aggregate one result. The SDK previously ran each client's entire chain sequentially, so a -1000 mutation on server 2 ran after a +1000 mutation on server 1, and each server's validations only saw its own server's mutations.

  • Orchestrator (InterceptorChainOrchestrator, internal): now operates on chain entries — descriptor + invoker routing to its host — mirroring the SEP's ChainEntry, with one global sort across all filtered entries. The stable sort preserves caller-supplied server order for exact ties.
  • New public API (InterceptorChain / InterceptorChainEntry): DiscoverAsync lists from every server in parallel (fail-closed: any list failure throws rather than silently dropping a server's interceptors), instance ExecuteAsync runs prebuilt entries (a future caching seam), and a one-shot static ExecuteAsync(servers, params) does discover + execute.
  • InterceptorChainRunner (used by the gateway and InterceptingMcpClient): one merged chain instead of the per-client sequential loop. The single-client client.ExecuteChainAsync(...) extension keeps its signature and delegates to the same path (still 1 list + N invokes).
  • Duplicate interceptor names across servers are not deduplicated — each entry invokes on its own host (results may share an InterceptorName; documented).

Behavior changes for multi-client gateways

  • Interceptors from all servers form one globally-ordered chain (previously server 1's full chain ran before server 2's).
  • Validations from all servers run as one parallel batch against the payload after all mutations.
  • Sinks run once per phase for the merged chain (previously once per client).
  • TimeoutMs bounds the whole merged chain, not each per-client sub-chain.
  • Any server's interceptors/list failure fails the chain up-front (fail-closed).
  • Cost per phase: M parallel interceptors/list calls + N interceptor/invoke calls.

Testing

dotnet test from csharp/sdk/: 97 passing (was 87 after #17). New coverage:

  • Orchestrator: global priority ordering across servers, alphabetical tie-break across servers, payload chaining across servers in one mutation pass, all-server validations seeing the post-mutation payload, duplicate names invoking once per host.
  • InterceptorChainTests (real in-memory servers): discovery merge with server attribution, merged execution order, fail-closed discovery, single-client path equivalence.
  • Gateway: two interceptor servers where the second hosts the lower-priority mutation — the backend receives the globally-ordered payload (fails under the old sequential runner).

Closes #15.

🤖 Generated with Claude Code

The SEP chain execution pattern discovers interceptors from one or more
MCP servers, merges them into a single chain sorted globally by
priorityHint (alphabetical tie-break), and routes each interceptor/invoke
to the server hosting it. The SDK previously ran each client's full chain
sequentially, so cross-server priorities were ignored and each server's
validations only saw its own mutations.

- The orchestrator now operates on chain entries (descriptor + invoker),
  mirroring the SEP's ChainEntry, with one global sort across servers.
- New public InterceptorChain/InterceptorChainEntry API: parallel
  fail-closed discovery via interceptors/list on every server, then
  merged execution. Duplicate names across servers are not deduplicated;
  each entry invokes on its own host.
- InterceptorChainRunner (gateway + InterceptingMcpClient) now executes
  one merged chain instead of per-client sequential chains; single-client
  ExecuteChainAsync delegates to the same path unchanged.

Behavior changes for multi-client gateways: validations from all servers
run as one parallel batch after all mutations, sinks run once per phase,
TimeoutMs bounds the whole merged chain, and any server's list failure
fails the chain.

Closes #15.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@PederHP PederHP force-pushed the fix/issue-15-multi-server-chain branch from 9a916c6 to e1bd0ec Compare June 11, 2026 17:52
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.

1 participant