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.
- package/LICENSE +21 -0
- package/README.md +494 -37
- package/dist/backendClient.d.ts +5 -0
- package/dist/backendClient.d.ts.map +1 -1
- package/dist/backendClient.js +17 -9
- package/dist/backendClient.js.map +1 -1
- package/dist/core.d.ts +293 -28
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +969 -175
- package/dist/core.js.map +1 -1
- package/dist/delegation.d.ts +89 -0
- package/dist/delegation.d.ts.map +1 -0
- package/dist/delegation.js +175 -0
- package/dist/delegation.js.map +1 -0
- package/dist/eventStore.d.ts +12 -0
- package/dist/eventStore.d.ts.map +1 -1
- package/dist/eventStore.js +43 -5
- package/dist/eventStore.js.map +1 -1
- package/dist/hiddenTools.d.ts +42 -0
- package/dist/hiddenTools.d.ts.map +1 -0
- package/dist/hiddenTools.js +53 -0
- package/dist/hiddenTools.js.map +1 -0
- package/dist/index.d.ts +15 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/mesh.d.ts +82 -0
- package/dist/mesh.d.ts.map +1 -0
- package/dist/mesh.js +141 -0
- package/dist/mesh.js.map +1 -0
- package/dist/middleware.d.ts +13 -0
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +27 -3
- package/dist/middleware.js.map +1 -1
- package/dist/proxyContext.d.ts +65 -5
- package/dist/proxyContext.d.ts.map +1 -1
- package/dist/proxyContext.js +39 -4
- package/dist/proxyContext.js.map +1 -1
- package/dist/rejection.d.ts +1 -1
- package/dist/rejection.d.ts.map +1 -1
- package/dist/rejection.js +1 -1
- package/dist/rejection.js.map +1 -1
- package/dist/sanitizeCatalog.d.ts +37 -0
- package/dist/sanitizeCatalog.d.ts.map +1 -0
- package/dist/sanitizeCatalog.js +86 -0
- package/dist/sanitizeCatalog.js.map +1 -0
- package/dist/sessionRegistry.d.ts +79 -0
- package/dist/sessionRegistry.d.ts.map +1 -0
- package/dist/sessionRegistry.js +83 -0
- package/dist/sessionRegistry.js.map +1 -0
- package/dist/telemetry.d.ts +29 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/testing.d.ts +18 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +30 -8
- package/dist/testing.js.map +1 -1
- package/dist/version.d.ts +7 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/package.json +30 -14
- package/src/__tests__/_helpers.ts +64 -0
- package/src/__tests__/backendClient.test.ts +198 -0
- package/src/__tests__/core.delegation.test.ts +964 -0
- package/src/__tests__/core.edge.test.ts +314 -0
- package/src/__tests__/core.hiddenTools.predicate.test.ts +236 -0
- package/src/__tests__/core.localTools.test.ts +314 -0
- package/src/__tests__/core.mesh.test.ts +1182 -0
- package/src/__tests__/core.stripRequestMeta.test.ts +226 -0
- package/src/__tests__/core.stripResultMeta.test.ts +217 -0
- package/src/__tests__/core.test.ts +1019 -0
- package/src/__tests__/eventStore.test.ts +141 -0
- package/src/__tests__/mapToolResult.test.ts +101 -0
- package/src/__tests__/middleware.edge.test.ts +126 -0
- package/src/__tests__/middleware.test.ts +233 -0
- package/src/__tests__/proxyContext.test.ts +140 -0
- package/src/__tests__/sanitizeCatalog.test.ts +359 -0
- package/src/__tests__/startHttpProxy.lifecycle.test.ts +638 -0
- package/src/__tests__/startHttpProxy.registry.test.ts +409 -0
- package/src/__tests__/startHttpProxy.replay.test.ts +116 -0
- package/src/__tests__/startHttpProxy.security.test.ts +288 -0
- package/src/__tests__/startHttpProxy.test.ts +985 -0
- package/src/__tests__/testing.test.ts +250 -0
- package/src/__tests__/version.test.ts +12 -0
- package/src/backendClient.ts +83 -0
- package/src/core.ts +2042 -0
- package/src/delegation.ts +224 -0
- package/src/eventStore.ts +106 -0
- package/src/hiddenTools.ts +84 -0
- package/src/identity.ts +15 -0
- package/src/index.ts +63 -0
- package/src/mesh.ts +204 -0
- package/src/middleware.ts +101 -0
- package/src/proxyContext.ts +108 -0
- package/src/rejection.ts +37 -0
- package/src/sanitizeCatalog.ts +120 -0
- package/src/sessionRegistry.ts +142 -0
- package/src/telemetry.ts +46 -0
- package/src/testing.ts +174 -0
- 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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"}
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -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
|
|
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
|
package/dist/telemetry.d.ts.map
CHANGED
|
@@ -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,
|
|
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 */
|
package/dist/testing.d.ts.map
CHANGED
|
@@ -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,
|
|
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,
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/version.js
ADDED
|
@@ -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": "
|
|
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
|
-
"
|
|
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
|
-
"
|
|
38
|
-
"
|
|
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": "
|
|
51
|
+
"@modelcontextprotocol/sdk": "^1.17.0"
|
|
45
52
|
},
|
|
46
53
|
"devDependencies": {
|
|
47
|
-
"@modelcontextprotocol/sdk": "
|
|
48
|
-
"@
|
|
49
|
-
"
|
|
50
|
-
"
|
|
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
|
+
}
|