@copilotkit/aimock 1.44.0 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +31 -0
  4. package/README.md +6 -0
  5. package/dist/agui-types.d.ts.map +1 -1
  6. package/dist/aimock-cli.cjs +31 -5
  7. package/dist/aimock-cli.cjs.map +1 -1
  8. package/dist/aimock-cli.d.cts.map +1 -1
  9. package/dist/aimock-cli.d.ts.map +1 -1
  10. package/dist/aimock-cli.js +31 -5
  11. package/dist/aimock-cli.js.map +1 -1
  12. package/dist/bedrock-converse.cjs +312 -39
  13. package/dist/bedrock-converse.cjs.map +1 -1
  14. package/dist/bedrock-converse.d.cts.map +1 -1
  15. package/dist/bedrock-converse.d.ts.map +1 -1
  16. package/dist/bedrock-converse.js +313 -40
  17. package/dist/bedrock-converse.js.map +1 -1
  18. package/dist/bedrock.cjs +195 -42
  19. package/dist/bedrock.cjs.map +1 -1
  20. package/dist/bedrock.d.cts.map +1 -1
  21. package/dist/bedrock.d.ts.map +1 -1
  22. package/dist/bedrock.js +196 -43
  23. package/dist/bedrock.js.map +1 -1
  24. package/dist/chaos.cjs +1 -0
  25. package/dist/chaos.js +1 -1
  26. package/dist/cli.cjs +152 -5
  27. package/dist/cli.cjs.map +1 -1
  28. package/dist/cli.js +155 -8
  29. package/dist/cli.js.map +1 -1
  30. package/dist/cohere.cjs +132 -71
  31. package/dist/cohere.cjs.map +1 -1
  32. package/dist/cohere.d.cts.map +1 -1
  33. package/dist/cohere.d.ts.map +1 -1
  34. package/dist/cohere.js +133 -72
  35. package/dist/cohere.js.map +1 -1
  36. package/dist/config-loader.cjs +62 -4
  37. package/dist/config-loader.cjs.map +1 -1
  38. package/dist/config-loader.d.cts +37 -4
  39. package/dist/config-loader.d.cts.map +1 -1
  40. package/dist/config-loader.d.ts +37 -4
  41. package/dist/config-loader.d.ts.map +1 -1
  42. package/dist/config-loader.js +62 -4
  43. package/dist/config-loader.js.map +1 -1
  44. package/dist/fakes-plugin.cjs +131 -0
  45. package/dist/fakes-plugin.cjs.map +1 -0
  46. package/dist/fakes-plugin.d.cts +23 -0
  47. package/dist/fakes-plugin.d.cts.map +1 -0
  48. package/dist/fakes-plugin.d.ts +23 -0
  49. package/dist/fakes-plugin.d.ts.map +1 -0
  50. package/dist/fakes-plugin.js +127 -0
  51. package/dist/fakes-plugin.js.map +1 -0
  52. package/dist/fixture-loader-services.cjs +1 -1
  53. package/dist/fixture-loader-services.cjs.map +1 -1
  54. package/dist/fixture-loader-services.js +1 -1
  55. package/dist/fixture-loader-services.js.map +1 -1
  56. package/dist/fixture-loader.cjs +389 -27
  57. package/dist/fixture-loader.cjs.map +1 -1
  58. package/dist/fixture-loader.d.cts +47 -3
  59. package/dist/fixture-loader.d.cts.map +1 -1
  60. package/dist/fixture-loader.d.ts +47 -3
  61. package/dist/fixture-loader.d.ts.map +1 -1
  62. package/dist/fixture-loader.js +384 -29
  63. package/dist/fixture-loader.js.map +1 -1
  64. package/dist/gemini-interactions.cjs +250 -52
  65. package/dist/gemini-interactions.cjs.map +1 -1
  66. package/dist/gemini-interactions.d.cts.map +1 -1
  67. package/dist/gemini-interactions.d.ts.map +1 -1
  68. package/dist/gemini-interactions.js +251 -53
  69. package/dist/gemini-interactions.js.map +1 -1
  70. package/dist/gemini.cjs +226 -37
  71. package/dist/gemini.cjs.map +1 -1
  72. package/dist/gemini.d.cts.map +1 -1
  73. package/dist/gemini.d.ts.map +1 -1
  74. package/dist/gemini.js +227 -38
  75. package/dist/gemini.js.map +1 -1
  76. package/dist/helpers.cjs +881 -7
  77. package/dist/helpers.cjs.map +1 -1
  78. package/dist/helpers.d.cts +85 -4
  79. package/dist/helpers.d.cts.map +1 -1
  80. package/dist/helpers.d.ts +85 -4
  81. package/dist/helpers.d.ts.map +1 -1
  82. package/dist/helpers.js +839 -8
  83. package/dist/helpers.js.map +1 -1
  84. package/dist/index.cjs +9 -0
  85. package/dist/index.d.cts +7 -5
  86. package/dist/index.d.ts +7 -5
  87. package/dist/index.js +4 -3
  88. package/dist/jest.cjs +31 -7
  89. package/dist/jest.cjs.map +1 -1
  90. package/dist/jest.d.cts +43 -3
  91. package/dist/jest.d.cts.map +1 -1
  92. package/dist/jest.d.ts +43 -3
  93. package/dist/jest.d.ts.map +1 -1
  94. package/dist/jest.js +33 -9
  95. package/dist/jest.js.map +1 -1
  96. package/dist/journal.cjs +135 -13
  97. package/dist/journal.cjs.map +1 -1
  98. package/dist/journal.d.cts +38 -4
  99. package/dist/journal.d.cts.map +1 -1
  100. package/dist/journal.d.ts +38 -4
  101. package/dist/journal.d.ts.map +1 -1
  102. package/dist/journal.js +135 -13
  103. package/dist/journal.js.map +1 -1
  104. package/dist/live-sanitize.cjs +4 -32
  105. package/dist/live-sanitize.cjs.map +1 -1
  106. package/dist/live-sanitize.js +4 -32
  107. package/dist/live-sanitize.js.map +1 -1
  108. package/dist/llmock.cjs +123 -30
  109. package/dist/llmock.cjs.map +1 -1
  110. package/dist/llmock.d.cts +16 -1
  111. package/dist/llmock.d.cts.map +1 -1
  112. package/dist/llmock.d.ts +16 -1
  113. package/dist/llmock.d.ts.map +1 -1
  114. package/dist/llmock.js +125 -32
  115. package/dist/llmock.js.map +1 -1
  116. package/dist/mcp-fakes-mount.cjs +1 -0
  117. package/dist/mcp-fakes-mount.cjs.map +1 -1
  118. package/dist/mcp-fakes-mount.js +1 -0
  119. package/dist/mcp-fakes-mount.js.map +1 -1
  120. package/dist/mcp-fakes-report.cjs +111 -0
  121. package/dist/mcp-fakes-report.cjs.map +1 -0
  122. package/dist/mcp-fakes-report.d.cts +40 -0
  123. package/dist/mcp-fakes-report.d.cts.map +1 -0
  124. package/dist/mcp-fakes-report.d.ts +40 -0
  125. package/dist/mcp-fakes-report.d.ts.map +1 -0
  126. package/dist/mcp-fakes-report.js +107 -0
  127. package/dist/mcp-fakes-report.js.map +1 -0
  128. package/dist/mcp-fakes.cjs +331 -8
  129. package/dist/mcp-fakes.cjs.map +1 -1
  130. package/dist/mcp-fakes.d.cts +406 -3
  131. package/dist/mcp-fakes.d.cts.map +1 -1
  132. package/dist/mcp-fakes.d.ts +406 -3
  133. package/dist/mcp-fakes.d.ts.map +1 -1
  134. package/dist/mcp-fakes.js +331 -8
  135. package/dist/mcp-fakes.js.map +1 -1
  136. package/dist/mcp-handler.cjs +209 -16
  137. package/dist/mcp-handler.cjs.map +1 -1
  138. package/dist/mcp-handler.d.cts +49 -0
  139. package/dist/mcp-handler.d.cts.map +1 -0
  140. package/dist/mcp-handler.d.ts +49 -0
  141. package/dist/mcp-handler.d.ts.map +1 -0
  142. package/dist/mcp-handler.js +207 -17
  143. package/dist/mcp-handler.js.map +1 -1
  144. package/dist/mcp-mock.cjs +112 -1
  145. package/dist/mcp-mock.cjs.map +1 -1
  146. package/dist/mcp-mock.d.cts +33 -2
  147. package/dist/mcp-mock.d.cts.map +1 -1
  148. package/dist/mcp-mock.d.ts +33 -2
  149. package/dist/mcp-mock.d.ts.map +1 -1
  150. package/dist/mcp-mock.js +112 -1
  151. package/dist/mcp-mock.js.map +1 -1
  152. package/dist/mcp-recorder.cjs +1224 -0
  153. package/dist/mcp-recorder.cjs.map +1 -0
  154. package/dist/mcp-recorder.d.cts +108 -0
  155. package/dist/mcp-recorder.d.cts.map +1 -0
  156. package/dist/mcp-recorder.d.ts +108 -0
  157. package/dist/mcp-recorder.d.ts.map +1 -0
  158. package/dist/mcp-recorder.js +1215 -0
  159. package/dist/mcp-recorder.js.map +1 -0
  160. package/dist/messages.cjs +362 -56
  161. package/dist/messages.cjs.map +1 -1
  162. package/dist/messages.d.cts.map +1 -1
  163. package/dist/messages.d.ts.map +1 -1
  164. package/dist/messages.js +363 -58
  165. package/dist/messages.js.map +1 -1
  166. package/dist/misbehavior.cjs +1523 -0
  167. package/dist/misbehavior.cjs.map +1 -0
  168. package/dist/misbehavior.d.cts +43 -0
  169. package/dist/misbehavior.d.cts.map +1 -0
  170. package/dist/misbehavior.d.ts +43 -0
  171. package/dist/misbehavior.d.ts.map +1 -0
  172. package/dist/misbehavior.js +1511 -0
  173. package/dist/misbehavior.js.map +1 -0
  174. package/dist/ollama.cjs +310 -112
  175. package/dist/ollama.cjs.map +1 -1
  176. package/dist/ollama.d.cts.map +1 -1
  177. package/dist/ollama.d.ts.map +1 -1
  178. package/dist/ollama.js +311 -113
  179. package/dist/ollama.js.map +1 -1
  180. package/dist/record-sanitize.cjs +231 -0
  181. package/dist/record-sanitize.cjs.map +1 -0
  182. package/dist/record-sanitize.js +224 -0
  183. package/dist/record-sanitize.js.map +1 -0
  184. package/dist/recorder.cjs +106 -5
  185. package/dist/recorder.cjs.map +1 -1
  186. package/dist/recorder.d.cts +2 -1
  187. package/dist/recorder.d.cts.map +1 -1
  188. package/dist/recorder.d.ts +2 -1
  189. package/dist/recorder.d.ts.map +1 -1
  190. package/dist/recorder.js +107 -7
  191. package/dist/recorder.js.map +1 -1
  192. package/dist/responses.cjs +667 -171
  193. package/dist/responses.cjs.map +1 -1
  194. package/dist/responses.d.cts +5 -13
  195. package/dist/responses.d.cts.map +1 -1
  196. package/dist/responses.d.ts +5 -13
  197. package/dist/responses.d.ts.map +1 -1
  198. package/dist/responses.js +665 -174
  199. package/dist/responses.js.map +1 -1
  200. package/dist/router.cjs +24 -2
  201. package/dist/router.cjs.map +1 -1
  202. package/dist/router.d.cts.map +1 -1
  203. package/dist/router.d.ts.map +1 -1
  204. package/dist/router.js +25 -4
  205. package/dist/router.js.map +1 -1
  206. package/dist/server.cjs +572 -76
  207. package/dist/server.cjs.map +1 -1
  208. package/dist/server.d.cts.map +1 -1
  209. package/dist/server.d.ts.map +1 -1
  210. package/dist/server.js +574 -78
  211. package/dist/server.js.map +1 -1
  212. package/dist/stream-collapse.cjs +236 -34
  213. package/dist/stream-collapse.cjs.map +1 -1
  214. package/dist/stream-collapse.d.cts +39 -4
  215. package/dist/stream-collapse.d.cts.map +1 -1
  216. package/dist/stream-collapse.d.ts +39 -4
  217. package/dist/stream-collapse.d.ts.map +1 -1
  218. package/dist/stream-collapse.js +235 -35
  219. package/dist/stream-collapse.js.map +1 -1
  220. package/dist/types.cjs.map +1 -1
  221. package/dist/types.d.cts +315 -4
  222. package/dist/types.d.cts.map +1 -1
  223. package/dist/types.d.ts +315 -4
  224. package/dist/types.d.ts.map +1 -1
  225. package/dist/types.js.map +1 -1
  226. package/dist/validate-cli.cjs +36 -4
  227. package/dist/validate-cli.cjs.map +1 -1
  228. package/dist/validate-cli.js +37 -5
  229. package/dist/validate-cli.js.map +1 -1
  230. package/dist/vector-types.d.ts.map +1 -1
  231. package/dist/version.cjs +45 -0
  232. package/dist/version.cjs.map +1 -0
  233. package/dist/version.js +44 -0
  234. package/dist/version.js.map +1 -0
  235. package/dist/vitest.cjs +66 -10
  236. package/dist/vitest.cjs.map +1 -1
  237. package/dist/vitest.d.cts +35 -4
  238. package/dist/vitest.d.cts.map +1 -1
  239. package/dist/vitest.d.ts +35 -4
  240. package/dist/vitest.d.ts.map +1 -1
  241. package/dist/vitest.js +67 -11
  242. package/dist/vitest.js.map +1 -1
  243. package/dist/watcher.cjs +6 -2
  244. package/dist/watcher.cjs.map +1 -1
  245. package/dist/watcher.js +6 -2
  246. package/dist/watcher.js.map +1 -1
  247. package/dist/ws-gemini-live.cjs +210 -101
  248. package/dist/ws-gemini-live.cjs.map +1 -1
  249. package/dist/ws-gemini-live.d.cts +9 -3
  250. package/dist/ws-gemini-live.d.cts.map +1 -1
  251. package/dist/ws-gemini-live.d.ts +9 -3
  252. package/dist/ws-gemini-live.d.ts.map +1 -1
  253. package/dist/ws-gemini-live.js +211 -102
  254. package/dist/ws-gemini-live.js.map +1 -1
  255. package/dist/ws-realtime.cjs +175 -62
  256. package/dist/ws-realtime.cjs.map +1 -1
  257. package/dist/ws-realtime.d.cts +8 -3
  258. package/dist/ws-realtime.d.cts.map +1 -1
  259. package/dist/ws-realtime.d.ts +8 -3
  260. package/dist/ws-realtime.d.ts.map +1 -1
  261. package/dist/ws-realtime.js +176 -63
  262. package/dist/ws-realtime.js.map +1 -1
  263. package/dist/ws-responses.cjs +109 -16
  264. package/dist/ws-responses.cjs.map +1 -1
  265. package/dist/ws-responses.d.cts +8 -12
  266. package/dist/ws-responses.d.cts.map +1 -1
  267. package/dist/ws-responses.d.ts +8 -12
  268. package/dist/ws-responses.d.ts.map +1 -1
  269. package/dist/ws-responses.js +111 -18
  270. package/dist/ws-responses.js.map +1 -1
  271. package/package.json +10 -2
  272. package/skills/write-fixtures/SKILL.md +179 -21
@@ -21,6 +21,27 @@ function newMCPRequestRecord() {
21
21
  messages: []
22
22
  };
23
23
  }
24
+ /**
25
+ * C8: the `onToolCall` handlers that answer a config `mcp.tools[].result`.
26
+ * `tools/call` reports such an answer as `answeredBy: "config"`. Internal:
27
+ * not exported from the package.
28
+ */
29
+ const configToolHandlers = /* @__PURE__ */ new WeakSet();
30
+ /** C7: the syslog levels of `logging/setLevel`, lowest first. */
31
+ const LOG_LEVELS = [
32
+ "debug",
33
+ "info",
34
+ "notice",
35
+ "warning",
36
+ "error",
37
+ "critical",
38
+ "alert",
39
+ "emergency"
40
+ ];
41
+ /** C7: the rank of a syslog level name, or -1 when it is not one. */
42
+ function logLevelRank(level) {
43
+ return typeof level === "string" ? LOG_LEVELS.indexOf(level) : -1;
44
+ }
24
45
  function jsonRpcResult(id, result) {
25
46
  return {
26
47
  jsonrpc: "2.0",
@@ -131,6 +152,19 @@ function fakeFailure(claim, tool, ctx, declaredTools) {
131
152
  function createMCPRequestHandler(state) {
132
153
  /** Per-request fake context, set by the outer handler for the dispatcher's methods. */
133
154
  const contexts = /* @__PURE__ */ new WeakMap();
155
+ /** C7: each session's `logging/setLevel` level; dropped on DELETE. */
156
+ const logLevels = /* @__PURE__ */ new Map();
157
+ /** The initialize capabilities: `logging` only on a mount with recorded logs (C7). */
158
+ const capabilities = () => state.fakes.hasRecordedLogs() ? {
159
+ tools: {},
160
+ resources: {},
161
+ prompts: {},
162
+ logging: {}
163
+ } : {
164
+ tools: {},
165
+ resources: {},
166
+ prompts: {}
167
+ };
134
168
  /**
135
169
  * Spec 10 steps 1-2 for `tools/call`: a fake answer or fake failure, or
136
170
  * `null` to fall through to today's behavior (step 3).
@@ -139,7 +173,8 @@ function createMCPRequestHandler(state) {
139
173
  const event = {
140
174
  identity: ctx.identity,
141
175
  tool,
142
- mount: ctx.mount
176
+ mount: ctx.mount,
177
+ args
143
178
  };
144
179
  let claim;
145
180
  try {
@@ -202,11 +237,7 @@ function createMCPRequestHandler(state) {
202
237
  initialize: async (_params, id) => {
203
238
  return jsonRpcResult(id, {
204
239
  protocolVersion: "2025-03-26",
205
- capabilities: {
206
- tools: {},
207
- resources: {},
208
- prompts: {}
209
- },
240
+ capabilities: capabilities(),
210
241
  serverInfo: state.serverInfo
211
242
  });
212
243
  },
@@ -219,10 +250,32 @@ function createMCPRequestHandler(state) {
219
250
  ping: async (_params, id) => {
220
251
  return jsonRpcResult(id, {});
221
252
  },
253
+ "logging/setLevel": async (params, id, req) => {
254
+ if (!state.fakes.hasRecordedLogs()) return jsonRpcError(id, -32601, "Method not found");
255
+ const level = params ?? {};
256
+ if (logLevelRank(level.level) === -1) return jsonRpcError(id, -32602, "Invalid log level");
257
+ const sessionId = req.headers["mcp-session-id"];
258
+ if (typeof sessionId === "string") logLevels.set(sessionId, String(level.level));
259
+ return jsonRpcResult(id, {});
260
+ },
222
261
  "tools/list": async (_params, id, req) => {
223
262
  const tools = [];
224
263
  for (const { def } of state.tools.values()) tools.push(def);
225
264
  const ctx = contexts.get(req);
265
+ const recorded = ctx ? state.fakes.recordedList(ctx.identity) : null;
266
+ if (ctx && recorded) {
267
+ const listed = [...recorded];
268
+ const names = new Set(recorded.map((t) => t.name));
269
+ for (const def of tools) if (!names.has(def.name)) {
270
+ names.add(def.name);
271
+ listed.push(def);
272
+ }
273
+ for (const fake of state.fakes.listTools(ctx.identity)) if (!names.has(fake.name)) {
274
+ names.add(fake.name);
275
+ listed.push(fake);
276
+ }
277
+ return jsonRpcResult(id, { tools: listed });
278
+ }
226
279
  if (ctx) {
227
280
  const registered = new Set(tools.map((t) => t.name));
228
281
  for (const fake of state.fakes.listTools(ctx.identity)) if (!registered.has(fake.name)) tools.push(fake);
@@ -230,22 +283,35 @@ function createMCPRequestHandler(state) {
230
283
  return jsonRpcResult(id, { tools });
231
284
  },
232
285
  "tools/call": async (params, id, req) => {
233
- const outcome = {
286
+ const ctx = contexts.get(req);
287
+ const precomputed = ctx?.precomputed;
288
+ if (ctx) ctx.precomputed = void 0;
289
+ const isNotification = !precomputed && ctx?.notifications[ctx.calls.length] === true;
290
+ if (!precomputed) ctx?.calls.push({
291
+ mcpFake: null,
292
+ error: null
293
+ });
294
+ const outcome = ctx?.calls[ctx.calls.length - 1] ?? {
234
295
  mcpFake: null,
235
296
  error: null
236
297
  };
237
- const ctx = contexts.get(req);
238
- const isNotification = ctx?.notifications[ctx.calls.length] === true;
239
- ctx?.calls.push(outcome);
240
298
  const { name: rawName, arguments: args } = params ?? {};
241
299
  if (!rawName) return jsonRpcError(id, -32602, "Missing tool name");
242
300
  if (typeof rawName !== "string") return jsonRpcError(id, -32602, require_message_text.build(require_message_text.msg`Invalid tool name: expected a string, got ${require_message_text.fixed(Array.isArray(rawName) ? "array" : typeof rawName)}`));
243
301
  const name = rawName;
244
- if (ctx && !isNotification) {
302
+ if (precomputed?.kind === "reply") return precomputed.value;
303
+ if (ctx && !isNotification && !precomputed) {
245
304
  const fake = callFake(ctx, outcome, name, args, id);
246
305
  if (fake) return fake;
247
306
  }
248
307
  const entry = state.tools.get(name);
308
+ if (ctx && !isNotification) state.onUnfaked({
309
+ identity: ctx.identity,
310
+ tool: name,
311
+ mount: ctx.mount,
312
+ args,
313
+ answeredBy: !entry ? "unknown-tool" : entry.handler && configToolHandlers.has(entry.handler) ? "config" : entry.handler ? "handler" : "empty"
314
+ });
249
315
  if (!entry) return jsonRpcError(id, -32602, `Unknown tool: ${name}`);
250
316
  if (entry.handler) try {
251
317
  const result = await entry.handler(args);
@@ -360,6 +426,8 @@ function createMCPRequestHandler(state) {
360
426
  * for the mount's journal entry.
361
427
  */
362
428
  return async (req, res, body, mount = "/", record = newMCPRequestRecord()) => {
429
+ /** T1: recorded `atMs` / `durationMs` are measured from here. */
430
+ const arrivedAt = performance.now();
363
431
  if (req.method === "DELETE") {
364
432
  const sessionId = req.headers["mcp-session-id"];
365
433
  if (!sessionId) {
@@ -375,6 +443,7 @@ function createMCPRequestHandler(state) {
375
443
  const resolution = require_helpers.resolveMcpIdentity(req, state.sessions.get(sessionId));
376
444
  if (resolution.ok) record.identity = resolution.identity;
377
445
  state.sessions.delete(sessionId);
446
+ logLevels.delete(sessionId);
378
447
  state.onSessionClosed(sessionId);
379
448
  res.writeHead(200, { "Content-Type": "application/json" });
380
449
  res.end(JSON.stringify({ ok: true }));
@@ -421,11 +490,7 @@ function createMCPRequestHandler(state) {
421
490
  id,
422
491
  result: {
423
492
  protocolVersion: "2025-03-26",
424
- capabilities: {
425
- tools: {},
426
- resources: {},
427
- prompts: {}
428
- },
493
+ capabilities: capabilities(),
429
494
  serverInfo: state.serverInfo
430
495
  }
431
496
  };
@@ -467,6 +532,29 @@ function createMCPRequestHandler(state) {
467
532
  calls: []
468
533
  };
469
534
  contexts.set(req, ctx);
535
+ const single = singleToolsCall(parsed);
536
+ if (single) {
537
+ const outcome = {
538
+ mcpFake: null,
539
+ error: null
540
+ };
541
+ ctx.calls.push(outcome);
542
+ const fake = callFake(ctx, outcome, single.name, single.args, single.id);
543
+ const replay = fake && outcome.mcpFake?.outcome === "answered" && outcome.mcpFake.id !== null ? state.fakes.replayOf(outcome.mcpFake.id) : null;
544
+ if (fake && "result" in fake && replay && (replay.notifications.length > 0 || replay.timing === "recorded" && replay.durationMs !== void 0)) {
545
+ await writeFakeToolAnswer(req, res, single.id, fake.result, replay, single.params, state.replaySpeed(), logLevels.get(sessionId) ?? null, arrivedAt);
546
+ record.messages = [{
547
+ body: single.message,
548
+ mcpFake: outcome.mcpFake,
549
+ error: outcome.error
550
+ }];
551
+ return record;
552
+ }
553
+ ctx.precomputed = fake ? {
554
+ kind: "reply",
555
+ value: fake
556
+ } : { kind: "none" };
557
+ }
470
558
  await dispatcher(req, res, body);
471
559
  let next = 0;
472
560
  const messageOf = (message) => {
@@ -486,6 +574,108 @@ function createMCPRequestHandler(state) {
486
574
  return record;
487
575
  };
488
576
  }
577
+ /**
578
+ * A single JSON-RPC `tools/call` request (not a batch, not a notification)
579
+ * with a string or number id and a string tool name, else `null`. Only such
580
+ * a message is claimed by the outer handler (MR15).
581
+ */
582
+ function singleToolsCall(parsed) {
583
+ if (!isToolsCall(parsed) || parsed.jsonrpc !== "2.0" || !("id" in parsed)) return null;
584
+ const id = parsed.id;
585
+ if (typeof id !== "string" && typeof id !== "number") return null;
586
+ const params = parsed.params;
587
+ if (typeof params !== "object" || params === null) return null;
588
+ const name = params.name;
589
+ if (typeof name !== "string" || name === "") return null;
590
+ return {
591
+ message: parsed,
592
+ id,
593
+ name,
594
+ args: params.arguments,
595
+ params
596
+ };
597
+ }
598
+ /** Resolve after `ms` (no wait when `ms <= 0`). */
599
+ function sleep(ms) {
600
+ return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
601
+ }
602
+ /** The client's `_meta.progressToken` of a request's params, else `undefined`. */
603
+ function progressTokenOf(params) {
604
+ if (typeof params !== "object" || params === null) return void 0;
605
+ const meta = params._meta;
606
+ if (typeof meta !== "object" || meta === null) return void 0;
607
+ const token = meta.progressToken;
608
+ return typeof token === "string" || typeof token === "number" ? token : void 0;
609
+ }
610
+ /**
611
+ * FA3 gate of one recorded notification for this request: the params to send,
612
+ * or `null` when it does not replay. `notifications/progress` replays only
613
+ * when the request carries a progress token, which replaces the recorded one;
614
+ * `notifications/message` only when the session set a log level (C7) and the
615
+ * line is at or above it. No other method replays.
616
+ */
617
+ function replayedParams(notification, progressToken, logLevel) {
618
+ if (notification.method === "notifications/progress") return progressToken === void 0 ? null : {
619
+ ...notification.params,
620
+ progressToken
621
+ };
622
+ if (notification.method === "notifications/message") {
623
+ const min = logLevelRank(logLevel);
624
+ const rank = logLevelRank(notification.params.level);
625
+ return min !== -1 && rank >= min ? { ...notification.params } : null;
626
+ }
627
+ return null;
628
+ }
629
+ /**
630
+ * Write a fake's tools/call answer. SSE iff the entry has a notification whose
631
+ * FA3 gate passes AND Accept lists text/event-stream; else JSON. Timing per T1
632
+ * (value / speed) unless the block's timing is "immediate". `logLevel` is the
633
+ * session's `logging/setLevel` level (C7), `arrivedAt` the `performance.now()`
634
+ * of the request's arrival, which `atMs` and `durationMs` are measured from.
635
+ */
636
+ async function writeFakeToolAnswer(req, res, id, result, replay, params, speed, logLevel = null, arrivedAt = performance.now()) {
637
+ const timed = replay !== null && replay.timing === "recorded";
638
+ const waitUntil = (atMs) => timed ? sleep(atMs / speed - (performance.now() - arrivedAt)) : Promise.resolve();
639
+ const token = progressTokenOf(params);
640
+ const notifications = [...replay?.notifications ?? []].sort((a, b) => a.atMs - b.atMs).flatMap((n) => {
641
+ const sent = replayedParams(n, token, logLevel);
642
+ return sent ? [{
643
+ atMs: n.atMs,
644
+ method: n.method,
645
+ params: sent
646
+ }] : [];
647
+ });
648
+ const accept = req.headers.accept ?? "";
649
+ const answer = {
650
+ jsonrpc: "2.0",
651
+ id,
652
+ result
653
+ };
654
+ if (notifications.length > 0 && accept.includes("text/event-stream")) {
655
+ res.writeHead(200, {
656
+ "Content-Type": "text/event-stream",
657
+ "Cache-Control": "no-cache"
658
+ });
659
+ res.flushHeaders();
660
+ for (const n of notifications) {
661
+ await waitUntil(n.atMs);
662
+ if (res.destroyed) return;
663
+ res.write(`event: message\ndata: ${JSON.stringify({
664
+ jsonrpc: "2.0",
665
+ method: n.method,
666
+ params: n.params
667
+ })}\n\n`);
668
+ }
669
+ if (replay?.durationMs !== void 0) await waitUntil(replay.durationMs);
670
+ if (res.destroyed) return;
671
+ res.end(`event: message\ndata: ${JSON.stringify(answer)}\n\n`);
672
+ return;
673
+ }
674
+ if (replay?.durationMs !== void 0) await waitUntil(replay.durationMs);
675
+ if (res.destroyed) return;
676
+ res.writeHead(200, { "Content-Type": "application/json" });
677
+ res.end(JSON.stringify(answer));
678
+ }
489
679
  /** The identity header of each field whose value can fall back (H1). */
490
680
  const IDENTITY_HEADERS = {
491
681
  testId: "x-test-id",
@@ -497,7 +687,10 @@ function isToolsCall(message) {
497
687
  }
498
688
 
499
689
  //#endregion
690
+ exports.configToolHandlers = configToolHandlers;
500
691
  exports.createMCPRequestHandler = createMCPRequestHandler;
501
692
  exports.describeMcpIdentity = describeMcpIdentity;
693
+ exports.fakeFailure = fakeFailure;
502
694
  exports.newMCPRequestRecord = newMCPRequestRecord;
695
+ exports.writeFakeToolAnswer = writeFakeToolAnswer;
503
696
  //# sourceMappingURL=mcp-handler.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-handler.cjs","names":["quote","jsonText","plainText","build","MCP_FAKE_ERROR_CODES","createJsonRpcDispatcher","fixed","resolveMcpIdentity","decodeMcpHeaderValue"],"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.cjs","names":["quote","jsonText","plainText","build","MCP_FAKE_ERROR_CODES","createJsonRpcDispatcher","fixed","resolveMcpIdentity","decodeMcpHeaderValue"],"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 for (const { def } of state.tools.values()) {\n tools.push(def);\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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"}
@@ -0,0 +1,49 @@
1
+ import { McpFakeErrorCode, McpFakeIdentity, McpFakeOutcome } from "./types.cjs";
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.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-handler.d.cts","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"}