@copilotkit/aimock 1.43.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 (396) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +46 -0
  4. package/README.md +14 -2
  5. package/dist/agui-handler.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 +344 -43
  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 +345 -44
  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 +9 -5
  25. package/dist/chaos.cjs.map +1 -1
  26. package/dist/chaos.d.cts.map +1 -1
  27. package/dist/chaos.d.ts.map +1 -1
  28. package/dist/chaos.js +10 -7
  29. package/dist/chaos.js.map +1 -1
  30. package/dist/cli.cjs +238 -9
  31. package/dist/cli.cjs.map +1 -1
  32. package/dist/cli.js +242 -13
  33. package/dist/cli.js.map +1 -1
  34. package/dist/cohere.cjs +159 -72
  35. package/dist/cohere.cjs.map +1 -1
  36. package/dist/cohere.d.cts.map +1 -1
  37. package/dist/cohere.d.ts.map +1 -1
  38. package/dist/cohere.js +160 -73
  39. package/dist/cohere.js.map +1 -1
  40. package/dist/config-loader.cjs +63 -5
  41. package/dist/config-loader.cjs.map +1 -1
  42. package/dist/config-loader.d.cts +37 -4
  43. package/dist/config-loader.d.cts.map +1 -1
  44. package/dist/config-loader.d.ts +37 -4
  45. package/dist/config-loader.d.ts.map +1 -1
  46. package/dist/config-loader.js +63 -5
  47. package/dist/config-loader.js.map +1 -1
  48. package/dist/constants.cjs +8 -0
  49. package/dist/constants.cjs.map +1 -1
  50. package/dist/constants.d.cts +6 -2
  51. package/dist/constants.d.ts +6 -2
  52. package/dist/constants.js +8 -1
  53. package/dist/constants.js.map +1 -1
  54. package/dist/echo-text.cjs +65 -0
  55. package/dist/echo-text.cjs.map +1 -0
  56. package/dist/echo-text.d.cts +22 -0
  57. package/dist/echo-text.d.cts.map +1 -0
  58. package/dist/echo-text.d.ts +22 -0
  59. package/dist/echo-text.d.ts.map +1 -0
  60. package/dist/echo-text.js +65 -0
  61. package/dist/echo-text.js.map +1 -0
  62. package/dist/embeddings.cjs +18 -1
  63. package/dist/embeddings.cjs.map +1 -1
  64. package/dist/embeddings.d.cts +1 -1
  65. package/dist/embeddings.d.cts.map +1 -1
  66. package/dist/embeddings.d.ts +1 -1
  67. package/dist/embeddings.d.ts.map +1 -1
  68. package/dist/embeddings.js +18 -1
  69. package/dist/embeddings.js.map +1 -1
  70. package/dist/fakes-plugin.cjs +131 -0
  71. package/dist/fakes-plugin.cjs.map +1 -0
  72. package/dist/fakes-plugin.d.cts +23 -0
  73. package/dist/fakes-plugin.d.cts.map +1 -0
  74. package/dist/fakes-plugin.d.ts +23 -0
  75. package/dist/fakes-plugin.d.ts.map +1 -0
  76. package/dist/fakes-plugin.js +127 -0
  77. package/dist/fakes-plugin.js.map +1 -0
  78. package/dist/files.cjs +9 -11
  79. package/dist/files.cjs.map +1 -1
  80. package/dist/files.d.cts.map +1 -1
  81. package/dist/files.d.ts.map +1 -1
  82. package/dist/files.js +10 -13
  83. package/dist/files.js.map +1 -1
  84. package/dist/fine-tuning.cjs +1 -19
  85. package/dist/fine-tuning.cjs.map +1 -1
  86. package/dist/fine-tuning.d.cts.map +1 -1
  87. package/dist/fine-tuning.d.ts.map +1 -1
  88. package/dist/fine-tuning.js +2 -20
  89. package/dist/fine-tuning.js.map +1 -1
  90. package/dist/fixture-loader-services.cjs +116 -0
  91. package/dist/fixture-loader-services.cjs.map +1 -0
  92. package/dist/fixture-loader-services.d.cts +48 -0
  93. package/dist/fixture-loader-services.d.cts.map +1 -0
  94. package/dist/fixture-loader-services.d.ts +48 -0
  95. package/dist/fixture-loader-services.d.ts.map +1 -0
  96. package/dist/fixture-loader-services.js +114 -0
  97. package/dist/fixture-loader-services.js.map +1 -0
  98. package/dist/fixture-loader.cjs +552 -29
  99. package/dist/fixture-loader.cjs.map +1 -1
  100. package/dist/fixture-loader.d.cts +140 -2
  101. package/dist/fixture-loader.d.cts.map +1 -1
  102. package/dist/fixture-loader.d.ts +140 -2
  103. package/dist/fixture-loader.d.ts.map +1 -1
  104. package/dist/fixture-loader.js +542 -31
  105. package/dist/fixture-loader.js.map +1 -1
  106. package/dist/gemini-embeddings.cjs +42 -2
  107. package/dist/gemini-embeddings.cjs.map +1 -1
  108. package/dist/gemini-embeddings.js +43 -3
  109. package/dist/gemini-embeddings.js.map +1 -1
  110. package/dist/gemini-interactions.cjs +288 -52
  111. package/dist/gemini-interactions.cjs.map +1 -1
  112. package/dist/gemini-interactions.d.cts.map +1 -1
  113. package/dist/gemini-interactions.d.ts.map +1 -1
  114. package/dist/gemini-interactions.js +289 -53
  115. package/dist/gemini-interactions.js.map +1 -1
  116. package/dist/gemini.cjs +234 -38
  117. package/dist/gemini.cjs.map +1 -1
  118. package/dist/gemini.d.cts.map +1 -1
  119. package/dist/gemini.d.ts.map +1 -1
  120. package/dist/gemini.js +235 -39
  121. package/dist/gemini.js.map +1 -1
  122. package/dist/helpers.cjs +1157 -12
  123. package/dist/helpers.cjs.map +1 -1
  124. package/dist/helpers.d.cts +85 -4
  125. package/dist/helpers.d.cts.map +1 -1
  126. package/dist/helpers.d.ts +85 -4
  127. package/dist/helpers.d.ts.map +1 -1
  128. package/dist/helpers.js +1113 -14
  129. package/dist/helpers.js.map +1 -1
  130. package/dist/images.cjs +3 -2
  131. package/dist/images.cjs.map +1 -1
  132. package/dist/images.js +3 -2
  133. package/dist/images.js.map +1 -1
  134. package/dist/index.cjs +39 -1
  135. package/dist/index.d.cts +11 -4
  136. package/dist/index.d.ts +11 -4
  137. package/dist/index.js +11 -4
  138. package/dist/jest.cjs +43 -14
  139. package/dist/jest.cjs.map +1 -1
  140. package/dist/jest.d.cts +47 -5
  141. package/dist/jest.d.cts.map +1 -1
  142. package/dist/jest.d.ts +47 -5
  143. package/dist/jest.d.ts.map +1 -1
  144. package/dist/jest.js +45 -16
  145. package/dist/jest.js.map +1 -1
  146. package/dist/journal.cjs +155 -13
  147. package/dist/journal.cjs.map +1 -1
  148. package/dist/journal.d.cts +54 -4
  149. package/dist/journal.d.cts.map +1 -1
  150. package/dist/journal.d.ts +54 -4
  151. package/dist/journal.d.ts.map +1 -1
  152. package/dist/journal.js +155 -13
  153. package/dist/journal.js.map +1 -1
  154. package/dist/live-sanitize.cjs +4 -32
  155. package/dist/live-sanitize.cjs.map +1 -1
  156. package/dist/live-sanitize.js +4 -32
  157. package/dist/live-sanitize.js.map +1 -1
  158. package/dist/llmock.cjs +204 -39
  159. package/dist/llmock.cjs.map +1 -1
  160. package/dist/llmock.d.cts +51 -1
  161. package/dist/llmock.d.cts.map +1 -1
  162. package/dist/llmock.d.ts +51 -1
  163. package/dist/llmock.d.ts.map +1 -1
  164. package/dist/llmock.js +206 -41
  165. package/dist/llmock.js.map +1 -1
  166. package/dist/mcp-fakes-mount.cjs +217 -0
  167. package/dist/mcp-fakes-mount.cjs.map +1 -0
  168. package/dist/mcp-fakes-mount.js +211 -0
  169. package/dist/mcp-fakes-mount.js.map +1 -0
  170. package/dist/mcp-fakes-report.cjs +111 -0
  171. package/dist/mcp-fakes-report.cjs.map +1 -0
  172. package/dist/mcp-fakes-report.d.cts +40 -0
  173. package/dist/mcp-fakes-report.d.cts.map +1 -0
  174. package/dist/mcp-fakes-report.d.ts +40 -0
  175. package/dist/mcp-fakes-report.d.ts.map +1 -0
  176. package/dist/mcp-fakes-report.js +107 -0
  177. package/dist/mcp-fakes-report.js.map +1 -0
  178. package/dist/mcp-fakes.cjs +1942 -0
  179. package/dist/mcp-fakes.cjs.map +1 -0
  180. package/dist/mcp-fakes.d.cts +504 -0
  181. package/dist/mcp-fakes.d.cts.map +1 -0
  182. package/dist/mcp-fakes.d.ts +504 -0
  183. package/dist/mcp-fakes.d.ts.map +1 -0
  184. package/dist/mcp-fakes.js +1937 -0
  185. package/dist/mcp-fakes.js.map +1 -0
  186. package/dist/mcp-handler.cjs +534 -27
  187. package/dist/mcp-handler.cjs.map +1 -1
  188. package/dist/mcp-handler.d.cts +49 -0
  189. package/dist/mcp-handler.d.cts.map +1 -0
  190. package/dist/mcp-handler.d.ts +49 -0
  191. package/dist/mcp-handler.d.ts.map +1 -0
  192. package/dist/mcp-handler.js +530 -28
  193. package/dist/mcp-handler.js.map +1 -1
  194. package/dist/mcp-mock.cjs +326 -47
  195. package/dist/mcp-mock.cjs.map +1 -1
  196. package/dist/mcp-mock.d.cts +97 -1
  197. package/dist/mcp-mock.d.cts.map +1 -1
  198. package/dist/mcp-mock.d.ts +97 -1
  199. package/dist/mcp-mock.d.ts.map +1 -1
  200. package/dist/mcp-mock.js +327 -48
  201. package/dist/mcp-mock.js.map +1 -1
  202. package/dist/mcp-recorder.cjs +1224 -0
  203. package/dist/mcp-recorder.cjs.map +1 -0
  204. package/dist/mcp-recorder.d.cts +108 -0
  205. package/dist/mcp-recorder.d.cts.map +1 -0
  206. package/dist/mcp-recorder.d.ts +108 -0
  207. package/dist/mcp-recorder.d.ts.map +1 -0
  208. package/dist/mcp-recorder.js +1215 -0
  209. package/dist/mcp-recorder.js.map +1 -0
  210. package/dist/mcp-stub.cjs +5 -1
  211. package/dist/mcp-stub.d.cts +3 -1
  212. package/dist/mcp-stub.d.ts +3 -1
  213. package/dist/mcp-stub.js +3 -1
  214. package/dist/mcp-types.d.cts +6 -0
  215. package/dist/mcp-types.d.ts +6 -0
  216. package/dist/message-text.cjs +192 -0
  217. package/dist/message-text.cjs.map +1 -0
  218. package/dist/message-text.d.cts +31 -0
  219. package/dist/message-text.d.cts.map +1 -0
  220. package/dist/message-text.d.ts +31 -0
  221. package/dist/message-text.d.ts.map +1 -0
  222. package/dist/message-text.js +184 -0
  223. package/dist/message-text.js.map +1 -0
  224. package/dist/messages.cjs +400 -56
  225. package/dist/messages.cjs.map +1 -1
  226. package/dist/messages.d.cts.map +1 -1
  227. package/dist/messages.d.ts.map +1 -1
  228. package/dist/messages.js +401 -58
  229. package/dist/messages.js.map +1 -1
  230. package/dist/metrics.cjs +46 -66
  231. package/dist/metrics.cjs.map +1 -1
  232. package/dist/metrics.d.cts +14 -6
  233. package/dist/metrics.d.cts.map +1 -1
  234. package/dist/metrics.d.ts +14 -6
  235. package/dist/metrics.d.ts.map +1 -1
  236. package/dist/metrics.js +32 -40
  237. package/dist/metrics.js.map +1 -1
  238. package/dist/misbehavior.cjs +1523 -0
  239. package/dist/misbehavior.cjs.map +1 -0
  240. package/dist/misbehavior.d.cts +43 -0
  241. package/dist/misbehavior.d.cts.map +1 -0
  242. package/dist/misbehavior.d.ts +43 -0
  243. package/dist/misbehavior.d.ts.map +1 -0
  244. package/dist/misbehavior.js +1511 -0
  245. package/dist/misbehavior.js.map +1 -0
  246. package/dist/ollama.cjs +315 -113
  247. package/dist/ollama.cjs.map +1 -1
  248. package/dist/ollama.d.cts.map +1 -1
  249. package/dist/ollama.d.ts.map +1 -1
  250. package/dist/ollama.js +316 -114
  251. package/dist/ollama.js.map +1 -1
  252. package/dist/openapi.cjs +3405 -0
  253. package/dist/openapi.cjs.map +1 -0
  254. package/dist/openapi.d.cts +30 -0
  255. package/dist/openapi.d.cts.map +1 -0
  256. package/dist/openapi.d.ts +30 -0
  257. package/dist/openapi.d.ts.map +1 -0
  258. package/dist/openapi.js +3404 -0
  259. package/dist/openapi.js.map +1 -0
  260. package/dist/openrouter-video.cjs +4 -4
  261. package/dist/openrouter-video.cjs.map +1 -1
  262. package/dist/openrouter-video.d.cts.map +1 -1
  263. package/dist/openrouter-video.d.ts.map +1 -1
  264. package/dist/openrouter-video.js +4 -4
  265. package/dist/openrouter-video.js.map +1 -1
  266. package/dist/record-sanitize.cjs +231 -0
  267. package/dist/record-sanitize.cjs.map +1 -0
  268. package/dist/record-sanitize.js +224 -0
  269. package/dist/record-sanitize.js.map +1 -0
  270. package/dist/recorder.cjs +115 -6
  271. package/dist/recorder.cjs.map +1 -1
  272. package/dist/recorder.d.cts +2 -1
  273. package/dist/recorder.d.cts.map +1 -1
  274. package/dist/recorder.d.ts +2 -1
  275. package/dist/recorder.d.ts.map +1 -1
  276. package/dist/recorder.js +116 -8
  277. package/dist/recorder.js.map +1 -1
  278. package/dist/responses.cjs +701 -166
  279. package/dist/responses.cjs.map +1 -1
  280. package/dist/responses.d.cts +5 -13
  281. package/dist/responses.d.cts.map +1 -1
  282. package/dist/responses.d.ts +5 -13
  283. package/dist/responses.d.ts.map +1 -1
  284. package/dist/responses.js +699 -169
  285. package/dist/responses.js.map +1 -1
  286. package/dist/route-registry.cjs +951 -0
  287. package/dist/route-registry.cjs.map +1 -0
  288. package/dist/route-registry.d.cts +33 -0
  289. package/dist/route-registry.d.cts.map +1 -0
  290. package/dist/route-registry.d.ts +33 -0
  291. package/dist/route-registry.d.ts.map +1 -0
  292. package/dist/route-registry.js +866 -0
  293. package/dist/route-registry.js.map +1 -0
  294. package/dist/router.cjs +24 -2
  295. package/dist/router.cjs.map +1 -1
  296. package/dist/router.d.cts.map +1 -1
  297. package/dist/router.d.ts.map +1 -1
  298. package/dist/router.js +25 -4
  299. package/dist/router.js.map +1 -1
  300. package/dist/server.cjs +1159 -264
  301. package/dist/server.cjs.map +1 -1
  302. package/dist/server.d.cts +4 -1
  303. package/dist/server.d.cts.map +1 -1
  304. package/dist/server.d.ts +4 -1
  305. package/dist/server.d.ts.map +1 -1
  306. package/dist/server.js +1067 -172
  307. package/dist/server.js.map +1 -1
  308. package/dist/speech.cjs +4 -2
  309. package/dist/speech.cjs.map +1 -1
  310. package/dist/speech.js +4 -2
  311. package/dist/speech.js.map +1 -1
  312. package/dist/stream-collapse.cjs +236 -34
  313. package/dist/stream-collapse.cjs.map +1 -1
  314. package/dist/stream-collapse.d.cts +39 -4
  315. package/dist/stream-collapse.d.cts.map +1 -1
  316. package/dist/stream-collapse.d.ts +39 -4
  317. package/dist/stream-collapse.d.ts.map +1 -1
  318. package/dist/stream-collapse.js +235 -35
  319. package/dist/stream-collapse.js.map +1 -1
  320. package/dist/suite.cjs +1 -1
  321. package/dist/suite.js +1 -1
  322. package/dist/transcription.cjs +2 -2
  323. package/dist/transcription.cjs.map +1 -1
  324. package/dist/transcription.d.cts.map +1 -1
  325. package/dist/transcription.d.ts.map +1 -1
  326. package/dist/transcription.js +2 -2
  327. package/dist/transcription.js.map +1 -1
  328. package/dist/types.cjs +18 -0
  329. package/dist/types.cjs.map +1 -0
  330. package/dist/types.d.cts +516 -5
  331. package/dist/types.d.cts.map +1 -1
  332. package/dist/types.d.ts +516 -5
  333. package/dist/types.d.ts.map +1 -1
  334. package/dist/types.js +17 -0
  335. package/dist/types.js.map +1 -0
  336. package/dist/validate-cli.cjs +123 -33
  337. package/dist/validate-cli.cjs.map +1 -1
  338. package/dist/validate-cli.js +125 -35
  339. package/dist/validate-cli.js.map +1 -1
  340. package/dist/vector-stores.cjs +1180 -0
  341. package/dist/vector-stores.cjs.map +1 -0
  342. package/dist/vector-stores.d.cts +106 -0
  343. package/dist/vector-stores.d.cts.map +1 -0
  344. package/dist/vector-stores.d.ts +106 -0
  345. package/dist/vector-stores.d.ts.map +1 -0
  346. package/dist/vector-stores.js +1164 -0
  347. package/dist/vector-stores.js.map +1 -0
  348. package/dist/vector-types.d.cts.map +1 -1
  349. package/dist/version.cjs +45 -0
  350. package/dist/version.cjs.map +1 -0
  351. package/dist/version.js +44 -0
  352. package/dist/version.js.map +1 -0
  353. package/dist/video.cjs +19 -0
  354. package/dist/video.cjs.map +1 -1
  355. package/dist/video.d.cts.map +1 -1
  356. package/dist/video.d.ts.map +1 -1
  357. package/dist/video.js +19 -0
  358. package/dist/video.js.map +1 -1
  359. package/dist/vitest.cjs +77 -16
  360. package/dist/vitest.cjs.map +1 -1
  361. package/dist/vitest.d.cts +38 -5
  362. package/dist/vitest.d.cts.map +1 -1
  363. package/dist/vitest.d.ts +38 -5
  364. package/dist/vitest.d.ts.map +1 -1
  365. package/dist/vitest.js +78 -17
  366. package/dist/vitest.js.map +1 -1
  367. package/dist/watcher.cjs +6 -2
  368. package/dist/watcher.cjs.map +1 -1
  369. package/dist/watcher.js +6 -2
  370. package/dist/watcher.js.map +1 -1
  371. package/dist/ws-gemini-live.cjs +210 -101
  372. package/dist/ws-gemini-live.cjs.map +1 -1
  373. package/dist/ws-gemini-live.d.cts +9 -3
  374. package/dist/ws-gemini-live.d.cts.map +1 -1
  375. package/dist/ws-gemini-live.d.ts +9 -3
  376. package/dist/ws-gemini-live.d.ts.map +1 -1
  377. package/dist/ws-gemini-live.js +211 -102
  378. package/dist/ws-gemini-live.js.map +1 -1
  379. package/dist/ws-realtime.cjs +175 -62
  380. package/dist/ws-realtime.cjs.map +1 -1
  381. package/dist/ws-realtime.d.cts +8 -3
  382. package/dist/ws-realtime.d.cts.map +1 -1
  383. package/dist/ws-realtime.d.ts +8 -3
  384. package/dist/ws-realtime.d.ts.map +1 -1
  385. package/dist/ws-realtime.js +176 -63
  386. package/dist/ws-realtime.js.map +1 -1
  387. package/dist/ws-responses.cjs +121 -16
  388. package/dist/ws-responses.cjs.map +1 -1
  389. package/dist/ws-responses.d.cts +8 -12
  390. package/dist/ws-responses.d.cts.map +1 -1
  391. package/dist/ws-responses.d.ts +8 -12
  392. package/dist/ws-responses.d.ts.map +1 -1
  393. package/dist/ws-responses.js +123 -18
  394. package/dist/ws-responses.js.map +1 -1
  395. package/package.json +13 -2
  396. package/skills/write-fixtures/SKILL.md +179 -21
@@ -0,0 +1,1942 @@
1
+ const require_constants = require('./constants.cjs');
2
+ const require_echo_text = require('./echo-text.cjs');
3
+ const require_message_text = require('./message-text.cjs');
4
+ const require_fixture_loader = require('./fixture-loader.cjs');
5
+
6
+ //#region src/mcp-fakes.ts
7
+ /**
8
+ * MCP fakes: the fake model core.
9
+ *
10
+ * Pure module, no I/O. It owns block validation (the bad-block cases (a)-(j)
11
+ * listed in `MCP_FAKES_LOAD_RULES`), entry ids and the per-mount counters in
12
+ * them, block-id and entry-id uniqueness per mount, scope matching, scope
13
+ * tiers, argument equality, the synchronous claim step with its mismatch and
14
+ * exhausted details (`firstDifference`), consumption state per test id with
15
+ * its FIFO cap, the undeclared-tool policy, and the fake tools for
16
+ * `tools/list`. One `McpFakeStore` serves one MCP mount.
17
+ */
18
+ /** Default `mount` path of a block. */
19
+ const MCP_FAKES_DEFAULT_MOUNT = "/mcp";
20
+ /**
21
+ * Name of the consumption state shared by requests that supply no test id:
22
+ * the journal's `DEFAULT_TEST_ID`. The store keeps that state apart from an
23
+ * explicitly supplied test id equal to this string;
24
+ * `resetState(MCP_FAKES_DEFAULT_TEST_ID)` resets both.
25
+ */
26
+ const MCP_FAKES_DEFAULT_TEST_ID = require_constants.DEFAULT_TEST_ID;
27
+ /**
28
+ * Default FIFO cap on distinct test ids (`McpFakeStoreOptions.maxTestIds`),
29
+ * for a mount with no journal. A mount with a journal mirrors the journal's
30
+ * effective cap (`fixtureCountsMaxTestIdsCap`): 500 under `createServer` unless
31
+ * the option is set, and `0` (unbounded) for a bare `Journal` or an explicit `0`.
32
+ */
33
+ const MCP_FAKES_DEFAULT_MAX_TEST_IDS = 500;
34
+ /**
35
+ * Deepest nesting of objects and arrays accepted in an `mcpFakes` value: the
36
+ * value itself (a block, or the array of blocks) is level 1. A container
37
+ * below this level is not copied; the input snapshot holds a "nested deeper
38
+ * than 64 levels" value there, which the check of the enclosing field reports
39
+ * as its own bad block.
40
+ */
41
+ const MCP_FAKES_MAX_DEPTH = 64;
42
+ /** The reason a container below `MCP_FAKES_MAX_DEPTH` levels is not copied. */
43
+ const DEPTH_REASON = require_message_text.msg`nested deeper than ${MCP_FAKES_MAX_DEPTH} levels`;
44
+ /** Longest `firstDifference` text (path plus both values), cut the same way. */
45
+ const DIFF_LIMIT = 1e3;
46
+ const BLOCK_KEYS = new Set([
47
+ "scope",
48
+ "mount",
49
+ "undeclaredTools",
50
+ "tools",
51
+ "list",
52
+ "recorded",
53
+ "timing"
54
+ ]);
55
+ const TOOL_KEYS = new Set([
56
+ "name",
57
+ "description",
58
+ "inputSchema",
59
+ "calls"
60
+ ]);
61
+ const CALL_KEYS = new Set([
62
+ "args",
63
+ "anyArgs",
64
+ "result",
65
+ "error",
66
+ "id",
67
+ "notifications",
68
+ "durationMs"
69
+ ]);
70
+ /**
71
+ * C9: most events one fake event log keeps (per test id, and the untagged
72
+ * log). Past it the oldest event is dropped and the log's report says
73
+ * `evicted: true`. Report-only: claims and snapshots never read it.
74
+ */
75
+ const MCP_FAKES_MAX_EVENTS_PER_LOG = 1e3;
76
+ /**
77
+ * Largest `args` (JSON, UTF-8 bytes) an event keeps. A larger one is stored as
78
+ * `{ __aimock_truncated: true, originalByteSize }`, the journal `capBody` shape.
79
+ */
80
+ const MCP_FAKES_MAX_EVENT_ARGS_BYTES = 4096;
81
+ const SCOPE_KEYS = new Set(["testId", "context"]);
82
+ const TIER_ORDER = [
83
+ "testId+context",
84
+ "testId",
85
+ "context",
86
+ "shared"
87
+ ];
88
+ /** The last `seq` given to an event, shared by every store in the process. */
89
+ let lastEventSeq = 0;
90
+ /**
91
+ * An event's `args` as the log keeps it: a frozen copy when its JSON is at
92
+ * most `MCP_FAKES_MAX_EVENT_ARGS_BYTES` UTF-8 bytes, else the journal's
93
+ * truncation marker. A value with no JSON text (`undefined`, or one whose
94
+ * `JSON.stringify` throws) is kept as its rendered text.
95
+ */
96
+ function boundedArgs(args) {
97
+ let text;
98
+ try {
99
+ text = JSON.stringify(args);
100
+ } catch {
101
+ text = void 0;
102
+ }
103
+ if (text === void 0) return args === void 0 ? void 0 : echo(args);
104
+ const size = Buffer.byteLength(text, "utf8");
105
+ if (size > MCP_FAKES_MAX_EVENT_ARGS_BYTES) return Object.freeze({
106
+ __aimock_truncated: true,
107
+ originalByteSize: size
108
+ });
109
+ return deepFreeze(JSON.parse(text));
110
+ }
111
+ /**
112
+ * The error a failed add throws. It is a `FixtureLoadError` whose `rule`,
113
+ * `file`, `blockId` and `entryId` are those of the first error, and whose
114
+ * `message` is the first error's message plus the count of the others.
115
+ * `errors` holds every error found in the input, in order (the first
116
+ * included; see `validateBlock` for the checks that stop early), and
117
+ * `warnings` the shadowed-entry warnings of the blocks that passed every
118
+ * check. Both are frozen, non-writable arrays; `toJSON()` returns copies.
119
+ */
120
+ var McpFakesAddError = class extends require_fixture_loader.FixtureLoadError {
121
+ name = "McpFakesAddError";
122
+ constructor(errors, warnings) {
123
+ const first = errors[0];
124
+ if (!first) throw new TypeError(require_message_text.build(require_message_text.msg`McpFakesAddError needs at least one error`));
125
+ super({
126
+ rule: first.rule,
127
+ file: first.file,
128
+ blockId: first.blockId,
129
+ entryId: first.entryId,
130
+ detail: ""
131
+ });
132
+ const more = errors.length - 1;
133
+ this.message = more > 0 ? require_message_text.build(require_message_text.msg`${require_message_text.fixed(first.message)} (and ${more} more ${require_message_text.fixed(more === 1 ? "error" : "errors")})`) : first.message;
134
+ Object.defineProperty(this, "errors", {
135
+ value: Object.freeze([...errors]),
136
+ enumerable: true,
137
+ writable: false
138
+ });
139
+ Object.defineProperty(this, "warnings", {
140
+ value: Object.freeze(warnings.map((w) => Object.freeze({ ...w }))),
141
+ enumerable: true,
142
+ writable: false
143
+ });
144
+ }
145
+ toJSON() {
146
+ return {
147
+ ...super.toJSON(),
148
+ name: this.name,
149
+ errors: this.errors.map((e) => e.toJSON()),
150
+ warnings: this.warnings.map((w) => ({ ...w }))
151
+ };
152
+ }
153
+ };
154
+ /** An object whose prototype is `Object.prototype` or `null` (not an array). */
155
+ function isPlainObject(value) {
156
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
157
+ const proto = Object.getPrototypeOf(value);
158
+ return proto === Object.prototype || proto === null;
159
+ }
160
+ /**
161
+ * The one own-key listing for input data: own, enumerable, string keys.
162
+ * Inherited names (`constructor`, `toString`, `__proto__`) are never listed.
163
+ *
164
+ * @internal
165
+ */
166
+ function ownKeys(obj) {
167
+ return Object.keys(obj);
168
+ }
169
+ /**
170
+ * The one key check: `key` is an own property of `obj`. Unlike `key in obj`,
171
+ * it never sees the prototype chain, so `hasOwnKey({}, "constructor")` is
172
+ * false.
173
+ *
174
+ * @internal
175
+ */
176
+ function hasOwnKey(obj, key) {
177
+ return Object.prototype.hasOwnProperty.call(obj, key);
178
+ }
179
+ /**
180
+ * A user value as one message part: a string is JSON-quoted (quotes and
181
+ * control chars escaped), any other value is rendered as JSON (or as
182
+ * `undefined`, `NaN`, `10n`, ... where JSON cannot at the top level), and a
183
+ * snapshot placeholder, a nested bigint, a cycle, a container too deep, a
184
+ * symbol or function, and a value whose reading throws each as a `<reason>`
185
+ * placeholder whose user text is quoted (see `render`). The builder cuts it
186
+ * once, to its share and to no more than its max, never inside a JSON escape
187
+ * (`\n`, `\u0001`). Whatever the caller's code throws is caught; an error of
188
+ * aimock's own is not.
189
+ */
190
+ function shown(value, max = require_constants.MCP_FAKES_ECHO_LIMIT) {
191
+ return require_message_text.jsonText(render(value), max);
192
+ }
193
+ /**
194
+ * One user value as message text: `shown`, built alone, so cut to `limit`
195
+ * chars as the builder cuts it. A `limit` below 1 keeps nothing (`""`), as
196
+ * `cutText` does; a `NaN` limit is a `RangeError`.
197
+ *
198
+ * @internal
199
+ */
200
+ function echo(value, limit = require_constants.MCP_FAKES_ECHO_LIMIT) {
201
+ if (!(limit >= 1)) return require_echo_text.cutText(render(value), limit, "json");
202
+ return require_message_text.build(require_message_text.msg`${shown(value, limit)}`);
203
+ }
204
+ function render(value) {
205
+ if (value === void 0) return "undefined";
206
+ if (typeof value === "number" && !Number.isFinite(value)) return String(value);
207
+ if (typeof value === "bigint") return `${value}n`;
208
+ return renderMember(() => value, "", []) ?? notJson(value);
209
+ }
210
+ /**
211
+ * Thrown by `userCode` when code the caller supplied throws: a `toJSON`, a
212
+ * getter, a `Proxy` trap, a `toString`. The renderer catches only this, so a
213
+ * defect in aimock's own rendering code is never hidden as a placeholder.
214
+ */
215
+ var UserCodeThrew = class {
216
+ constructor(error) {
217
+ this.error = error;
218
+ }
219
+ };
220
+ /**
221
+ * `run()`, which touches the caller's value and so may run the caller's code;
222
+ * whatever that code throws comes out as a `UserCodeThrew`.
223
+ */
224
+ function userCode(run) {
225
+ try {
226
+ return run();
227
+ } catch (error) {
228
+ throw new UserCodeThrew(error);
229
+ }
230
+ }
231
+ /** A placeholder in a rendered value: `<reason>`, the reason's text whole. */
232
+ function placeholder(reason) {
233
+ return `<${require_message_text.fullText(reason)}>`;
234
+ }
235
+ /** The reason a rendered value holds where it reaches one of its own containers. */
236
+ const CIRCULAR_REASON = require_message_text.msg`a circular reference`;
237
+ /**
238
+ * A value that `renderJson` gives no JSON for (a symbol, a function): a
239
+ * placeholder that names its type and, when it has one, quotes its text.
240
+ */
241
+ function notJson(value) {
242
+ const reason = require_message_text.msg`not a JSON value (got ${require_message_text.fixed(typeof value)})`;
243
+ let text;
244
+ try {
245
+ text = userCode(() => String(value));
246
+ } catch (err) {
247
+ if (!(err instanceof UserCodeThrew)) throw err;
248
+ return placeholder(reason);
249
+ }
250
+ return placeholder(require_message_text.msg`${reason}: ${require_message_text.quote(text)}`);
251
+ }
252
+ /**
253
+ * One value of a rendered value, read by `read` (which may run the caller's
254
+ * code), rendered by `renderJson`. Where the caller's code throws while it is
255
+ * read or rendered, it renders as an "unreadable value" placeholder that
256
+ * quotes what was thrown, and the rest of the value is kept.
257
+ */
258
+ function renderMember(read, key, stack) {
259
+ try {
260
+ return renderJson(userCode(read), key, stack);
261
+ } catch (err) {
262
+ if (!(err instanceof UserCodeThrew)) throw err;
263
+ return placeholder(require_message_text.msg`an unreadable value (reading it threw ${describeThrown(err.error)})`);
264
+ }
265
+ }
266
+ /**
267
+ * `value` as `JSON.stringify` renders it, except that a snapshot placeholder,
268
+ * a bigint below the top level, a cycle (found through `stack`, the
269
+ * containers being rendered) and a container more than `MCP_FAKES_MAX_DEPTH`
270
+ * levels down each render as a `<reason>` placeholder. A placeholder is known
271
+ * by its class, never by its text, so no user value can pose as one.
272
+ * `undefined` where `JSON.stringify` gives `undefined`. Every part it joins
273
+ * is `JSON.stringify` output or placeholder text. Every read of the caller's
274
+ * value goes through `userCode`, so the only error it lets out from the
275
+ * caller's code is a `UserCodeThrew`.
276
+ */
277
+ function renderJson(value, key, stack) {
278
+ if (userCode(() => value instanceof NotPlain)) return placeholder(value.reason);
279
+ if (value !== null && (typeof value === "object" || typeof value === "bigint")) {
280
+ const toJSON = userCode(() => value.toJSON);
281
+ if (typeof toJSON === "function") value = userCode(() => toJSON.call(value, key));
282
+ }
283
+ if (typeof value === "bigint") return placeholder(require_message_text.msg`a bigint (${require_message_text.fixed(`${value}n`)})`);
284
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
285
+ const container = value;
286
+ if (userCode(() => container instanceof Number || container instanceof String || container instanceof Boolean)) return userCode(() => JSON.stringify(container));
287
+ if (stack.includes(container)) return placeholder(CIRCULAR_REASON);
288
+ if (stack.length >= MCP_FAKES_MAX_DEPTH) return placeholder(DEPTH_REASON);
289
+ stack.push(container);
290
+ try {
291
+ if (userCode(() => Array.isArray(container))) {
292
+ const array = container;
293
+ const length = userCode(() => array.length);
294
+ const items = [];
295
+ for (let i = 0; i < length; i++) items.push(renderMember(() => array[i], String(i), stack) ?? "null");
296
+ return `[${items.join(",")}]`;
297
+ }
298
+ const record = container;
299
+ const members = [];
300
+ for (const k of userCode(() => ownKeys(record))) {
301
+ const item = renderMember(() => record[k], k, stack);
302
+ if (item !== void 0) members.push(`${JSON.stringify(k)}:${item}`);
303
+ }
304
+ return `{${members.join(",")}}`;
305
+ } finally {
306
+ stack.pop();
307
+ }
308
+ }
309
+ /**
310
+ * A thrown value as a message part: its text (user text: a Proxy trap's or a
311
+ * `toJSON`'s message) JSON-quoted; never throws.
312
+ */
313
+ function describeThrown(err) {
314
+ try {
315
+ return require_message_text.quote(err instanceof Error ? `${err.name}: ${err.message}` : String(err));
316
+ } catch {
317
+ return require_message_text.fixed("an unprintable value");
318
+ }
319
+ }
320
+ /**
321
+ * A built-in `Object.prototype.toString` tag (`[object Date]`). Any other tag
322
+ * can come from a user `Symbol.toStringTag`, so a message JSON-quotes it: it
323
+ * then cannot pose as JSON structure or a `<...>` placeholder, and holds no
324
+ * backslash that is not a JSON escape.
325
+ */
326
+ const BUILTIN_TAG = /^\[object [A-Za-z][A-Za-z0-9]*\]$/;
327
+ /** A `toString` tag as a message part: a built-in one as is, any other quoted. */
328
+ function tagPart(tag) {
329
+ return BUILTIN_TAG.test(tag) ? require_message_text.fixed(tag) : require_message_text.quote(tag);
330
+ }
331
+ /**
332
+ * A place in the caller's input that the boundary snapshot could not copy as
333
+ * plain data. It replaces that value in the snapshot, so every later check
334
+ * sees a non-plain value and reports its own rule with `reason`.
335
+ */
336
+ var NotPlain = class {
337
+ constructor(reason) {
338
+ this.reason = reason;
339
+ Object.freeze(this);
340
+ }
341
+ };
342
+ /**
343
+ * The input boundary: one plain-data copy of a caller's value, made once at
344
+ * `McpFakeStore.add` / `validateMcpFakes` entry; every check after it reads
345
+ * only the copy. It reads own, enumerable, string-keyed data properties only:
346
+ * getters are never run and inherited fields never seen. Never throws. Where
347
+ * the value is not plain data, the copy holds a `NotPlain`: a non-plain
348
+ * object or array (`Date`, `Map`, a class instance, an object with a custom
349
+ * prototype), a symbol key, an accessor property, a sparse array, an array
350
+ * with a non-index key, a cycle, a function, symbol or bigint, or a value
351
+ * whose reading throws (a hostile or revoked `Proxy`). Strings, numbers
352
+ * (non-finite ones too), booleans, `null` and `undefined` are kept as is.
353
+ * Copied objects have a `null` prototype.
354
+ *
355
+ * An object or array below `maxDepth` levels (the root is level 1) is not
356
+ * read: the copy holds a "nested deeper than `MCP_FAKES_MAX_DEPTH` levels"
357
+ * `NotPlain` there, so the copy never recurses deeper than that. Only the
358
+ * reflective reads of the caller's value (`readShape`) are guarded by a
359
+ * `catch`; a throw from aimock's own copy code is a bug and propagates.
360
+ *
361
+ * Each caller object is read once, and copied once per level it is reached
362
+ * at: an object reached through several paths (shared references) is not
363
+ * copied once per path, so the time is linear in the number of objects, not
364
+ * exponential in the depth. The copy keeps that sharing. An object on a
365
+ * cycle is still never copied without a placeholder somewhere under it.
366
+ */
367
+ function snapshotInput(value, maxDepth = MCP_FAKES_MAX_DEPTH) {
368
+ return snapshotValue(value, {
369
+ maxDepth,
370
+ ancestors: [],
371
+ shapes: /* @__PURE__ */ new Map(),
372
+ copies: /* @__PURE__ */ new Map()
373
+ });
374
+ }
375
+ function snapshotValue(value, state) {
376
+ if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
377
+ if (typeof value !== "object") return new NotPlain(require_message_text.msg`not a JSON value (got ${require_message_text.fixed(typeof value)})`);
378
+ const { ancestors } = state;
379
+ if (ancestors.includes(value)) return new NotPlain(require_message_text.msg`a circular reference`);
380
+ const level = ancestors.length;
381
+ if (level >= state.maxDepth) return new NotPlain(DEPTH_REASON);
382
+ let byLevel = state.copies.get(value);
383
+ if (byLevel?.has(level)) return byLevel.get(level);
384
+ let shape = state.shapes.get(value);
385
+ if (shape === void 0) {
386
+ shape = readShape(value);
387
+ state.shapes.set(value, shape);
388
+ }
389
+ if (shape instanceof NotPlain) return shape;
390
+ ancestors.push(value);
391
+ let copy;
392
+ try {
393
+ copy = copyShape(shape, state);
394
+ } finally {
395
+ ancestors.pop();
396
+ }
397
+ if (!byLevel) {
398
+ byLevel = /* @__PURE__ */ new Map();
399
+ state.copies.set(value, byLevel);
400
+ }
401
+ byLevel.set(level, copy);
402
+ return copy;
403
+ }
404
+ /**
405
+ * Every reflective read of one caller object, and nothing else: the only
406
+ * code a hostile or revoked `Proxy` can make throw. A throw here is the
407
+ * caller's value being unreadable, so it becomes a `NotPlain`.
408
+ */
409
+ function readShape(value) {
410
+ try {
411
+ const isArray = Array.isArray(value);
412
+ const proto = Object.getPrototypeOf(value);
413
+ if (isArray ? proto !== Array.prototype : proto !== Object.prototype && proto !== null) {
414
+ const tag = Object.prototype.toString.call(value);
415
+ return new NotPlain(require_message_text.msg`not a plain JSON ${require_message_text.fixed(isArray ? "array" : "object")} (got ${tagPart(tag)})`);
416
+ }
417
+ const data = [];
418
+ for (const key of Reflect.ownKeys(value)) {
419
+ const desc = Reflect.getOwnPropertyDescriptor(value, key);
420
+ if (!desc?.enumerable) continue;
421
+ if (typeof key === "symbol") return new NotPlain(require_message_text.msg`an object with a symbol key`);
422
+ data.push([key, hasOwnKey(desc, "value") ? desc.value : ACCESSOR]);
423
+ }
424
+ const lengthDesc = isArray ? Reflect.getOwnPropertyDescriptor(value, "length") : void 0;
425
+ return {
426
+ isArray,
427
+ data,
428
+ length: typeof lengthDesc?.value === "number" ? lengthDesc.value : 0
429
+ };
430
+ } catch (err) {
431
+ return new NotPlain(require_message_text.msg`an unreadable value (reading it threw ${describeThrown(err)})`);
432
+ }
433
+ }
434
+ /**
435
+ * Most holes a sparse array may have and still be copied element by element.
436
+ * Its `length` costs the caller nothing, so a longer one is not walked.
437
+ */
438
+ const SPARSE_HOLE_LIMIT = 1e4;
439
+ /**
440
+ * Copy what `readShape` read; recurses through `snapshotValue`. A hole in a
441
+ * sparse array becomes a placeholder in its own place, so the array is still
442
+ * an array and every other element is still checked.
443
+ */
444
+ function copyShape(shape, state) {
445
+ const { data, length } = shape;
446
+ if (shape.isArray) {
447
+ const own = new Map(data);
448
+ for (const [key] of data) {
449
+ const n = Number(key);
450
+ if (key !== String(n) || !Number.isInteger(n) || n < 0 || n >= length) return new NotPlain(require_message_text.msg`an array with a non-index key ${require_message_text.quote(key)}`);
451
+ }
452
+ const holes = length - own.size;
453
+ if (holes > SPARSE_HOLE_LIMIT) {
454
+ let hole = 0;
455
+ while (own.has(String(hole))) hole += 1;
456
+ return new NotPlain(require_message_text.msg`a sparse array with ${holes} holes (the first at [${hole}])`);
457
+ }
458
+ const out = [];
459
+ for (let i = 0; i < length; i++) {
460
+ const key = String(i);
461
+ out.push(own.has(key) ? snapshotField(own.get(key), state) : new NotPlain(require_message_text.msg`a hole in a sparse array`));
462
+ }
463
+ return out;
464
+ }
465
+ const out = Object.create(null);
466
+ for (const [key, item] of data) Object.defineProperty(out, key, {
467
+ value: snapshotField(item, state),
468
+ enumerable: true,
469
+ writable: true,
470
+ configurable: true
471
+ });
472
+ return out;
473
+ }
474
+ /** Marks an accessor property in `readShape` (its getter is never run). */
475
+ const ACCESSOR = Symbol("accessor");
476
+ function snapshotField(item, state) {
477
+ return item === ACCESSOR ? new NotPlain(require_message_text.msg`an accessor property (getters are not read)`) : snapshotValue(item, state);
478
+ }
479
+ /**
480
+ * A copy of `value`, a snapshot copy, with every container below `levels`
481
+ * levels (the value itself is level 1) replaced by the depth placeholder.
482
+ * Used by `McpFakeStore.add` for a block of an array-form `mcpFakes`, whose
483
+ * array is level 1. It does not change `value`: a snapshot can share one copy
484
+ * between paths at different levels. Each container is copied once per level
485
+ * (`memo`), so shared references stay linear.
486
+ */
487
+ function capDepth(value, levels, memo = /* @__PURE__ */ new Map()) {
488
+ if (value instanceof NotPlain || typeof value !== "object" || value === null) return value;
489
+ if (levels < 1) return new NotPlain(DEPTH_REASON);
490
+ let byLevel = memo.get(value);
491
+ if (byLevel?.has(levels)) return byLevel.get(levels);
492
+ let out;
493
+ if (Array.isArray(value)) out = value.map((item) => capDepth(item, levels - 1, memo));
494
+ else {
495
+ const src = value;
496
+ const obj = Object.create(null);
497
+ for (const key of ownKeys(src)) Object.defineProperty(obj, key, {
498
+ value: capDepth(src[key], levels - 1, memo),
499
+ enumerable: true,
500
+ writable: true,
501
+ configurable: true
502
+ });
503
+ out = obj;
504
+ }
505
+ if (!byLevel) {
506
+ byLevel = /* @__PURE__ */ new Map();
507
+ memo.set(value, byLevel);
508
+ }
509
+ byLevel.set(levels, out);
510
+ return out;
511
+ }
512
+ /**
513
+ * Freeze `value` and everything under it (plain data only). A frozen object
514
+ * is not walked again: this module freezes children before their parent, and
515
+ * a shared child is then frozen once.
516
+ */
517
+ function deepFreeze(value) {
518
+ if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
519
+ const obj = value;
520
+ for (const key of ownKeys(obj)) deepFreeze(obj[key]);
521
+ Object.freeze(value);
522
+ }
523
+ return value;
524
+ }
525
+ function isNonEmptyString(value) {
526
+ return typeof value === "string" && value.length > 0;
527
+ }
528
+ /** A field is present when it is an own key whose value is not `undefined`. */
529
+ function has(raw, key) {
530
+ return hasOwnKey(raw, key) && raw[key] !== void 0;
531
+ }
532
+ /** Own keys whose value is not `undefined` (an `undefined` field is absent). */
533
+ function presentKeys(raw) {
534
+ return ownKeys(raw).filter((key) => raw[key] !== void 0);
535
+ }
536
+ /**
537
+ * The first place under `value` that is not plain JSON (as `JSON.parse` could
538
+ * produce it), or `null`. An object field set to `undefined` is absent, as in
539
+ * `JSON.stringify`. Rejects an `undefined` array item, functions, symbols,
540
+ * bigints, non-finite numbers, sparse-array holes and objects whose prototype
541
+ * is not `Object.prototype` or `null` (`Date`, `Map`, class instances). Its
542
+ * input is always the input snapshot, which holds no cycle (`snapshotInput`
543
+ * puts a placeholder there) and no container deeper than
544
+ * `MCP_FAKES_MAX_DEPTH`, so the recursion is bounded. A container found clean
545
+ * is not checked again (`clean`), so a shared one is walked once.
546
+ */
547
+ function jsonProblem(value, path, clean = /* @__PURE__ */ new WeakSet()) {
548
+ if (value instanceof NotPlain) return require_message_text.msg`${pathPart(path)} is ${value.reason}`;
549
+ if (value === null || typeof value === "string" || typeof value === "boolean") return null;
550
+ if (typeof value === "number") return Number.isFinite(value) ? null : require_message_text.msg`${pathPart(path)} must be a finite number, got ${shown(value)}`;
551
+ if (typeof value !== "object") return require_message_text.msg`${pathPart(path)} is not a JSON value (got ${require_message_text.fixed(typeof value)})`;
552
+ if (clean.has(value)) return null;
553
+ let problem = null;
554
+ if (Array.isArray(value)) for (let i = 0; i < value.length && !problem; i++) problem = hasOwnKey(value, String(i)) ? jsonProblem(value[i], `${path}[${i}]`, clean) : require_message_text.msg`${pathPart(`${path}[${i}]`)} is a hole in a sparse array`;
555
+ else {
556
+ const proto = Object.getPrototypeOf(value);
557
+ if (proto !== Object.prototype && proto !== null) {
558
+ const tag = Object.prototype.toString.call(value);
559
+ problem = require_message_text.msg`${pathPart(path)} must be a plain JSON object, got ${tagPart(tag)}`;
560
+ } else {
561
+ const obj = value;
562
+ for (const key of presentKeys(obj)) {
563
+ problem = jsonProblem(obj[key], pathKey(path, key), clean);
564
+ if (problem) break;
565
+ }
566
+ }
567
+ }
568
+ if (!problem) clean.add(value);
569
+ return problem;
570
+ }
571
+ /**
572
+ * Deep copy of a plain JSON value (checked by `jsonProblem`), optionally
573
+ * deep-frozen. Keys are defined, not assigned, so `"__proto__"` stays data;
574
+ * a field set to `undefined` is left out. A container reached through several
575
+ * paths is copied once (`memo`), and the copy shares it the same way.
576
+ */
577
+ function copyJson(value, freeze = false, memo = /* @__PURE__ */ new Map()) {
578
+ if (typeof value !== "object" || value === null) return value;
579
+ if (memo.has(value)) return memo.get(value);
580
+ let out;
581
+ if (Array.isArray(value)) out = value.map((item) => copyJson(item, freeze, memo));
582
+ else {
583
+ const src = value;
584
+ const obj = {};
585
+ for (const key of presentKeys(src)) Object.defineProperty(obj, key, {
586
+ value: copyJson(src[key], freeze, memo),
587
+ enumerable: true,
588
+ writable: true,
589
+ configurable: true
590
+ });
591
+ out = obj;
592
+ }
593
+ if (freeze) Object.freeze(out);
594
+ memo.set(value, out);
595
+ return out;
596
+ }
597
+ /** Block id: `<source><load>[<block>]`, with no `[<block>]` for a single-object `mcpFakes`. */
598
+ function blockIdOf(source, loadSuffix, blockIndex) {
599
+ return `${source}${loadSuffix}${blockIndex === null ? "" : `[${blockIndex}]`}`;
600
+ }
601
+ function localOf(toolName, index, userId) {
602
+ return isNonEmptyString(userId) ? userId : `${toolName}#${index}`;
603
+ }
604
+ function tierOf(scope) {
605
+ if (scope === "shared") return "shared";
606
+ if (scope.testId !== void 0 && scope.context !== void 0) return "testId+context";
607
+ if (scope.testId !== void 0) return "testId";
608
+ return "context";
609
+ }
610
+ /** Scope match: exact, case-sensitive, no normalization; `"shared"` matches every request. */
611
+ function scopeMatches(scope, identity) {
612
+ if (scope === "shared") return true;
613
+ if (scope.testId !== void 0 && identity.testId !== scope.testId) return false;
614
+ if (scope.context !== void 0 && identity.context !== scope.context) return false;
615
+ return true;
616
+ }
617
+ function badBlock(ctx, letter, detail, entryId = null) {
618
+ return new require_fixture_loader.FixtureLoadError({
619
+ rule: `mcp-fakes/bad-block:${letter}`,
620
+ file: ctx.source,
621
+ blockId: ctx.blockId,
622
+ entryId,
623
+ detail
624
+ });
625
+ }
626
+ /** Every scope error goes to `errors`; returns the scope only when it is clean. */
627
+ function validateScope(raw, ctx, errors) {
628
+ if (raw === void 0) {
629
+ errors.push(badBlock(ctx, "a", require_message_text.msg`block has no scope`));
630
+ return null;
631
+ }
632
+ if (raw === "shared") return "shared";
633
+ if (!isPlainObject(raw)) {
634
+ errors.push(badBlock(ctx, "b", require_message_text.msg`scope must be "shared" or an object, got ${shown(raw)}`));
635
+ return null;
636
+ }
637
+ const before = errors.length;
638
+ for (const key of presentKeys(raw)) if (!SCOPE_KEYS.has(key)) errors.push(badBlock(ctx, "b", require_message_text.msg`scope has unknown key ${shown(key)}`));
639
+ if (!has(raw, "testId") && !has(raw, "context")) errors.push(badBlock(ctx, "b", require_message_text.msg`scope object has neither testId nor context`));
640
+ for (const key of ["testId", "context"]) if (has(raw, key) && !isNonEmptyString(raw[key])) errors.push(badBlock(ctx, "b", require_message_text.msg`scope.${require_message_text.fixed(key)} must be a non-empty string, got ${shown(raw[key])}`));
641
+ if (errors.length > before) return null;
642
+ const testId = raw.testId;
643
+ const context = raw.context;
644
+ if (testId !== void 0 && context !== void 0) return Object.freeze({
645
+ testId,
646
+ context
647
+ });
648
+ if (testId !== void 0) return Object.freeze({ testId });
649
+ return Object.freeze({ context });
650
+ }
651
+ const mustBeString = (v, p) => typeof v === "string" ? null : require_message_text.msg`${pathPart(p)} must be a string, got ${shown(v)}`;
652
+ const mustBeNumber = (v, p) => typeof v === "number" ? null : require_message_text.msg`${pathPart(p)} must be a number, got ${shown(v)}`;
653
+ const mustBeBoolean = (v, p) => typeof v === "boolean" ? null : require_message_text.msg`${pathPart(p)} must be a boolean, got ${shown(v)}`;
654
+ const mustBeObject = (v, p) => isPlainObject(v) ? null : require_message_text.msg`${pathPart(p)} must be a JSON object, got ${shown(v)}`;
655
+ /** Base64 as the SDK's `Base64Schema` accepts it (`atob` does not throw). */
656
+ const mustBeBase64 = (v, p) => {
657
+ if (typeof v === "string") try {
658
+ atob(v);
659
+ return null;
660
+ } catch {}
661
+ return require_message_text.msg`${pathPart(p)} must be a base64 string, got ${shown(v)}`;
662
+ };
663
+ function oneOf(values) {
664
+ const list = values.map((v) => require_message_text.quoteJson(v)).join(", ");
665
+ return (v, p) => typeof v === "string" && values.includes(v) ? null : require_message_text.msg`${pathPart(p)} must be one of ${require_message_text.fixed(list)}, got ${shown(v)}`;
666
+ }
667
+ function arrayOf(item, noun = "an array") {
668
+ return (v, p) => {
669
+ if (!Array.isArray(v)) return require_message_text.msg`${pathPart(p)} must be ${require_message_text.fixed(noun)}, got ${shown(v)}`;
670
+ for (let i = 0; i < v.length; i++) {
671
+ const problem = item(v[i], `${p}[${i}]`);
672
+ if (problem) return problem;
673
+ }
674
+ return null;
675
+ };
676
+ }
677
+ const mustBeStringArray = arrayOf(mustBeString, "an array of strings");
678
+ /**
679
+ * An object with `required` fields and `optional` ones (an `undefined` field
680
+ * is absent). Other keys are not checked.
681
+ */
682
+ function shape(required, optional = {}) {
683
+ return (v, p) => {
684
+ if (!isPlainObject(v)) return require_message_text.msg`${pathPart(p)} must be a JSON object, got ${shown(v)}`;
685
+ for (const key of ownKeys(required)) {
686
+ const check = required[key];
687
+ const problem = check(has(v, key) ? v[key] : void 0, pathKey(p, key));
688
+ if (problem) return problem;
689
+ }
690
+ for (const key of ownKeys(optional)) {
691
+ const check = optional[key];
692
+ if (!has(v, key)) continue;
693
+ const problem = check(v[key], pathKey(p, key));
694
+ if (problem) return problem;
695
+ }
696
+ return null;
697
+ };
698
+ }
699
+ /**
700
+ * RFC 3339 date-time with seconds and a `Z` or `±hh:mm` offset: the pattern of
701
+ * zod's `z.iso.datetime({ offset: true })`, which the SDK uses for
702
+ * `Annotations.lastModified`.
703
+ */
704
+ const ISO_DATETIME = /^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
705
+ const mustBeIsoDateTime = (v, p) => typeof v === "string" && ISO_DATETIME.test(v) ? null : require_message_text.msg`${pathPart(p)} must be an ISO 8601 date-time with seconds and an offset, got ${shown(v)}`;
706
+ const ANNOTATIONS = shape({}, {
707
+ audience: arrayOf(oneOf(["user", "assistant"])),
708
+ priority: (v, p) => typeof v === "number" && v >= 0 && v <= 1 ? null : require_message_text.msg`${pathPart(p)} must be a number from 0 to 1, got ${shown(v)}`,
709
+ lastModified: mustBeIsoDateTime
710
+ });
711
+ const ICON = shape({ src: mustBeString }, {
712
+ mimeType: mustBeString,
713
+ sizes: mustBeStringArray,
714
+ theme: oneOf(["light", "dark"])
715
+ });
716
+ /** MCP `TextResourceContents` / `BlobResourceContents`: exactly one of text / blob. */
717
+ const RESOURCE_CONTENTS = (v, p) => {
718
+ if (!isPlainObject(v)) return require_message_text.msg`${pathPart(p)} must be a JSON object with uri and text or blob, got ${shown(v)}`;
719
+ if (has(v, "text") === has(v, "blob")) return require_message_text.msg`${pathPart(p)} needs exactly one of text / blob`;
720
+ return shape({
721
+ uri: mustBeString,
722
+ ...has(v, "text") ? { text: mustBeString } : { blob: mustBeBase64 }
723
+ }, {
724
+ mimeType: mustBeString,
725
+ _meta: mustBeObject
726
+ })(v, p);
727
+ };
728
+ /** Fields every content block may carry. */
729
+ const CONTENT_COMMON = {
730
+ annotations: ANNOTATIONS,
731
+ _meta: mustBeObject
732
+ };
733
+ /** Each MCP content block type, with its fields. */
734
+ const CONTENT_TYPES = new Map([
735
+ ["text", shape({ text: mustBeString }, CONTENT_COMMON)],
736
+ ["image", shape({
737
+ data: mustBeBase64,
738
+ mimeType: mustBeString
739
+ }, CONTENT_COMMON)],
740
+ ["audio", shape({
741
+ data: mustBeBase64,
742
+ mimeType: mustBeString
743
+ }, CONTENT_COMMON)],
744
+ ["resource_link", shape({
745
+ uri: mustBeString,
746
+ name: mustBeString
747
+ }, {
748
+ title: mustBeString,
749
+ description: mustBeString,
750
+ mimeType: mustBeString,
751
+ size: mustBeNumber,
752
+ icons: arrayOf(ICON),
753
+ ...CONTENT_COMMON
754
+ })],
755
+ ["resource", shape({ resource: RESOURCE_CONTENTS }, CONTENT_COMMON)]
756
+ ]);
757
+ const CONTENT_TYPE_LIST = [...CONTENT_TYPES.keys()].map((type) => require_message_text.quoteJson(type)).join(", ");
758
+ /** One MCP content block, checked by its `type`. */
759
+ const CONTENT_BLOCK = (v, p) => {
760
+ if (!isPlainObject(v)) return require_message_text.msg`${pathPart(p)} must be a content object, got ${shown(v)}`;
761
+ const check = typeof v.type === "string" ? CONTENT_TYPES.get(v.type) : void 0;
762
+ if (!check) return require_message_text.msg`${pathPart(p)}.type must be one of ${require_message_text.fixed(CONTENT_TYPE_LIST)}, got ${shown(v.type)}`;
763
+ return check(v, p);
764
+ };
765
+ const CONTENT = arrayOf(CONTENT_BLOCK, "an array of content blocks");
766
+ /** The SDK client's `RequestMeta` keys on a result's `_meta`; other keys pass through. */
767
+ const RESULT_META = shape({}, {
768
+ progressToken: (v, p) => typeof v === "string" || Number.isSafeInteger(v) ? null : require_message_text.msg`${pathPart(p)} must be a string or an integer, got ${shown(v)}`,
769
+ "io.modelcontextprotocol/related-task": shape({ taskId: mustBeString })
770
+ });
771
+ /**
772
+ * The keys of a full `CallToolResult`. An object `result` with any of
773
+ * them is that form, and must have a `content` array; any other object is
774
+ * structured data.
775
+ */
776
+ const FULL_RESULT_KEYS = new Set([
777
+ "content",
778
+ "isError",
779
+ "structuredContent",
780
+ "_meta"
781
+ ]);
782
+ const FULL_RESULT = shape({ content: CONTENT }, {
783
+ isError: mustBeBoolean,
784
+ structuredContent: mustBeObject,
785
+ _meta: RESULT_META
786
+ });
787
+ function isFullResult(obj) {
788
+ return [...FULL_RESULT_KEYS].some((key) => has(obj, key));
789
+ }
790
+ /**
791
+ * A valid `result`: a string; a content array; a full `CallToolResult` (`content`
792
+ * array, optional boolean `isError`, optional object `structuredContent` and
793
+ * `_meta`, nothing else); or a JSON object with none of those keys (served as
794
+ * text plus `structuredContent`).
795
+ */
796
+ function validateResult(result) {
797
+ if (typeof result === "string") return null;
798
+ if (!Array.isArray(result) && !isPlainObject(result)) return require_message_text.msg`result must be a string, a content array or an object, got ${shown(result)}`;
799
+ const json = jsonProblem(result, "result");
800
+ if (json) return json;
801
+ if (Array.isArray(result)) return CONTENT(result, "result");
802
+ if (!isFullResult(result)) return null;
803
+ for (const key of presentKeys(result)) if (!FULL_RESULT_KEYS.has(key)) return require_message_text.msg`result has unknown key ${shown(key)}`;
804
+ return FULL_RESULT(result, "result");
805
+ }
806
+ /**
807
+ * MCP `Tool.inputSchema`: a JSON Schema object with `type: "object"` at the
808
+ * root; `properties`, when present, maps names to schema objects, and
809
+ * `required`, when present, lists names. Any other JSON Schema keyword is
810
+ * allowed.
811
+ */
812
+ const INPUT_SCHEMA = shape({ type: (v, p) => v === "object" ? null : require_message_text.msg`${pathPart(p)} must be "object", got ${shown(v)}` }, {
813
+ properties: (v, p) => {
814
+ if (!isPlainObject(v)) return mustBeObject(v, p);
815
+ for (const key of presentKeys(v)) {
816
+ const problem = mustBeObject(v[key], pathKey(p, key));
817
+ if (problem) return problem;
818
+ }
819
+ return null;
820
+ },
821
+ required: mustBeStringArray
822
+ });
823
+ function inputSchemaProblem(schema) {
824
+ if (!isPlainObject(schema)) return mustBeObject(schema, "inputSchema");
825
+ return jsonProblem(schema, "inputSchema") ?? INPUT_SCHEMA(schema, "inputSchema");
826
+ }
827
+ /** A finite number >= 0 (FA3 `atMs`, FA4 `durationMs`). */
828
+ const mustBeNonNegative = (v, p) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? null : require_message_text.msg`${pathPart(p)} must be a number >= 0, got ${shown(v)}`;
829
+ /** FA2 `recorded`. Other keys are not checked. */
830
+ const RECORDED = shape({
831
+ upstream: mustBeString,
832
+ protocolVersion: mustBeString,
833
+ aimockVersion: mustBeString,
834
+ at: mustBeIsoDateTime
835
+ }, { serverInfo: mustBeObject });
836
+ /** One FA3 notification. Other keys are not checked. */
837
+ const NOTIFICATION = shape({
838
+ atMs: mustBeNonNegative,
839
+ method: mustBeString,
840
+ params: mustBeObject
841
+ });
842
+ /** FA1: an array of JSON objects, each with a string `name`, no name twice. */
843
+ function listProblem(v) {
844
+ if (!Array.isArray(v)) return require_message_text.msg`list must be an array of tool objects, got ${shown(v)}`;
845
+ const json = jsonProblem(v, "list");
846
+ if (json) return json;
847
+ const names = /* @__PURE__ */ new Set();
848
+ for (let i = 0; i < v.length; i++) {
849
+ const tool = v[i];
850
+ if (!isPlainObject(tool) || typeof tool.name !== "string") return require_message_text.msg`list[${i}] must be a tool object with a string name, got ${shown(tool)}`;
851
+ if (names.has(tool.name)) return require_message_text.msg`list[${i}] repeats the tool name ${require_message_text.quote(tool.name)}`;
852
+ names.add(tool.name);
853
+ }
854
+ return null;
855
+ }
856
+ /** FA2. */
857
+ function recordedProblem(v) {
858
+ if (!isPlainObject(v)) return require_message_text.msg`recorded must be a JSON object, got ${shown(v)}`;
859
+ return jsonProblem(v, "recorded") ?? RECORDED(v, "recorded");
860
+ }
861
+ /** FA3. */
862
+ function notificationsProblem(v) {
863
+ if (!Array.isArray(v)) return require_message_text.msg`notifications must be an array, got ${shown(v)}`;
864
+ return jsonProblem(v, "notifications") ?? arrayOf(NOTIFICATION, "an array of notifications")(v, "notifications");
865
+ }
866
+ /**
867
+ * A malformed FA key of one block (and, for FA3/FA4, one entry): the 1.44.0
868
+ * rule `mcp-fakes/bad-block:h`, with the `record-replay/fa-*` value first in
869
+ * the message detail (`[mcp-fakes/bad-block:h] [record-replay/fa-list] ...`).
870
+ */
871
+ function faError(ctx, faRule, detail, entryId = null) {
872
+ return new require_fixture_loader.FixtureLoadError({
873
+ rule: "mcp-fakes/bad-block:h",
874
+ file: ctx.source,
875
+ blockId: ctx.blockId,
876
+ entryId,
877
+ detail: require_message_text.msg`[${require_message_text.fixed(faRule)}] ${detail}`
878
+ });
879
+ }
880
+ /**
881
+ * Validate one call entry of the input snapshot. Every error goes to
882
+ * `errors`; returns its entry id whether or not it is clean, so that the
883
+ * block's entry-id uniqueness check also sees invalid entries, and the frozen call
884
+ * only when it is clean. `toolName` is the tool's name, or a `tools[<i>]`
885
+ * placeholder when that name is invalid.
886
+ */
887
+ function validateCall(raw, toolName, label, index, ctx, errors) {
888
+ const idRaw = isPlainObject(raw) ? raw.id : void 0;
889
+ const entryId = `${ctx.blockId}:${localOf(toolName, index, idRaw)}`;
890
+ const where = require_message_text.msg`${label} calls[${index}]`;
891
+ const bad = (letter, detail) => {
892
+ errors.push(badBlock(ctx, letter, require_message_text.msg`${where} ${detail}`, entryId));
893
+ };
894
+ if (!isPlainObject(raw)) {
895
+ bad("d", require_message_text.msg`must be an object, got ${shown(raw)}`);
896
+ return {
897
+ entryId,
898
+ call: null
899
+ };
900
+ }
901
+ const before = errors.length;
902
+ for (const key of presentKeys(raw)) if (!CALL_KEYS.has(key)) bad("h", require_message_text.msg`has unknown key ${shown(key)}`);
903
+ if (has(raw, "notifications")) {
904
+ const problem = notificationsProblem(raw.notifications);
905
+ if (problem) errors.push(faError(ctx, "record-replay/fa-notifications", require_message_text.msg`${where} ${problem}`, entryId));
906
+ }
907
+ if (has(raw, "durationMs")) {
908
+ const problem = mustBeNonNegative(raw.durationMs, "durationMs");
909
+ if (problem) errors.push(faError(ctx, "record-replay/fa-duration", require_message_text.msg`${where} ${problem}`, entryId));
910
+ }
911
+ if (has(raw, "id") && !isNonEmptyString(raw.id)) bad("d", require_message_text.msg`id must be a non-empty string, got ${shown(raw.id)}`);
912
+ if (has(raw, "anyArgs") && raw.anyArgs !== true) bad("j", require_message_text.msg`anyArgs must be true, got ${shown(raw.anyArgs)}`);
913
+ const hasArgs = has(raw, "args");
914
+ if (hasArgs === has(raw, "anyArgs")) bad("d", require_message_text.msg`needs exactly one of args / anyArgs`);
915
+ if (hasArgs) {
916
+ const problem = isPlainObject(raw.args) ? jsonProblem(raw.args, "args") : require_message_text.msg`args must be a JSON object, got ${shown(raw.args)}`;
917
+ if (problem) bad("d", problem);
918
+ }
919
+ const hasResult = has(raw, "result");
920
+ const hasError = has(raw, "error");
921
+ if (hasResult === hasError) bad("d", require_message_text.msg`needs exactly one of result / error`);
922
+ if (hasError && typeof raw.error !== "string") bad("d", require_message_text.msg`error must be a string, got ${shown(raw.error)}`);
923
+ if (hasResult) {
924
+ const problem = validateResult(raw.result);
925
+ if (problem) bad("d", problem);
926
+ }
927
+ if (errors.length > before) return {
928
+ entryId,
929
+ call: null
930
+ };
931
+ const match = hasArgs ? { args: copyJson(raw.args, true) } : { anyArgs: true };
932
+ const answer = hasResult ? { result: copyJson(raw.result, true) } : { error: raw.error };
933
+ const recorded = {};
934
+ if (has(raw, "notifications")) recorded.notifications = copyJson(raw.notifications, true);
935
+ if (has(raw, "durationMs")) recorded.durationMs = raw.durationMs;
936
+ return {
937
+ entryId,
938
+ call: Object.freeze({
939
+ id: entryId,
940
+ index,
941
+ ...match,
942
+ ...answer,
943
+ ...recorded
944
+ })
945
+ };
946
+ }
947
+ /**
948
+ * Validate one tool of the input snapshot. Every error goes to `errors`, and
949
+ * the entry ids of all its calls (valid or not; with an invalid tool name,
950
+ * only the user ids) to `entryIds`; returns the frozen tool only when it is
951
+ * clean.
952
+ */
953
+ function validateTool(raw, index, ctx, errors, entryIds) {
954
+ const where = `tools[${index}]`;
955
+ const place = require_message_text.msg`tools[${index}]`;
956
+ if (!isPlainObject(raw)) {
957
+ errors.push(badBlock(ctx, "g", require_message_text.msg`${place} must be an object, got ${shown(raw)}`));
958
+ return null;
959
+ }
960
+ const before = errors.length;
961
+ for (const key of presentKeys(raw)) if (!TOOL_KEYS.has(key)) errors.push(badBlock(ctx, "h", require_message_text.msg`${place} has unknown key ${shown(key)}`));
962
+ const name = isNonEmptyString(raw.name) ? raw.name : null;
963
+ if (name === null) errors.push(badBlock(ctx, "g", require_message_text.msg`${place} needs a string name, got ${shown(raw.name)}`));
964
+ const tw = name === null ? place : require_message_text.msg`tool ${require_message_text.quote(name)}`;
965
+ if (has(raw, "description") && typeof raw.description !== "string") errors.push(badBlock(ctx, "g", require_message_text.msg`${tw} description must be a string, got ${shown(raw.description)}`));
966
+ if (has(raw, "inputSchema")) {
967
+ const problem = inputSchemaProblem(raw.inputSchema);
968
+ if (problem) errors.push(badBlock(ctx, "g", require_message_text.msg`${tw} ${problem}`));
969
+ }
970
+ const calls = [];
971
+ if (!Array.isArray(raw.calls) || raw.calls.length === 0) errors.push(badBlock(ctx, "g", require_message_text.msg`${tw} calls must be a non-empty array, got ${shown(raw.calls)}`));
972
+ else raw.calls.forEach((rawCall, i) => {
973
+ const { entryId, call } = validateCall(rawCall, name ?? where, tw, i, ctx, errors);
974
+ if (name !== null || isPlainObject(rawCall) && isNonEmptyString(rawCall.id)) entryIds.push(entryId);
975
+ if (call) calls.push(call);
976
+ });
977
+ if (errors.length > before || name === null) return null;
978
+ const tool = {
979
+ name,
980
+ calls: Object.freeze(calls)
981
+ };
982
+ if (typeof raw.description === "string") tool.description = raw.description;
983
+ if (isPlainObject(raw.inputSchema)) tool.inputSchema = copyJson(raw.inputSchema, true);
984
+ return Object.freeze(tool);
985
+ }
986
+ /**
987
+ * Validate one block of the input snapshot. Every check runs and each problem
988
+ * goes to `errors` (no first-stop), with two limits: a block, tool or call
989
+ * entry that is not an object is not checked further, and one `result` or
990
+ * `inputSchema` value reports only its first problem. Entry-id uniqueness
991
+ * inside the block is checked here over every declared entry, valid or not;
992
+ * uniqueness across blocks is the caller's.
993
+ */
994
+ function validateBlock(raw, source, blockIndex, load, errors) {
995
+ const ctx = {
996
+ source,
997
+ load,
998
+ blockIndex,
999
+ blockId: blockIdOf(source, load, blockIndex)
1000
+ };
1001
+ if (!isPlainObject(raw)) {
1002
+ errors.push(badBlock(ctx, "i", require_message_text.msg`mcpFakes block must be an object, got ${shown(raw)}`));
1003
+ return {
1004
+ block: null,
1005
+ entryIds: [],
1006
+ warnings: []
1007
+ };
1008
+ }
1009
+ const before = errors.length;
1010
+ for (const key of presentKeys(raw)) if (!BLOCK_KEYS.has(key)) errors.push(badBlock(ctx, "h", require_message_text.msg`block has unknown key ${shown(key)}`));
1011
+ if (has(raw, "list")) {
1012
+ const problem = listProblem(raw.list);
1013
+ if (problem) errors.push(faError(ctx, "record-replay/fa-list", problem));
1014
+ }
1015
+ if (has(raw, "recorded")) {
1016
+ const problem = recordedProblem(raw.recorded);
1017
+ if (problem) errors.push(faError(ctx, "record-replay/fa-recorded", problem));
1018
+ }
1019
+ let timing = "recorded";
1020
+ if (has(raw, "timing")) if (raw.timing === "recorded" || raw.timing === "immediate") timing = raw.timing;
1021
+ else errors.push(faError(ctx, "record-replay/fa-timing", require_message_text.msg`timing must be "recorded" or "immediate", got ${shown(raw.timing)}`));
1022
+ const scope = validateScope(raw.scope, ctx, errors);
1023
+ let undeclared;
1024
+ if (has(raw, "undeclaredTools")) if (raw.undeclaredTools === "allow" || raw.undeclaredTools === "deny") undeclared = raw.undeclaredTools;
1025
+ else errors.push(badBlock(ctx, "g", require_message_text.msg`undeclaredTools must be "allow" or "deny", got ${shown(raw.undeclaredTools)}`));
1026
+ if (scope === "shared" && undeclared === "deny") errors.push(badBlock(ctx, "c", require_message_text.msg`scope "shared" cannot have undeclaredTools "deny"`));
1027
+ let mount = MCP_FAKES_DEFAULT_MOUNT;
1028
+ if (has(raw, "mount")) if (typeof raw.mount === "string" && raw.mount.startsWith("/")) mount = raw.mount;
1029
+ else errors.push(badBlock(ctx, "g", require_message_text.msg`mount must be a string starting with "/", got ${shown(raw.mount)}`));
1030
+ const tools = [];
1031
+ const declaredIds = [];
1032
+ if (!Array.isArray(raw.tools)) errors.push(badBlock(ctx, "g", require_message_text.msg`tools must be an array, got ${shown(raw.tools)}`));
1033
+ else if (raw.tools.length === 0) {
1034
+ if (undeclared !== "deny" || scope === "shared") errors.push(badBlock(ctx, "g", require_message_text.msg`tools is empty; an empty tools array (tools: []) is allowed only on a scoped (not "shared") block with undeclaredTools "deny"`));
1035
+ } else {
1036
+ const names = /* @__PURE__ */ new Set();
1037
+ raw.tools.forEach((rawTool, i) => {
1038
+ const name = isPlainObject(rawTool) && isNonEmptyString(rawTool.name) ? rawTool.name : null;
1039
+ const duplicate = name !== null && names.has(name);
1040
+ if (duplicate) errors.push(badBlock(ctx, "e", require_message_text.msg`tool ${require_message_text.quote(name)} is declared twice in one block`));
1041
+ else if (name !== null) names.add(name);
1042
+ const tool = validateTool(rawTool, i, ctx, errors, duplicate ? [] : declaredIds);
1043
+ if (tool && !duplicate) tools.push(tool);
1044
+ });
1045
+ }
1046
+ const seen = /* @__PURE__ */ new Set();
1047
+ for (const id of declaredIds) {
1048
+ if (seen.has(id)) errors.push(badBlock(ctx, "f", require_message_text.msg`entry id ${require_message_text.quote(id)} is not unique`, id));
1049
+ seen.add(id);
1050
+ }
1051
+ const entryIds = [...seen];
1052
+ if (errors.length > before || scope === null) return {
1053
+ block: null,
1054
+ entryIds,
1055
+ warnings: []
1056
+ };
1057
+ const warnings = [];
1058
+ for (const tool of tools) {
1059
+ let lastAny = null;
1060
+ for (const call of tool.calls) if (call.anyArgs) lastAny = call;
1061
+ else if (lastAny) warnings.push({
1062
+ file: source,
1063
+ blockId: ctx.blockId,
1064
+ entryId: call.id,
1065
+ message: require_message_text.build(require_message_text.msg`entry ${require_message_text.quote(call.id)} is shadowed until the preceding anyArgs entry ${require_message_text.quote(lastAny.id)} is consumed`)
1066
+ });
1067
+ }
1068
+ return {
1069
+ block: Object.freeze({
1070
+ source,
1071
+ load,
1072
+ blockIndex,
1073
+ blockId: ctx.blockId,
1074
+ scope,
1075
+ tier: tierOf(scope),
1076
+ mount,
1077
+ ...undeclared !== void 0 ? { undeclaredTools: undeclared } : {},
1078
+ tools: Object.freeze(tools),
1079
+ ...has(raw, "list") ? { list: copyJson(raw.list, true) } : {},
1080
+ ...has(raw, "recorded") ? { recorded: copyJson(raw.recorded, true) } : {},
1081
+ timing
1082
+ }),
1083
+ entryIds,
1084
+ warnings
1085
+ };
1086
+ }
1087
+ /**
1088
+ * Standalone validation of a raw `mcpFakes` value (one object, or an array of
1089
+ * blocks) from one `source`. It takes the plain-data snapshot of `raw`
1090
+ * (getters are not run, inherited fields not read) and reports every
1091
+ * bad-block case (a)-(j) found as a `FixtureLoadError` naming the source and
1092
+ * block; entry-id collisions inside a block are (f). Never throws. Ids use the
1093
+ * first-load form (empty `load`). The store does not use this function:
1094
+ * `McpFakeStore.add` runs the same per-block checks (`validateBlock`) with its
1095
+ * own load suffix, and adds the entry-id check across blocks (against the
1096
+ * mount and the earlier blocks of the same input). `blocks` holds only the
1097
+ * clean blocks (deeply frozen), `warnings` only their shadowed-entry warnings.
1098
+ */
1099
+ function validateMcpFakes(raw, source) {
1100
+ const errors = [];
1101
+ const warnings = [];
1102
+ const blocks = [];
1103
+ const input = snapshotInput(raw);
1104
+ let items;
1105
+ if (Array.isArray(input) && input.length === 0) {
1106
+ errors.push(badBlock({
1107
+ source,
1108
+ blockId: null
1109
+ }, "i", require_message_text.msg`mcpFakes is an empty array`));
1110
+ return {
1111
+ blocks,
1112
+ errors,
1113
+ warnings
1114
+ };
1115
+ } else if (Array.isArray(input)) items = input.map((item, i) => ({
1116
+ raw: item,
1117
+ blockIndex: i
1118
+ }));
1119
+ else if (isPlainObject(input)) items = [{
1120
+ raw: input,
1121
+ blockIndex: null
1122
+ }];
1123
+ else {
1124
+ errors.push(badBlock({
1125
+ source,
1126
+ blockId: null
1127
+ }, "i", require_message_text.msg`mcpFakes must be an object or an array, got ${shown(input)}`));
1128
+ return {
1129
+ blocks,
1130
+ errors,
1131
+ warnings
1132
+ };
1133
+ }
1134
+ for (const item of items) {
1135
+ const outcome = validateBlock(item.raw, source, item.blockIndex, "", errors);
1136
+ if (outcome.block) {
1137
+ blocks.push(outcome.block);
1138
+ warnings.push(...outcome.warnings);
1139
+ }
1140
+ }
1141
+ return {
1142
+ blocks,
1143
+ errors,
1144
+ warnings
1145
+ };
1146
+ }
1147
+ function blockEntryIds(block) {
1148
+ return block.tools.flatMap((t) => t.calls.map((c) => c.id));
1149
+ }
1150
+ /**
1151
+ * Turn a call entry's `result` or `error` into a `tools/call` result. The
1152
+ * returned object is a fresh copy the caller may change. A full
1153
+ * `CallToolResult` (validation guarantees any object with one of its keys
1154
+ * has a `content` array) is copied unchanged, with no `isError` added;
1155
+ * every other shape sets `isError`. Throws when the entry has
1156
+ * neither (validation makes that a bad block (d)).
1157
+ */
1158
+ function toCallToolResult(call) {
1159
+ if (call.error !== void 0) return {
1160
+ content: [{
1161
+ type: "text",
1162
+ text: call.error
1163
+ }],
1164
+ isError: true
1165
+ };
1166
+ const result = call.result;
1167
+ if (result === void 0 || result === null) throw new Error(require_message_text.build(require_message_text.msg`toCallToolResult: call entry has neither result nor error`));
1168
+ if (typeof result === "string") return {
1169
+ content: [{
1170
+ type: "text",
1171
+ text: result
1172
+ }],
1173
+ isError: false
1174
+ };
1175
+ if (Array.isArray(result)) return {
1176
+ content: copyJson(result),
1177
+ isError: false
1178
+ };
1179
+ const obj = copyJson(result);
1180
+ if (Array.isArray(obj.content)) return {
1181
+ ...obj,
1182
+ content: obj.content
1183
+ };
1184
+ return {
1185
+ content: [{
1186
+ type: "text",
1187
+ text: JSON.stringify(obj)
1188
+ }],
1189
+ structuredContent: obj,
1190
+ isError: false
1191
+ };
1192
+ }
1193
+ function jsonEqual(a, b) {
1194
+ if (a === b) return true;
1195
+ if (Array.isArray(a) || Array.isArray(b)) {
1196
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
1197
+ return a.every((v, i) => jsonEqual(v, b[i]));
1198
+ }
1199
+ if (isPlainObject(a) && isPlainObject(b)) {
1200
+ const ak = ownKeys(a);
1201
+ const bk = ownKeys(b);
1202
+ if (ak.length !== bk.length) return false;
1203
+ return ak.every((k) => hasOwnKey(b, k) && jsonEqual(a[k], b[k]));
1204
+ }
1205
+ return false;
1206
+ }
1207
+ /**
1208
+ * Argument equality: key order ignored, array order matters, no coercion,
1209
+ * numbers equal as parsed JSON, absent `arguments` is `{}`, non-object
1210
+ * `arguments` never equals an `args` entry (only an `anyArgs` entry matches
1211
+ * it).
1212
+ */
1213
+ function mcpArgsEqual(expected, received) {
1214
+ const actual = received === void 0 ? {} : received;
1215
+ if (!isPlainObject(actual)) return false;
1216
+ return jsonEqual(expected, actual);
1217
+ }
1218
+ /**
1219
+ * `path` and one more key: `.key` for a short identifier, else `[<key>]` with
1220
+ * the key JSON-quoted whole. The path is cut once, where a message shows it
1221
+ * (`pathPart`).
1222
+ */
1223
+ function pathKey(path, key) {
1224
+ return key.length <= require_constants.MCP_FAKES_ECHO_LIMIT && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${path}.${key}` : `${path}[${require_message_text.quoteJson(key)}]`;
1225
+ }
1226
+ /**
1227
+ * A JSON path (built by `pathKey`) as a message part: user data, cut as JSON
1228
+ * text, so the cut never splits an escape of a quoted key.
1229
+ */
1230
+ function pathPart(path) {
1231
+ return require_message_text.jsonText(path);
1232
+ }
1233
+ function diffAt(path, expected, received) {
1234
+ if (Array.isArray(expected)) {
1235
+ if (!Array.isArray(received)) return require_message_text.msg`${pathPart(path)}: expected an array, received ${shown(received)}`;
1236
+ if (expected.length !== received.length) return require_message_text.msg`${pathPart(path)}: expected an array of length ${expected.length}, received length ${received.length}`;
1237
+ for (let i = 0; i < expected.length; i++) {
1238
+ const d = diffAt(`${path}[${i}]`, expected[i], received[i]);
1239
+ if (d) return d;
1240
+ }
1241
+ return null;
1242
+ }
1243
+ if (isPlainObject(expected)) {
1244
+ if (!isPlainObject(received)) return require_message_text.msg`${pathPart(path)}: expected an object, received ${shown(received)}`;
1245
+ for (const key of ownKeys(expected)) {
1246
+ const p = pathKey(path, key);
1247
+ if (!hasOwnKey(received, key)) return require_message_text.msg`${pathPart(p)}: expected ${shown(expected[key])}, received nothing (key missing)`;
1248
+ const d = diffAt(p, expected[key], received[key]);
1249
+ if (d) return d;
1250
+ }
1251
+ for (const key of ownKeys(received)) if (!hasOwnKey(expected, key)) return require_message_text.msg`${pathPart(pathKey(path, key))}: unexpected key, received ${shown(received[key])}`;
1252
+ return null;
1253
+ }
1254
+ if (expected === received) return null;
1255
+ return require_message_text.msg`${pathPart(path)}: expected ${shown(expected)}, received ${shown(received)}`;
1256
+ }
1257
+ /**
1258
+ * A JSON path to the first unequal value, with both values (the mismatch
1259
+ * detail). Returns `""` when `mcpArgsEqual(expected, received)` holds. The
1260
+ * path and each value are cut at `MCP_FAKES_ECHO_LIMIT` chars and the whole
1261
+ * text at 1000, each part once.
1262
+ */
1263
+ function firstDifference(expected, received) {
1264
+ const diff = diffAt("$", expected, received === void 0 ? {} : received);
1265
+ return diff ? require_message_text.build(diff, DIFF_LIMIT) : "";
1266
+ }
1267
+ /**
1268
+ * Approximate count of differences, used to pick the closest declared entry.
1269
+ * A key or array element present on one side only counts as 1, whatever its
1270
+ * subtree size.
1271
+ */
1272
+ function diffCount(expected, received) {
1273
+ if (Array.isArray(expected) && Array.isArray(received)) {
1274
+ const n = Math.max(expected.length, received.length);
1275
+ let count = 0;
1276
+ for (let i = 0; i < n; i++) if (i >= expected.length || i >= received.length) count += 1;
1277
+ else count += diffCount(expected[i], received[i]);
1278
+ return count;
1279
+ }
1280
+ if (isPlainObject(expected) && isPlainObject(received)) {
1281
+ let count = 0;
1282
+ for (const key of new Set([...ownKeys(expected), ...ownKeys(received)])) {
1283
+ const inE = hasOwnKey(expected, key);
1284
+ const inR = hasOwnKey(received, key);
1285
+ count += inE && inR ? diffCount(expected[key], received[key]) : 1;
1286
+ }
1287
+ return count;
1288
+ }
1289
+ return jsonEqual(expected, received) ? 0 : 1;
1290
+ }
1291
+ function callMatches(call, received) {
1292
+ return call.anyArgs === true || call.args !== void 0 && mcpArgsEqual(call.args, received);
1293
+ }
1294
+ var McpFakeStore = class {
1295
+ blocks = [];
1296
+ /** Entry id → the block id that owns it, for every loaded entry. */
1297
+ ids = /* @__PURE__ */ new Map();
1298
+ /** Block id → its `source`, for every loaded block. */
1299
+ blockIds = /* @__PURE__ */ new Map();
1300
+ /**
1301
+ * Consumption state: supplied test id → consumed entry ids. Map insertion
1302
+ * order is the FIFO order of the cap.
1303
+ */
1304
+ consumed = /* @__PURE__ */ new Map();
1305
+ /** Consumed entry ids for requests with no test id. Never evicted. */
1306
+ untagged = /* @__PURE__ */ new Set();
1307
+ /**
1308
+ * Test ids whose state the cap evicted. A claim from one of them, of a tool
1309
+ * an applicable block declares, is `evicted` until the test id is reset; a
1310
+ * mark never ages out, because a forgotten mark would silently replay
1311
+ * entries the test id already consumed. Memory stays bounded per evicted
1312
+ * test id: one id string, against the entry-id set the cap dropped. The set only grows with distinct test ids, and every
1313
+ * reset door clears it (`resetState`, `clear`). A cap of `0` evicts
1314
+ * nothing, so marks made under an earlier cap stay until reset.
1315
+ */
1316
+ evicted = /* @__PURE__ */ new Set();
1317
+ /**
1318
+ * The last `<n>` of `code#<n>` / `control-api#<n>` used by a run-time
1319
+ * addition (see `nextRuntimeNumber`). Never rewound.
1320
+ */
1321
+ runtimeAdds = 0;
1322
+ /**
1323
+ * The `<k>` of `@<k>`: loads per source string, skipping a `@<k>` that a
1324
+ * loaded block id already starts (see `add`). Never rewound.
1325
+ */
1326
+ loadCounts = /* @__PURE__ */ new Map();
1327
+ maxTestIds;
1328
+ /**
1329
+ * RP2 fake event logs: supplied test id → its events, oldest first. Map
1330
+ * insertion order is the FIFO order of the test-id cap.
1331
+ */
1332
+ events = /* @__PURE__ */ new Map();
1333
+ /** The event log of requests with no test id: a ring of the newest events. */
1334
+ untaggedEvents = [];
1335
+ /** The untagged event log dropped an event (C9). */
1336
+ untaggedOverflow = false;
1337
+ /**
1338
+ * C9: test ids whose event log overflowed or was evicted, so their report
1339
+ * is incomplete (`evicted: true`). Report-only: `claim`, `snapshot` and
1340
+ * `policy` never read it, so tool calls are unchanged.
1341
+ */
1342
+ reportOverflow = /* @__PURE__ */ new Set();
1343
+ constructor(options = {}) {
1344
+ this.maxTestIds = checkCap(options.maxTestIds ?? MCP_FAKES_DEFAULT_MAX_TEST_IDS);
1345
+ }
1346
+ /**
1347
+ * Set the FIFO cap on distinct test ids; an MCP mount can pass the
1348
+ * journal's `fixtureCountsMaxTestIdsCap`. A lower cap evicts the oldest test
1349
+ * ids at once. `0` is unbounded: nothing is evicted, and test ids already
1350
+ * marked evicted stay marked until reset. An invalid cap throws a
1351
+ * `RangeError` and leaves the cap unchanged.
1352
+ */
1353
+ setMaxTestIds(cap) {
1354
+ this.maxTestIds = checkCap(cap);
1355
+ this.enforceCap();
1356
+ }
1357
+ /**
1358
+ * Take one plain-data snapshot of `sources` and `origin` (getters are not
1359
+ * run, inherited fields not read, holes, symbol keys, proxies that throw and
1360
+ * non-plain objects are errors), validate the whole snapshot, derive entry
1361
+ * ids and their counters, check that every block id and every entry id is
1362
+ * unique against the mount and inside the input (bad block (f), naming the
1363
+ * block that already owns the id and, for a block id, that block's source),
1364
+ * then append. Every block is checked,
1365
+ * valid or not (no first-stop; `validateBlock` says which checks stop
1366
+ * early). On any error, throws one `McpFakesAddError` carrying every error
1367
+ * found, and the warnings of the blocks that passed every check, and adds
1368
+ * nothing (no counter is consumed); it never throws anything else for a bad
1369
+ * input (a throw from a defect in aimock itself is not caught). An empty
1370
+ * input, an invalid `origin` or an invalid source item (not an object, an
1371
+ * empty `source`, a `blockIndex` that is not `null` or a non-negative
1372
+ * integer) is a bad block (i).
1373
+ *
1374
+ * With `kind: "file"`, consecutive sources with the same `source` string
1375
+ * and increasing `blockIndex` form one load (one `@<k>`). A load whose
1376
+ * derived block ids a loaded block already has (a file named `"a@2"`
1377
+ * starts the ids of `@2`; a file named `"a[0]"` has the id of block 0 of
1378
+ * the first load) takes the next free `@<k>`, so a file name never blocks
1379
+ * later loads of another source. A file loaded after the block whose id
1380
+ * its name spells is still the bad block (f). With a run-time origin,
1381
+ * blocks are numbered per `McpFakeAddOrigin`; a block given with a null
1382
+ * `blockIndex` keeps the single-object depth limit.
1383
+ */
1384
+ add(sources, origin) {
1385
+ const originData = snapshotInput(origin);
1386
+ const kind = isPlainObject(originData) ? originData.kind : void 0;
1387
+ if (kind !== "file" && kind !== "code" && kind !== "control-api" && kind !== "record") throw new McpFakesAddError([badBlock({
1388
+ source: null,
1389
+ blockId: null
1390
+ }, "i", isPlainObject(originData) ? require_message_text.msg`origin.kind must be "file", "code", "control-api" or "record", got ${shown(kind)}` : require_message_text.msg`origin must be an object { kind }, got ${shown(originData)}`)], []);
1391
+ const runtimeNumber = kind === "file" ? null : this.nextRuntimeNumber(kind);
1392
+ const runtimeSource = runtimeNumber === null ? null : `${kind}#${runtimeNumber}`;
1393
+ const input = snapshotInput(sources, MCP_FAKES_MAX_DEPTH + 2);
1394
+ if (!Array.isArray(input) || input.length === 0) {
1395
+ const detail = Array.isArray(input) ? require_message_text.msg`mcpFakes holds no blocks` : require_message_text.msg`the blocks to add must be an array, got ${shown(input)}`;
1396
+ throw new McpFakesAddError([badBlock({
1397
+ source: runtimeSource,
1398
+ blockId: null
1399
+ }, "i", detail)], []);
1400
+ }
1401
+ const errors = [];
1402
+ const warnings = [];
1403
+ const accepted = [];
1404
+ /** Entry id → owning block id, for this input. */
1405
+ const newIds = /* @__PURE__ */ new Map();
1406
+ /** Block id → source, for this input. */
1407
+ const newBlockIds = /* @__PURE__ */ new Map();
1408
+ const pendingLoads = /* @__PURE__ */ new Map();
1409
+ const items = [];
1410
+ input.forEach((item, position) => {
1411
+ const fail = (detail, file = runtimeSource) => {
1412
+ errors.push(badBlock({
1413
+ source: file,
1414
+ blockId: null
1415
+ }, "i", require_message_text.msg`blocks[${position}] ${detail}`));
1416
+ };
1417
+ if (!isPlainObject(item)) {
1418
+ fail(require_message_text.msg`must be an object { source, blockIndex, raw }, got ${shown(item)}`);
1419
+ return;
1420
+ }
1421
+ const { source, blockIndex } = item;
1422
+ if (runtimeSource === null && !isNonEmptyString(source)) {
1423
+ fail(require_message_text.msg`source must be a non-empty string, got ${shown(source)}`);
1424
+ return;
1425
+ }
1426
+ const sourceText = typeof source === "string" ? source : "";
1427
+ const index = blockIndex === null || typeof blockIndex === "number" && Number.isSafeInteger(blockIndex) && blockIndex >= 0 ? blockIndex : void 0;
1428
+ if (index === void 0) {
1429
+ fail(require_message_text.msg`blockIndex must be null or a non-negative integer, got ${shown(blockIndex)}`, runtimeSource ?? sourceText);
1430
+ return;
1431
+ }
1432
+ items.push({
1433
+ position,
1434
+ source: sourceText,
1435
+ blockIndex: index,
1436
+ raw: item.raw
1437
+ });
1438
+ });
1439
+ const singleObject = input.length === 1 && items.length === 1 && items[0].blockIndex === null;
1440
+ /** Two consecutive file items with one source and a rising block index are one load. */
1441
+ const sameLoad = (prev, item) => prev !== void 0 && prev.source === item.source && prev.blockIndex !== null && item.blockIndex !== null && item.blockIndex > prev.blockIndex;
1442
+ let load = "";
1443
+ for (const [at, item] of items.entries()) {
1444
+ const source = runtimeSource ?? item.source;
1445
+ let blockIndex = item.blockIndex;
1446
+ if (runtimeSource === null) {
1447
+ if (!sameLoad(items[at - 1], item)) {
1448
+ const indices = [];
1449
+ for (let j = at; j < items.length && (j === at || sameLoad(items[j - 1], items[j])); j++) {
1450
+ const index = items[j].blockIndex;
1451
+ if (index !== null) indices.push(index);
1452
+ }
1453
+ let k = (pendingLoads.get(source) ?? this.loadCounts.get(source) ?? 0) + 1;
1454
+ while (k > 1 ? this.blockIdPrefixTaken(blockIdOf(source, `@${k}`, null), newBlockIds) : indices.some((index) => {
1455
+ const derived = blockIdOf(source, "", index);
1456
+ return (this.blockIds.get(derived) ?? newBlockIds.get(derived)) === derived;
1457
+ })) k += 1;
1458
+ pendingLoads.set(source, k);
1459
+ load = k === 1 ? "" : `@${k}`;
1460
+ }
1461
+ } else blockIndex = singleObject ? null : item.position;
1462
+ const outcome = validateBlock(item.blockIndex === null ? item.raw : capDepth(item.raw, MCP_FAKES_MAX_DEPTH - 1), source, blockIndex, load, errors);
1463
+ const blockId = blockIdOf(source, load, blockIndex);
1464
+ const blockOwner = this.blockIds.get(blockId) ?? newBlockIds.get(blockId);
1465
+ let collided = blockOwner !== void 0;
1466
+ if (blockOwner !== void 0) errors.push(badBlock({
1467
+ source,
1468
+ blockId
1469
+ }, "f", blockCollisionDetail(source, blockOwner, this.blockIds.has(blockId))));
1470
+ else newBlockIds.set(blockId, source);
1471
+ for (const id of outcome.entryIds) {
1472
+ const loaded = this.ids.get(id);
1473
+ const earlier = newIds.get(id);
1474
+ if (!newIds.has(id)) newIds.set(id, blockId);
1475
+ if (blockOwner !== void 0) continue;
1476
+ const where = loaded !== void 0 ? require_message_text.msg`is already loaded on this mount, in block ${require_message_text.quote(loaded)}` : earlier !== void 0 ? require_message_text.msg`collides with an entry of block ${require_message_text.quote(earlier)} earlier in the same input` : null;
1477
+ if (where === null) continue;
1478
+ errors.push(badBlock({
1479
+ source,
1480
+ blockId
1481
+ }, "f", require_message_text.msg`entry id ${require_message_text.quote(id)} ${where}`, id));
1482
+ collided = true;
1483
+ }
1484
+ if (outcome.block && !collided) {
1485
+ accepted.push(outcome.block);
1486
+ warnings.push(...outcome.warnings);
1487
+ }
1488
+ }
1489
+ if (errors.length > 0) throw new McpFakesAddError(errors, warnings);
1490
+ for (const [source, k] of pendingLoads) this.loadCounts.set(source, k);
1491
+ if (runtimeNumber !== null) this.runtimeAdds = runtimeNumber;
1492
+ for (const [id, owner] of newIds) this.ids.set(id, owner);
1493
+ for (const [blockId, source] of newBlockIds) this.blockIds.set(blockId, source);
1494
+ this.blocks.push(...accepted);
1495
+ return { warnings };
1496
+ }
1497
+ /**
1498
+ * The `<n>` for the next run-time add of `kind`: one more than the last one
1499
+ * used, skipping any number whose `<kind>#<n>` already starts a loaded block
1500
+ * id (and so every entry id of that block). A file source may be spelled
1501
+ * `code#1`; without the skip, every later run-time add would collide with it
1502
+ * and fail, and a failed add does not advance `<n>`. Blocks loaded after a
1503
+ * run-time add still collide with it as usual (the later block fails).
1504
+ */
1505
+ nextRuntimeNumber(kind) {
1506
+ let n = this.runtimeAdds + 1;
1507
+ while (this.blockIdPrefixTaken(`${kind}#${n}`)) n += 1;
1508
+ return n;
1509
+ }
1510
+ /**
1511
+ * A loaded block id (or one in `pending`, the blocks earlier in the same
1512
+ * input) starts with `prefix` and does not go on with a digit, so `prefix`
1513
+ * would start a colliding block or entry id (`a@2` is taken by `a@2` and
1514
+ * `a@2[0]`, not by `a@23`).
1515
+ */
1516
+ blockIdPrefixTaken(prefix, pending) {
1517
+ for (const ids of [this.blockIds, pending ?? /* @__PURE__ */ new Map()]) for (const blockId of ids.keys()) if (blockId.startsWith(prefix) && !/^[0-9]/.test(blockId.slice(prefix.length))) return true;
1518
+ return false;
1519
+ }
1520
+ /** Unload every fake and its state. The id counters (`<n>`, `<k>`) are kept. */
1521
+ clear() {
1522
+ this.blocks = [];
1523
+ this.ids.clear();
1524
+ this.blockIds.clear();
1525
+ this.resetState();
1526
+ }
1527
+ /**
1528
+ * Reset consumption state for one test id, or all. Also clears an
1529
+ * `evicted` mark. `MCP_FAKES_DEFAULT_TEST_ID` resets the untagged state as
1530
+ * well as an explicit test id of that name. That matches
1531
+ * `Journal.clearMatchCounts` (which `LLMock.resetMatchCounts` calls), where
1532
+ * untagged requests and an explicit `__default__` share one key.
1533
+ */
1534
+ resetState(testId) {
1535
+ if (testId === void 0) {
1536
+ this.consumed.clear();
1537
+ this.untagged.clear();
1538
+ this.evicted.clear();
1539
+ this.events.clear();
1540
+ this.reportOverflow.clear();
1541
+ this.untaggedEvents = [];
1542
+ this.untaggedOverflow = false;
1543
+ return;
1544
+ }
1545
+ this.consumed.delete(testId);
1546
+ this.evicted.delete(testId);
1547
+ this.events.delete(testId);
1548
+ this.reportOverflow.delete(testId);
1549
+ if (testId === MCP_FAKES_DEFAULT_TEST_ID) {
1550
+ this.untagged.clear();
1551
+ this.untaggedEvents = [];
1552
+ this.untaggedOverflow = false;
1553
+ }
1554
+ }
1555
+ /**
1556
+ * MR12: mark entries consumed for `testId` (null = untagged), as a claim
1557
+ * would. Ids not loaded on this mount are ignored, and so is a test id the
1558
+ * cap evicted (its claims fail as `evicted` until it is reset).
1559
+ */
1560
+ markConsumed(testId, entryIds) {
1561
+ if (testId !== null && this.evicted.has(testId)) return;
1562
+ const known = entryIds.filter((entryId) => this.ids.has(entryId));
1563
+ if (known.length === 0) return;
1564
+ const state = this.stateFor(testId);
1565
+ for (const entryId of known) state.add(entryId);
1566
+ }
1567
+ /**
1568
+ * AM7/FA1: the `list` of the most specific applicable tier that has one,
1569
+ * else null. Inside a tier, the first block in load order with a `list`.
1570
+ */
1571
+ recordedList(identity) {
1572
+ const blocks = this.applicable(identity);
1573
+ for (const tier of TIER_ORDER) {
1574
+ const block = blocks.find((b) => b.tier === tier && b.list !== void 0);
1575
+ if (block?.list) return block.list;
1576
+ }
1577
+ return null;
1578
+ }
1579
+ /** FA5 timing and the FA3/FA4 values of an answered entry (for MR15 framing). */
1580
+ replayOf(entryId) {
1581
+ for (const block of this.blocks) for (const tool of block.tools) {
1582
+ const entry = tool.calls.find((c) => c.id === entryId);
1583
+ if (entry) return {
1584
+ timing: block.timing,
1585
+ notifications: entry.notifications ?? NO_NOTIFICATIONS,
1586
+ durationMs: entry.durationMs
1587
+ };
1588
+ }
1589
+ return null;
1590
+ }
1591
+ /** C7: true when any loaded call entry has a `notifications/message` (gates logging). */
1592
+ hasRecordedLogs() {
1593
+ return this.blocks.some((block) => block.tools.some((tool) => tool.calls.some((call) => call.notifications?.some((n) => n.method === "notifications/message"))));
1594
+ }
1595
+ /**
1596
+ * RP2: append one event to the identity's log, with the next process-wide
1597
+ * `seq` and its `args` bounded (`MCP_FAKES_MAX_EVENT_ARGS_BYTES`). A log
1598
+ * past `MCP_FAKES_MAX_EVENTS_PER_LOG` drops its oldest event and its report
1599
+ * says `evicted: true`; a new test id past the test-id cap evicts the
1600
+ * oldest test id's log the same way (C9). Never touches consumption state.
1601
+ */
1602
+ logEvent(identity, event) {
1603
+ lastEventSeq += 1;
1604
+ const stored = Object.freeze({
1605
+ ...event,
1606
+ args: boundedArgs(event.args),
1607
+ seq: lastEventSeq
1608
+ });
1609
+ const testId = identity.testId;
1610
+ if (testId === null) {
1611
+ this.untaggedEvents.push(stored);
1612
+ if (this.untaggedEvents.length > MCP_FAKES_MAX_EVENTS_PER_LOG) {
1613
+ this.untaggedEvents.shift();
1614
+ this.untaggedOverflow = true;
1615
+ }
1616
+ return;
1617
+ }
1618
+ let log = this.events.get(testId);
1619
+ if (!log) {
1620
+ log = [];
1621
+ this.events.set(testId, log);
1622
+ this.enforceEventCap();
1623
+ }
1624
+ log.push(stored);
1625
+ if (log.length > MCP_FAKES_MAX_EVENTS_PER_LOG) {
1626
+ log.shift();
1627
+ this.reportOverflow.add(testId);
1628
+ }
1629
+ }
1630
+ /**
1631
+ * RP3 parts for this mount (report assembly is src/mcp-fakes-report.ts).
1632
+ * `served`, `failures` and `unfaked` come from the events whose context is
1633
+ * exactly `context`, in `seq` order; `unconsumed` (tiers `testId+context`
1634
+ * and `testId`) and `sharedUnconsumed` (tiers `context` and `shared`) are
1635
+ * the applicable entries of this store that `testId` has not consumed,
1636
+ * labelled with `mount`, the path the store's mock is served at. Every
1637
+ * block in the store answers there (`claim` does not read `block.mount`),
1638
+ * so none is left out by its own `mount` value: a mock that loaded its own
1639
+ * fakes (default mount "/mcp") and is mounted at another path still reports
1640
+ * them. Reads only: building a report changes no state.
1641
+ */
1642
+ reportPart(testId, context, mount) {
1643
+ const part = {
1644
+ evicted: testId === null ? this.untaggedOverflow : this.evicted.has(testId) || this.reportOverflow.has(testId),
1645
+ served: [],
1646
+ unconsumed: [],
1647
+ unfaked: [],
1648
+ failures: [],
1649
+ sharedUnconsumed: []
1650
+ };
1651
+ const log = testId === null ? this.untaggedEvents : this.events.get(testId) ?? [];
1652
+ for (const event of log) {
1653
+ if (event.context !== context) continue;
1654
+ const { seq, tool, args } = event;
1655
+ if (event.outcome === "answered") part.served.push({
1656
+ entryId: event.entryId,
1657
+ mount: event.mount,
1658
+ tool,
1659
+ args,
1660
+ seq
1661
+ });
1662
+ else if (event.outcome === "unfaked") part.unfaked.push({
1663
+ mount: event.mount,
1664
+ tool,
1665
+ args,
1666
+ answeredBy: event.answeredBy
1667
+ });
1668
+ else part.failures.push({
1669
+ code: event.code,
1670
+ mount: event.mount,
1671
+ tool,
1672
+ args,
1673
+ seq
1674
+ });
1675
+ }
1676
+ const used = this.stateOf(testId);
1677
+ for (const block of this.applicable({
1678
+ testId,
1679
+ context,
1680
+ undeclared: null
1681
+ })) {
1682
+ const own = block.tier === "testId+context" || block.tier === "testId";
1683
+ for (const tool of block.tools) for (const call of tool.calls) {
1684
+ if (used?.has(call.id)) continue;
1685
+ if (!own) part.sharedUnconsumed.push({
1686
+ entryId: call.id,
1687
+ mount,
1688
+ tool: tool.name
1689
+ });
1690
+ else if (call.args) part.unconsumed.push({
1691
+ entryId: call.id,
1692
+ mount,
1693
+ tool: tool.name,
1694
+ args: copyJson(call.args)
1695
+ });
1696
+ else part.unconsumed.push({
1697
+ entryId: call.id,
1698
+ mount,
1699
+ tool: tool.name,
1700
+ anyArgs: true
1701
+ });
1702
+ }
1703
+ }
1704
+ return part;
1705
+ }
1706
+ /** Every entry id on this mount, in load order. */
1707
+ allEntryIds() {
1708
+ return this.blocks.flatMap(blockEntryIds);
1709
+ }
1710
+ /** The blocks whose scope matches `identity`, in load order. A frozen list of frozen blocks. */
1711
+ applicable(identity) {
1712
+ return Object.freeze(this.blocks.filter((b) => scopeMatches(b.scope, identity)));
1713
+ }
1714
+ /** The candidate entries for `tool` from the most specific tier declaring it. */
1715
+ selectTier(tool, identity) {
1716
+ const blocks = this.applicable(identity);
1717
+ for (const tier of TIER_ORDER) {
1718
+ const entries = [];
1719
+ for (const block of blocks) {
1720
+ if (block.tier !== tier) continue;
1721
+ const t = block.tools.find((x) => x.name === tool);
1722
+ if (t) for (const entry of t.calls) entries.push({
1723
+ block,
1724
+ entry
1725
+ });
1726
+ }
1727
+ if (entries.length > 0) return {
1728
+ tier,
1729
+ entries
1730
+ };
1731
+ }
1732
+ return null;
1733
+ }
1734
+ /**
1735
+ * Find the first unconsumed matching entry of the selected tier (load order,
1736
+ * then `calls` order) and mark it consumed, synchronously (no `await`
1737
+ * between find and mark), so concurrent calls never share an entry. The
1738
+ * result is built and checked to serialize as JSON before the entry is
1739
+ * marked, so a result that cannot be sent throws and leaves the entry
1740
+ * unconsumed. An `anyArgs` entry answers any `arguments`, a non-object
1741
+ * value included; only an `args` entry needs an object.
1742
+ */
1743
+ claim(tool, args, identity) {
1744
+ const selection = this.selectTier(tool, identity);
1745
+ if (!selection) return Object.freeze({ kind: "none" });
1746
+ const testId = identity.testId;
1747
+ if (testId !== null && this.evicted.has(testId)) return Object.freeze({
1748
+ kind: "evicted",
1749
+ tool,
1750
+ testId,
1751
+ maxTestIds: this.maxTestIds
1752
+ });
1753
+ const used = this.stateOf(testId);
1754
+ const received = args === void 0 ? {} : args;
1755
+ for (const { block, entry } of selection.entries) {
1756
+ if (used?.has(entry.id)) continue;
1757
+ if (!callMatches(entry, received)) continue;
1758
+ let result;
1759
+ try {
1760
+ result = toCallToolResult(entry);
1761
+ } catch (err) {
1762
+ throw new Error(require_message_text.build(require_message_text.msg`MCP fake entry ${require_message_text.quote(entry.id)}: its tools/call result cannot be built: ${describeThrown(err)}`), { cause: err });
1763
+ }
1764
+ try {
1765
+ JSON.stringify(result);
1766
+ } catch (err) {
1767
+ throw new Error(require_message_text.build(require_message_text.msg`MCP fake entry ${require_message_text.quote(entry.id)}: result cannot be serialized as JSON: ${describeThrown(err)}`), { cause: err });
1768
+ }
1769
+ this.stateFor(testId).add(entry.id);
1770
+ return Object.freeze({
1771
+ kind: "answer",
1772
+ tool,
1773
+ entry,
1774
+ block,
1775
+ tier: selection.tier,
1776
+ result
1777
+ });
1778
+ }
1779
+ const matching = selection.entries.filter(({ entry }) => callMatches(entry, received));
1780
+ if (matching.length > 0) return Object.freeze({
1781
+ kind: "exhausted",
1782
+ tool,
1783
+ received,
1784
+ matchingDeclared: matching.length,
1785
+ matchingConsumed: matching.filter(({ entry }) => used?.has(entry.id)).length,
1786
+ matchingIds: Object.freeze(matching.map(({ entry }) => entry.id))
1787
+ });
1788
+ let closest = null;
1789
+ let best = Infinity;
1790
+ for (const { entry } of selection.entries) {
1791
+ if (!entry.args) continue;
1792
+ const score = diffCount(entry.args, received);
1793
+ if (score < best) {
1794
+ best = score;
1795
+ closest = entry;
1796
+ }
1797
+ }
1798
+ return Object.freeze({
1799
+ kind: "mismatch",
1800
+ tool,
1801
+ received,
1802
+ declared: deepFreeze(selection.entries.map(({ entry }) => declaredEntry(entry, used))),
1803
+ firstDifference: closest?.args ? firstDifference(closest.args, received) : ""
1804
+ });
1805
+ }
1806
+ /**
1807
+ * The undeclared-tool policy, first of: `override` (`null` or omitted falls
1808
+ * back to `identity.undeclared`, the request's or session's
1809
+ * `X-AIMock-MCP-Undeclared` / `?undeclared=` value) → `deny` when any
1810
+ * applicable scoped block says `deny` → `allow`.
1811
+ */
1812
+ policy(identity, override = null) {
1813
+ const effective = override ?? identity.undeclared;
1814
+ if (effective) return effective;
1815
+ for (const block of this.applicable(identity)) if (block.scope !== "shared" && block.undeclaredTools === "deny") return "deny";
1816
+ return "allow";
1817
+ }
1818
+ /** Tool names any applicable block (any tier) declares, in load order. */
1819
+ declaredTools(identity) {
1820
+ const names = /* @__PURE__ */ new Set();
1821
+ for (const block of this.applicable(identity)) for (const t of block.tools) names.add(t.name);
1822
+ return [...names];
1823
+ }
1824
+ /**
1825
+ * Fake tools for `tools/list`, metadata from the most specific
1826
+ * tier declaring each tool; inside that tier, the first block in load order
1827
+ * that declares it. A tool with no `inputSchema` lists `{ type: "object" }`.
1828
+ * Merging with registered tools is the mount's job.
1829
+ */
1830
+ listTools(identity) {
1831
+ return this.declaredTools(identity).map((name) => {
1832
+ const tool = this.selectTier(name, identity)?.entries[0].block.tools.find((t) => t.name === name);
1833
+ const listing = {
1834
+ name,
1835
+ inputSchema: tool?.inputSchema ? copyJson(tool.inputSchema) : { type: "object" }
1836
+ };
1837
+ if (tool?.description !== void 0) listing.description = tool.description;
1838
+ return listing;
1839
+ });
1840
+ }
1841
+ /**
1842
+ * For `GET /__aimock/mcp/fakes`: applicable blocks with consumed state for
1843
+ * `testId`. For a test id the cap evicted, every block says `evicted: true`.
1844
+ * Deeply frozen.
1845
+ */
1846
+ snapshot(testId, context) {
1847
+ const used = this.stateOf(testId);
1848
+ const evicted = testId !== null && this.evicted.has(testId);
1849
+ return deepFreeze(this.applicable({
1850
+ testId,
1851
+ context,
1852
+ undeclared: null
1853
+ }).map((block) => ({
1854
+ blockId: block.blockId,
1855
+ source: block.source,
1856
+ mount: block.mount,
1857
+ scope: block.scope,
1858
+ undeclaredTools: block.undeclaredTools ?? "allow",
1859
+ evicted,
1860
+ tools: block.tools.map((t) => ({
1861
+ name: t.name,
1862
+ entries: t.calls.map((c) => declaredEntry(c, used))
1863
+ }))
1864
+ })));
1865
+ }
1866
+ /** The consumed ids for `testId` (`null`: untagged), without creating state. */
1867
+ stateOf(testId) {
1868
+ return testId === null ? this.untagged : this.consumed.get(testId);
1869
+ }
1870
+ stateFor(testId) {
1871
+ if (testId === null) return this.untagged;
1872
+ let set = this.consumed.get(testId);
1873
+ if (!set) {
1874
+ set = /* @__PURE__ */ new Set();
1875
+ this.consumed.set(testId, set);
1876
+ this.enforceCap();
1877
+ }
1878
+ return set;
1879
+ }
1880
+ /**
1881
+ * FIFO cap: evict the oldest test ids until the cap holds, marking each
1882
+ * evicted. A cap of `0` evicts nothing. The marks are not capped (see
1883
+ * `evicted`).
1884
+ */
1885
+ enforceCap() {
1886
+ if (this.maxTestIds === 0) return;
1887
+ for (const oldest of this.consumed.keys()) {
1888
+ if (this.consumed.size <= this.maxTestIds) break;
1889
+ this.consumed.delete(oldest);
1890
+ this.evicted.add(oldest);
1891
+ if (this.events.delete(oldest)) this.reportOverflow.add(oldest);
1892
+ }
1893
+ this.enforceEventCap();
1894
+ }
1895
+ /**
1896
+ * The test-id FIFO cap on event logs: evict the oldest logs until the cap
1897
+ * holds, marking each in the report-only `reportOverflow` (never `evicted`).
1898
+ */
1899
+ enforceEventCap() {
1900
+ if (this.maxTestIds === 0) return;
1901
+ for (const oldest of this.events.keys()) {
1902
+ if (this.events.size <= this.maxTestIds) break;
1903
+ this.events.delete(oldest);
1904
+ this.reportOverflow.add(oldest);
1905
+ }
1906
+ }
1907
+ };
1908
+ /** `replayOf` notifications of an entry that recorded none. */
1909
+ const NO_NOTIFICATIONS = Object.freeze([]);
1910
+ /**
1911
+ * The detail of a block-id collision (bad block (f)). The message prefix
1912
+ * already names the block id, so the detail names the two sources.
1913
+ */
1914
+ function blockCollisionDetail(source, owner, loaded) {
1915
+ return loaded ? require_message_text.msg`block id (this block's source: ${require_message_text.quote(source)}) is already loaded on this mount by source ${require_message_text.quote(owner)}` : require_message_text.msg`block id (this block's source: ${require_message_text.quote(source)}) collides with a block earlier in the same input, from source ${require_message_text.quote(owner)}`;
1916
+ }
1917
+ /** The test-id cap: a non-negative safe integer, `0` meaning unbounded. Anything else throws. */
1918
+ function checkCap(cap) {
1919
+ if (!Number.isSafeInteger(cap) || cap < 0) throw new RangeError(require_message_text.build(require_message_text.msg`MCP fakes maxTestIds must be a non-negative integer (0 = unbounded), got ${shown(cap)}`));
1920
+ return cap;
1921
+ }
1922
+ function declaredEntry(entry, used) {
1923
+ const consumed = used?.has(entry.id) ?? false;
1924
+ return entry.args ? {
1925
+ id: entry.id,
1926
+ args: copyJson(entry.args),
1927
+ consumed
1928
+ } : {
1929
+ id: entry.id,
1930
+ anyArgs: true,
1931
+ consumed
1932
+ };
1933
+ }
1934
+
1935
+ //#endregion
1936
+ exports.MCP_FAKES_DEFAULT_MOUNT = MCP_FAKES_DEFAULT_MOUNT;
1937
+ exports.MCP_FAKES_DEFAULT_TEST_ID = MCP_FAKES_DEFAULT_TEST_ID;
1938
+ exports.McpFakeStore = McpFakeStore;
1939
+ exports.McpFakesAddError = McpFakesAddError;
1940
+ exports.blockIdOf = blockIdOf;
1941
+ exports.validateMcpFakes = validateMcpFakes;
1942
+ //# sourceMappingURL=mcp-fakes.cjs.map