mcpose 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backendClient.js +8 -1
- package/dist/backendClient.js.map +1 -1
- package/dist/core.d.ts +75 -8
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +196 -40
- package/dist/core.js.map +1 -1
- package/dist/eventStore.d.ts +17 -0
- package/dist/eventStore.d.ts.map +1 -0
- package/dist/eventStore.js +34 -0
- package/dist/eventStore.js.map +1 -0
- package/dist/identity.d.ts +16 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +2 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +9 -3
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +4 -3
- package/dist/middleware.js.map +1 -1
- package/dist/proxyContext.d.ts +18 -0
- package/dist/proxyContext.d.ts.map +1 -0
- package/dist/proxyContext.js +14 -0
- package/dist/proxyContext.js.map +1 -0
- package/dist/rejection.d.ts +19 -0
- package/dist/rejection.d.ts.map +1 -0
- package/dist/rejection.js +10 -0
- package/dist/rejection.js.map +1 -0
- package/dist/telemetry.d.ts +15 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +2 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/testing.d.ts +2 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +3 -2
- package/dist/testing.js.map +1 -1
- package/package.json +27 -13
- package/README.md +0 -311
package/README.md
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
# mcpose
|
|
2
|
-
|
|
3
|
-
Composable middleware proxy for MCP servers.
|
|
4
|
-
|
|
5
|
-
## New in 1.1.1
|
|
6
|
-
|
|
7
|
-
- mirrors only upstream-advertised MCP capabilities
|
|
8
|
-
- forwards abort signals and progress updates through the proxy
|
|
9
|
-
- advertises and fans out list-changed notifications correctly
|
|
10
|
-
- closes active HTTP proxy sessions on shutdown
|
|
11
|
-
- ships a stronger `mcpose/testing` mock backend
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Background
|
|
17
|
-
|
|
18
|
-
mcpose was extracted from [`financial-elastic-mcp-server`](https://github.com/amir-gorji/financial-elastic-mcp-server), an Elasticsearch MCP server built for financial institutions that needed PII redaction and audit logging on every tool call. Those cross-cutting concerns were originally hardcoded into a single server. mcpose lifts that pattern into a reusable, composable middleware layer that can wrap **any** upstream MCP server.
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Concept
|
|
23
|
-
|
|
24
|
-
mcpose is a **transparent proxy** between an LLM client and an upstream MCP server. It mirrors the upstream MCP surface and routes supported calls through middleware. The client sees a normal MCP server; the upstream sees a normal MCP client.
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Install
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npm install mcpose
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
**Peer dependency** — must be installed separately:
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
npm install @modelcontextprotocol/sdk@>=1.0.0
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Quick Start
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
import { createBackendClient, startProxy } from 'mcpose';
|
|
46
|
-
import type { ToolMiddleware } from 'mcpose';
|
|
47
|
-
|
|
48
|
-
// 1. Connect to the upstream MCP server (stdio)
|
|
49
|
-
const backend = await createBackendClient({
|
|
50
|
-
command: 'node',
|
|
51
|
-
args: ['/path/to/backend-server.mjs'],
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// 2. Define middleware
|
|
55
|
-
const loggingMW: ToolMiddleware = async (req, next) => {
|
|
56
|
-
console.error(`→ ${req.params.name}`);
|
|
57
|
-
const result = await next(req);
|
|
58
|
-
console.error(`← ${req.params.name} done`);
|
|
59
|
-
return result;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// 3. Start the proxy on stdio
|
|
63
|
-
await startProxy(backend, {
|
|
64
|
-
toolMiddleware: [loggingMW],
|
|
65
|
-
});
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## Proxy model
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
┌──────────────┐ ┌────────────────────────────────┐ ┌────────────────────┐
|
|
74
|
-
│ LLM client │ ◄────► │ mcpose │ ◄────► │ Upstream MCP │
|
|
75
|
-
│ (Claude, │ │ · visibility filters │ │ server │
|
|
76
|
-
│ Cursor…) │ │ · middleware pipelines │ │ (stdio or HTTP) │
|
|
77
|
-
└──────────────┘ └────────────────────────────────┘ └────────────────────┘
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
For each supported tool or resource, mcpose picks one of three routing paths:
|
|
81
|
-
|
|
82
|
-
| Path | Option | Behavior |
|
|
83
|
-
|---|---|---|
|
|
84
|
-
| **Hidden** | `hiddenTools` / `hiddenResources` | Omitted from list responses; rejected with an error at call time |
|
|
85
|
-
| **Pass-through** | `passThroughTools` / `passThroughResources` | Forwarded raw to upstream — all middleware skipped |
|
|
86
|
-
| **Middleware** | everything else | Routed through the full `toolMiddleware` / `resourceMiddleware` pipeline |
|
|
87
|
-
|
|
88
|
-
Prompts are forwarded as-is when the upstream supports prompts.
|
|
89
|
-
|
|
90
|
-
The proxy preserves core request semantics end to end:
|
|
91
|
-
|
|
92
|
-
- advertised capabilities are mirrored from the upstream server
|
|
93
|
-
- abort signals are forwarded to upstream tool, resource, and prompt calls
|
|
94
|
-
- upstream progress updates are relayed back to the downstream client
|
|
95
|
-
- list-changed notifications are advertised and fanned out when the upstream supports them
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Middleware model
|
|
100
|
-
|
|
101
|
-
Middleware follows the **onion model**: outer layers run code before *and* after inner layers. Each middleware receives the request and a `next` function to invoke the rest of the pipeline.
|
|
102
|
-
|
|
103
|
-
```
|
|
104
|
-
request ──►
|
|
105
|
-
┌──────────────────────────────────────────┐
|
|
106
|
-
│ outerMW (enter) │
|
|
107
|
-
│ ┌────────────────────────────────────┐ │
|
|
108
|
-
│ │ innerMW (enter) │ │
|
|
109
|
-
│ │ ┌──────────────────────────────┐ │ │
|
|
110
|
-
│ │ │ upstream call │ │ │
|
|
111
|
-
│ │ └──────────────────────────────┘ │ │
|
|
112
|
-
│ │ innerMW (exit) ◄── response │ │
|
|
113
|
-
│ └────────────────────────────────────┘ │
|
|
114
|
-
│ outerMW (exit) ◄── response │
|
|
115
|
-
└──────────────────────────────────────────┘
|
|
116
|
-
◄── response
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
**Array order in `ProxyOptions`** uses **response-processing order**: the first element processes the response *first* (innermost layer). `ProxyOptions` calls `pipe()` internally — no need to wrap manually. To guarantee audit never sees raw PII:
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
toolMiddleware: [piiMW, auditMW]
|
|
123
|
-
// Execution:
|
|
124
|
-
// 1. auditMW enter → capture startTime (outermost)
|
|
125
|
-
// 2. piiMW enter → transform request
|
|
126
|
-
// 3. upstream call
|
|
127
|
-
// 4. piiMW exit → redact PII from response (processes response first)
|
|
128
|
-
// 5. auditMW exit → log already-clean data (processes response last)
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
`compose([outerMW, innerMW])` uses the **opposite** (outermost-first) convention — `ProxyOptions` arrays are **not** interchangeable with `compose()` arguments.
|
|
132
|
-
|
|
133
|
-
A middleware can **short-circuit** by returning without calling `next`, or **handle upstream errors** by wrapping `await next(req)` in a try/catch.
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
## API Reference
|
|
138
|
-
|
|
139
|
-
### `Middleware<Req, Res>` · `ToolMiddleware` · `ResourceMiddleware` · `compose()`
|
|
140
|
-
|
|
141
|
-
```ts
|
|
142
|
-
type Middleware<Req, Res> = (
|
|
143
|
-
req: Req,
|
|
144
|
-
next: (req: Req) => Promise<Res>,
|
|
145
|
-
) => Promise<Res>;
|
|
146
|
-
|
|
147
|
-
// Convenience aliases for the two pipeline types:
|
|
148
|
-
type ToolMiddleware = Middleware<CallToolRequest, CompatibilityCallToolResult>;
|
|
149
|
-
type ResourceMiddleware = Middleware<ReadResourceRequest, ReadResourceResult>;
|
|
150
|
-
|
|
151
|
-
function compose<Req, Res>(
|
|
152
|
-
middlewares: ReadonlyArray<Middleware<Req, Res>>,
|
|
153
|
-
): Middleware<Req, Res>;
|
|
154
|
-
|
|
155
|
-
// Type guard — narrows CompatibilityCallToolResult to CallToolResult
|
|
156
|
-
// (safe access to .content and .isError without casts):
|
|
157
|
-
function hasToolContent(r: CompatibilityCallToolResult): r is CallToolResult;
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
`compose` takes an array in **outermost-first** order. Use `hasToolContent` in middleware implementations before accessing `.content` or `.isError`, since `CompatibilityCallToolResult` also covers the legacy `{ toolResult }` shape.
|
|
161
|
-
|
|
162
|
-
---
|
|
163
|
-
|
|
164
|
-
### `BackendConfig` · `createBackendClient()`
|
|
165
|
-
|
|
166
|
-
```ts
|
|
167
|
-
interface BackendConfig {
|
|
168
|
-
command?: string; // Executable to spawn for stdio transport (e.g., "node")
|
|
169
|
-
args?: string[]; // Arguments for the spawned process
|
|
170
|
-
url?: string; // HTTP endpoint of a running MCP server (takes precedence over stdio)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function createBackendClient(config: BackendConfig): Promise<BackendClient>;
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
`BackendClient` is an alias for the SDK `Client`. It throws if neither `command` nor `url` is provided, or if the connection fails.
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
### `ProxyOptions` · `startProxy()` · `createProxyServer()`
|
|
181
|
-
|
|
182
|
-
```ts
|
|
183
|
-
interface ProxyOptions {
|
|
184
|
-
toolMiddleware?: ReadonlyArray<ToolMiddleware>;
|
|
185
|
-
resourceMiddleware?: ReadonlyArray<ResourceMiddleware>;
|
|
186
|
-
passThroughTools?: ReadonlyArray<string>;
|
|
187
|
-
passThroughResources?: ReadonlyArray<string>;
|
|
188
|
-
hiddenTools?: ReadonlyArray<string>;
|
|
189
|
-
hiddenResources?: ReadonlyArray<string>;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async function startProxy(backend: BackendClient, options?: ProxyOptions): Promise<void>;
|
|
193
|
-
function createProxyServer(backend: BackendClient, options?: ProxyOptions): Server;
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
| Option | Description |
|
|
197
|
-
|---|---|
|
|
198
|
-
| `toolMiddleware` | Middleware stack for tool calls, in response-processing order (first element processes response first). |
|
|
199
|
-
| `resourceMiddleware` | Middleware stack for resource reads, in response-processing order. |
|
|
200
|
-
| `passThroughTools` | Tool names forwarded raw to upstream — middleware skipped entirely. |
|
|
201
|
-
| `passThroughResources` | Resource URIs forwarded raw to upstream — middleware skipped entirely. |
|
|
202
|
-
| `hiddenTools` | Tool names removed from `list_tools` **and** rejected at call time with `MethodNotFound`. |
|
|
203
|
-
| `hiddenResources` | Resource URIs removed from `list_resources` **and** rejected at call time with `InvalidRequest`. |
|
|
204
|
-
|
|
205
|
-
`createProxyServer` mirrors only the upstream capabilities exposed by `backend.getServerCapabilities()`. Unsupported prompt, resource, and tool endpoints are not advertised or registered.
|
|
206
|
-
|
|
207
|
-
`startProxy` connects the proxy to a `StdioServerTransport`. `createProxyServer` returns the configured `Server` without connecting — useful for testing request handlers without a live transport.
|
|
208
|
-
|
|
209
|
-
---
|
|
210
|
-
|
|
211
|
-
### `HttpProxyOptions` · `startHttpProxy()`
|
|
212
|
-
|
|
213
|
-
```ts
|
|
214
|
-
interface HttpProxyOptions {
|
|
215
|
-
port?: number; // Default: 3000
|
|
216
|
-
host?: string; // Default: all interfaces
|
|
217
|
-
path?: string; // Default: '/mcp'
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function startHttpProxy(
|
|
221
|
-
backend: BackendClient,
|
|
222
|
-
options?: ProxyOptions,
|
|
223
|
-
httpOptions?: HttpProxyOptions,
|
|
224
|
-
): Promise<http.Server>;
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
Starts the proxy over Streamable HTTP with stateful sessions. Each client connection is assigned an `mcp-session-id`. Upstream list-change notifications (`tools/list_changed`, `resources/list_changed`, `prompts/list_changed`) are fanned out to all active sessions when the upstream advertises them.
|
|
228
|
-
|
|
229
|
-
```ts
|
|
230
|
-
import { createBackendClient, startHttpProxy } from 'mcpose';
|
|
231
|
-
|
|
232
|
-
const backend = await createBackendClient({ url: 'http://upstream-mcp-server/mcp' });
|
|
233
|
-
const server = await startHttpProxy(backend, { toolMiddleware: [loggingMW] }, { port: 8080 });
|
|
234
|
-
// HTTP server is now listening on port 8080 at /mcp
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
On shutdown, active proxy sessions are closed before the underlying `http.Server` finishes closing.
|
|
238
|
-
|
|
239
|
-
**Limitations:**
|
|
240
|
-
- Sessions have no idle timeout.
|
|
241
|
-
- SSE reconnect replay is not supported (no `EventStore`).
|
|
242
|
-
|
|
243
|
-
---
|
|
244
|
-
|
|
245
|
-
### `mcpose/testing`
|
|
246
|
-
|
|
247
|
-
```ts
|
|
248
|
-
import { createMockBackendClient, runToolMiddleware } from 'mcpose/testing';
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
`createMockBackendClient()` returns an in-memory backend stub with capability lookup and notification hooks. It works with both `createProxyServer()` and `startHttpProxy()` tests.
|
|
252
|
-
|
|
253
|
-
---
|
|
254
|
-
|
|
255
|
-
## Recipe: PII redaction
|
|
256
|
-
|
|
257
|
-
The origin use case for mcpose: a financial-grade MCP server where every Elasticsearch tool response must be scrubbed of PII before it reaches the LLM or the audit log.
|
|
258
|
-
|
|
259
|
-
Use a factory to keep middleware configurable and testable:
|
|
260
|
-
|
|
261
|
-
```ts
|
|
262
|
-
import { hasToolContent } from 'mcpose';
|
|
263
|
-
import type { ToolMiddleware } from 'mcpose';
|
|
264
|
-
|
|
265
|
-
function createPiiMiddleware(patterns: RegExp[]): ToolMiddleware {
|
|
266
|
-
return async (req, next) => {
|
|
267
|
-
const result = await next(req);
|
|
268
|
-
if (!hasToolContent(result)) return result;
|
|
269
|
-
return {
|
|
270
|
-
...result,
|
|
271
|
-
content: result.content.map((item) =>
|
|
272
|
-
item.type === 'text'
|
|
273
|
-
? { ...item, text: redactPii(item.text, patterns) }
|
|
274
|
-
: item,
|
|
275
|
-
),
|
|
276
|
-
};
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function redactPii(text: string, patterns: RegExp[]): string {
|
|
281
|
-
return patterns.reduce((t, re) => t.replace(re, '[REDACTED]'), text);
|
|
282
|
-
}
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
Stack it with audit middleware — PII first in the array so audit always sees clean data:
|
|
286
|
-
|
|
287
|
-
```ts
|
|
288
|
-
await startProxy(backend, {
|
|
289
|
-
toolMiddleware: [
|
|
290
|
-
createPiiMiddleware([/\b\d{9}\b/g, /[A-Z]{2}\d{6}/g]), // SSNs, account numbers
|
|
291
|
-
createAuditMiddleware({ destination: auditLog }),
|
|
292
|
-
],
|
|
293
|
-
});
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
The array order guarantees: PII is redacted *before* the audit layer ever sees the response. No raw PII reaches a log, satisfying financial regulatory requirements.
|
|
297
|
-
|
|
298
|
-
> **Reference implementation:** [`elastic-pii-proxy`](https://github.com/amir-gorji/elastic-pii-proxy) is a production example of this pattern — an Elasticsearch MCP proxy that uses mcpose with a PII redaction middleware and an audit middleware to serve financial data safely to LLM agents.
|
|
299
|
-
|
|
300
|
-
---
|
|
301
|
-
|
|
302
|
-
## Roadmap
|
|
303
|
-
|
|
304
|
-
- [x] **HTTP/SSE server transport** — `startHttpProxy()` adds a Streamable HTTP server-side transport with stateful sessions
|
|
305
|
-
- [ ] **ATXP protocol support** — enable MCP monetization by implementing the ATXP (Agent Transaction Protocol) standard, letting tool providers attach pricing and billing metadata to responses
|
|
306
|
-
|
|
307
|
-
---
|
|
308
|
-
|
|
309
|
-
## License
|
|
310
|
-
|
|
311
|
-
MIT
|