Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a86d341
Intiial implementation of TypeScript SDK
BobDickinson May 24, 2026
056e814
Changes from PR review feedback.
BobDickinson Jun 11, 2026
697309c
Merge remote-tracking branch 'origin/main' into fix/ts-sdk-sep-alignment
olaservo Jul 23, 2026
b5e8c75
docs: update SEP references from SEP-1763 to SEP-2624
olaservo Jul 23, 2026
d60b3ce
fix: align interceptor mode values with SEP-2624 ('active' canonical,…
olaservo Jul 23, 2026
28303b6
fix: advertise interceptor capability under SEP-2133 extensions key
olaservo Jul 23, 2026
2a14872
feat: implement SEP-2624 InterceptorOverrides and fix chain cancellat…
olaservo Jul 23, 2026
e259ab3
fix: enforce interceptor/invoke timeoutMs server-side with SEP -32000…
olaservo Jul 23, 2026
41d0d49
fix: forward mutated tools/call and prompts/get payloads as-is
olaservo Jul 23, 2026
92b5d76
fix: honor interceptors/list pagination in chain discovery and gatewa…
olaservo Jul 23, 2026
213dc88
fix: gateway lifecycle and notification forwarding
olaservo Jul 23, 2026
837f89a
chore: drop CJS require export condition, use npm ci in CI, document …
olaservo Jul 23, 2026
5bdf03d
refactor: migrate to MCP TypeScript SDK v2 packages
olaservo Aug 6, 2026
4ec69b1
fix: address Copilot review feedback
olaservo Aug 9, 2026
fe6c28f
fix: reflection parameter binding for signal and default values
olaservo Aug 9, 2026
6bf6e67
Merge remote-tracking branch 'origin/main' into fix/ts-sdk-sep-alignment
olaservo Aug 12, 2026
cfdb30d
docs: clarify that phase `both` is authoring-only, never on the wire
olaservo Aug 13, 2026
b77e8eb
Merge branch 'main' into main
olaservo Aug 19, 2026
f872f32
docs: correct C# init capability key in design doc
olaservo Aug 19, 2026
4ff5ae0
fix(gateway): reject interceptor/invoke for names held by two hosts
olaservo Aug 19, 2026
d74be88
chore(ts): raise minimum Node to 22
olaservo Aug 19, 2026
ec9b842
fix(reflection): bind `params` and stop silently passing undefined
olaservo Aug 19, 2026
5417f28
Merge remote-tracking branch 'origin/main' into pr13-head
olaservo Aug 19, 2026
9465cff
fix(runner): honour the wildcard event in shouldIntercept
olaservo Aug 20, 2026
ab3d377
refactor(protocol): parse interceptor results with the wire schema
olaservo Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/status-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ jobs:
],
typescript: [
'TypeScript CI / Linting',
'TypeScript CI / Unit Tests (20)',
'TypeScript CI / Unit Tests (22)'
'TypeScript CI / Unit Tests (22)',
'TypeScript CI / Unit Tests (24)'
]
};
Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/typescript.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "20"
node-version: "22"
- name: Install dependencies
run: |
cd typescript/sdk
npm install
npm ci
- name: Run TypeScript compiler check
run: |
cd typescript/sdk
npm run build
npm run typecheck
- name: Run ESLint
run: |
cd typescript/sdk
Expand All @@ -43,7 +44,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ["20", "22"]
node-version: ["22", "24"]
steps:
- name: Check out code
uses: actions/checkout@v6
Expand All @@ -54,7 +55,7 @@ jobs:
- name: Install dependencies
run: |
cd typescript/sdk
npm install
npm ci
- name: Run tests
run: |
cd typescript/sdk
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This repository provides a multi-language reference implementation of the propos
| C# | `csharp/sdk/` | `ModelContextProtocol.Interceptors` | In Progress |
| Go | `go/sdk/` | `github.com/modelcontextprotocol/ext-interceptors/go/sdk` | Planned |
| Python | `python/sdk/` | `mcp-ext-interceptors` | Planned |
| TypeScript | `typescript/sdk/` | `@ext-modelcontextprotocol/interceptors` | Planned |
| TypeScript | `typescript/sdk/` | `mcp-ext-interceptors` | In progress |


## CI/CD
Expand Down
2 changes: 1 addition & 1 deletion typescript/sdk/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"project": "./tsconfig.json"
"project": "./tsconfig.eslint.json"
},
"plugins": ["@typescript-eslint"],
"extends": [
Expand Down
2 changes: 1 addition & 1 deletion typescript/sdk/.nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20
22
177 changes: 162 additions & 15 deletions typescript/sdk/README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,187 @@
# MCP Interceptors TypeScript SDK

TypeScript implementation of the Model Context Protocol (MCP) interceptor framework.
TypeScript implementation of [SEP-2624](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/2624) — gateway-level interceptors for the [Model Context Protocol](https://modelcontextprotocol.io/).

Requires the **MCP TypeScript SDK v2 packages** (`@modelcontextprotocol/client`, `@modelcontextprotocol/server`) as peer dependencies.

## Installation

```bash
npm install @ext-modelcontextprotocol/interceptors
npm install mcp-ext-interceptors @modelcontextprotocol/client @modelcontextprotocol/server
```

## Overview

```
Client ──▶ Interceptor host ──▶ Application MCP server
◀── (validate/mutate) ◀── (tools, resources, …)
```

An **interceptor host** is a normal MCP server that exposes `interceptors/list` and `interceptor/invoke`. It is not the same role as your tools/resources **backend** server.

## Quick start — interceptor host (stdio)

```typescript
import { Server } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import {
registerInterceptorsOnServer,
InterceptionEvents,
validationSuccess,
type RegisteredInterceptor,
} from 'mcp-ext-interceptors';

const interceptors: RegisteredInterceptor[] = [
{
descriptor: {
name: 'pii-validator',
type: 'validation',
hooks: [{ events: [InterceptionEvents.ToolsCall], phase: 'request' }],
},
handler: () => validationSuccess('request'),
},
];

const server = new Server(
{ name: 'my-interceptor-host', version: '1.0.0' },
{ capabilities: {} },
);
registerInterceptorsOnServer(server, interceptors);

await server.connect(new StdioServerTransport());
```

Or use **`defineInterceptor`** for a C#-style handler definition:

```typescript
import { defineInterceptor, InterceptionEvents } from 'mcp-ext-interceptors';

const entry = defineInterceptor(
{
name: 'email-redactor',
type: 'mutation',
events: [InterceptionEvents.ToolsCall],
phase: 'request',
priorityHint: -1000,
},
(payload) => ({
type: 'mutation',
phase: 'request',
modified: true,
payload: redactEmails(payload),
}),
);
```

## Usage
`defineInterceptor` binds handler parameters using `Function.prototype.toString` heuristics (not full reflection like C#). Prefer:

- `(params) => { … }` — `params` is the full invoke request (`payload`, `event`, `phase`, `context`, …)
- `({ payload, phase }) => { … }` — destructuring from the same object
- `(payload, event, phase, context, signal) =>` — positional by arity

Avoid `(...rest) =>` and relying on default parameters alone; use `(params) =>` instead.

## Quick start — client API

```typescript
import { Interceptor } from '@ext-modelcontextprotocol/interceptors';
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
import {
listInterceptors,
invokeInterceptor,
executeInterceptorChainOnClient,
} from 'mcp-ext-interceptors';

// The MCP SDK is available as a peer dependency
import { Client, Server } from '@modelcontextprotocol/sdk';
const client = new Client({ name: 'app', version: '1.0.0' }, { capabilities: {} });
await client.connect(
new StdioClientTransport({
command: 'npx',
args: ['tsx', 'path/to/interceptor-server/src/index.ts'],
}),
);

// Example usage will be added as the implementation progresses
const listed = await listInterceptors(client);
const result = await invokeInterceptor(client, {
name: 'pii-validator',
event: 'tools/call',
phase: 'request',
payload: { name: 'my-tool', arguments: {} },
});

const chain = await executeInterceptorChainOnClient(client, {
event: 'tools/call',
phase: 'request',
payload: { name: 'my-tool', arguments: { message: 'hello' } },
});
```

Chain execution is orchestrated in the SDK (`list` + ordered `invoke`); there is no `interceptor/executeChain` wire method.

## Quick start — InterceptingMcpClient

```typescript
import { InterceptingMcpClient } from 'mcp-ext-interceptors';

const gateway = new InterceptingMcpClient(backendClient, {
interceptorClient: interceptorHostClient,
events: ['tools/call'],
});

const result = await gateway.callTool('echo', { message: 'hello' });
```

## Quick start — transparent proxy (`McpInterceptorGateway`)

```typescript
import { Client } from '@modelcontextprotocol/client';
import { Server } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import { McpInterceptorGateway } from 'mcp-ext-interceptors';

const gateway = new McpInterceptorGateway({
backendClient,
interceptorClients: [interceptorHostClient],
events: ['tools/call'],
});

const server = new Server({ name: 'interceptor-proxy', version: '1.0.0' }, { capabilities: {} });
gateway.configureServer(server); // before connect
gateway.registerNotificationForwarding(server);

await server.connect(new StdioServerTransport());
```

Connecting clients use the proxy as the backend; the parent process spawns interceptor and backend servers over stdio (same pattern as the C# `TransparentProxySample`).

## Capabilities

Interceptor hosts advertise SEP **`capabilities.extensions["io.modelcontextprotocol/interceptors"]`** with `supportedEvents` (merged automatically by `registerInterceptorsOnServer`; key exported as `InterceptorExtensionCapabilityKey`).

The C# SDK in this repository advertises the same key, so mixed TS/C# deployments interoperate directly.

## Examples

From `typescript/sdk` after `npm run build` (examples import `dist/` from the local build):

| Script | C# sample | Description |
|--------|-----------|-------------|
| `npm run example:interceptor-server` | `InterceptorServerSample` | Stdio interceptor host (PII validator, email redactor, logger sink) |
| `npm run example:interceptor-client` | `InterceptorClientSample` | Spawns the server via `StdioClientTransport`; list, invoke, chain |
| `npm run example:gateway` | `GatewaySample` | `InterceptingMcpClient` → interceptor host → everything server |
| `npm run example:transparent-proxy` | `TransparentProxySample` | Stdio transparent proxy (`McpInterceptorGateway`) |
| `npm run example:gateway-chain` | `GatewayChainSample` | Notes on multi-host ordering with `interceptorClients` |

The client sample spawns the server process and talks over its stdio pipes (same pattern as the C# `StdioClientTransport` + `dotnet run --project …`).

## Development

```bash
# Install dependencies
npm install

# Build
npm run build

# Run tests
npm test

# Lint
npm run lint
```

## License

Apache License 2.0 - See LICENSE file in the root directory for details.
Apache-2.0 — see the repository [LICENSE](https://github.com/modelcontextprotocol/ext-interceptors/blob/main/LICENSE).
Loading
Loading