@copilotkit/aimock 1.44.0 → 1.45.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +42 -0
- package/README.md +6 -0
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/aimock-cli.cjs +31 -5
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +31 -5
- package/dist/aimock-cli.js.map +1 -1
- package/dist/bedrock-converse.cjs +312 -39
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +313 -40
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +195 -42
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +196 -43
- package/dist/bedrock.js.map +1 -1
- package/dist/chaos.cjs +1 -0
- package/dist/chaos.js +1 -1
- package/dist/cli.cjs +152 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +155 -8
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +132 -71
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +133 -72
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +62 -4
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +37 -4
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +37 -4
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +62 -4
- package/dist/config-loader.js.map +1 -1
- package/dist/fakes-plugin.cjs +131 -0
- package/dist/fakes-plugin.cjs.map +1 -0
- package/dist/fakes-plugin.d.cts +23 -0
- package/dist/fakes-plugin.d.cts.map +1 -0
- package/dist/fakes-plugin.d.ts +23 -0
- package/dist/fakes-plugin.d.ts.map +1 -0
- package/dist/fakes-plugin.js +127 -0
- package/dist/fakes-plugin.js.map +1 -0
- package/dist/fixture-loader-services.cjs +1 -1
- package/dist/fixture-loader-services.cjs.map +1 -1
- package/dist/fixture-loader-services.js +1 -1
- package/dist/fixture-loader-services.js.map +1 -1
- package/dist/fixture-loader.cjs +389 -27
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +47 -3
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +47 -3
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +384 -29
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +250 -52
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +251 -53
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +226 -37
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +227 -38
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +881 -7
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +85 -4
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +85 -4
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +839 -8
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +9 -0
- package/dist/index.d.cts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -3
- package/dist/jest.cjs +31 -7
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +43 -3
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +43 -3
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +33 -9
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +135 -13
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +38 -4
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +38 -4
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +135 -13
- package/dist/journal.js.map +1 -1
- package/dist/live-sanitize.cjs +4 -32
- package/dist/live-sanitize.cjs.map +1 -1
- package/dist/live-sanitize.js +4 -32
- package/dist/live-sanitize.js.map +1 -1
- package/dist/llmock.cjs +123 -30
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +16 -1
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +16 -1
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +125 -32
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +1 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -1
- package/dist/mcp-fakes-mount.js +1 -0
- package/dist/mcp-fakes-mount.js.map +1 -1
- package/dist/mcp-fakes-report.cjs +111 -0
- package/dist/mcp-fakes-report.cjs.map +1 -0
- package/dist/mcp-fakes-report.d.cts +40 -0
- package/dist/mcp-fakes-report.d.cts.map +1 -0
- package/dist/mcp-fakes-report.d.ts +40 -0
- package/dist/mcp-fakes-report.d.ts.map +1 -0
- package/dist/mcp-fakes-report.js +107 -0
- package/dist/mcp-fakes-report.js.map +1 -0
- package/dist/mcp-fakes.cjs +331 -8
- package/dist/mcp-fakes.cjs.map +1 -1
- package/dist/mcp-fakes.d.cts +406 -3
- package/dist/mcp-fakes.d.cts.map +1 -1
- package/dist/mcp-fakes.d.ts +406 -3
- package/dist/mcp-fakes.d.ts.map +1 -1
- package/dist/mcp-fakes.js +331 -8
- package/dist/mcp-fakes.js.map +1 -1
- package/dist/mcp-handler.cjs +216 -17
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.d.cts +49 -0
- package/dist/mcp-handler.d.cts.map +1 -0
- package/dist/mcp-handler.d.ts +49 -0
- package/dist/mcp-handler.d.ts.map +1 -0
- package/dist/mcp-handler.js +214 -18
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +112 -1
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +33 -2
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +33 -2
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +112 -1
- package/dist/mcp-mock.js.map +1 -1
- package/dist/mcp-recorder.cjs +1224 -0
- package/dist/mcp-recorder.cjs.map +1 -0
- package/dist/mcp-recorder.d.cts +108 -0
- package/dist/mcp-recorder.d.cts.map +1 -0
- package/dist/mcp-recorder.d.ts +108 -0
- package/dist/mcp-recorder.d.ts.map +1 -0
- package/dist/mcp-recorder.js +1215 -0
- package/dist/mcp-recorder.js.map +1 -0
- package/dist/messages.cjs +362 -56
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +363 -58
- package/dist/messages.js.map +1 -1
- package/dist/misbehavior.cjs +1523 -0
- package/dist/misbehavior.cjs.map +1 -0
- package/dist/misbehavior.d.cts +43 -0
- package/dist/misbehavior.d.cts.map +1 -0
- package/dist/misbehavior.d.ts +43 -0
- package/dist/misbehavior.d.ts.map +1 -0
- package/dist/misbehavior.js +1511 -0
- package/dist/misbehavior.js.map +1 -0
- package/dist/ollama.cjs +310 -112
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +311 -113
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-video.cjs +30 -2
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +30 -2
- package/dist/openrouter-video.js.map +1 -1
- package/dist/record-sanitize.cjs +231 -0
- package/dist/record-sanitize.cjs.map +1 -0
- package/dist/record-sanitize.js +224 -0
- package/dist/record-sanitize.js.map +1 -0
- package/dist/recorder.cjs +106 -5
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +2 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts +2 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +107 -7
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +667 -171
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +5 -13
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +5 -13
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +665 -174
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +24 -2
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +25 -4
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +572 -76
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +574 -78
- package/dist/server.js.map +1 -1
- package/dist/stream-collapse.cjs +236 -34
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +39 -4
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +39 -4
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +235 -35
- package/dist/stream-collapse.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +315 -4
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +315 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validate-cli.cjs +36 -4
- package/dist/validate-cli.cjs.map +1 -1
- package/dist/validate-cli.js +37 -5
- package/dist/validate-cli.js.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/version.cjs +45 -0
- package/dist/version.cjs.map +1 -0
- package/dist/version.js +44 -0
- package/dist/version.js.map +1 -0
- package/dist/vitest.cjs +66 -10
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +35 -4
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +35 -4
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +67 -11
- package/dist/vitest.js.map +1 -1
- package/dist/watcher.cjs +6 -2
- package/dist/watcher.cjs.map +1 -1
- package/dist/watcher.js +6 -2
- package/dist/watcher.js.map +1 -1
- package/dist/ws-gemini-live.cjs +210 -101
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts +9 -3
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts +9 -3
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +211 -102
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +175 -62
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +8 -3
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts +8 -3
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +176 -63
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +109 -16
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts +8 -12
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts +8 -12
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +111 -18
- package/dist/ws-responses.js.map +1 -1
- package/package.json +10 -2
- package/skills/write-fixtures/SKILL.md +179 -21
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { McpFakeErrorCode, McpFakeIdentity, McpFakeOutcome } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/mcp-handler.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `data.aimock.code` of a `tools/call` whose fake claim threw: an aimock
|
|
7
|
+
* defect (a validated entry whose result could not be built or sent), not a
|
|
8
|
+
* scenario failure. Answered with the JSON-RPC internal-error code `-32603`;
|
|
9
|
+
* the entry stays unconsumed.
|
|
10
|
+
*/
|
|
11
|
+
declare const MCP_FAKE_INTERNAL_ERROR = "MCP_FAKE_INTERNAL_ERROR";
|
|
12
|
+
/** What every fake event carries: who called which tool on which mount (I9). */
|
|
13
|
+
interface McpFakeEventBase {
|
|
14
|
+
identity: McpFakeIdentity;
|
|
15
|
+
tool: string;
|
|
16
|
+
/** The mount path the request came in on (I9). */
|
|
17
|
+
mount: string;
|
|
18
|
+
/** The call's `arguments` as received (RP2). */
|
|
19
|
+
args: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One MCP fake outcome of a `tools/call`, for the mount's side channels
|
|
23
|
+
* (metric and log line; spec 9.3). `answered` names the entry; every other
|
|
24
|
+
* kind is a failure with its `data.aimock.code` and the JSON-RPC error
|
|
25
|
+
* message sent to the client. `evicted` also carries the evicted test id and
|
|
26
|
+
* the cap; `internal_error` is a claim that threw (`MCP_FAKE_INTERNAL_ERROR`).
|
|
27
|
+
*/
|
|
28
|
+
type McpFakeEvent = McpFakeEventBase & ({
|
|
29
|
+
kind: "answered";
|
|
30
|
+
entryId: string;
|
|
31
|
+
} | {
|
|
32
|
+
kind: Exclude<McpFakeOutcome, "answered" | "evicted">;
|
|
33
|
+
code: McpFakeErrorCode;
|
|
34
|
+
message: string;
|
|
35
|
+
} | {
|
|
36
|
+
kind: "evicted";
|
|
37
|
+
code: "MCP_FAKE_EVICTED";
|
|
38
|
+
message: string;
|
|
39
|
+
testId: string;
|
|
40
|
+
cap: number;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "internal_error";
|
|
43
|
+
code: typeof MCP_FAKE_INTERNAL_ERROR;
|
|
44
|
+
message: string;
|
|
45
|
+
});
|
|
46
|
+
/** An identity field whose value can fall back to the raw value (H1). */
|
|
47
|
+
//#endregion
|
|
48
|
+
export { McpFakeEvent };
|
|
49
|
+
//# sourceMappingURL=mcp-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-handler.d.ts","names":[],"sources":["../src/mcp-handler.ts"],"sourcesContent":[],"mappings":";;;;AAiDA;;;;;;AAS6C,cA7BhC,uBAAA,GA6BgC,yBAAA;;UAzBnC,gBAAA;YACE;;;;;;;;;;;;;;KAeA,YAAA,GAAe;;;;QAIb,QAAQ;QACR;;;;;;;;;;eAI+B"}
|
package/dist/mcp-handler.js
CHANGED
|
@@ -20,6 +20,27 @@ function newMCPRequestRecord() {
|
|
|
20
20
|
messages: []
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* C8: the `onToolCall` handlers that answer a config `mcp.tools[].result`.
|
|
25
|
+
* `tools/call` reports such an answer as `answeredBy: "config"`. Internal:
|
|
26
|
+
* not exported from the package.
|
|
27
|
+
*/
|
|
28
|
+
const configToolHandlers = /* @__PURE__ */ new WeakSet();
|
|
29
|
+
/** C7: the syslog levels of `logging/setLevel`, lowest first. */
|
|
30
|
+
const LOG_LEVELS = [
|
|
31
|
+
"debug",
|
|
32
|
+
"info",
|
|
33
|
+
"notice",
|
|
34
|
+
"warning",
|
|
35
|
+
"error",
|
|
36
|
+
"critical",
|
|
37
|
+
"alert",
|
|
38
|
+
"emergency"
|
|
39
|
+
];
|
|
40
|
+
/** C7: the rank of a syslog level name, or -1 when it is not one. */
|
|
41
|
+
function logLevelRank(level) {
|
|
42
|
+
return typeof level === "string" ? LOG_LEVELS.indexOf(level) : -1;
|
|
43
|
+
}
|
|
23
44
|
function jsonRpcResult(id, result) {
|
|
24
45
|
return {
|
|
25
46
|
jsonrpc: "2.0",
|
|
@@ -130,6 +151,19 @@ function fakeFailure(claim, tool, ctx, declaredTools) {
|
|
|
130
151
|
function createMCPRequestHandler(state) {
|
|
131
152
|
/** Per-request fake context, set by the outer handler for the dispatcher's methods. */
|
|
132
153
|
const contexts = /* @__PURE__ */ new WeakMap();
|
|
154
|
+
/** C7: each session's `logging/setLevel` level; dropped on DELETE. */
|
|
155
|
+
const logLevels = /* @__PURE__ */ new Map();
|
|
156
|
+
/** The initialize capabilities: `logging` only on a mount with recorded logs (C7). */
|
|
157
|
+
const capabilities = () => state.fakes.hasRecordedLogs() ? {
|
|
158
|
+
tools: {},
|
|
159
|
+
resources: {},
|
|
160
|
+
prompts: {},
|
|
161
|
+
logging: {}
|
|
162
|
+
} : {
|
|
163
|
+
tools: {},
|
|
164
|
+
resources: {},
|
|
165
|
+
prompts: {}
|
|
166
|
+
};
|
|
133
167
|
/**
|
|
134
168
|
* Spec 10 steps 1-2 for `tools/call`: a fake answer or fake failure, or
|
|
135
169
|
* `null` to fall through to today's behavior (step 3).
|
|
@@ -138,7 +172,8 @@ function createMCPRequestHandler(state) {
|
|
|
138
172
|
const event = {
|
|
139
173
|
identity: ctx.identity,
|
|
140
174
|
tool,
|
|
141
|
-
mount: ctx.mount
|
|
175
|
+
mount: ctx.mount,
|
|
176
|
+
args
|
|
142
177
|
};
|
|
143
178
|
let claim;
|
|
144
179
|
try {
|
|
@@ -201,11 +236,7 @@ function createMCPRequestHandler(state) {
|
|
|
201
236
|
initialize: async (_params, id) => {
|
|
202
237
|
return jsonRpcResult(id, {
|
|
203
238
|
protocolVersion: "2025-03-26",
|
|
204
|
-
capabilities:
|
|
205
|
-
tools: {},
|
|
206
|
-
resources: {},
|
|
207
|
-
prompts: {}
|
|
208
|
-
},
|
|
239
|
+
capabilities: capabilities(),
|
|
209
240
|
serverInfo: state.serverInfo
|
|
210
241
|
});
|
|
211
242
|
},
|
|
@@ -218,10 +249,38 @@ function createMCPRequestHandler(state) {
|
|
|
218
249
|
ping: async (_params, id) => {
|
|
219
250
|
return jsonRpcResult(id, {});
|
|
220
251
|
},
|
|
252
|
+
"logging/setLevel": async (params, id, req) => {
|
|
253
|
+
if (!state.fakes.hasRecordedLogs()) return jsonRpcError(id, -32601, "Method not found");
|
|
254
|
+
const level = params ?? {};
|
|
255
|
+
if (logLevelRank(level.level) === -1) return jsonRpcError(id, -32602, "Invalid log level");
|
|
256
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
257
|
+
if (typeof sessionId === "string") logLevels.set(sessionId, String(level.level));
|
|
258
|
+
return jsonRpcResult(id, {});
|
|
259
|
+
},
|
|
221
260
|
"tools/list": async (_params, id, req) => {
|
|
222
261
|
const tools = [];
|
|
223
|
-
for (const { def } of state.tools.values()) tools.push(def
|
|
262
|
+
for (const { def } of state.tools.values()) tools.push(def.inputSchema?.type !== void 0 ? def : {
|
|
263
|
+
...def,
|
|
264
|
+
inputSchema: {
|
|
265
|
+
type: "object",
|
|
266
|
+
...def.inputSchema
|
|
267
|
+
}
|
|
268
|
+
});
|
|
224
269
|
const ctx = contexts.get(req);
|
|
270
|
+
const recorded = ctx ? state.fakes.recordedList(ctx.identity) : null;
|
|
271
|
+
if (ctx && recorded) {
|
|
272
|
+
const listed = [...recorded];
|
|
273
|
+
const names = new Set(recorded.map((t) => t.name));
|
|
274
|
+
for (const def of tools) if (!names.has(def.name)) {
|
|
275
|
+
names.add(def.name);
|
|
276
|
+
listed.push(def);
|
|
277
|
+
}
|
|
278
|
+
for (const fake of state.fakes.listTools(ctx.identity)) if (!names.has(fake.name)) {
|
|
279
|
+
names.add(fake.name);
|
|
280
|
+
listed.push(fake);
|
|
281
|
+
}
|
|
282
|
+
return jsonRpcResult(id, { tools: listed });
|
|
283
|
+
}
|
|
225
284
|
if (ctx) {
|
|
226
285
|
const registered = new Set(tools.map((t) => t.name));
|
|
227
286
|
for (const fake of state.fakes.listTools(ctx.identity)) if (!registered.has(fake.name)) tools.push(fake);
|
|
@@ -229,22 +288,35 @@ function createMCPRequestHandler(state) {
|
|
|
229
288
|
return jsonRpcResult(id, { tools });
|
|
230
289
|
},
|
|
231
290
|
"tools/call": async (params, id, req) => {
|
|
232
|
-
const
|
|
291
|
+
const ctx = contexts.get(req);
|
|
292
|
+
const precomputed = ctx?.precomputed;
|
|
293
|
+
if (ctx) ctx.precomputed = void 0;
|
|
294
|
+
const isNotification = !precomputed && ctx?.notifications[ctx.calls.length] === true;
|
|
295
|
+
if (!precomputed) ctx?.calls.push({
|
|
296
|
+
mcpFake: null,
|
|
297
|
+
error: null
|
|
298
|
+
});
|
|
299
|
+
const outcome = ctx?.calls[ctx.calls.length - 1] ?? {
|
|
233
300
|
mcpFake: null,
|
|
234
301
|
error: null
|
|
235
302
|
};
|
|
236
|
-
const ctx = contexts.get(req);
|
|
237
|
-
const isNotification = ctx?.notifications[ctx.calls.length] === true;
|
|
238
|
-
ctx?.calls.push(outcome);
|
|
239
303
|
const { name: rawName, arguments: args } = params ?? {};
|
|
240
304
|
if (!rawName) return jsonRpcError(id, -32602, "Missing tool name");
|
|
241
305
|
if (typeof rawName !== "string") return jsonRpcError(id, -32602, build(msg`Invalid tool name: expected a string, got ${fixed(Array.isArray(rawName) ? "array" : typeof rawName)}`));
|
|
242
306
|
const name = rawName;
|
|
243
|
-
if (
|
|
307
|
+
if (precomputed?.kind === "reply") return precomputed.value;
|
|
308
|
+
if (ctx && !isNotification && !precomputed) {
|
|
244
309
|
const fake = callFake(ctx, outcome, name, args, id);
|
|
245
310
|
if (fake) return fake;
|
|
246
311
|
}
|
|
247
312
|
const entry = state.tools.get(name);
|
|
313
|
+
if (ctx && !isNotification) state.onUnfaked({
|
|
314
|
+
identity: ctx.identity,
|
|
315
|
+
tool: name,
|
|
316
|
+
mount: ctx.mount,
|
|
317
|
+
args,
|
|
318
|
+
answeredBy: !entry ? "unknown-tool" : entry.handler && configToolHandlers.has(entry.handler) ? "config" : entry.handler ? "handler" : "empty"
|
|
319
|
+
});
|
|
248
320
|
if (!entry) return jsonRpcError(id, -32602, `Unknown tool: ${name}`);
|
|
249
321
|
if (entry.handler) try {
|
|
250
322
|
const result = await entry.handler(args);
|
|
@@ -359,6 +431,8 @@ function createMCPRequestHandler(state) {
|
|
|
359
431
|
* for the mount's journal entry.
|
|
360
432
|
*/
|
|
361
433
|
return async (req, res, body, mount = "/", record = newMCPRequestRecord()) => {
|
|
434
|
+
/** T1: recorded `atMs` / `durationMs` are measured from here. */
|
|
435
|
+
const arrivedAt = performance.now();
|
|
362
436
|
if (req.method === "DELETE") {
|
|
363
437
|
const sessionId = req.headers["mcp-session-id"];
|
|
364
438
|
if (!sessionId) {
|
|
@@ -374,6 +448,7 @@ function createMCPRequestHandler(state) {
|
|
|
374
448
|
const resolution = resolveMcpIdentity(req, state.sessions.get(sessionId));
|
|
375
449
|
if (resolution.ok) record.identity = resolution.identity;
|
|
376
450
|
state.sessions.delete(sessionId);
|
|
451
|
+
logLevels.delete(sessionId);
|
|
377
452
|
state.onSessionClosed(sessionId);
|
|
378
453
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
379
454
|
res.end(JSON.stringify({ ok: true }));
|
|
@@ -420,11 +495,7 @@ function createMCPRequestHandler(state) {
|
|
|
420
495
|
id,
|
|
421
496
|
result: {
|
|
422
497
|
protocolVersion: "2025-03-26",
|
|
423
|
-
capabilities:
|
|
424
|
-
tools: {},
|
|
425
|
-
resources: {},
|
|
426
|
-
prompts: {}
|
|
427
|
-
},
|
|
498
|
+
capabilities: capabilities(),
|
|
428
499
|
serverInfo: state.serverInfo
|
|
429
500
|
}
|
|
430
501
|
};
|
|
@@ -466,6 +537,29 @@ function createMCPRequestHandler(state) {
|
|
|
466
537
|
calls: []
|
|
467
538
|
};
|
|
468
539
|
contexts.set(req, ctx);
|
|
540
|
+
const single = singleToolsCall(parsed);
|
|
541
|
+
if (single) {
|
|
542
|
+
const outcome = {
|
|
543
|
+
mcpFake: null,
|
|
544
|
+
error: null
|
|
545
|
+
};
|
|
546
|
+
ctx.calls.push(outcome);
|
|
547
|
+
const fake = callFake(ctx, outcome, single.name, single.args, single.id);
|
|
548
|
+
const replay = fake && outcome.mcpFake?.outcome === "answered" && outcome.mcpFake.id !== null ? state.fakes.replayOf(outcome.mcpFake.id) : null;
|
|
549
|
+
if (fake && "result" in fake && replay && (replay.notifications.length > 0 || replay.timing === "recorded" && replay.durationMs !== void 0)) {
|
|
550
|
+
await writeFakeToolAnswer(req, res, single.id, fake.result, replay, single.params, state.replaySpeed(), logLevels.get(sessionId) ?? null, arrivedAt);
|
|
551
|
+
record.messages = [{
|
|
552
|
+
body: single.message,
|
|
553
|
+
mcpFake: outcome.mcpFake,
|
|
554
|
+
error: outcome.error
|
|
555
|
+
}];
|
|
556
|
+
return record;
|
|
557
|
+
}
|
|
558
|
+
ctx.precomputed = fake ? {
|
|
559
|
+
kind: "reply",
|
|
560
|
+
value: fake
|
|
561
|
+
} : { kind: "none" };
|
|
562
|
+
}
|
|
469
563
|
await dispatcher(req, res, body);
|
|
470
564
|
let next = 0;
|
|
471
565
|
const messageOf = (message) => {
|
|
@@ -485,6 +579,108 @@ function createMCPRequestHandler(state) {
|
|
|
485
579
|
return record;
|
|
486
580
|
};
|
|
487
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* A single JSON-RPC `tools/call` request (not a batch, not a notification)
|
|
584
|
+
* with a string or number id and a string tool name, else `null`. Only such
|
|
585
|
+
* a message is claimed by the outer handler (MR15).
|
|
586
|
+
*/
|
|
587
|
+
function singleToolsCall(parsed) {
|
|
588
|
+
if (!isToolsCall(parsed) || parsed.jsonrpc !== "2.0" || !("id" in parsed)) return null;
|
|
589
|
+
const id = parsed.id;
|
|
590
|
+
if (typeof id !== "string" && typeof id !== "number") return null;
|
|
591
|
+
const params = parsed.params;
|
|
592
|
+
if (typeof params !== "object" || params === null) return null;
|
|
593
|
+
const name = params.name;
|
|
594
|
+
if (typeof name !== "string" || name === "") return null;
|
|
595
|
+
return {
|
|
596
|
+
message: parsed,
|
|
597
|
+
id,
|
|
598
|
+
name,
|
|
599
|
+
args: params.arguments,
|
|
600
|
+
params
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
/** Resolve after `ms` (no wait when `ms <= 0`). */
|
|
604
|
+
function sleep(ms) {
|
|
605
|
+
return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
|
606
|
+
}
|
|
607
|
+
/** The client's `_meta.progressToken` of a request's params, else `undefined`. */
|
|
608
|
+
function progressTokenOf(params) {
|
|
609
|
+
if (typeof params !== "object" || params === null) return void 0;
|
|
610
|
+
const meta = params._meta;
|
|
611
|
+
if (typeof meta !== "object" || meta === null) return void 0;
|
|
612
|
+
const token = meta.progressToken;
|
|
613
|
+
return typeof token === "string" || typeof token === "number" ? token : void 0;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* FA3 gate of one recorded notification for this request: the params to send,
|
|
617
|
+
* or `null` when it does not replay. `notifications/progress` replays only
|
|
618
|
+
* when the request carries a progress token, which replaces the recorded one;
|
|
619
|
+
* `notifications/message` only when the session set a log level (C7) and the
|
|
620
|
+
* line is at or above it. No other method replays.
|
|
621
|
+
*/
|
|
622
|
+
function replayedParams(notification, progressToken, logLevel) {
|
|
623
|
+
if (notification.method === "notifications/progress") return progressToken === void 0 ? null : {
|
|
624
|
+
...notification.params,
|
|
625
|
+
progressToken
|
|
626
|
+
};
|
|
627
|
+
if (notification.method === "notifications/message") {
|
|
628
|
+
const min = logLevelRank(logLevel);
|
|
629
|
+
const rank = logLevelRank(notification.params.level);
|
|
630
|
+
return min !== -1 && rank >= min ? { ...notification.params } : null;
|
|
631
|
+
}
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Write a fake's tools/call answer. SSE iff the entry has a notification whose
|
|
636
|
+
* FA3 gate passes AND Accept lists text/event-stream; else JSON. Timing per T1
|
|
637
|
+
* (value / speed) unless the block's timing is "immediate". `logLevel` is the
|
|
638
|
+
* session's `logging/setLevel` level (C7), `arrivedAt` the `performance.now()`
|
|
639
|
+
* of the request's arrival, which `atMs` and `durationMs` are measured from.
|
|
640
|
+
*/
|
|
641
|
+
async function writeFakeToolAnswer(req, res, id, result, replay, params, speed, logLevel = null, arrivedAt = performance.now()) {
|
|
642
|
+
const timed = replay !== null && replay.timing === "recorded";
|
|
643
|
+
const waitUntil = (atMs) => timed ? sleep(atMs / speed - (performance.now() - arrivedAt)) : Promise.resolve();
|
|
644
|
+
const token = progressTokenOf(params);
|
|
645
|
+
const notifications = [...replay?.notifications ?? []].sort((a, b) => a.atMs - b.atMs).flatMap((n) => {
|
|
646
|
+
const sent = replayedParams(n, token, logLevel);
|
|
647
|
+
return sent ? [{
|
|
648
|
+
atMs: n.atMs,
|
|
649
|
+
method: n.method,
|
|
650
|
+
params: sent
|
|
651
|
+
}] : [];
|
|
652
|
+
});
|
|
653
|
+
const accept = req.headers.accept ?? "";
|
|
654
|
+
const answer = {
|
|
655
|
+
jsonrpc: "2.0",
|
|
656
|
+
id,
|
|
657
|
+
result
|
|
658
|
+
};
|
|
659
|
+
if (notifications.length > 0 && accept.includes("text/event-stream")) {
|
|
660
|
+
res.writeHead(200, {
|
|
661
|
+
"Content-Type": "text/event-stream",
|
|
662
|
+
"Cache-Control": "no-cache"
|
|
663
|
+
});
|
|
664
|
+
res.flushHeaders();
|
|
665
|
+
for (const n of notifications) {
|
|
666
|
+
await waitUntil(n.atMs);
|
|
667
|
+
if (res.destroyed) return;
|
|
668
|
+
res.write(`event: message\ndata: ${JSON.stringify({
|
|
669
|
+
jsonrpc: "2.0",
|
|
670
|
+
method: n.method,
|
|
671
|
+
params: n.params
|
|
672
|
+
})}\n\n`);
|
|
673
|
+
}
|
|
674
|
+
if (replay?.durationMs !== void 0) await waitUntil(replay.durationMs);
|
|
675
|
+
if (res.destroyed) return;
|
|
676
|
+
res.end(`event: message\ndata: ${JSON.stringify(answer)}\n\n`);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (replay?.durationMs !== void 0) await waitUntil(replay.durationMs);
|
|
680
|
+
if (res.destroyed) return;
|
|
681
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
682
|
+
res.end(JSON.stringify(answer));
|
|
683
|
+
}
|
|
488
684
|
/** The identity header of each field whose value can fall back (H1). */
|
|
489
685
|
const IDENTITY_HEADERS = {
|
|
490
686
|
testId: "x-test-id",
|
|
@@ -496,5 +692,5 @@ function isToolsCall(message) {
|
|
|
496
692
|
}
|
|
497
693
|
|
|
498
694
|
//#endregion
|
|
499
|
-
export { createMCPRequestHandler, describeMcpIdentity, newMCPRequestRecord };
|
|
695
|
+
export { configToolHandlers, createMCPRequestHandler, describeMcpIdentity, fakeFailure, newMCPRequestRecord, writeFakeToolAnswer };
|
|
500
696
|
//# sourceMappingURL=mcp-handler.js.map
|
package/dist/mcp-handler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-handler.js","names":[],"sources":["../src/mcp-handler.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\nimport { createJsonRpcDispatcher } from \"./jsonrpc.js\";\nimport type {\n MCPToolDefinition,\n MCPResourceDefinition,\n MCPResourceContent,\n MCPPromptDefinition,\n MCPPromptResult,\n MCPContent,\n MCPSession,\n} from \"./mcp-types.js\";\nimport type { McpFakeClaim, McpFakeStore } from \"./mcp-fakes.js\";\nimport {\n MCP_FAKE_ERROR_CODES,\n type McpFakeErrorCode,\n type McpFakeIdentity,\n type McpFakeOutcome,\n} from \"./types.js\";\nimport { decodeMcpHeaderValue, resolveMcpIdentity, type McpIdentityResolution } from \"./helpers.js\";\nimport { Message, build, fixed, jsonText, msg, plainText, quote } from \"./message-text.js\";\n\n/**\n * `data.aimock.code` of a `tools/call` whose fake claim threw: an aimock\n * defect (a validated entry whose result could not be built or sent), not a\n * scenario failure. Answered with the JSON-RPC internal-error code `-32603`;\n * the entry stays unconsumed.\n */\nexport const MCP_FAKE_INTERNAL_ERROR = \"MCP_FAKE_INTERNAL_ERROR\";\nconst MCP_FAKE_INTERNAL_ERROR_RPC_CODE = -32603;\n\n/** What every fake event carries: who called which tool on which mount (I9). */\ninterface McpFakeEventBase {\n identity: McpFakeIdentity;\n tool: string;\n /** The mount path the request came in on (I9). */\n mount: string;\n}\n\n/**\n * One MCP fake outcome of a `tools/call`, for the mount's side channels\n * (metric and log line; spec 9.3). `answered` names the entry; every other\n * kind is a failure with its `data.aimock.code` and the JSON-RPC error\n * message sent to the client. `evicted` also carries the evicted test id and\n * the cap; `internal_error` is a claim that threw (`MCP_FAKE_INTERNAL_ERROR`).\n */\nexport type McpFakeEvent = McpFakeEventBase &\n (\n | { kind: \"answered\"; entryId: string }\n | {\n kind: Exclude<McpFakeOutcome, \"answered\" | \"evicted\">;\n code: McpFakeErrorCode;\n message: string;\n }\n | { kind: \"evicted\"; code: \"MCP_FAKE_EVICTED\"; message: string; testId: string; cap: number }\n | { kind: \"internal_error\"; code: typeof MCP_FAKE_INTERNAL_ERROR; message: string }\n );\n\n/** An identity field whose value can fall back to the raw value (H1). */\nexport type McpDecodeField = \"testId\" | \"context\";\n\n/**\n * A decode fallback of one request, for L2: a test id or context value that\n * is not valid percent-encoding (on the header or the query; `used` is false\n * for a query value the header overrode), or query names that are not valid\n * percent-encoding and so match no identity field.\n */\nexport type McpDecodeFallback =\n | { kind: \"value\"; field: McpDecodeField; source: \"header\" | \"query\"; raw: string; used: boolean }\n | { kind: \"names\"; count: number };\n\n/** One JSON-RPC message of a request, for one journal entry (B2). */\nexport interface MCPMessageRecord {\n /** The JSON-RPC request object of a `tools/call`, else `null`. */\n body: Record<string, unknown> | null;\n /** Set when a fake answered or failed this `tools/call`. */\n mcpFake: { id: string | null; outcome: McpFakeOutcome } | null;\n /** What the mount's own code threw while serving this message, else `null`. */\n error: string | null;\n}\n\n/**\n * What one request handled by `createMCPRequestHandler` resolved, for the\n * mount's journal (B2, B3). `identity` is `null` when the request had none to\n * resolve (a rejected identity; a DELETE with a missing or unknown session).\n * `messages` has one record per JSON-RPC message: one for a single message,\n * one per element of a batch, so each `tools/call` of a batch is journaled\n * with its own body and fake outcome. The mount writes one journal entry per\n * record, and one with no body when there is none (a request answered before\n * any message was read, or an empty batch).\n */\nexport interface MCPRequestRecord {\n identity: McpFakeIdentity | null;\n messages: MCPMessageRecord[];\n}\n\n/** An empty record, for the caller to pass in and read even if the handler throws. */\nexport function newMCPRequestRecord(): MCPRequestRecord {\n return { identity: null, messages: [] };\n}\n\nexport interface MCPState {\n serverInfo: { name: string; version: string };\n tools: Map<string, { def: MCPToolDefinition; handler?: (...args: unknown[]) => unknown }>;\n resources: Map<string, { def: MCPResourceDefinition; content?: MCPResourceContent }>;\n prompts: Map<\n string,\n {\n def: MCPPromptDefinition;\n handler?: (...args: unknown[]) => MCPPromptResult | Promise<MCPPromptResult>;\n }\n >;\n sessions: Map<string, MCPSession>;\n /** The mount's fakes (one store per mount). */\n fakes: McpFakeStore;\n /** Whether any fake block is loaded on the mount (gates L2). */\n hasFakes(): boolean;\n /** A fake answered or failed a `tools/call` (metric, L1/L10 log). */\n onFakeEvent(evt: McpFakeEvent): void;\n /**\n * A request of `sessionId` sent an identity value (or query name) that is\n * not valid percent-encoding (H1). Called only when `hasFakes()`; the mount\n * logs L2 once per session per field and source.\n */\n onDecodeFallback(sessionId: string, fallback: McpDecodeFallback): void;\n /** `sessionId` was deleted (DELETE): the mount drops its per-session state. */\n onSessionClosed(sessionId: string): void;\n}\n\nfunction jsonRpcResult(id: string | number, result: unknown) {\n return { jsonrpc: \"2.0\" as const, id, result };\n}\n\nfunction jsonRpcError(id: string | number | null, code: number, message: string, data?: unknown) {\n return {\n jsonrpc: \"2.0\" as const,\n id,\n error: data === undefined ? { code, message } : { code, message, data },\n };\n}\n\n/**\n * The identity part of a fake message or log line (I9): `testId \"<id>\"`,\n * `context \"<ctx>\"`, both, or `no testId or context`.\n */\nexport function describeMcpIdentity(identity: McpFakeIdentity): Message {\n const { testId, context } = identity;\n if (testId !== null && context !== null) {\n return msg`testId ${quote(testId)}, context ${quote(context)}`;\n }\n if (testId !== null) return msg`testId ${quote(testId)}`;\n if (context !== null) return msg`context ${quote(context)}`;\n return msg`no testId or context`;\n}\n\n/** A value as JSON text for a message part. */\nfunction jsonPart(value: unknown): ReturnType<typeof jsonText> {\n return jsonText(JSON.stringify(value) ?? \"null\");\n}\n\n/** The outcome of one `tools/call` invocation, for its message record. */\ntype CallOutcome = Omit<MCPMessageRecord, \"body\">;\n\n/**\n * What `tools/call` reads of one request (its resolved identity and mount,\n * and which invocations are notifications) and writes back: one outcome per\n * `tools/call` invocation, in order, so a batch's calls each keep their own.\n */\ninterface FakeRequestContext {\n identity: McpFakeIdentity;\n mount: string;\n /**\n * Per `tools/call` invocation, in order: true when its message has no `id`\n * member. The dispatcher passes a null id both for a notification and for\n * a request whose id is null or not a string or number, so the handler\n * cannot tell them apart from its id.\n */\n notifications: boolean[];\n calls: CallOutcome[];\n}\n\n/**\n * A fake failure: the JSON-RPC error to send and the event to report; an\n * `evicted` one also carries the evicted test id and the cap.\n */\ntype FakeFailure = { message: string; aimock: Record<string, unknown> } & (\n | { outcome: Exclude<McpFakeOutcome, \"answered\" | \"evicted\">; code: McpFakeErrorCode }\n | { outcome: \"evicted\"; code: \"MCP_FAKE_EVICTED\"; evicted: { testId: string; cap: number } }\n);\n\n/**\n * The 9.2 error for a failed claim (`mismatch`, `exhausted`, `evicted`), or\n * for an undeclared tool under `deny` (`not_declared`).\n */\nfunction fakeFailure(\n claim: Exclude<McpFakeClaim, { kind: \"answer\" } | { kind: \"none\" }> | { kind: \"not_declared\" },\n tool: string,\n ctx: FakeRequestContext,\n declaredTools: () => string[],\n): FakeFailure {\n const who = describeMcpIdentity(ctx.identity);\n const base = {\n tool,\n testId: ctx.identity.testId,\n context: ctx.identity.context,\n mount: ctx.mount,\n };\n switch (claim.kind) {\n case \"mismatch\": {\n const declared = claim.declared.map((d) =>\n d.args !== undefined\n ? msg`${plainText(d.id)} ${jsonPart(d.args)}`\n : msg`${plainText(d.id)} anyArgs`,\n );\n const list = declared.reduce<Message>(\n (acc, part, i) => (i === 0 ? part : msg`${acc}; ${part}`),\n msg`none`,\n );\n return {\n code: \"MCP_FAKE_MISMATCH\",\n outcome: \"mismatch\",\n message: build(\n msg`aimock MCP fake mismatch: tool ${quote(tool)} was called with arguments that match no declared fake (${who}). Received ${jsonPart(claim.received)}. Declared: ${list}.`,\n ),\n aimock: {\n code: \"MCP_FAKE_MISMATCH\",\n ...base,\n received: claim.received,\n declared: claim.declared,\n firstDifference: claim.firstDifference,\n },\n };\n }\n case \"exhausted\": {\n const used =\n claim.matchingDeclared === 1\n ? msg`the 1 matching fake is`\n : msg`all ${claim.matchingDeclared} matching fakes are`;\n return {\n code: \"MCP_FAKE_EXHAUSTED\",\n outcome: \"exhausted\",\n message: build(\n msg`aimock MCP fake exhausted: tool ${quote(tool)} with ${jsonPart(claim.received)} was called again, but ${used} already used (${who}).`,\n ),\n aimock: {\n code: \"MCP_FAKE_EXHAUSTED\",\n ...base,\n received: claim.received,\n matchingDeclared: claim.matchingDeclared,\n matchingConsumed: claim.matchingConsumed,\n matchingIds: claim.matchingIds,\n },\n };\n }\n case \"evicted\":\n // Only a test id is ever evicted (never the untagged state), so the\n // claim's test id is the request's.\n return {\n code: \"MCP_FAKE_EVICTED\",\n outcome: \"evicted\",\n evicted: { testId: claim.testId, cap: claim.maxTestIds },\n message: build(\n msg`aimock MCP fake evicted: consumption state for testId ${quote(claim.testId)} was evicted by the per-mount test-id cap (${claim.maxTestIds}); reset before reusing this test id (${who}).`,\n ),\n aimock: { code: \"MCP_FAKE_EVICTED\", ...base, testId: claim.testId, cap: claim.maxTestIds },\n };\n case \"not_declared\":\n return {\n code: \"MCP_FAKE_NOT_DECLARED\",\n outcome: \"not_declared\",\n message: build(\n msg`Unknown tool: ${plainText(tool)} (aimock MCP fake not declared: this scenario denies undeclared tools; ${who}).`,\n ),\n aimock: { code: \"MCP_FAKE_NOT_DECLARED\", ...base, declaredTools: declaredTools() },\n };\n }\n}\n\nexport function createMCPRequestHandler(state: MCPState) {\n /** Per-request fake context, set by the outer handler for the dispatcher's methods. */\n const contexts = new WeakMap<http.IncomingMessage, FakeRequestContext>();\n\n /**\n * Spec 10 steps 1-2 for `tools/call`: a fake answer or fake failure, or\n * `null` to fall through to today's behavior (step 3).\n */\n const callFake = (\n ctx: FakeRequestContext,\n outcome: CallOutcome,\n tool: string,\n args: unknown,\n id: string | number,\n ): ReturnType<typeof jsonRpcResult> | ReturnType<typeof jsonRpcError> | null => {\n const event = { identity: ctx.identity, tool, mount: ctx.mount };\n let claim: McpFakeClaim;\n try {\n claim = state.fakes.claim(tool, args, ctx.identity);\n } catch (err) {\n // A validated entry whose result cannot be built or sent: an aimock\n // defect. Fail loud on every side channel, never a generic -32603.\n const thrown = err instanceof Error ? err.message : String(err);\n const message = build(\n msg`aimock MCP fake internal error: tool ${quote(tool)} could not be answered (${describeMcpIdentity(ctx.identity)}): ${plainText(thrown)}`,\n );\n outcome.error = message;\n state.onFakeEvent({\n ...event,\n kind: \"internal_error\",\n code: MCP_FAKE_INTERNAL_ERROR,\n message,\n });\n return jsonRpcError(id, MCP_FAKE_INTERNAL_ERROR_RPC_CODE, message, {\n aimock: {\n code: MCP_FAKE_INTERNAL_ERROR,\n tool,\n testId: ctx.identity.testId,\n context: ctx.identity.context,\n mount: ctx.mount,\n error: thrown,\n },\n });\n }\n if (claim.kind === \"answer\") {\n outcome.mcpFake = { id: claim.entry.id, outcome: \"answered\" };\n state.onFakeEvent({ ...event, kind: \"answered\", entryId: claim.entry.id });\n return jsonRpcResult(id, claim.result);\n }\n let failure: FakeFailure;\n if (claim.kind === \"none\") {\n if (state.fakes.policy(ctx.identity) !== \"deny\") return null;\n failure = fakeFailure({ kind: \"not_declared\" }, tool, ctx, () =>\n state.fakes.declaredTools(ctx.identity),\n );\n } else {\n failure = fakeFailure(claim, tool, ctx, () => []);\n }\n outcome.mcpFake = { id: null, outcome: failure.outcome };\n state.onFakeEvent(\n failure.outcome === \"evicted\"\n ? {\n ...event,\n kind: \"evicted\",\n code: failure.code,\n message: failure.message,\n ...failure.evicted,\n }\n : { ...event, kind: failure.outcome, code: failure.code, message: failure.message },\n );\n return jsonRpcError(id, MCP_FAKE_ERROR_CODES[failure.code], failure.message, {\n aimock: failure.aimock,\n });\n };\n\n const dispatcher = createJsonRpcDispatcher({\n methods: {\n // initialize is handled directly in the outer function — this entry is\n // only here so the dispatcher doesn't return \"Method not found\" if the\n // request somehow reaches it.\n initialize: async (_params, id) => {\n return jsonRpcResult(id, {\n protocolVersion: \"2025-03-26\",\n capabilities: { tools: {}, resources: {}, prompts: {} },\n serverInfo: state.serverInfo,\n });\n },\n\n \"notifications/initialized\": async (_params, _id, req) => {\n const sessionId = req.headers[\"mcp-session-id\"] as string;\n const session = state.sessions.get(sessionId);\n if (session) {\n session.initialized = true;\n }\n return null;\n },\n\n ping: async (_params, id) => {\n return jsonRpcResult(id, {});\n },\n\n \"tools/list\": async (_params, id, req) => {\n const tools: MCPToolDefinition[] = [];\n for (const { def } of state.tools.values()) {\n tools.push(def);\n }\n // Spec 10: the union with the applicable fake tools; a registered\n // definition wins for metadata.\n const ctx = contexts.get(req);\n if (ctx) {\n const registered = new Set(tools.map((t) => t.name));\n for (const fake of state.fakes.listTools(ctx.identity)) {\n if (!registered.has(fake.name)) tools.push(fake);\n }\n }\n return jsonRpcResult(id, { tools });\n },\n\n \"tools/call\": async (params, id, req) => {\n // One outcome per invocation, in order, even when nothing is claimed,\n // so each message of a batch is matched with its own.\n const outcome: CallOutcome = { mcpFake: null, error: null };\n const ctx = contexts.get(req);\n const isNotification = ctx?.notifications[ctx.calls.length] === true;\n ctx?.calls.push(outcome);\n // A notification (no `id` member) gets no answer (JSON-RPC 2.0) and\n // claims no fake entry, but a registered handler still runs for its\n // side effects, as on origin/main. A request whose id is null or of\n // another type is still served, with id null.\n const { name: rawName, arguments: args } = (params ?? {}) as {\n name?: unknown;\n arguments?: unknown;\n };\n if (!rawName) {\n return jsonRpcError(id, -32602, \"Missing tool name\");\n }\n if (typeof rawName !== \"string\") {\n const got = Array.isArray(rawName) ? \"array\" : typeof rawName;\n return jsonRpcError(\n id,\n -32602,\n build(msg`Invalid tool name: expected a string, got ${fixed(got)}`),\n );\n }\n const name = rawName;\n // Spec 10 steps 1-2: fakes, then the deny policy; never a handler\n // after a fake failure.\n if (ctx && !isNotification) {\n const fake = callFake(ctx, outcome, name, args, id);\n if (fake) return fake;\n }\n const entry = state.tools.get(name);\n if (!entry) {\n return jsonRpcError(id, -32602, `Unknown tool: ${name}`);\n }\n if (entry.handler) {\n try {\n const result = await entry.handler(args);\n const content: MCPContent[] = Array.isArray(result)\n ? result\n : [{ type: \"text\", text: String(result) }];\n return jsonRpcResult(id, { content, isError: false });\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n return jsonRpcResult(id, {\n content: [{ type: \"text\", text: message }],\n isError: true,\n });\n }\n }\n // No handler — return empty content\n return jsonRpcResult(id, { content: [], isError: false });\n },\n\n \"resources/list\": async (_params, id) => {\n const resources: MCPResourceDefinition[] = [];\n for (const { def } of state.resources.values()) {\n resources.push(def);\n }\n return jsonRpcResult(id, { resources });\n },\n\n \"resources/read\": async (params, id) => {\n const { uri } = (params ?? {}) as { uri?: string };\n if (!uri) {\n return jsonRpcError(id, -32602, \"Missing resource URI\");\n }\n const entry = state.resources.get(uri);\n if (!entry) {\n return jsonRpcError(id, -32602, `Unknown resource: ${uri}`);\n }\n return jsonRpcResult(id, {\n contents: [\n {\n uri,\n ...(entry.content?.text !== undefined && { text: entry.content.text }),\n ...(entry.content?.blob !== undefined && { blob: entry.content.blob }),\n ...(entry.content?.mimeType !== undefined && { mimeType: entry.content.mimeType }),\n },\n ],\n });\n },\n\n \"prompts/list\": async (_params, id) => {\n const prompts: MCPPromptDefinition[] = [];\n for (const { def } of state.prompts.values()) {\n prompts.push(def);\n }\n return jsonRpcResult(id, { prompts });\n },\n\n \"prompts/get\": async (params, id) => {\n const { name, arguments: args } = (params ?? {}) as { name?: string; arguments?: unknown };\n if (!name) {\n return jsonRpcError(id, -32602, \"Missing prompt name\");\n }\n const entry = state.prompts.get(name);\n if (!entry) {\n return jsonRpcError(id, -32602, `Unknown prompt: ${name}`);\n }\n if (entry.handler) {\n try {\n const result = await entry.handler(args);\n return jsonRpcResult(id, result);\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n return jsonRpcError(id, -32603, `Prompt handler error: ${message}`);\n }\n }\n // No handler — return empty messages\n return jsonRpcResult(id, { messages: [] });\n },\n },\n });\n\n /** Answer a rejected identity (H4, H5) with HTTP 400, in the session-error shape. */\n const rejectIdentity = (\n res: http.ServerResponse,\n resolution: Extract<McpIdentityResolution, { ok: false }>,\n ): void => {\n res.writeHead(resolution.error.status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: resolution.error.message, code: resolution.error.code }));\n };\n\n /**\n * L2: report each identity value that fell back to the raw value (H1),\n * naming its source (header or query) and the value itself, and any query\n * name that did not decode. `fellBack` says only that some value of the\n * field fell back; the header is checked on its own, and the query by the\n * same resolver given the query alone, so the rules are the resolver's.\n */\n const reportFallback = (\n sessionId: string,\n req: http.IncomingMessage,\n resolution: Extract<McpIdentityResolution, { ok: true }>,\n ): void => {\n if (!state.hasFakes()) return;\n const { fellBack } = resolution;\n if (fellBack.testId || fellBack.context) {\n const queryOnly = resolveMcpIdentity({ headers: {}, url: req.url });\n for (const field of [\"testId\", \"context\"] as const) {\n if (!fellBack[field]) continue;\n const header = req.headers[IDENTITY_HEADERS[field]];\n const headerSent = typeof header === \"string\" && header !== \"\";\n if (headerSent && decodeMcpHeaderValue(header).fellBack) {\n state.onDecodeFallback(sessionId, {\n kind: \"value\",\n field,\n source: \"header\",\n raw: header,\n used: true,\n });\n }\n const fromQuery =\n queryOnly.ok && queryOnly.fellBack[field] ? queryOnly.identity[field] : null;\n if (fromQuery !== null) {\n state.onDecodeFallback(sessionId, {\n kind: \"value\",\n field,\n source: \"query\",\n raw: fromQuery,\n used: !headerSent,\n });\n }\n }\n }\n if (resolution.undecodedQueryNames) {\n state.onDecodeFallback(sessionId, { kind: \"names\", count: resolution.undecodedQueryNames });\n }\n };\n\n /**\n * Handle one MCP request. `mount` is the path the mount is served at (I9;\n * `/` for a standalone server). Resolves with what the request resolved,\n * for the mount's journal entry.\n */\n return async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n mount = \"/\",\n record: MCPRequestRecord = newMCPRequestRecord(),\n ): Promise<MCPRequestRecord> => {\n // DELETE handler: session teardown\n if (req.method === \"DELETE\") {\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n if (!sessionId) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing mcp-session-id header\" }));\n return record;\n }\n if (!state.sessions.has(sessionId)) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Session not found\" }));\n return record;\n }\n // Journal only: identity inputs are not checked on a session teardown.\n const resolution = resolveMcpIdentity(req, state.sessions.get(sessionId));\n if (resolution.ok) record.identity = resolution.identity;\n state.sessions.delete(sessionId);\n state.onSessionClosed(sessionId);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n return record;\n }\n\n // Identity inputs (I1-I3, H4, H5): on every POST, a bad undeclared\n // override or a repeated field is rejected before the request is served.\n const headerSessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n const knownSession = headerSessionId ? state.sessions.get(headerSessionId) : undefined;\n\n // Parse the body to determine method for session validation\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n const resolution = resolveMcpIdentity(req, knownSession);\n if (!resolution.ok) {\n rejectIdentity(res, resolution);\n return record;\n }\n record.identity = resolution.identity;\n if (headerSessionId && knownSession) reportFallback(headerSessionId, req, resolution);\n // Let the dispatcher handle parse errors\n await dispatcher(req, res, body);\n return record;\n }\n\n const method =\n typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n ? (parsed as { method: unknown }).method\n : undefined;\n\n // Handle initialize directly to control response headers\n if (method === \"initialize\") {\n const id =\n typeof parsed === \"object\" && parsed !== null && \"id\" in parsed\n ? (parsed as { id: unknown }).id\n : null;\n\n // I3: only the values the initialize request itself supplies are bound.\n const resolution = resolveMcpIdentity(req);\n if (!resolution.ok) {\n rejectIdentity(res, resolution);\n return record;\n }\n record.identity = resolution.identity;\n const { identity, supplied } = resolution;\n\n const sessionId = randomUUID();\n state.sessions.set(sessionId, {\n id: sessionId,\n initialized: false,\n createdAt: Date.now(),\n ...(supplied.testId && identity.testId !== null ? { testId: identity.testId } : {}),\n ...(supplied.context && identity.context !== null ? { context: identity.context } : {}),\n ...(supplied.undeclared && identity.undeclared !== null\n ? { undeclared: identity.undeclared }\n : {}),\n });\n reportFallback(sessionId, req, resolution);\n\n const response = {\n jsonrpc: \"2.0\",\n id,\n result: {\n protocolVersion: \"2025-03-26\",\n capabilities: { tools: {}, resources: {}, prompts: {} },\n serverInfo: state.serverInfo,\n },\n };\n\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Mcp-Session-Id\": sessionId,\n });\n res.end(JSON.stringify(response));\n return record;\n }\n\n const resolution = resolveMcpIdentity(req, knownSession);\n if (!resolution.ok) {\n rejectIdentity(res, resolution);\n return record;\n }\n record.identity = resolution.identity;\n\n // Session validation for all other methods\n const sessionId = headerSessionId;\n if (!sessionId) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing mcp-session-id header\" }));\n return record;\n }\n if (!state.sessions.has(sessionId)) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Session not found\" }));\n return record;\n }\n reportFallback(sessionId, req, resolution);\n\n // Enforce initialization: only allow notifications/initialized through\n // before the session is fully initialized\n const session = state.sessions.get(sessionId)!;\n if (!session.initialized && method !== \"notifications/initialized\") {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify(\n jsonRpcError(\n typeof parsed === \"object\" && parsed !== null && \"id\" in parsed\n ? ((parsed as { id: unknown }).id as string | number)\n : null,\n -32002,\n \"Session not initialized\",\n ),\n ),\n );\n return record;\n }\n\n // Delegate to the JSON-RPC dispatcher for all other methods\n // The dispatcher invokes `tools/call` once per message that is an object\n // with `jsonrpc: \"2.0\"` and that method, in order (see B2 below).\n const notifications = (Array.isArray(parsed) ? parsed : [parsed])\n .filter((m): m is Record<string, unknown> => isToolsCall(m) && m.jsonrpc === \"2.0\")\n .map((m) => !(\"id\" in m));\n const ctx: FakeRequestContext = {\n identity: resolution.identity,\n mount,\n notifications,\n calls: [],\n };\n contexts.set(req, ctx);\n await dispatcher(req, res, body);\n // B2: each tools/call message is journaled with its body and its own\n // outcome. The dispatcher invokes `tools/call` once per message that is\n // an object with `jsonrpc: \"2.0\"` and that method, in order, so the n-th\n // such message takes the n-th outcome.\n let next = 0;\n const messageOf = (message: unknown): MCPMessageRecord => {\n if (!isToolsCall(message)) return { body: null, mcpFake: null, error: null };\n const outcome = message.jsonrpc === \"2.0\" ? ctx.calls[next++] : undefined;\n return { body: message, mcpFake: outcome?.mcpFake ?? null, error: outcome?.error ?? null };\n };\n record.messages = Array.isArray(parsed) ? parsed.map(messageOf) : [messageOf(parsed)];\n return record;\n };\n}\n\n/** The identity header of each field whose value can fall back (H1). */\nconst IDENTITY_HEADERS: Record<McpDecodeField, string> = {\n testId: \"x-test-id\",\n context: \"x-aimock-context\",\n};\n\n/** A JSON-RPC message object whose method is `tools/call`. */\nfunction isToolsCall(message: unknown): message is Record<string, unknown> {\n return (\n typeof message === \"object\" &&\n message !== null &&\n !Array.isArray(message) &&\n \"method\" in message &&\n message.method === \"tools/call\"\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mcp-handler.js","names":[],"sources":["../src/mcp-handler.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\nimport { createJsonRpcDispatcher } from \"./jsonrpc.js\";\nimport type {\n MCPToolDefinition,\n MCPResourceDefinition,\n MCPResourceContent,\n MCPPromptDefinition,\n MCPPromptResult,\n MCPContent,\n MCPSession,\n} from \"./mcp-types.js\";\nimport type { McpFakeClaim, McpFakeStore } from \"./mcp-fakes.js\";\nimport {\n MCP_FAKE_ERROR_CODES,\n type McpFakeErrorCode,\n type McpFakeIdentity,\n type McpFakeNotification,\n type McpFakeOutcome,\n} from \"./types.js\";\nimport { decodeMcpHeaderValue, resolveMcpIdentity, type McpIdentityResolution } from \"./helpers.js\";\nimport { Message, build, fixed, jsonText, msg, plainText, quote } from \"./message-text.js\";\n\n/**\n * `data.aimock.code` of a `tools/call` whose fake claim threw: an aimock\n * defect (a validated entry whose result could not be built or sent), not a\n * scenario failure. Answered with the JSON-RPC internal-error code `-32603`;\n * the entry stays unconsumed.\n */\nexport const MCP_FAKE_INTERNAL_ERROR = \"MCP_FAKE_INTERNAL_ERROR\";\nconst MCP_FAKE_INTERNAL_ERROR_RPC_CODE = -32603;\n\n/** What every fake event carries: who called which tool on which mount (I9). */\ninterface McpFakeEventBase {\n identity: McpFakeIdentity;\n tool: string;\n /** The mount path the request came in on (I9). */\n mount: string;\n /** The call's `arguments` as received (RP2). */\n args: unknown;\n}\n\n/**\n * One MCP fake outcome of a `tools/call`, for the mount's side channels\n * (metric and log line; spec 9.3). `answered` names the entry; every other\n * kind is a failure with its `data.aimock.code` and the JSON-RPC error\n * message sent to the client. `evicted` also carries the evicted test id and\n * the cap; `internal_error` is a claim that threw (`MCP_FAKE_INTERNAL_ERROR`).\n */\nexport type McpFakeEvent = McpFakeEventBase &\n (\n | { kind: \"answered\"; entryId: string }\n | {\n kind: Exclude<McpFakeOutcome, \"answered\" | \"evicted\">;\n code: McpFakeErrorCode;\n message: string;\n }\n | { kind: \"evicted\"; code: \"MCP_FAKE_EVICTED\"; message: string; testId: string; cap: number }\n | { kind: \"internal_error\"; code: typeof MCP_FAKE_INTERNAL_ERROR; message: string }\n );\n\n/** An identity field whose value can fall back to the raw value (H1). */\nexport type McpDecodeField = \"testId\" | \"context\";\n\n/**\n * A decode fallback of one request, for L2: a test id or context value that\n * is not valid percent-encoding (on the header or the query; `used` is false\n * for a query value the header overrode), or query names that are not valid\n * percent-encoding and so match no identity field.\n */\nexport type McpDecodeFallback =\n | { kind: \"value\"; field: McpDecodeField; source: \"header\" | \"query\"; raw: string; used: boolean }\n | { kind: \"names\"; count: number };\n\n/** One JSON-RPC message of a request, for one journal entry (B2). */\nexport interface MCPMessageRecord {\n /** The JSON-RPC request object of a `tools/call`, else `null`. */\n body: Record<string, unknown> | null;\n /** Set when a fake answered or failed this `tools/call`. */\n mcpFake: { id: string | null; outcome: McpFakeOutcome } | null;\n /** What the mount's own code threw while serving this message, else `null`. */\n error: string | null;\n}\n\n/**\n * What one request handled by `createMCPRequestHandler` resolved, for the\n * mount's journal (B2, B3). `identity` is `null` when the request had none to\n * resolve (a rejected identity; a DELETE with a missing or unknown session).\n * `messages` has one record per JSON-RPC message: one for a single message,\n * one per element of a batch, so each `tools/call` of a batch is journaled\n * with its own body and fake outcome. The mount writes one journal entry per\n * record, and one with no body when there is none (a request answered before\n * any message was read, or an empty batch).\n */\nexport interface MCPRequestRecord {\n identity: McpFakeIdentity | null;\n messages: MCPMessageRecord[];\n}\n\n/** An empty record, for the caller to pass in and read even if the handler throws. */\nexport function newMCPRequestRecord(): MCPRequestRecord {\n return { identity: null, messages: [] };\n}\n\nexport interface MCPState {\n serverInfo: { name: string; version: string };\n tools: Map<string, { def: MCPToolDefinition; handler?: (...args: unknown[]) => unknown }>;\n resources: Map<string, { def: MCPResourceDefinition; content?: MCPResourceContent }>;\n prompts: Map<\n string,\n {\n def: MCPPromptDefinition;\n handler?: (...args: unknown[]) => MCPPromptResult | Promise<MCPPromptResult>;\n }\n >;\n sessions: Map<string, MCPSession>;\n /** The mount's fakes (one store per mount). */\n fakes: McpFakeStore;\n /** Whether any fake block is loaded on the mount (gates L2). */\n hasFakes(): boolean;\n /** A fake answered or failed a `tools/call` (metric, L1/L10 log). */\n onFakeEvent(evt: McpFakeEvent): void;\n /**\n * A request of `sessionId` sent an identity value (or query name) that is\n * not valid percent-encoding (H1). Called only when `hasFakes()`; the mount\n * logs L2 once per session per field and source.\n */\n onDecodeFallback(sessionId: string, fallback: McpDecodeFallback): void;\n /** `sessionId` was deleted (DELETE): the mount drops its per-session state. */\n onSessionClosed(sessionId: string): void;\n /** RP2: a tools/call no fake applied to (fakes:10 step 3), with who answered. */\n onUnfaked(evt: {\n identity: McpFakeIdentity;\n tool: string;\n mount: string;\n args: unknown;\n answeredBy: \"handler\" | \"config\" | \"empty\" | \"unknown-tool\";\n }): void;\n /** T1: the replay speed recorded timing plays at (`value / speed`). */\n replaySpeed(): number;\n}\n\n/**\n * C8: the `onToolCall` handlers that answer a config `mcp.tools[].result`.\n * `tools/call` reports such an answer as `answeredBy: \"config\"`. Internal:\n * not exported from the package.\n */\nexport const configToolHandlers = new WeakSet<(...args: unknown[]) => unknown>();\n\n/** C7: the syslog levels of `logging/setLevel`, lowest first. */\nconst LOG_LEVELS = [\n \"debug\",\n \"info\",\n \"notice\",\n \"warning\",\n \"error\",\n \"critical\",\n \"alert\",\n \"emergency\",\n] as const;\n\n/** C7: the rank of a syslog level name, or -1 when it is not one. */\nfunction logLevelRank(level: unknown): number {\n return typeof level === \"string\" ? (LOG_LEVELS as readonly string[]).indexOf(level) : -1;\n}\n\nfunction jsonRpcResult(id: string | number, result: unknown) {\n return { jsonrpc: \"2.0\" as const, id, result };\n}\n\nfunction jsonRpcError(id: string | number | null, code: number, message: string, data?: unknown) {\n return {\n jsonrpc: \"2.0\" as const,\n id,\n error: data === undefined ? { code, message } : { code, message, data },\n };\n}\n\n/**\n * The identity part of a fake message or log line (I9): `testId \"<id>\"`,\n * `context \"<ctx>\"`, both, or `no testId or context`.\n */\nexport function describeMcpIdentity(identity: McpFakeIdentity): Message {\n const { testId, context } = identity;\n if (testId !== null && context !== null) {\n return msg`testId ${quote(testId)}, context ${quote(context)}`;\n }\n if (testId !== null) return msg`testId ${quote(testId)}`;\n if (context !== null) return msg`context ${quote(context)}`;\n return msg`no testId or context`;\n}\n\n/** A value as JSON text for a message part. */\nfunction jsonPart(value: unknown): ReturnType<typeof jsonText> {\n return jsonText(JSON.stringify(value) ?? \"null\");\n}\n\n/** The outcome of one `tools/call` invocation, for its message record. */\ntype CallOutcome = Omit<MCPMessageRecord, \"body\">;\n\n/**\n * What `tools/call` reads of one request (its resolved identity and mount,\n * and which invocations are notifications) and writes back: one outcome per\n * `tools/call` invocation, in order, so a batch's calls each keep their own.\n */\ninterface FakeRequestContext {\n identity: McpFakeIdentity;\n mount: string;\n /**\n * Per `tools/call` invocation, in order: true when its message has no `id`\n * member. The dispatcher passes a null id both for a notification and for\n * a request whose id is null or not a string or number, so the handler\n * cannot tell them apart from its id.\n */\n notifications: boolean[];\n calls: CallOutcome[];\n /**\n * Set by the outer handler for a single `tools/call` it already claimed\n * (MR15 interception), so the method does not claim twice: `reply` is the\n * answer or failure `callFake` built, `none` means no fake applied. The\n * outcome of that call is already in `calls`.\n */\n precomputed?:\n | {\n kind: \"reply\";\n value: ReturnType<typeof jsonRpcResult> | ReturnType<typeof jsonRpcError>;\n }\n | { kind: \"none\" };\n}\n\n/**\n * A fake failure: the JSON-RPC error to send and the event to report; an\n * `evicted` one also carries the evicted test id and the cap.\n */\ntype FakeFailure = { message: string; aimock: Record<string, unknown> } & (\n | { outcome: Exclude<McpFakeOutcome, \"answered\" | \"evicted\">; code: McpFakeErrorCode }\n | { outcome: \"evicted\"; code: \"MCP_FAKE_EVICTED\"; evicted: { testId: string; cap: number } }\n);\n\n/**\n * The 9.2 error for a failed claim (`mismatch`, `exhausted`, `evicted`), or\n * for an undeclared tool under `deny` (`not_declared`).\n */\nexport function fakeFailure(\n claim: Exclude<McpFakeClaim, { kind: \"answer\" } | { kind: \"none\" }> | { kind: \"not_declared\" },\n tool: string,\n ctx: FakeRequestContext,\n declaredTools: () => string[],\n): FakeFailure {\n const who = describeMcpIdentity(ctx.identity);\n const base = {\n tool,\n testId: ctx.identity.testId,\n context: ctx.identity.context,\n mount: ctx.mount,\n };\n switch (claim.kind) {\n case \"mismatch\": {\n const declared = claim.declared.map((d) =>\n d.args !== undefined\n ? msg`${plainText(d.id)} ${jsonPart(d.args)}`\n : msg`${plainText(d.id)} anyArgs`,\n );\n const list = declared.reduce<Message>(\n (acc, part, i) => (i === 0 ? part : msg`${acc}; ${part}`),\n msg`none`,\n );\n return {\n code: \"MCP_FAKE_MISMATCH\",\n outcome: \"mismatch\",\n message: build(\n msg`aimock MCP fake mismatch: tool ${quote(tool)} was called with arguments that match no declared fake (${who}). Received ${jsonPart(claim.received)}. Declared: ${list}.`,\n ),\n aimock: {\n code: \"MCP_FAKE_MISMATCH\",\n ...base,\n received: claim.received,\n declared: claim.declared,\n firstDifference: claim.firstDifference,\n },\n };\n }\n case \"exhausted\": {\n const used =\n claim.matchingDeclared === 1\n ? msg`the 1 matching fake is`\n : msg`all ${claim.matchingDeclared} matching fakes are`;\n return {\n code: \"MCP_FAKE_EXHAUSTED\",\n outcome: \"exhausted\",\n message: build(\n msg`aimock MCP fake exhausted: tool ${quote(tool)} with ${jsonPart(claim.received)} was called again, but ${used} already used (${who}).`,\n ),\n aimock: {\n code: \"MCP_FAKE_EXHAUSTED\",\n ...base,\n received: claim.received,\n matchingDeclared: claim.matchingDeclared,\n matchingConsumed: claim.matchingConsumed,\n matchingIds: claim.matchingIds,\n },\n };\n }\n case \"evicted\":\n // Only a test id is ever evicted (never the untagged state), so the\n // claim's test id is the request's.\n return {\n code: \"MCP_FAKE_EVICTED\",\n outcome: \"evicted\",\n evicted: { testId: claim.testId, cap: claim.maxTestIds },\n message: build(\n msg`aimock MCP fake evicted: consumption state for testId ${quote(claim.testId)} was evicted by the per-mount test-id cap (${claim.maxTestIds}); reset before reusing this test id (${who}).`,\n ),\n aimock: { code: \"MCP_FAKE_EVICTED\", ...base, testId: claim.testId, cap: claim.maxTestIds },\n };\n case \"not_declared\":\n return {\n code: \"MCP_FAKE_NOT_DECLARED\",\n outcome: \"not_declared\",\n message: build(\n msg`Unknown tool: ${plainText(tool)} (aimock MCP fake not declared: this scenario denies undeclared tools; ${who}).`,\n ),\n aimock: { code: \"MCP_FAKE_NOT_DECLARED\", ...base, declaredTools: declaredTools() },\n };\n }\n}\n\nexport function createMCPRequestHandler(state: MCPState) {\n /** Per-request fake context, set by the outer handler for the dispatcher's methods. */\n const contexts = new WeakMap<http.IncomingMessage, FakeRequestContext>();\n /** C7: each session's `logging/setLevel` level; dropped on DELETE. */\n const logLevels = new Map<string, string>();\n\n /** The initialize capabilities: `logging` only on a mount with recorded logs (C7). */\n const capabilities = (): Record<string, unknown> =>\n state.fakes.hasRecordedLogs()\n ? { tools: {}, resources: {}, prompts: {}, logging: {} }\n : { tools: {}, resources: {}, prompts: {} };\n\n /**\n * Spec 10 steps 1-2 for `tools/call`: a fake answer or fake failure, or\n * `null` to fall through to today's behavior (step 3).\n */\n const callFake = (\n ctx: FakeRequestContext,\n outcome: CallOutcome,\n tool: string,\n args: unknown,\n id: string | number,\n ): ReturnType<typeof jsonRpcResult> | ReturnType<typeof jsonRpcError> | null => {\n const event = { identity: ctx.identity, tool, mount: ctx.mount, args };\n let claim: McpFakeClaim;\n try {\n claim = state.fakes.claim(tool, args, ctx.identity);\n } catch (err) {\n // A validated entry whose result cannot be built or sent: an aimock\n // defect. Fail loud on every side channel, never a generic -32603.\n const thrown = err instanceof Error ? err.message : String(err);\n const message = build(\n msg`aimock MCP fake internal error: tool ${quote(tool)} could not be answered (${describeMcpIdentity(ctx.identity)}): ${plainText(thrown)}`,\n );\n outcome.error = message;\n state.onFakeEvent({\n ...event,\n kind: \"internal_error\",\n code: MCP_FAKE_INTERNAL_ERROR,\n message,\n });\n return jsonRpcError(id, MCP_FAKE_INTERNAL_ERROR_RPC_CODE, message, {\n aimock: {\n code: MCP_FAKE_INTERNAL_ERROR,\n tool,\n testId: ctx.identity.testId,\n context: ctx.identity.context,\n mount: ctx.mount,\n error: thrown,\n },\n });\n }\n if (claim.kind === \"answer\") {\n outcome.mcpFake = { id: claim.entry.id, outcome: \"answered\" };\n state.onFakeEvent({ ...event, kind: \"answered\", entryId: claim.entry.id });\n return jsonRpcResult(id, claim.result);\n }\n let failure: FakeFailure;\n if (claim.kind === \"none\") {\n if (state.fakes.policy(ctx.identity) !== \"deny\") return null;\n failure = fakeFailure({ kind: \"not_declared\" }, tool, ctx, () =>\n state.fakes.declaredTools(ctx.identity),\n );\n } else {\n failure = fakeFailure(claim, tool, ctx, () => []);\n }\n outcome.mcpFake = { id: null, outcome: failure.outcome };\n state.onFakeEvent(\n failure.outcome === \"evicted\"\n ? {\n ...event,\n kind: \"evicted\",\n code: failure.code,\n message: failure.message,\n ...failure.evicted,\n }\n : { ...event, kind: failure.outcome, code: failure.code, message: failure.message },\n );\n return jsonRpcError(id, MCP_FAKE_ERROR_CODES[failure.code], failure.message, {\n aimock: failure.aimock,\n });\n };\n\n const dispatcher = createJsonRpcDispatcher({\n methods: {\n // initialize is handled directly in the outer function — this entry is\n // only here so the dispatcher doesn't return \"Method not found\" if the\n // request somehow reaches it.\n initialize: async (_params, id) => {\n return jsonRpcResult(id, {\n protocolVersion: \"2025-03-26\",\n capabilities: capabilities(),\n serverInfo: state.serverInfo,\n });\n },\n\n \"notifications/initialized\": async (_params, _id, req) => {\n const sessionId = req.headers[\"mcp-session-id\"] as string;\n const session = state.sessions.get(sessionId);\n if (session) {\n session.initialized = true;\n }\n return null;\n },\n\n ping: async (_params, id) => {\n return jsonRpcResult(id, {});\n },\n\n // C7: only a mount with recorded log lines serves logging/setLevel;\n // every other mount answers exactly as the dispatcher does for an\n // unknown method (src/jsonrpc.ts).\n \"logging/setLevel\": async (params, id, req) => {\n if (!state.fakes.hasRecordedLogs()) return jsonRpcError(id, -32601, \"Method not found\");\n const level = (params ?? {}) as { level?: unknown };\n if (logLevelRank(level.level) === -1) {\n return jsonRpcError(id, -32602, \"Invalid log level\");\n }\n const sessionId = req.headers[\"mcp-session-id\"];\n if (typeof sessionId === \"string\") logLevels.set(sessionId, String(level.level));\n return jsonRpcResult(id, {});\n },\n\n \"tools/list\": async (_params, id, req) => {\n const tools: MCPToolDefinition[] = [];\n // inputSchema is required on every listed Tool, with a root `type`; a\n // tool registered without one lists `{ type: \"object\" }`, as fake\n // tools already do, and a schema without a `type` gets\n // `type: \"object\"` added. An explicit `type` is left as is.\n for (const { def } of state.tools.values()) {\n tools.push(\n def.inputSchema?.type !== undefined\n ? def\n : { ...def, inputSchema: { type: \"object\", ...def.inputSchema } },\n );\n }\n const ctx = contexts.get(req);\n // AM7: a recorded list comes first, in recorded order (its definition\n // wins), then registered tools missing from it, then the other fake\n // tools.\n const recorded = ctx ? state.fakes.recordedList(ctx.identity) : null;\n if (ctx && recorded) {\n const listed: unknown[] = [...recorded];\n const names = new Set(recorded.map((t) => t.name));\n for (const def of tools) {\n if (!names.has(def.name)) {\n names.add(def.name);\n listed.push(def);\n }\n }\n for (const fake of state.fakes.listTools(ctx.identity)) {\n if (!names.has(fake.name)) {\n names.add(fake.name);\n listed.push(fake);\n }\n }\n return jsonRpcResult(id, { tools: listed });\n }\n // Spec 10: the union with the applicable fake tools; a registered\n // definition wins for metadata.\n if (ctx) {\n const registered = new Set(tools.map((t) => t.name));\n for (const fake of state.fakes.listTools(ctx.identity)) {\n if (!registered.has(fake.name)) tools.push(fake);\n }\n }\n return jsonRpcResult(id, { tools });\n },\n\n \"tools/call\": async (params, id, req) => {\n // One outcome per invocation, in order, even when nothing is claimed,\n // so each message of a batch is matched with its own.\n const ctx = contexts.get(req);\n // A single call the outer handler already claimed: its outcome is\n // already in `calls`, and its claim is not run again.\n const precomputed = ctx?.precomputed;\n if (ctx) ctx.precomputed = undefined;\n const isNotification = !precomputed && ctx?.notifications[ctx.calls.length] === true;\n if (!precomputed) ctx?.calls.push({ mcpFake: null, error: null });\n const outcome: CallOutcome = ctx?.calls[ctx.calls.length - 1] ?? {\n mcpFake: null,\n error: null,\n };\n // A notification (no `id` member) gets no answer (JSON-RPC 2.0) and\n // claims no fake entry, but a registered handler still runs for its\n // side effects, as on origin/main. A request whose id is null or of\n // another type is still served, with id null.\n const { name: rawName, arguments: args } = (params ?? {}) as {\n name?: unknown;\n arguments?: unknown;\n };\n if (!rawName) {\n return jsonRpcError(id, -32602, \"Missing tool name\");\n }\n if (typeof rawName !== \"string\") {\n const got = Array.isArray(rawName) ? \"array\" : typeof rawName;\n return jsonRpcError(\n id,\n -32602,\n build(msg`Invalid tool name: expected a string, got ${fixed(got)}`),\n );\n }\n const name = rawName;\n // Spec 10 steps 1-2: fakes, then the deny policy; never a handler\n // after a fake failure.\n if (precomputed?.kind === \"reply\") return precomputed.value;\n if (ctx && !isNotification && !precomputed) {\n const fake = callFake(ctx, outcome, name, args, id);\n if (fake) return fake;\n }\n const entry = state.tools.get(name);\n // RP2: every tools/call no fake applied to is logged with who answers it.\n if (ctx && !isNotification) {\n state.onUnfaked({\n identity: ctx.identity,\n tool: name,\n mount: ctx.mount,\n args,\n answeredBy: !entry\n ? \"unknown-tool\"\n : entry.handler && configToolHandlers.has(entry.handler)\n ? \"config\"\n : entry.handler\n ? \"handler\"\n : \"empty\",\n });\n }\n if (!entry) {\n return jsonRpcError(id, -32602, `Unknown tool: ${name}`);\n }\n if (entry.handler) {\n try {\n const result = await entry.handler(args);\n const content: MCPContent[] = Array.isArray(result)\n ? result\n : [{ type: \"text\", text: String(result) }];\n return jsonRpcResult(id, { content, isError: false });\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n return jsonRpcResult(id, {\n content: [{ type: \"text\", text: message }],\n isError: true,\n });\n }\n }\n // No handler — return empty content\n return jsonRpcResult(id, { content: [], isError: false });\n },\n\n \"resources/list\": async (_params, id) => {\n const resources: MCPResourceDefinition[] = [];\n for (const { def } of state.resources.values()) {\n resources.push(def);\n }\n return jsonRpcResult(id, { resources });\n },\n\n \"resources/read\": async (params, id) => {\n const { uri } = (params ?? {}) as { uri?: string };\n if (!uri) {\n return jsonRpcError(id, -32602, \"Missing resource URI\");\n }\n const entry = state.resources.get(uri);\n if (!entry) {\n return jsonRpcError(id, -32602, `Unknown resource: ${uri}`);\n }\n return jsonRpcResult(id, {\n contents: [\n {\n uri,\n ...(entry.content?.text !== undefined && { text: entry.content.text }),\n ...(entry.content?.blob !== undefined && { blob: entry.content.blob }),\n ...(entry.content?.mimeType !== undefined && { mimeType: entry.content.mimeType }),\n },\n ],\n });\n },\n\n \"prompts/list\": async (_params, id) => {\n const prompts: MCPPromptDefinition[] = [];\n for (const { def } of state.prompts.values()) {\n prompts.push(def);\n }\n return jsonRpcResult(id, { prompts });\n },\n\n \"prompts/get\": async (params, id) => {\n const { name, arguments: args } = (params ?? {}) as { name?: string; arguments?: unknown };\n if (!name) {\n return jsonRpcError(id, -32602, \"Missing prompt name\");\n }\n const entry = state.prompts.get(name);\n if (!entry) {\n return jsonRpcError(id, -32602, `Unknown prompt: ${name}`);\n }\n if (entry.handler) {\n try {\n const result = await entry.handler(args);\n return jsonRpcResult(id, result);\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n return jsonRpcError(id, -32603, `Prompt handler error: ${message}`);\n }\n }\n // No handler — return empty messages\n return jsonRpcResult(id, { messages: [] });\n },\n },\n });\n\n /** Answer a rejected identity (H4, H5) with HTTP 400, in the session-error shape. */\n const rejectIdentity = (\n res: http.ServerResponse,\n resolution: Extract<McpIdentityResolution, { ok: false }>,\n ): void => {\n res.writeHead(resolution.error.status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: resolution.error.message, code: resolution.error.code }));\n };\n\n /**\n * L2: report each identity value that fell back to the raw value (H1),\n * naming its source (header or query) and the value itself, and any query\n * name that did not decode. `fellBack` says only that some value of the\n * field fell back; the header is checked on its own, and the query by the\n * same resolver given the query alone, so the rules are the resolver's.\n */\n const reportFallback = (\n sessionId: string,\n req: http.IncomingMessage,\n resolution: Extract<McpIdentityResolution, { ok: true }>,\n ): void => {\n if (!state.hasFakes()) return;\n const { fellBack } = resolution;\n if (fellBack.testId || fellBack.context) {\n const queryOnly = resolveMcpIdentity({ headers: {}, url: req.url });\n for (const field of [\"testId\", \"context\"] as const) {\n if (!fellBack[field]) continue;\n const header = req.headers[IDENTITY_HEADERS[field]];\n const headerSent = typeof header === \"string\" && header !== \"\";\n if (headerSent && decodeMcpHeaderValue(header).fellBack) {\n state.onDecodeFallback(sessionId, {\n kind: \"value\",\n field,\n source: \"header\",\n raw: header,\n used: true,\n });\n }\n const fromQuery =\n queryOnly.ok && queryOnly.fellBack[field] ? queryOnly.identity[field] : null;\n if (fromQuery !== null) {\n state.onDecodeFallback(sessionId, {\n kind: \"value\",\n field,\n source: \"query\",\n raw: fromQuery,\n used: !headerSent,\n });\n }\n }\n }\n if (resolution.undecodedQueryNames) {\n state.onDecodeFallback(sessionId, { kind: \"names\", count: resolution.undecodedQueryNames });\n }\n };\n\n /**\n * Handle one MCP request. `mount` is the path the mount is served at (I9;\n * `/` for a standalone server). Resolves with what the request resolved,\n * for the mount's journal entry.\n */\n return async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n mount = \"/\",\n record: MCPRequestRecord = newMCPRequestRecord(),\n ): Promise<MCPRequestRecord> => {\n /** T1: recorded `atMs` / `durationMs` are measured from here. */\n const arrivedAt = performance.now();\n // DELETE handler: session teardown\n if (req.method === \"DELETE\") {\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n if (!sessionId) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing mcp-session-id header\" }));\n return record;\n }\n if (!state.sessions.has(sessionId)) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Session not found\" }));\n return record;\n }\n // Journal only: identity inputs are not checked on a session teardown.\n const resolution = resolveMcpIdentity(req, state.sessions.get(sessionId));\n if (resolution.ok) record.identity = resolution.identity;\n state.sessions.delete(sessionId);\n logLevels.delete(sessionId);\n state.onSessionClosed(sessionId);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n return record;\n }\n\n // Identity inputs (I1-I3, H4, H5): on every POST, a bad undeclared\n // override or a repeated field is rejected before the request is served.\n const headerSessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n const knownSession = headerSessionId ? state.sessions.get(headerSessionId) : undefined;\n\n // Parse the body to determine method for session validation\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n const resolution = resolveMcpIdentity(req, knownSession);\n if (!resolution.ok) {\n rejectIdentity(res, resolution);\n return record;\n }\n record.identity = resolution.identity;\n if (headerSessionId && knownSession) reportFallback(headerSessionId, req, resolution);\n // Let the dispatcher handle parse errors\n await dispatcher(req, res, body);\n return record;\n }\n\n const method =\n typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n ? (parsed as { method: unknown }).method\n : undefined;\n\n // Handle initialize directly to control response headers\n if (method === \"initialize\") {\n const id =\n typeof parsed === \"object\" && parsed !== null && \"id\" in parsed\n ? (parsed as { id: unknown }).id\n : null;\n\n // I3: only the values the initialize request itself supplies are bound.\n const resolution = resolveMcpIdentity(req);\n if (!resolution.ok) {\n rejectIdentity(res, resolution);\n return record;\n }\n record.identity = resolution.identity;\n const { identity, supplied } = resolution;\n\n const sessionId = randomUUID();\n state.sessions.set(sessionId, {\n id: sessionId,\n initialized: false,\n createdAt: Date.now(),\n ...(supplied.testId && identity.testId !== null ? { testId: identity.testId } : {}),\n ...(supplied.context && identity.context !== null ? { context: identity.context } : {}),\n ...(supplied.undeclared && identity.undeclared !== null\n ? { undeclared: identity.undeclared }\n : {}),\n });\n reportFallback(sessionId, req, resolution);\n\n const response = {\n jsonrpc: \"2.0\",\n id,\n result: {\n protocolVersion: \"2025-03-26\",\n capabilities: capabilities(),\n serverInfo: state.serverInfo,\n },\n };\n\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Mcp-Session-Id\": sessionId,\n });\n res.end(JSON.stringify(response));\n return record;\n }\n\n const resolution = resolveMcpIdentity(req, knownSession);\n if (!resolution.ok) {\n rejectIdentity(res, resolution);\n return record;\n }\n record.identity = resolution.identity;\n\n // Session validation for all other methods\n const sessionId = headerSessionId;\n if (!sessionId) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing mcp-session-id header\" }));\n return record;\n }\n if (!state.sessions.has(sessionId)) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Session not found\" }));\n return record;\n }\n reportFallback(sessionId, req, resolution);\n\n // Enforce initialization: only allow notifications/initialized through\n // before the session is fully initialized\n const session = state.sessions.get(sessionId)!;\n if (!session.initialized && method !== \"notifications/initialized\") {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify(\n jsonRpcError(\n typeof parsed === \"object\" && parsed !== null && \"id\" in parsed\n ? ((parsed as { id: unknown }).id as string | number)\n : null,\n -32002,\n \"Session not initialized\",\n ),\n ),\n );\n return record;\n }\n\n // Delegate to the JSON-RPC dispatcher for all other methods\n // The dispatcher invokes `tools/call` once per message that is an object\n // with `jsonrpc: \"2.0\"` and that method, in order (see B2 below).\n const notifications = (Array.isArray(parsed) ? parsed : [parsed])\n .filter((m): m is Record<string, unknown> => isToolsCall(m) && m.jsonrpc === \"2.0\")\n .map((m) => !(\"id\" in m));\n const ctx: FakeRequestContext = {\n identity: resolution.identity,\n mount,\n notifications,\n calls: [],\n };\n contexts.set(req, ctx);\n // MR15 / T1: a single tools/call is claimed here, before the dispatcher,\n // because an answer with recorded timing or notifications is written by\n // `writeFakeToolAnswer` (SSE or delayed JSON), which the dispatcher's\n // JSON-only writer cannot do. Any other outcome is handed to the method\n // through `ctx.precomputed`, so the claim runs once.\n const single = singleToolsCall(parsed);\n if (single) {\n const outcome: CallOutcome = { mcpFake: null, error: null };\n ctx.calls.push(outcome);\n const fake = callFake(ctx, outcome, single.name, single.args, single.id);\n const replay =\n fake && outcome.mcpFake?.outcome === \"answered\" && outcome.mcpFake.id !== null\n ? state.fakes.replayOf(outcome.mcpFake.id)\n : null;\n if (\n fake &&\n \"result\" in fake &&\n replay &&\n (replay.notifications.length > 0 ||\n (replay.timing === \"recorded\" && replay.durationMs !== undefined))\n ) {\n await writeFakeToolAnswer(\n req,\n res,\n single.id,\n fake.result,\n replay,\n single.params,\n state.replaySpeed(),\n logLevels.get(sessionId) ?? null,\n arrivedAt,\n );\n record.messages = [\n { body: single.message, mcpFake: outcome.mcpFake, error: outcome.error },\n ];\n return record;\n }\n ctx.precomputed = fake ? { kind: \"reply\", value: fake } : { kind: \"none\" };\n }\n await dispatcher(req, res, body);\n // B2: each tools/call message is journaled with its body and its own\n // outcome. The dispatcher invokes `tools/call` once per message that is\n // an object with `jsonrpc: \"2.0\"` and that method, in order, so the n-th\n // such message takes the n-th outcome.\n let next = 0;\n const messageOf = (message: unknown): MCPMessageRecord => {\n if (!isToolsCall(message)) return { body: null, mcpFake: null, error: null };\n const outcome = message.jsonrpc === \"2.0\" ? ctx.calls[next++] : undefined;\n return { body: message, mcpFake: outcome?.mcpFake ?? null, error: outcome?.error ?? null };\n };\n record.messages = Array.isArray(parsed) ? parsed.map(messageOf) : [messageOf(parsed)];\n return record;\n };\n}\n\n/**\n * A single JSON-RPC `tools/call` request (not a batch, not a notification)\n * with a string or number id and a string tool name, else `null`. Only such\n * a message is claimed by the outer handler (MR15).\n */\nfunction singleToolsCall(parsed: unknown): {\n message: Record<string, unknown>;\n id: string | number;\n name: string;\n args: unknown;\n params: unknown;\n} | null {\n if (!isToolsCall(parsed) || parsed.jsonrpc !== \"2.0\" || !(\"id\" in parsed)) return null;\n const id = parsed.id;\n if (typeof id !== \"string\" && typeof id !== \"number\") return null;\n const params = parsed.params;\n if (typeof params !== \"object\" || params === null) return null;\n const name: unknown = (params as { name?: unknown }).name;\n if (typeof name !== \"string\" || name === \"\") return null;\n return { message: parsed, id, name, args: (params as { arguments?: unknown }).arguments, params };\n}\n\n/** Resolve after `ms` (no wait when `ms <= 0`). */\nfunction sleep(ms: number): Promise<void> {\n return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();\n}\n\n/** The client's `_meta.progressToken` of a request's params, else `undefined`. */\nfunction progressTokenOf(params: unknown): string | number | undefined {\n if (typeof params !== \"object\" || params === null) return undefined;\n const meta: unknown = (params as { _meta?: unknown })._meta;\n if (typeof meta !== \"object\" || meta === null) return undefined;\n const token: unknown = (meta as { progressToken?: unknown }).progressToken;\n return typeof token === \"string\" || typeof token === \"number\" ? token : undefined;\n}\n\n/**\n * FA3 gate of one recorded notification for this request: the params to send,\n * or `null` when it does not replay. `notifications/progress` replays only\n * when the request carries a progress token, which replaces the recorded one;\n * `notifications/message` only when the session set a log level (C7) and the\n * line is at or above it. No other method replays.\n */\nfunction replayedParams(\n notification: McpFakeNotification,\n progressToken: string | number | undefined,\n logLevel: string | null,\n): Record<string, unknown> | null {\n if (notification.method === \"notifications/progress\") {\n return progressToken === undefined ? null : { ...notification.params, progressToken };\n }\n if (notification.method === \"notifications/message\") {\n const min = logLevelRank(logLevel);\n const rank = logLevelRank(notification.params.level);\n return min !== -1 && rank >= min ? { ...notification.params } : null;\n }\n return null;\n}\n\n/**\n * Write a fake's tools/call answer. SSE iff the entry has a notification whose\n * FA3 gate passes AND Accept lists text/event-stream; else JSON. Timing per T1\n * (value / speed) unless the block's timing is \"immediate\". `logLevel` is the\n * session's `logging/setLevel` level (C7), `arrivedAt` the `performance.now()`\n * of the request's arrival, which `atMs` and `durationMs` are measured from.\n */\nexport async function writeFakeToolAnswer(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n id: string | number | null,\n result: unknown,\n replay: ReturnType<McpFakeStore[\"replayOf\"]>,\n params: unknown,\n speed: number,\n logLevel: string | null = null,\n arrivedAt: number = performance.now(),\n): Promise<void> {\n const timed = replay !== null && replay.timing === \"recorded\";\n const waitUntil = (atMs: number): Promise<void> =>\n timed ? sleep(atMs / speed - (performance.now() - arrivedAt)) : Promise.resolve();\n const token = progressTokenOf(params);\n const notifications = [...(replay?.notifications ?? [])]\n .sort((a, b) => a.atMs - b.atMs)\n .flatMap((n) => {\n const sent = replayedParams(n, token, logLevel);\n return sent ? [{ atMs: n.atMs, method: n.method, params: sent }] : [];\n });\n const accept = req.headers.accept ?? \"\";\n const answer = { jsonrpc: \"2.0\" as const, id, result };\n if (notifications.length > 0 && accept.includes(\"text/event-stream\")) {\n res.writeHead(200, { \"Content-Type\": \"text/event-stream\", \"Cache-Control\": \"no-cache\" });\n res.flushHeaders();\n for (const n of notifications) {\n await waitUntil(n.atMs);\n if (res.destroyed) return;\n res.write(\n `event: message\\ndata: ${JSON.stringify({ jsonrpc: \"2.0\", method: n.method, params: n.params })}\\n\\n`,\n );\n }\n if (replay?.durationMs !== undefined) await waitUntil(replay.durationMs);\n if (res.destroyed) return;\n res.end(`event: message\\ndata: ${JSON.stringify(answer)}\\n\\n`);\n return;\n }\n if (replay?.durationMs !== undefined) await waitUntil(replay.durationMs);\n if (res.destroyed) return;\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(answer));\n}\n\n/** The identity header of each field whose value can fall back (H1). */\nconst IDENTITY_HEADERS: Record<McpDecodeField, string> = {\n testId: \"x-test-id\",\n context: \"x-aimock-context\",\n};\n\n/** A JSON-RPC message object whose method is `tools/call`. */\nfunction isToolsCall(message: unknown): message is Record<string, unknown> {\n return (\n typeof message === \"object\" &&\n message !== null &&\n !Array.isArray(message) &&\n \"method\" in message &&\n message.method === \"tools/call\"\n );\n}\n"],"mappings":"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"}
|