Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,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
169 changes: 154 additions & 15 deletions typescript/sdk/README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,179 @@
# MCP Interceptors TypeScript SDK

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

Requires **`@modelcontextprotocol/sdk` v1.x** as a peer dependency.

## Installation

```bash
npm install @ext-modelcontextprotocol/interceptors
npm install mcp-ext-interceptors @modelcontextprotocol/sdk
```

## 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/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
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),
}),
);
```

## Quick start — client API

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

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'],
}),
);

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' } },
});
```

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

## Quick start — InterceptingMcpClient

```typescript
import { Interceptor } from '@ext-modelcontextprotocol/interceptors';
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/sdk/client/index.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { McpInterceptorGateway } from 'mcp-ext-interceptors';

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

// The MCP SDK is available as a peer dependency
import { Client, Server } from '@modelcontextprotocol/sdk';
const server = new Server({ name: 'interceptor-proxy', version: '1.0.0' }, { capabilities: {} });
gateway.configureServer(server); // before connect
gateway.registerNotificationForwarding(server);

// Example usage will be added as the implementation progresses
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.interceptor`** with `supportedEvents` (merged automatically by `registerInterceptorsOnServer`).

The C# SDK in this repository uses `capabilities.extensions["interceptors"]` instead. Mixed deployments may need dual-read logic.

## 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