mcpose 2.0.2 → 3.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.
Files changed (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +494 -37
  3. package/dist/backendClient.d.ts +5 -0
  4. package/dist/backendClient.d.ts.map +1 -1
  5. package/dist/backendClient.js +17 -9
  6. package/dist/backendClient.js.map +1 -1
  7. package/dist/core.d.ts +293 -28
  8. package/dist/core.d.ts.map +1 -1
  9. package/dist/core.js +969 -175
  10. package/dist/core.js.map +1 -1
  11. package/dist/delegation.d.ts +89 -0
  12. package/dist/delegation.d.ts.map +1 -0
  13. package/dist/delegation.js +175 -0
  14. package/dist/delegation.js.map +1 -0
  15. package/dist/eventStore.d.ts +12 -0
  16. package/dist/eventStore.d.ts.map +1 -1
  17. package/dist/eventStore.js +43 -5
  18. package/dist/eventStore.js.map +1 -1
  19. package/dist/hiddenTools.d.ts +42 -0
  20. package/dist/hiddenTools.d.ts.map +1 -0
  21. package/dist/hiddenTools.js +53 -0
  22. package/dist/hiddenTools.js.map +1 -0
  23. package/dist/index.d.ts +15 -6
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +7 -3
  26. package/dist/index.js.map +1 -1
  27. package/dist/mesh.d.ts +82 -0
  28. package/dist/mesh.d.ts.map +1 -0
  29. package/dist/mesh.js +141 -0
  30. package/dist/mesh.js.map +1 -0
  31. package/dist/middleware.d.ts +13 -0
  32. package/dist/middleware.d.ts.map +1 -1
  33. package/dist/middleware.js +27 -3
  34. package/dist/middleware.js.map +1 -1
  35. package/dist/proxyContext.d.ts +65 -5
  36. package/dist/proxyContext.d.ts.map +1 -1
  37. package/dist/proxyContext.js +39 -4
  38. package/dist/proxyContext.js.map +1 -1
  39. package/dist/rejection.d.ts +1 -1
  40. package/dist/rejection.d.ts.map +1 -1
  41. package/dist/rejection.js +1 -1
  42. package/dist/rejection.js.map +1 -1
  43. package/dist/sanitizeCatalog.d.ts +37 -0
  44. package/dist/sanitizeCatalog.d.ts.map +1 -0
  45. package/dist/sanitizeCatalog.js +86 -0
  46. package/dist/sanitizeCatalog.js.map +1 -0
  47. package/dist/sessionRegistry.d.ts +79 -0
  48. package/dist/sessionRegistry.d.ts.map +1 -0
  49. package/dist/sessionRegistry.js +83 -0
  50. package/dist/sessionRegistry.js.map +1 -0
  51. package/dist/telemetry.d.ts +29 -1
  52. package/dist/telemetry.d.ts.map +1 -1
  53. package/dist/testing.d.ts +18 -2
  54. package/dist/testing.d.ts.map +1 -1
  55. package/dist/testing.js +30 -8
  56. package/dist/testing.js.map +1 -1
  57. package/dist/version.d.ts +7 -0
  58. package/dist/version.d.ts.map +1 -0
  59. package/dist/version.js +7 -0
  60. package/dist/version.js.map +1 -0
  61. package/package.json +30 -14
  62. package/src/__tests__/_helpers.ts +64 -0
  63. package/src/__tests__/backendClient.test.ts +198 -0
  64. package/src/__tests__/core.delegation.test.ts +964 -0
  65. package/src/__tests__/core.edge.test.ts +314 -0
  66. package/src/__tests__/core.hiddenTools.predicate.test.ts +236 -0
  67. package/src/__tests__/core.localTools.test.ts +314 -0
  68. package/src/__tests__/core.mesh.test.ts +1182 -0
  69. package/src/__tests__/core.stripRequestMeta.test.ts +226 -0
  70. package/src/__tests__/core.stripResultMeta.test.ts +217 -0
  71. package/src/__tests__/core.test.ts +1019 -0
  72. package/src/__tests__/eventStore.test.ts +141 -0
  73. package/src/__tests__/mapToolResult.test.ts +101 -0
  74. package/src/__tests__/middleware.edge.test.ts +126 -0
  75. package/src/__tests__/middleware.test.ts +233 -0
  76. package/src/__tests__/proxyContext.test.ts +140 -0
  77. package/src/__tests__/sanitizeCatalog.test.ts +359 -0
  78. package/src/__tests__/startHttpProxy.lifecycle.test.ts +638 -0
  79. package/src/__tests__/startHttpProxy.registry.test.ts +409 -0
  80. package/src/__tests__/startHttpProxy.replay.test.ts +116 -0
  81. package/src/__tests__/startHttpProxy.security.test.ts +288 -0
  82. package/src/__tests__/startHttpProxy.test.ts +985 -0
  83. package/src/__tests__/testing.test.ts +250 -0
  84. package/src/__tests__/version.test.ts +12 -0
  85. package/src/backendClient.ts +83 -0
  86. package/src/core.ts +2042 -0
  87. package/src/delegation.ts +224 -0
  88. package/src/eventStore.ts +106 -0
  89. package/src/hiddenTools.ts +84 -0
  90. package/src/identity.ts +15 -0
  91. package/src/index.ts +63 -0
  92. package/src/mesh.ts +204 -0
  93. package/src/middleware.ts +101 -0
  94. package/src/proxyContext.ts +108 -0
  95. package/src/rejection.ts +37 -0
  96. package/src/sanitizeCatalog.ts +120 -0
  97. package/src/sessionRegistry.ts +142 -0
  98. package/src/telemetry.ts +46 -0
  99. package/src/testing.ts +174 -0
  100. package/src/version.ts +6 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Egress sanitizer for the tool catalog: `tools/list` forwards upstream
3
+ * descriptions verbatim into model context, and real upstreams leak org
4
+ * slugs and internal hostnames there. See ADR-0010.
5
+ */
6
+ /** Matches http(s) URLs; always stripped regardless of `patterns`. */
7
+ const URL_PATTERN = /\bhttps?:\/\/[^\s"'<>()[\]]+/g;
8
+ /**
9
+ * Rebuilds a schema node with every string-valued `description` property
10
+ * sanitized, at any depth through plain objects and arrays. Returns the
11
+ * original node when nothing changed.
12
+ */
13
+ function sanitizeSchema(node, sanitize) {
14
+ if (Array.isArray(node)) {
15
+ const mapped = node.map((item) => sanitizeSchema(item, sanitize));
16
+ return mapped.some((item, i) => item !== node[i]) ? mapped : node;
17
+ }
18
+ if (node === null || typeof node !== 'object')
19
+ return node;
20
+ const entries = Object.entries(node).map(([key, value]) => [
21
+ key,
22
+ key === 'description' && typeof value === 'string'
23
+ ? sanitize(value)
24
+ : sanitizeSchema(value, sanitize),
25
+ ]);
26
+ return entries.some(([key, value]) => value !== node[key])
27
+ ? Object.fromEntries(entries)
28
+ : node;
29
+ }
30
+ /**
31
+ * A {@link ListToolsMiddleware} that sanitizes tool descriptions and every
32
+ * `description` field nested in `inputSchema` / `outputSchema`. Names,
33
+ * titles, and schema structure are untouched, because clients route on
34
+ * them.
35
+ *
36
+ * Always strips http(s) URLs; `patterns` adds more. Place it **last** in
37
+ * `listToolsMiddleware` so it sanitizes the output of other list
38
+ * middleware and local tools.
39
+ *
40
+ * @example
41
+ * listToolsMiddleware: [
42
+ * sanitizeToolDescriptions({
43
+ * patterns: ['acme-corp', /\bi-[0-9a-f]{8,17}\b/],
44
+ * replacement: '[redacted]',
45
+ * }),
46
+ * ]
47
+ */
48
+ export function sanitizeToolDescriptions(options = {}) {
49
+ const replacement = options.replacement ?? '';
50
+ const patterns = [
51
+ URL_PATTERN,
52
+ // Rebuild with exactly one `g` and no `y`: String.replace with a
53
+ // sticky regex only replaces matches anchored at index 0, so a sticky
54
+ // pattern would silently leak later occurrences.
55
+ ...(options.patterns ?? []).map((p) => p instanceof RegExp
56
+ ? new RegExp(p.source, p.flags.replace(/[gy]/g, '') + 'g')
57
+ : p),
58
+ ];
59
+ // replaceAll treats a string pattern literally and requires the g flag
60
+ // on a RegExp, which the normalization above guarantees.
61
+ const sanitize = (text) => patterns.reduce((acc, p) => acc.replaceAll(p, replacement), text);
62
+ return async (req, next) => {
63
+ const result = await next(req);
64
+ const tools = result.tools.map((tool) => {
65
+ const description = typeof tool.description === 'string'
66
+ ? sanitize(tool.description)
67
+ : tool.description;
68
+ const inputSchema = sanitizeSchema(tool.inputSchema, sanitize);
69
+ const outputSchema = sanitizeSchema(tool.outputSchema, sanitize);
70
+ return description === tool.description &&
71
+ inputSchema === tool.inputSchema &&
72
+ outputSchema === tool.outputSchema
73
+ ? tool
74
+ : {
75
+ ...tool,
76
+ ...(description === undefined ? {} : { description }),
77
+ inputSchema,
78
+ ...(outputSchema === undefined ? {} : { outputSchema }),
79
+ };
80
+ });
81
+ return tools.some((tool, i) => tool !== result.tools[i])
82
+ ? { ...result, tools }
83
+ : result;
84
+ };
85
+ }
86
+ //# sourceMappingURL=sanitizeCatalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeCatalog.js","sourceRoot":"","sources":["../src/sanitizeCatalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import type { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
2
+ import { type InitializeRequestParams } from '@modelcontextprotocol/sdk/types.js';
3
+ import type { Identity } from './identity.js';
4
+ /**
5
+ * Everything another instance needs to take over a session. Plain JSON: an
6
+ * adapter serializes it as is, so `identity.claims` must survive a JSON
7
+ * round-trip.
8
+ */
9
+ export interface SessionRecord {
10
+ /**
11
+ * The client's `initialize` params, verbatim. A resuming instance replays
12
+ * them into a fresh SDK transport, so the rebuilt session negotiates exactly
13
+ * what the original did: protocol version, client capabilities, clientInfo.
14
+ */
15
+ initialize: InitializeRequestParams;
16
+ /**
17
+ * The identity `resolveIdentity` produced at initialize. Never re-resolved
18
+ * on resume; `validateSession` still runs on every routed request.
19
+ */
20
+ identity?: Identity;
21
+ /**
22
+ * Epoch milliseconds after which the session is dead on every instance.
23
+ * Fixed at creation from `sessionTtlMs` and absent when that is `Infinity`.
24
+ * A resuming instance honours the remaining lifetime, not its own TTL.
25
+ */
26
+ expiresAt?: number;
27
+ }
28
+ /**
29
+ * Where `startHttpProxy` keeps its session records when one is supplied. The
30
+ * local in-memory map stays authoritative for sessions this instance holds;
31
+ * the registry is consulted only for an id this instance does not know.
32
+ *
33
+ * `@mcpose/store-redis` and `@mcpose/store-postgres` ship implementations
34
+ * alongside their `EventStore`s. Share the event store too, or a resumed
35
+ * session has a live id and no replay history.
36
+ */
37
+ export interface SessionRegistry {
38
+ /**
39
+ * Persists a record. Awaited before the client learns its session id, so a
40
+ * rejection fails the initialize with a 500 rather than handing out an id
41
+ * that no other instance could honour.
42
+ */
43
+ set(sessionId: string, record: SessionRecord): Promise<void>;
44
+ /** `undefined` for an unknown or expired id. */
45
+ get(sessionId: string): Promise<SessionRecord | undefined>;
46
+ /**
47
+ * Called on client DELETE and TTL expiry. Never on server shutdown: a
48
+ * session that outlives the process is the point of the registry.
49
+ */
50
+ delete(sessionId: string): Promise<void>;
51
+ }
52
+ /**
53
+ * Pulls the `initialize` params out of a POST body the SDK has already
54
+ * accepted as an initialize request, so a parse failure here is a bug in the
55
+ * SDK's acceptance, not client input to be tolerated.
56
+ */
57
+ export declare function initializeParamsOf(body: string): InitializeRequestParams;
58
+ /**
59
+ * Drives a fresh transport through the SDK's own initialize path with the
60
+ * stored params, so it accepts `sessionId` on later requests. The SDK cannot
61
+ * rehydrate a transport and its initialized flag is private, so the public
62
+ * request path is the one that survives an SDK bump.
63
+ *
64
+ * The request never touches a socket: the SDK's Node adapter reads method,
65
+ * url, headers and the body stream, and the response is captured rather than
66
+ * written anywhere. `Host` and `Origin` are copied from the request being
67
+ * resumed so the transport's DNS-rebinding check sees what the client sent.
68
+ *
69
+ * @returns the status and body the SDK answered, for the caller to relay if
70
+ * the transport did not come up.
71
+ */
72
+ export declare function replayInitialize(transport: StreamableHTTPServerTransport, params: InitializeRequestParams, headers: {
73
+ host?: string;
74
+ origin?: string;
75
+ }): Promise<{
76
+ status: number;
77
+ body: string;
78
+ }>;
79
+ //# sourceMappingURL=sessionRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionRegistry.d.ts","sourceRoot":"","sources":["../src/sessionRegistry.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACxG,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,UAAU,EAAE,uBAAuB,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,gDAAgD;IAChD,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;IAC3D;;;OAGG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,uBAAuB,CAQxE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,6BAA6B,EACxC,MAAM,EAAE,uBAAuB,EAC/B,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1C,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA6C3C"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Shared session registry: the transport-level record of live session ids,
3
+ * so a resume can survive a proxy restart or reach another instance (#155).
4
+ */
5
+ import * as http from 'node:http';
6
+ import * as net from 'node:net';
7
+ import { isInitializeRequest, } from '@modelcontextprotocol/sdk/types.js';
8
+ /**
9
+ * Pulls the `initialize` params out of a POST body the SDK has already
10
+ * accepted as an initialize request, so a parse failure here is a bug in the
11
+ * SDK's acceptance, not client input to be tolerated.
12
+ */
13
+ export function initializeParamsOf(body) {
14
+ const parsed = JSON.parse(body);
15
+ const messages = Array.isArray(parsed) ? parsed : [parsed];
16
+ const initialize = messages.find(isInitializeRequest);
17
+ if (initialize === undefined) {
18
+ throw new Error('mcpose: initialize request not found in accepted body');
19
+ }
20
+ return initialize.params;
21
+ }
22
+ /**
23
+ * Drives a fresh transport through the SDK's own initialize path with the
24
+ * stored params, so it accepts `sessionId` on later requests. The SDK cannot
25
+ * rehydrate a transport and its initialized flag is private, so the public
26
+ * request path is the one that survives an SDK bump.
27
+ *
28
+ * The request never touches a socket: the SDK's Node adapter reads method,
29
+ * url, headers and the body stream, and the response is captured rather than
30
+ * written anywhere. `Host` and `Origin` are copied from the request being
31
+ * resumed so the transport's DNS-rebinding check sees what the client sent.
32
+ *
33
+ * @returns the status and body the SDK answered, for the caller to relay if
34
+ * the transport did not come up.
35
+ */
36
+ export function replayInitialize(transport, params, headers) {
37
+ const body = JSON.stringify({
38
+ jsonrpc: '2.0',
39
+ id: 0,
40
+ method: 'initialize',
41
+ params,
42
+ });
43
+ const req = new http.IncomingMessage(new net.Socket());
44
+ req.method = 'POST';
45
+ req.url = '/';
46
+ req.headers = {
47
+ 'content-type': 'application/json',
48
+ 'content-length': String(Buffer.byteLength(body)),
49
+ accept: 'application/json, text/event-stream',
50
+ ...(headers.host === undefined ? {} : { host: headers.host }),
51
+ ...(headers.origin === undefined ? {} : { origin: headers.origin }),
52
+ };
53
+ // The SDK's adapter reads the raw header list, not the parsed record.
54
+ req.rawHeaders = Object.entries(req.headers).flatMap(([name, value]) => [
55
+ name,
56
+ String(value),
57
+ ]);
58
+ req.push(body);
59
+ req.push(null);
60
+ const res = new http.ServerResponse(req);
61
+ let out = '';
62
+ const append = (chunk) => {
63
+ if (typeof chunk === 'string')
64
+ out += chunk;
65
+ else if (chunk instanceof Uint8Array)
66
+ out += Buffer.from(chunk).toString();
67
+ };
68
+ // No socket: a real write would buffer forever and report back-pressure,
69
+ // and 'finish' would never fire. Capture instead.
70
+ res.write = (chunk) => {
71
+ append(chunk);
72
+ return true;
73
+ };
74
+ const done = new Promise((resolve) => {
75
+ res.end = ((chunk) => {
76
+ append(chunk);
77
+ resolve({ status: res.statusCode, body: out });
78
+ return res;
79
+ });
80
+ });
81
+ return transport.handleRequest(req, res).then(() => done);
82
+ }
83
+ //# sourceMappingURL=sessionRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionRegistry.js","sourceRoot":"","sources":["../src/sessionRegistry.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import type { Identity } from './identity.js';
2
+ import type { ProxyIdentity } from './proxyContext.js';
2
3
  import type { RejectionReason } from './rejection.js';
3
4
  /** Emitted after every tool call (success, error, or rejection). */
4
- export interface TelemetryEvent {
5
+ export interface ToolCallTelemetryEvent {
5
6
  type: 'tool_call';
6
7
  requestId: string;
7
8
  sessionId?: string;
@@ -11,5 +12,32 @@ export interface TelemetryEvent {
11
12
  /** Populated when `outcome` is `'rejected'`. */
12
13
  rejectionReason?: RejectionReason;
13
14
  identity?: Identity;
15
+ /** The proxy instance that handled the call (ADR-0012). */
16
+ proxy?: ProxyIdentity;
14
17
  }
18
+ /**
19
+ * Emitted when one backend of a mesh fails a list call and the proxy
20
+ * returns the remaining backends' entries instead of failing the whole
21
+ * list (ADR-0013). Without an `onTelemetry` sink a degraded mesh is
22
+ * invisible, so an operator running one should wire it.
23
+ */
24
+ export interface BackendDegradedTelemetryEvent {
25
+ type: 'backend_degraded';
26
+ requestId: string;
27
+ sessionId?: string;
28
+ /** The backend key whose entries are missing from the response. */
29
+ backend: string;
30
+ /** The list call that degraded. */
31
+ method: 'tools/list' | 'prompts/list' | 'resources/list';
32
+ /** Whatever the backend threw. */
33
+ error: unknown;
34
+ identity?: Identity;
35
+ /** The proxy instance that handled the call (ADR-0012). */
36
+ proxy?: ProxyIdentity;
37
+ }
38
+ /**
39
+ * An observability signal from the proxy, discriminated on `type`. Narrow
40
+ * on it before reading a member's own fields.
41
+ */
42
+ export type TelemetryEvent = ToolCallTelemetryEvent | BackendDegradedTelemetryEvent;
15
43
  //# sourceMappingURL=telemetry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,oEAAoE;AACpE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;IAC1C,gDAAgD;IAChD,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB"}
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,oEAAoE;AACpE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;IAC1C,gDAAgD;IAChD,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,kBAAkB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,MAAM,EAAE,YAAY,GAAG,cAAc,GAAG,gBAAgB,CAAC;IACzD,kCAAkC;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GACxB,sBAAsB,GAAG,6BAA6B,CAAC"}
package/dist/testing.d.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * Test helpers for mcpose middleware. No test framework imports — works in any env.
3
3
  * @module mcpose/testing
4
4
  */
5
- import type { CallToolRequest, CallToolResult, ReadResourceResult, Tool, Resource, Prompt, CallToolRequestParams, GetPromptResult, ServerCapabilities } from '@modelcontextprotocol/sdk/types.js';
6
- import type { ToolMiddleware } from './core.js';
5
+ import type { CallToolRequest, CallToolResult, ListToolsRequest, ListToolsResult, ReadResourceRequest, ReadResourceResult, Tool, Resource, Prompt, CallToolRequestParams, GetPromptResult, ServerCapabilities } from '@modelcontextprotocol/sdk/types.js';
6
+ import type { ListToolsMiddleware, ResourceMiddleware, ToolMiddleware } from './core.js';
7
7
  import type { BackendClient } from './backendClient.js';
8
8
  import { type ProxyContext } from './proxyContext.js';
9
9
  /**
@@ -15,6 +15,22 @@ import { type ProxyContext } from './proxyContext.js';
15
15
  * expect(result.content[0]).toMatchObject({ type: 'text' });
16
16
  */
17
17
  export declare function runToolMiddleware(mw: ToolMiddleware, req: CallToolRequest, next: (req: CallToolRequest) => Promise<CallToolResult>, context?: ProxyContext): Promise<CallToolResult>;
18
+ /**
19
+ * Runs a `ListToolsMiddleware` with a defaulted `ProxyContext`, so tests
20
+ * do not pass `undefined as never` for the context argument.
21
+ *
22
+ * `ListToolsResult` has no legacy variant, so unlike `runToolMiddleware`
23
+ * there is nothing to narrow and this never throws on shape.
24
+ */
25
+ export declare function runListToolsMiddleware(mw: ListToolsMiddleware, req: ListToolsRequest, next: (req: ListToolsRequest) => Promise<ListToolsResult>, context?: ProxyContext): Promise<ListToolsResult>;
26
+ /**
27
+ * Runs a `ResourceMiddleware` with a defaulted `ProxyContext`.
28
+ *
29
+ * `ReadResourceResult` has no legacy variant, so unlike
30
+ * `runToolMiddleware` there is nothing to narrow and this never throws on
31
+ * shape.
32
+ */
33
+ export declare function runResourceMiddleware(mw: ResourceMiddleware, req: ReadResourceRequest, next: (req: ReadResourceRequest) => Promise<ReadResourceResult>, context?: ProxyContext): Promise<ReadResourceResult>;
18
34
  /** Config for mock backend client. */
19
35
  export interface MockBackendClientOptions {
20
36
  /** Default: tools/resources/prompts enabled */
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EAId,kBAAkB,EAClB,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,qBAAqB,EAGrB,eAAe,EACf,kBAAkB,EACnB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE1E;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,cAAc,EAClB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,cAAc,CAAC,EACvD,OAAO,GAAE,YAAmC,GAC3C,OAAO,CAAC,cAAc,CAAC,CAQzB;AAED,sCAAsC;AACtC,MAAM,WAAW,wBAAwB;IACvC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,oBAAoB;IACpB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf;;;OAGG;IACH,gBAAgB,CAAC,EACb,cAAc,GACd,CAAC,CAAC,MAAM,EAAE,qBAAqB,KAAK,cAAc,CAAC,CAAC;IACxD,oBAAoB;IACpB,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,oEAAoE;IACpE,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;IAC1C,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kCAAkC;IAClC,iBAAiB,CAAC,EAAE,eAAe,CAAC;CACrC;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,aAAa,CA8Cf"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,eAAe,EAGf,mBAAmB,EACnB,kBAAkB,EAClB,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,qBAAqB,EAGrB,eAAe,EACf,kBAAkB,EACnB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACf,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE1E;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,cAAc,EAClB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,cAAc,CAAC,EACvD,OAAO,GAAE,YAAmC,GAC3C,OAAO,CAAC,cAAc,CAAC,CAQzB;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,mBAAmB,EACvB,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,eAAe,CAAC,EACzD,OAAO,GAAE,YAAmC,GAC3C,OAAO,CAAC,eAAe,CAAC,CAE1B;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,kBAAkB,EACtB,GAAG,EAAE,mBAAmB,EACxB,IAAI,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,OAAO,CAAC,kBAAkB,CAAC,EAC/D,OAAO,GAAE,YAAmC,GAC3C,OAAO,CAAC,kBAAkB,CAAC,CAE7B;AAED,sCAAsC;AACtC,MAAM,WAAW,wBAAwB;IACvC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,oBAAoB;IACpB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf;;;OAGG;IACH,gBAAgB,CAAC,EACf,cAAc,GAAG,CAAC,CAAC,MAAM,EAAE,qBAAqB,KAAK,cAAc,CAAC,CAAC;IACvE,oBAAoB;IACpB,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,oEAAoE;IACpE,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;IAC1C,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kCAAkC;IAClC,iBAAiB,CAAC,EAAE,eAAe,CAAC;CACrC;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,aAAa,CAqDf"}
package/dist/testing.js CHANGED
@@ -15,6 +15,26 @@ export async function runToolMiddleware(mw, req, next, context = createProxyCont
15
15
  }
16
16
  return result;
17
17
  }
18
+ /**
19
+ * Runs a `ListToolsMiddleware` with a defaulted `ProxyContext`, so tests
20
+ * do not pass `undefined as never` for the context argument.
21
+ *
22
+ * `ListToolsResult` has no legacy variant, so unlike `runToolMiddleware`
23
+ * there is nothing to narrow and this never throws on shape.
24
+ */
25
+ export async function runListToolsMiddleware(mw, req, next, context = createProxyContext()) {
26
+ return mw(req, next, context);
27
+ }
28
+ /**
29
+ * Runs a `ResourceMiddleware` with a defaulted `ProxyContext`.
30
+ *
31
+ * `ReadResourceResult` has no legacy variant, so unlike
32
+ * `runToolMiddleware` there is nothing to narrow and this never throws on
33
+ * shape.
34
+ */
35
+ export async function runResourceMiddleware(mw, req, next, context = createProxyContext()) {
36
+ return mw(req, next, context);
37
+ }
18
38
  /**
19
39
  * Creates an in-memory `BackendClient` for unit tests. No process/network.
20
40
  *
@@ -31,27 +51,29 @@ export function createMockBackendClient(options = {}) {
31
51
  resources: {},
32
52
  prompts: {},
33
53
  };
54
+ // Fixtures are cloned per call so a mutating middleware cannot corrupt
55
+ // them across calls.
34
56
  return {
35
- getServerCapabilities: () => capabilities,
57
+ getServerCapabilities: () => structuredClone(capabilities),
36
58
  listTools: async () => ({
37
- tools: options.tools ?? [],
59
+ tools: structuredClone(options.tools ?? []),
38
60
  }),
39
61
  callTool: async (params) => {
40
62
  const resp = options.callToolResponse;
41
63
  if (typeof resp === 'function')
42
64
  return resp(params);
43
- return resp ?? { content: [{ type: 'text', text: 'mock response' }] };
65
+ return structuredClone(resp ?? { content: [{ type: 'text', text: 'mock response' }] });
44
66
  },
45
67
  listResources: async () => ({
46
- resources: options.resources ?? [],
68
+ resources: structuredClone(options.resources ?? []),
47
69
  }),
48
- readResource: async (_params) => options.readResourceResponse ?? {
70
+ readResource: async (_params) => structuredClone(options.readResourceResponse ?? {
49
71
  contents: [{ uri: '', text: 'mock resource' }],
50
- },
72
+ }),
51
73
  listPrompts: async () => ({
52
- prompts: options.prompts ?? [],
74
+ prompts: structuredClone(options.prompts ?? []),
53
75
  }),
54
- getPrompt: async (_params) => options.getPromptResponse ?? { messages: [] },
76
+ getPrompt: async (_params) => structuredClone(options.getPromptResponse ?? { messages: [] }),
55
77
  setNotificationHandler: () => undefined,
56
78
  removeNotificationHandler: () => undefined,
57
79
  close: async () => undefined,
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG3C,OAAO,EAAE,kBAAkB,EAAqB,MAAM,mBAAmB,CAAC;AAE1E;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,EAAkB,EAClB,GAAoB,EACpB,IAAuD,EACvD,UAAwB,kBAAkB,EAAE;IAE5C,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAC5C,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,8FAA8F,CAC/F,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAyBD;;;;;;;;;GASG;AACH,MAAM,UAAU,uBAAuB,CACrC,UAAoC,EAAE;IAEtC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI;QAC3C,KAAK,EAAE,EAAE;QACT,SAAS,EAAE,EAAE;QACb,OAAO,EAAE,EAAE;KACZ,CAAC;IAEF,OAAO;QACL,qBAAqB,EAAE,GAAuB,EAAE,CAAC,YAAY;QAE7D,SAAS,EAAE,KAAK,IAA8B,EAAE,CAAC,CAAC;YAChD,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE;SAC3B,CAAC;QAEF,QAAQ,EAAE,KAAK,EACb,MAA6B,EACJ,EAAE;YAC3B,MAAM,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC;YACtC,IAAI,OAAO,IAAI,KAAK,UAAU;gBAAE,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;YACpD,OAAO,IAAI,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;QACxE,CAAC;QAED,aAAa,EAAE,KAAK,IAAkC,EAAE,CAAC,CAAC;YACxD,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,EAAE;SACnC,CAAC;QAEF,YAAY,EAAE,KAAK,EACjB,OAAkC,EACL,EAAE,CAC/B,OAAO,CAAC,oBAAoB,IAAI;YAC9B,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;SAC/C;QAEH,WAAW,EAAE,KAAK,IAAgC,EAAE,CAAC,CAAC;YACpD,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;SAC/B,CAAC;QAEF,SAAS,EAAE,KAAK,EACd,OAA+B,EACL,EAAE,CAC5B,OAAO,CAAC,iBAAiB,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;QAE/C,sBAAsB,EAAE,GAAG,EAAE,CAAC,SAAS;QACvC,yBAAyB,EAAE,GAAG,EAAE,CAAC,SAAS;QAC1C,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS;KACD,CAAC;AAChC,CAAC"}
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The mcpose library version, inlined so it survives bundling (a
3
+ * `createRequire('../package.json')` lookup breaks when consumers bundle
4
+ * the package). Kept in sync with package.json by `version.test.ts`.
5
+ */
6
+ export declare const VERSION = "3.0.0";
7
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,OAAO,UAAU,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The mcpose library version, inlined so it survives bundling (a
3
+ * `createRequire('../package.json')` lookup breaks when consumers bundle
4
+ * the package). Kept in sync with package.json by `version.test.ts`.
5
+ */
6
+ export const VERSION = '3.0.0';
7
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcpose",
3
- "version": "2.0.2",
3
+ "version": "3.0.0",
4
4
  "description": "The audit and governance layer for MCP",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,9 +13,15 @@
13
13
  "./testing": {
14
14
  "types": "./dist/testing.d.ts",
15
15
  "default": "./dist/testing.js"
16
- }
16
+ },
17
+ "./package.json": "./package.json"
17
18
  },
18
- "files": ["dist", "README.md"],
19
+ "sideEffects": false,
20
+ "files": [
21
+ "dist",
22
+ "src",
23
+ "README.md"
24
+ ],
19
25
  "keywords": [
20
26
  "mcp",
21
27
  "middleware",
@@ -29,24 +35,34 @@
29
35
  ],
30
36
  "author": "Amir Gorji",
31
37
  "license": "MIT",
38
+ "homepage": "https://github.com/amir-gorji/mcpose#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/amir-gorji/mcpose/issues"
41
+ },
32
42
  "repository": {
33
43
  "type": "git",
34
44
  "url": "git+https://github.com/amir-gorji/mcpose.git",
35
45
  "directory": "packages/core"
36
46
  },
37
- "scripts": {
38
- "build": "tsc -p tsconfig.build.json",
39
- "ts:ci": "tsc --noEmit",
40
- "test": "vitest run --passWithNoTests",
41
- "prepublishOnly": "pnpm run ts:ci && pnpm run test && pnpm run build"
47
+ "engines": {
48
+ "node": ">=20"
42
49
  },
43
50
  "peerDependencies": {
44
- "@modelcontextprotocol/sdk": ">=1.0.0"
51
+ "@modelcontextprotocol/sdk": "^1.17.0"
45
52
  },
46
53
  "devDependencies": {
47
- "@modelcontextprotocol/sdk": "^1.26.0",
48
- "@types/node": "^22.0.0",
49
- "typescript": "^5.0.0",
50
- "vitest": "^3.0.0"
54
+ "@modelcontextprotocol/sdk": "1.30.0",
55
+ "@stryker-mutator/core": "10.0.0",
56
+ "@stryker-mutator/vitest-runner": "10.0.0",
57
+ "@types/node": "^26.5.1",
58
+ "@vitest/coverage-v8": "5.0.1",
59
+ "typescript": "6.0.3",
60
+ "vitest": "5.0.1"
61
+ },
62
+ "scripts": {
63
+ "build": "tsc -p tsconfig.build.json",
64
+ "ts:ci": "tsc --noEmit",
65
+ "test": "vitest run --coverage",
66
+ "mutation": "stryker run"
51
67
  }
52
- }
68
+ }
@@ -0,0 +1,64 @@
1
+ import * as http from 'node:http';
2
+ import { vi } from 'vitest';
3
+ import type { BackendClient } from '../backendClient.js';
4
+ import type { ServerCapabilities } from '@modelcontextprotocol/sdk/types.js';
5
+
6
+ /** Minimal mocked BackendClient suitable for HTTP integration tests. */
7
+ export function makeMockBackend(
8
+ overrides: Partial<{
9
+ capabilities: ServerCapabilities;
10
+ }> = {},
11
+ ): BackendClient {
12
+ return {
13
+ getServerCapabilities: vi
14
+ .fn()
15
+ .mockReturnValue(
16
+ overrides.capabilities ?? { tools: {}, resources: {}, prompts: {} },
17
+ ),
18
+ listTools: vi.fn().mockResolvedValue({ tools: [] }),
19
+ callTool: vi.fn().mockResolvedValue({ content: [] }),
20
+ listResources: vi.fn().mockResolvedValue({ resources: [] }),
21
+ readResource: vi.fn().mockResolvedValue({ contents: [] }),
22
+ listPrompts: vi.fn().mockResolvedValue({ prompts: [] }),
23
+ getPrompt: vi.fn().mockResolvedValue({ messages: [] }),
24
+ setNotificationHandler: vi.fn(),
25
+ removeNotificationHandler: vi.fn(),
26
+ close: vi.fn().mockResolvedValue(undefined),
27
+ } as unknown as BackendClient;
28
+ }
29
+
30
+ export function getPort(server: http.Server): number {
31
+ const addr = server.address();
32
+ if (!addr || typeof addr === 'string') throw new Error('Unexpected address');
33
+ return addr.port;
34
+ }
35
+
36
+ export function closeServer(server: http.Server): Promise<void> {
37
+ return new Promise<void>((resolve) => server.close(() => resolve()));
38
+ }
39
+
40
+ /**
41
+ * POST over a dedicated connection (`agent: false`), bypassing fetch's
42
+ * keep-alive pool. Under fake timers, undici (Node >= 24) hangs when it
43
+ * reuses a pooled connection whose idle window spans faked time — any
44
+ * request sent after `vi.advanceTimersByTimeAsync()` must use this instead
45
+ * of `fetch`.
46
+ */
47
+ export function postOnFreshConnection(
48
+ url: string,
49
+ headers: Record<string, string>,
50
+ body: string,
51
+ ): Promise<number> {
52
+ return new Promise<number>((resolve, reject) => {
53
+ const req = http.request(
54
+ url,
55
+ { method: 'POST', agent: false, headers },
56
+ (res) => {
57
+ res.resume();
58
+ resolve(res.statusCode ?? 0);
59
+ },
60
+ );
61
+ req.on('error', reject);
62
+ req.end(body);
63
+ });
64
+ }