Skip to content

Initial implementation of TypeScript SDK#13

Open
BobDickinson wants to merge 12 commits into
modelcontextprotocol:mainfrom
BobDickinson:main
Open

Initial implementation of TypeScript SDK#13
BobDickinson wants to merge 12 commits into
modelcontextprotocol:mainfrom
BobDickinson:main

Conversation

@BobDickinson

Copy link
Copy Markdown

Initial implementation of MCP Interceptor TypeScript SDK

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

See typescript/sdk/docs/design-and-implementation.md for design rationale and implementation details.

@BobDickinson BobDickinson changed the title Intiial implementation of TypeScript SDK Initial implementation of TypeScript SDK May 24, 2026
@BobDickinson

Copy link
Copy Markdown
Author

Here's a video with an overview and some demos: https://youtu.be/1El_XjABrbA

Comment thread typescript/sdk/examples/transparent-proxy/src/index.ts

@jeongukjae jeongukjae left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this looks really nice! thanks a lot!

Comment thread typescript/sdk/vitest.config.js Outdated
Comment thread typescript/sdk/src/server/reflection.ts
Comment thread typescript/sdk/src/client/chain-orchestrator.ts
Comment thread typescript/sdk/src/server/register-interceptors.ts Outdated
Comment thread typescript/sdk/src/gateway/gateway-protocol-bridge.ts Outdated
Comment thread typescript/sdk/src/client/intercepting-client.ts
Comment thread typescript/sdk/src/client/intercepting-client.ts
Comment thread typescript/sdk/src/client/merge-interceptor-chain-entries.ts
Return processed list responses from InterceptingMcpClient; use MCP -32602 for unknown interceptors; add gateway name→client routing cache with invalidation on invalid-params; parallelize interceptors/list across hosts; fix defineInterceptor destructuring to receive full invoke params.
jeongukjae
jeongukjae previously approved these changes Jun 15, 2026

@jeongukjae jeongukjae left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks solid for me. Thanks a lot!

@wi-ski

wi-ski commented Jul 15, 2026

Copy link
Copy Markdown

@BobDickinson this has been approved-but-conflicting for a few weeks, and I would like to help get it in. Happy to take a pass at the conflict resolution and send it to your branch (PR-to-your-fork or a plain diff, whichever you prefer). Motive is simple: I have been building conformance fixtures over on #20 and want a merged mainline TypeScript SDK as the canonical target to certify against.

olaservo and others added 10 commits July 23, 2026 07:10
# Conflicts:
#	typescript/sdk/src/index.ts
#	typescript/sdk/src/interceptors.ts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 'enforce' legacy-read-only)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Aligns with sep.md (PR modelcontextprotocol#25) and the C# SDK: capability now lives at
capabilities.extensions["io.modelcontextprotocol/interceptors"] instead of a
top-level 'interceptor' key. The extensions record is typed in the v1 SDK's
ServerCapabilitiesSchema, so the capability now also survives stock v1 Client
initialize parsing (the old key was silently stripped).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion semantics

- ChainInterceptorEntry with per-entry InterceptorOverrides (failOpen,
  priorityHint, mode, timeoutMs, hook narrowing); widening overrides are
  rejected with InterceptorOverrideHookError per the SEP MUST.
- resolvePriority takes override precedence (overrides.priorityHint ??
  interceptor.priorityHint).
- Chain timeout is now actually enforced against real interceptor hosts:
  the orchestrator's signal is threaded through invokeInterceptor into
  client.request, with already-aborted signals short-circuited.
- Per-interceptor timeoutMs aborts just that invoke and routes through
  resolved failOpen; the chain-aggregate timeout maps to status 'timeout'.
- Caller cancellation now rejects with the abort reason instead of being
  misreported as chain status 'timeout'.
- All validations complete before rejecting; blocked chains aggregate every
  validation result and summary count (SEP aggregation rule).
- Per-interceptor config from ExecuteChainRequestParams.config is forwarded
  to interceptor/invoke (was silently dropped).
- AbortSignal.any fallback for Node 20.0-20.2.
- Empty host list is a no-op success chain (enables gateway pass-through).
- Overrides plumbed through executeInterceptorChainOnClients options and
  InterceptorChainRunner options, keyed by interceptor name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… error

The invoke handler now races the interceptor handler against the timeout
signal, so a handler that ignores the signal can no longer hold the request
past timeoutMs. Timeouts surface as JSON-RPC -32000 with
{ interceptor, timeoutMs, phase } data per SEP-2624. The client-side wire
timeout becomes a backstop with headroom so the host's richer error wins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A mutation that removed fields (e.g. argument redaction) previously had the
original arguments silently restored via '?? args' fallbacks, forwarding the
exact data the interceptor stripped. Malformed mutated payloads (no string
name) now fail loudly instead of silently discarding the mutation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y routing

listAllInterceptors drains every page (nextCursor) from a host; chain
discovery, the gateway routing table, and the bridge's list aggregation all
use it, so interceptors beyond page one can no longer be silently skipped.
The bridge also rejects foreign cursors and no longer forwards one host's
cursor to every other host. A failed routing-table build is dropped instead
of poisoning every subsequent interceptor/invoke with the cached rejection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Resolver-only gateways pass requests through instead of erroring when the
  resolver returns no connections for a request.
- Partial connect failures in the client pool close already-connected owned
  clients instead of leaking live transports per request.
- Resolved-client dispose settles every owned client even if one close throws,
  and a failing per-request dispose no longer masks the request outcome.
- Backend notifications/resources/updated and notifications/message are now
  relayed to proxied clients (subscribe was a silent end-to-end no-op), and
  tools/call progress is relayed under the caller's progressToken.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…overrides

The package is ESM-only ('type': 'module'); the 'require' condition pointed
at the ESM build and produced ERR_REQUIRE_ESM at runtime for CJS consumers.
CI now uses npm ci against the committed lockfile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@olaservo

Copy link
Copy Markdown
Member

Pushed a batch of commits on top of @BobDickinson's work to unblock this and merge. Bob's original commits are untouched; everything lands as additive commits. Suite is green: 91 tests (up from 74), typecheck, lint, and build.

Merge + SEP alignment (main moved under this PR — #17, #21, #24, #25):

  • Merged main and resolved conflicts; SEP-1763 → SEP-2624 references updated.
  • Mode values aligned to the spec: 'active' is canonical, 'enforce' accepted read-only as a legacy value (the design doc had these inverted).
  • Capability key moved to capabilities.extensions["io.modelcontextprotocol/interceptors"] (SEP-2133 format per Align capability key to SEP-2133 extensions format #25, same key as C#). Side benefit: the v1 SDK's ServerCapabilitiesSchema types extensions, so the capability now survives stock-client initialize parsing — the old top-level interceptor key was silently stripped.
  • Implemented InterceptorOverrides (Introduce InterceptorOverrides in the chain execution model #24): per-entry failOpen / priorityHint / mode / timeoutMs / hook narrowing (widening rejected per the SEP MUST), plumbed through the orchestrator, executeInterceptorChainOnClients, and InterceptorChainRunner. First SDK in the repo to implement it.

Correctness fixes found in deep review:

  • Chain timeoutMs is now actually enforced against real hosts — the orchestrator signal was previously dropped before reaching client.request, so a hung host blocked for the SDK default 60s. Caller cancellation now rejects with the abort reason instead of reporting status timeout.
  • Server-side invoke handler races the interceptor against the timeout signal (a handler ignoring the signal could previously hang forever) and reports SEP -32000 with {interceptor, timeoutMs, phase}.
  • Mutations that remove fields (e.g. argument redaction) are forwarded as-is — ?? args fallbacks were silently restoring the original unredacted arguments.
  • interceptors/list pagination is drained everywhere (listAllInterceptors); a paginating host's page-2 interceptors were silently skipped.
  • Gateway: empty-resolver requests pass through instead of erroring; partial connect failures no longer leak clients; a transient failure during routing-table build no longer poisons every later interceptor/invoke; resources/updated, notifications/message, and tools/call progress are now forwarded (subscribe was a silent no-op through the proxy); per-interceptor config is forwarded to interceptor/invoke.
  • Packaging: dropped the require export condition (ESM-only package; it produced ERR_REQUIRE_ESM), added an AbortSignal.any fallback for Node 20.0–20.2, CI uses npm ci.

Not addressed (follow-ups): sink semantics should be re-checked against #28 once that merges; reflection param-name binding under minification; pool eviction for dead cached clients; descriptor caching for the transparent path.

🤖 Generated with Claude Code

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.

5 participants