@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,217 @@
1
+ const require_message_text = require('./message-text.cjs');
2
+ const require_fixture_loader = require('./fixture-loader.cjs');
3
+ const require_mcp_fakes = require('./mcp-fakes.cjs');
4
+ const require_mcp_mock = require('./mcp-mock.cjs');
5
+
6
+ //#region src/mcp-fakes-mount.ts
7
+ /**
8
+ * The control-API prefix. The server hands every request whose path starts
9
+ * with it to the control API, before any mount, so no mount under it is
10
+ * reachable.
11
+ */
12
+ const CONTROL_PREFIX = "/__aimock";
13
+ /** The handlers an auto-mount created (W3, W6), whichever path created them. */
14
+ const autoMounts = /* @__PURE__ */ new WeakSet();
15
+ /** `handler` was auto-mounted for MCP fakes (at start, after start or by the control API). */
16
+ function isAutoMount(handler) {
17
+ return autoMounts.has(handler);
18
+ }
19
+ /** A mount "is an MCPMock" for fakes if and only if it implements `addMcpFakes`. */
20
+ function isFakeMount(handler) {
21
+ return typeof handler.addMcpFakes === "function";
22
+ }
23
+ /**
24
+ * The mount that serves `path`, in dispatch order: an MCPMock at exactly
25
+ * `path`; a conflict when the path is under the control prefix, when a mount
26
+ * that is not an MCPMock serves it first (it would shadow the fakes), or when
27
+ * an MCPMock at `path` without its trailing "/" answers it as its root; none
28
+ * when no mount serves it. An MCPMock mounted at a prefix of `path` lets any
29
+ * other sub-path fall through, so it is skipped. `pending` are the paths an
30
+ * add will auto-mount before this one, in order (they follow `mounts`).
31
+ */
32
+ function findFakeMount(mounts, path, pending = []) {
33
+ if (path.startsWith(CONTROL_PREFIX)) return {
34
+ kind: "conflict",
35
+ mountPath: CONTROL_PREFIX,
36
+ reason: "control"
37
+ };
38
+ for (const { path: mountPath, handler } of mounts) {
39
+ if (path !== mountPath && !path.startsWith(mountPath + "/")) continue;
40
+ if (!isFakeMount(handler)) return {
41
+ kind: "conflict",
42
+ mountPath,
43
+ reason: "not-mcp"
44
+ };
45
+ if (mountPath === path) return {
46
+ kind: "mcp",
47
+ handler
48
+ };
49
+ if (path === mountPath + "/") return {
50
+ kind: "conflict",
51
+ mountPath,
52
+ reason: "mcp-root"
53
+ };
54
+ }
55
+ for (const mountPath of pending) if (path === mountPath + "/") return {
56
+ kind: "conflict",
57
+ mountPath,
58
+ reason: "mcp-root"
59
+ };
60
+ return { kind: "none" };
61
+ }
62
+ /** Why the fakes for `path` cannot be served there (no location prefix). */
63
+ function mountConflictDetail(path, target) {
64
+ switch (target.reason) {
65
+ case "control": return require_message_text.msg`mount ${require_message_text.quote(path)} is under the control API prefix ${require_message_text.quote(target.mountPath)}, which takes its requests first, so its fakes cannot be served`;
66
+ case "mcp-root": return require_message_text.msg`mount ${require_message_text.quote(path)} is answered by the MCP mock at ${require_message_text.quote(target.mountPath)} as its root, so its fakes cannot be served`;
67
+ case "not-mcp": return require_message_text.msg`mount ${require_message_text.quote(path)} is served by ${require_message_text.quote(target.mountPath)}, which is not an MCP mock, so its fakes cannot be served`;
68
+ }
69
+ }
70
+ /** Wire, push and log (L4, L5) an auto-mounted MCPMock. */
71
+ function pushAutoMount(mounts, path, handler, wiring, logger) {
72
+ if (wiring.journal) handler.setJournal(wiring.journal);
73
+ if (wiring.registry) handler.setRegistry(wiring.registry);
74
+ if (wiring.logger) handler.setLogger(wiring.logger);
75
+ handler.setReplaySpeed(wiring.replaySpeed ?? 1);
76
+ const others = mounts.filter((m) => m.path !== path && isFakeMount(m.handler));
77
+ mounts.push({
78
+ path,
79
+ handler
80
+ });
81
+ autoMounts.add(handler);
82
+ logger.info(require_message_text.build(require_message_text.msg`MCP fakes: auto-mounted an MCP mock at ${require_message_text.quote(path)}`));
83
+ for (const other of others) logger.warn(require_message_text.build(require_message_text.msg`MCP fakes: auto-mounted an MCP mock at ${require_message_text.quote(path)} while an MCP mock is mounted at ${require_message_text.quote(other.path)}; fakes for one path do not reach the other`));
84
+ }
85
+ /**
86
+ * The MCPMock for `path`: the one already mounted there, or a new one pushed
87
+ * onto `mounts` (auto-mount, logs L4 and L5). A conflict is returned, not
88
+ * thrown, so the caller can name the offending block.
89
+ */
90
+ function ensureFakeMount(mounts, path, wiring, logger) {
91
+ const target = findFakeMount(mounts, path);
92
+ if (target.kind === "conflict") return { conflict: target.mountPath };
93
+ if (target.kind === "mcp") return {
94
+ handler: target.handler,
95
+ created: false
96
+ };
97
+ const handler = new require_mcp_mock.MCPMock();
98
+ pushAutoMount(mounts, path, handler, wiring, logger);
99
+ return {
100
+ handler,
101
+ created: true
102
+ };
103
+ }
104
+ /** A block's `mount` path, read without running getters; the add validates it. */
105
+ function mountOf(raw) {
106
+ if (typeof raw !== "object" || raw === null) return require_mcp_fakes.MCP_FAKES_DEFAULT_MOUNT;
107
+ const value = Object.getOwnPropertyDescriptor(raw, "mount")?.value;
108
+ return typeof value === "string" ? value : require_mcp_fakes.MCP_FAKES_DEFAULT_MOUNT;
109
+ }
110
+ /** The mount-conflict error for one file-origin block. */
111
+ function mountConflictError(block, path, target) {
112
+ return new require_fixture_loader.FixtureLoadError({
113
+ rule: "mcp-fakes/mount-conflict",
114
+ file: block.source,
115
+ blockId: require_mcp_fakes.blockIdOf(block.source, "", block.blockIndex),
116
+ entryId: null,
117
+ detail: mountConflictDetail(path, target)
118
+ });
119
+ }
120
+ /**
121
+ * Check file-origin `blocks` against `mounts` without changing anything:
122
+ * group them by `mount` path (default `/mcp`), find each group's mount (a
123
+ * mount conflict is one error per block), and add each group to a throwaway
124
+ * store (bad blocks and id collisions inside the input). Throws one
125
+ * `McpFakesAddError` holding every error. With `checked`, only the groups
126
+ * that hold a checked block are checked, and only checked blocks get a
127
+ * mount-conflict error (the others were checked before).
128
+ */
129
+ function planFileFakes(mounts, blocks, checked) {
130
+ const groups = /* @__PURE__ */ new Map();
131
+ for (const block of blocks) {
132
+ const path = mountOf(block.raw);
133
+ const group = groups.get(path);
134
+ if (group) group.push(block);
135
+ else groups.set(path, [block]);
136
+ }
137
+ const errors = [];
138
+ const warnings = [];
139
+ const plan = {
140
+ onExisting: [],
141
+ onNew: [],
142
+ warnings
143
+ };
144
+ for (const [path, group] of groups) {
145
+ if (checked && !group.some((block) => checked.has(block))) continue;
146
+ const target = findFakeMount(mounts, path, plan.onNew.map((g) => g.path));
147
+ if (target.kind === "conflict") {
148
+ for (const block of group) if (!checked || checked.has(block)) errors.push(mountConflictError(block, path, target));
149
+ continue;
150
+ }
151
+ try {
152
+ warnings.push(...new require_mcp_fakes.McpFakeStore().add(group, { kind: "file" }).warnings);
153
+ } catch (err) {
154
+ if (!(err instanceof require_mcp_fakes.McpFakesAddError)) throw err;
155
+ errors.push(...err.errors);
156
+ warnings.push(...err.warnings);
157
+ continue;
158
+ }
159
+ if (target.kind === "mcp") plan.onExisting.push({
160
+ handler: target.handler,
161
+ group
162
+ });
163
+ else plan.onNew.push({
164
+ path,
165
+ group
166
+ });
167
+ }
168
+ if (errors.length > 0) throw new require_mcp_fakes.McpFakesAddError(errors, warnings);
169
+ return plan;
170
+ }
171
+ /**
172
+ * Start-time hand-off (F2): check every block first (`planFileFakes`; on any
173
+ * error, a bad block, an id collision or a mount conflict, throws one
174
+ * `McpFakesAddError` holding every error and changes nothing). Then the
175
+ * groups for paths no mount serves are added to new MCPMocks, pushed onto
176
+ * `mounts` (auto-mount, L4, L5) so the mount loop wires them. The groups for
177
+ * an MCPMock already mounted are added by `commit`, once the server listens,
178
+ * so a start that fails after the hand-off leaves those mounts untouched;
179
+ * `undo` removes the auto-mounts. The add warnings (L8) are logged at `warn`.
180
+ */
181
+ function handOffMcpFakes(mounts, blocks, logger) {
182
+ const plan = planFileFakes(mounts, blocks);
183
+ const created = [];
184
+ for (const { path, group } of plan.onNew) {
185
+ const handler = new require_mcp_mock.MCPMock();
186
+ for (const warning of handler.addMcpFakes(group, { kind: "file" }).warnings) logger.warn(warning.message);
187
+ created.push({
188
+ path,
189
+ handler
190
+ });
191
+ }
192
+ for (const { path, handler } of created) pushAutoMount(mounts, path, handler, {}, logger);
193
+ return {
194
+ commit() {
195
+ for (const { handler, group } of plan.onExisting) {
196
+ const result = handler.addMcpFakes?.(group, { kind: "file" });
197
+ for (const warning of result?.warnings ?? []) logger.warn(warning.message);
198
+ }
199
+ },
200
+ undo() {
201
+ for (const { handler } of created) {
202
+ const at = mounts.findIndex((m) => m.handler === handler);
203
+ if (at !== -1) mounts.splice(at, 1);
204
+ }
205
+ }
206
+ };
207
+ }
208
+
209
+ //#endregion
210
+ exports.ensureFakeMount = ensureFakeMount;
211
+ exports.findFakeMount = findFakeMount;
212
+ exports.handOffMcpFakes = handOffMcpFakes;
213
+ exports.isAutoMount = isAutoMount;
214
+ exports.isFakeMount = isFakeMount;
215
+ exports.mountConflictDetail = mountConflictDetail;
216
+ exports.planFileFakes = planFileFakes;
217
+ //# sourceMappingURL=mcp-fakes-mount.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-fakes-mount.cjs","names":["quote","build","MCPMock","MCP_FAKES_DEFAULT_MOUNT","FixtureLoadError","blockIdOf","McpFakeStore","McpFakesAddError"],"sources":["../src/mcp-fakes-mount.ts"],"sourcesContent":["/**\n * Where MCP fakes go on a server (spec 5.2): find the mount that serves a\n * block's `mount` path, auto-mount an `MCPMock` when none does (W3, W6), and\n * the start-time hand-off of `ServiceFixtures.mcpFakes` (F2).\n */\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type { McpFakeSource, Mountable } from \"./types.js\";\nimport { FixtureLoadError } from \"./fixture-loader.js\";\nimport { MCPMock } from \"./mcp-mock.js\";\nimport {\n MCP_FAKES_DEFAULT_MOUNT,\n McpFakeStore,\n McpFakesAddError,\n blockIdOf,\n type McpFakeIssue,\n} from \"./mcp-fakes.js\";\nimport { build, msg, quote, type Message } from \"./message-text.js\";\n\nexport type MountList = Array<{ path: string; handler: Mountable }>;\n\n/** Dependencies an auto-mount gets when it is created (after start: W4 wiring). */\nexport interface FakeMountWiring {\n journal?: Journal;\n registry?: MetricsRegistry;\n logger?: Logger;\n /** T1: the server's replay speed for recorded fake timing (default 1). */\n replaySpeed?: number;\n}\n\n/**\n * The control-API prefix. The server hands every request whose path starts\n * with it to the control API, before any mount, so no mount under it is\n * reachable.\n */\nexport const CONTROL_PREFIX = \"/__aimock\";\n\n/**\n * What serves a block's `mount` path. A conflict is a path no fake there can\n * be served at: a mount that is not an MCPMock serves it first (`not-mcp`), an\n * MCPMock mounted at `mountPath` answers it as its root (`mcp-root`, the path\n * is `mountPath` plus a trailing \"/\"), or it is under the control prefix\n * (`control`).\n */\nexport type FakeMountTarget =\n | { kind: \"mcp\"; handler: Mountable }\n | { kind: \"conflict\"; mountPath: string; reason: \"not-mcp\" | \"mcp-root\" | \"control\" }\n | { kind: \"none\" };\n\n/** The handlers an auto-mount created (W3, W6), whichever path created them. */\nconst autoMounts = new WeakSet<Mountable>();\n\n/** `handler` was auto-mounted for MCP fakes (at start, after start or by the control API). */\nexport function isAutoMount(handler: Mountable): boolean {\n return autoMounts.has(handler);\n}\n\n/** A mount \"is an MCPMock\" for fakes if and only if it implements `addMcpFakes`. */\nexport function isFakeMount(handler: Mountable): boolean {\n return typeof handler.addMcpFakes === \"function\";\n}\n\n/**\n * The mount that serves `path`, in dispatch order: an MCPMock at exactly\n * `path`; a conflict when the path is under the control prefix, when a mount\n * that is not an MCPMock serves it first (it would shadow the fakes), or when\n * an MCPMock at `path` without its trailing \"/\" answers it as its root; none\n * when no mount serves it. An MCPMock mounted at a prefix of `path` lets any\n * other sub-path fall through, so it is skipped. `pending` are the paths an\n * add will auto-mount before this one, in order (they follow `mounts`).\n */\nexport function findFakeMount(\n mounts: MountList,\n path: string,\n pending: readonly string[] = [],\n): FakeMountTarget {\n if (path.startsWith(CONTROL_PREFIX)) {\n return { kind: \"conflict\", mountPath: CONTROL_PREFIX, reason: \"control\" };\n }\n for (const { path: mountPath, handler } of mounts) {\n if (path !== mountPath && !path.startsWith(mountPath + \"/\")) continue;\n if (!isFakeMount(handler)) return { kind: \"conflict\", mountPath, reason: \"not-mcp\" };\n if (mountPath === path) return { kind: \"mcp\", handler };\n if (path === mountPath + \"/\") return { kind: \"conflict\", mountPath, reason: \"mcp-root\" };\n }\n for (const mountPath of pending) {\n if (path === mountPath + \"/\") return { kind: \"conflict\", mountPath, reason: \"mcp-root\" };\n }\n return { kind: \"none\" };\n}\n\n/** Why the fakes for `path` cannot be served there (no location prefix). */\nexport function mountConflictDetail(\n path: string,\n target: Extract<FakeMountTarget, { kind: \"conflict\" }>,\n): Message {\n switch (target.reason) {\n case \"control\":\n return msg`mount ${quote(path)} is under the control API prefix ${quote(target.mountPath)}, which takes its requests first, so its fakes cannot be served`;\n case \"mcp-root\":\n return msg`mount ${quote(path)} is answered by the MCP mock at ${quote(target.mountPath)} as its root, so its fakes cannot be served`;\n case \"not-mcp\":\n return msg`mount ${quote(path)} is served by ${quote(target.mountPath)}, which is not an MCP mock, so its fakes cannot be served`;\n }\n}\n\n/** Wire, push and log (L4, L5) an auto-mounted MCPMock. */\nfunction pushAutoMount(\n mounts: MountList,\n path: string,\n handler: MCPMock,\n wiring: FakeMountWiring,\n logger: Logger,\n): void {\n if (wiring.journal) handler.setJournal(wiring.journal);\n if (wiring.registry) handler.setRegistry(wiring.registry);\n if (wiring.logger) handler.setLogger(wiring.logger);\n handler.setReplaySpeed(wiring.replaySpeed ?? 1);\n const others = mounts.filter((m) => m.path !== path && isFakeMount(m.handler));\n mounts.push({ path, handler });\n autoMounts.add(handler);\n logger.info(build(msg`MCP fakes: auto-mounted an MCP mock at ${quote(path)}`));\n for (const other of others) {\n logger.warn(\n build(\n msg`MCP fakes: auto-mounted an MCP mock at ${quote(path)} while an MCP mock is mounted at ${quote(other.path)}; fakes for one path do not reach the other`,\n ),\n );\n }\n}\n\n/**\n * The MCPMock for `path`: the one already mounted there, or a new one pushed\n * onto `mounts` (auto-mount, logs L4 and L5). A conflict is returned, not\n * thrown, so the caller can name the offending block.\n */\nexport function ensureFakeMount(\n mounts: MountList,\n path: string,\n wiring: FakeMountWiring,\n logger: Logger,\n): { handler: Mountable; created: boolean } | { conflict: string } {\n const target = findFakeMount(mounts, path);\n if (target.kind === \"conflict\") return { conflict: target.mountPath };\n if (target.kind === \"mcp\") return { handler: target.handler, created: false };\n const handler = new MCPMock();\n pushAutoMount(mounts, path, handler, wiring, logger);\n return { handler, created: true };\n}\n\n/** A block's `mount` path, read without running getters; the add validates it. */\nfunction mountOf(raw: unknown): string {\n if (typeof raw !== \"object\" || raw === null) return MCP_FAKES_DEFAULT_MOUNT;\n const value: unknown = Object.getOwnPropertyDescriptor(raw, \"mount\")?.value;\n return typeof value === \"string\" ? value : MCP_FAKES_DEFAULT_MOUNT;\n}\n\n/** The mount-conflict error for one file-origin block. */\nexport function mountConflictError(\n block: McpFakeSource,\n path: string,\n target: Extract<FakeMountTarget, { kind: \"conflict\" }>,\n): FixtureLoadError {\n return new FixtureLoadError({\n rule: \"mcp-fakes/mount-conflict\",\n file: block.source,\n blockId: blockIdOf(block.source, \"\", block.blockIndex),\n entryId: null,\n detail: mountConflictDetail(path, target),\n });\n}\n\n/** Where a checked add of file blocks goes: each group by its `mount` path. */\nexport interface FileFakesPlan {\n /** Groups for a path an MCPMock already serves, with that mount. */\n onExisting: Array<{ handler: Mountable; group: McpFakeSource[] }>;\n /** Groups for a path no mount serves, in order: each gets an auto-mount. */\n onNew: Array<{ path: string; group: McpFakeSource[] }>;\n /** The add warnings (L8), as a throwaway store reports them. */\n warnings: McpFakeIssue[];\n}\n\n/**\n * Check file-origin `blocks` against `mounts` without changing anything:\n * group them by `mount` path (default `/mcp`), find each group's mount (a\n * mount conflict is one error per block), and add each group to a throwaway\n * store (bad blocks and id collisions inside the input). Throws one\n * `McpFakesAddError` holding every error. With `checked`, only the groups\n * that hold a checked block are checked, and only checked blocks get a\n * mount-conflict error (the others were checked before).\n */\nexport function planFileFakes(\n mounts: MountList,\n blocks: readonly McpFakeSource[],\n checked?: ReadonlySet<McpFakeSource>,\n): FileFakesPlan {\n const groups = new Map<string, McpFakeSource[]>();\n for (const block of blocks) {\n const path = mountOf(block.raw);\n const group = groups.get(path);\n if (group) group.push(block);\n else groups.set(path, [block]);\n }\n\n const errors: FixtureLoadError[] = [];\n const warnings: McpFakeIssue[] = [];\n const plan: FileFakesPlan = { onExisting: [], onNew: [], warnings };\n for (const [path, group] of groups) {\n if (checked && !group.some((block) => checked.has(block))) continue;\n const target = findFakeMount(\n mounts,\n path,\n plan.onNew.map((g) => g.path),\n );\n if (target.kind === \"conflict\") {\n for (const block of group) {\n if (!checked || checked.has(block)) errors.push(mountConflictError(block, path, target));\n }\n continue;\n }\n try {\n warnings.push(...new McpFakeStore().add(group, { kind: \"file\" }).warnings);\n } catch (err) {\n if (!(err instanceof McpFakesAddError)) throw err;\n errors.push(...err.errors);\n warnings.push(...err.warnings);\n continue;\n }\n if (target.kind === \"mcp\") plan.onExisting.push({ handler: target.handler, group });\n else plan.onNew.push({ path, group });\n }\n if (errors.length > 0) throw new McpFakesAddError(errors, warnings);\n return plan;\n}\n\n/** A start-time hand-off: the auto-mounts are in place, the rest waits for `commit`. */\nexport interface McpFakesHandOff {\n /** Add the groups for existing MCPMocks (after the server listens). */\n commit(): void;\n /** Take the auto-mounts back off the mounts array (start failed). */\n undo(): void;\n}\n\n/**\n * Start-time hand-off (F2): check every block first (`planFileFakes`; on any\n * error, a bad block, an id collision or a mount conflict, throws one\n * `McpFakesAddError` holding every error and changes nothing). Then the\n * groups for paths no mount serves are added to new MCPMocks, pushed onto\n * `mounts` (auto-mount, L4, L5) so the mount loop wires them. The groups for\n * an MCPMock already mounted are added by `commit`, once the server listens,\n * so a start that fails after the hand-off leaves those mounts untouched;\n * `undo` removes the auto-mounts. The add warnings (L8) are logged at `warn`.\n */\nexport function handOffMcpFakes(\n mounts: MountList,\n blocks: McpFakeSource[],\n logger: Logger,\n): McpFakesHandOff {\n const plan = planFileFakes(mounts, blocks);\n const created: Array<{ path: string; handler: MCPMock }> = [];\n for (const { path, group } of plan.onNew) {\n const handler = new MCPMock();\n for (const warning of handler.addMcpFakes(group, { kind: \"file\" }).warnings) {\n logger.warn(warning.message);\n }\n created.push({ path, handler });\n }\n for (const { path, handler } of created) pushAutoMount(mounts, path, handler, {}, logger);\n return {\n commit(): void {\n for (const { handler, group } of plan.onExisting) {\n const result = handler.addMcpFakes?.(group, { kind: \"file\" });\n for (const warning of result?.warnings ?? []) logger.warn(warning.message);\n }\n },\n undo(): void {\n for (const { handler } of created) {\n const at = mounts.findIndex((m) => m.handler === handler);\n if (at !== -1) mounts.splice(at, 1);\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;AAoCA,MAAa,iBAAiB;;AAe9B,MAAM,6BAAa,IAAI,SAAoB;;AAG3C,SAAgB,YAAY,SAA6B;AACvD,QAAO,WAAW,IAAI,QAAQ;;;AAIhC,SAAgB,YAAY,SAA6B;AACvD,QAAO,OAAO,QAAQ,gBAAgB;;;;;;;;;;;AAYxC,SAAgB,cACd,QACA,MACA,UAA6B,EAAE,EACd;AACjB,KAAI,KAAK,WAAW,eAAe,CACjC,QAAO;EAAE,MAAM;EAAY,WAAW;EAAgB,QAAQ;EAAW;AAE3E,MAAK,MAAM,EAAE,MAAM,WAAW,aAAa,QAAQ;AACjD,MAAI,SAAS,aAAa,CAAC,KAAK,WAAW,YAAY,IAAI,CAAE;AAC7D,MAAI,CAAC,YAAY,QAAQ,CAAE,QAAO;GAAE,MAAM;GAAY;GAAW,QAAQ;GAAW;AACpF,MAAI,cAAc,KAAM,QAAO;GAAE,MAAM;GAAO;GAAS;AACvD,MAAI,SAAS,YAAY,IAAK,QAAO;GAAE,MAAM;GAAY;GAAW,QAAQ;GAAY;;AAE1F,MAAK,MAAM,aAAa,QACtB,KAAI,SAAS,YAAY,IAAK,QAAO;EAAE,MAAM;EAAY;EAAW,QAAQ;EAAY;AAE1F,QAAO,EAAE,MAAM,QAAQ;;;AAIzB,SAAgB,oBACd,MACA,QACS;AACT,SAAQ,OAAO,QAAf;EACE,KAAK,UACH,QAAO,wBAAG,SAASA,2BAAM,KAAK,CAAC,mCAAmCA,2BAAM,OAAO,UAAU,CAAC;EAC5F,KAAK,WACH,QAAO,wBAAG,SAASA,2BAAM,KAAK,CAAC,kCAAkCA,2BAAM,OAAO,UAAU,CAAC;EAC3F,KAAK,UACH,QAAO,wBAAG,SAASA,2BAAM,KAAK,CAAC,gBAAgBA,2BAAM,OAAO,UAAU,CAAC;;;;AAK7E,SAAS,cACP,QACA,MACA,SACA,QACA,QACM;AACN,KAAI,OAAO,QAAS,SAAQ,WAAW,OAAO,QAAQ;AACtD,KAAI,OAAO,SAAU,SAAQ,YAAY,OAAO,SAAS;AACzD,KAAI,OAAO,OAAQ,SAAQ,UAAU,OAAO,OAAO;AACnD,SAAQ,eAAe,OAAO,eAAe,EAAE;CAC/C,MAAM,SAAS,OAAO,QAAQ,MAAM,EAAE,SAAS,QAAQ,YAAY,EAAE,QAAQ,CAAC;AAC9E,QAAO,KAAK;EAAE;EAAM;EAAS,CAAC;AAC9B,YAAW,IAAI,QAAQ;AACvB,QAAO,KAAKC,2BAAM,wBAAG,0CAA0CD,2BAAM,KAAK,GAAG,CAAC;AAC9E,MAAK,MAAM,SAAS,OAClB,QAAO,KACLC,2BACE,wBAAG,0CAA0CD,2BAAM,KAAK,CAAC,mCAAmCA,2BAAM,MAAM,KAAK,CAAC,6CAC/G,CACF;;;;;;;AASL,SAAgB,gBACd,QACA,MACA,QACA,QACiE;CACjE,MAAM,SAAS,cAAc,QAAQ,KAAK;AAC1C,KAAI,OAAO,SAAS,WAAY,QAAO,EAAE,UAAU,OAAO,WAAW;AACrE,KAAI,OAAO,SAAS,MAAO,QAAO;EAAE,SAAS,OAAO;EAAS,SAAS;EAAO;CAC7E,MAAM,UAAU,IAAIE,0BAAS;AAC7B,eAAc,QAAQ,MAAM,SAAS,QAAQ,OAAO;AACpD,QAAO;EAAE;EAAS,SAAS;EAAM;;;AAInC,SAAS,QAAQ,KAAsB;AACrC,KAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAOC;CACpD,MAAM,QAAiB,OAAO,yBAAyB,KAAK,QAAQ,EAAE;AACtE,QAAO,OAAO,UAAU,WAAW,QAAQA;;;AAI7C,SAAgB,mBACd,OACA,MACA,QACkB;AAClB,QAAO,IAAIC,wCAAiB;EAC1B,MAAM;EACN,MAAM,MAAM;EACZ,SAASC,4BAAU,MAAM,QAAQ,IAAI,MAAM,WAAW;EACtD,SAAS;EACT,QAAQ,oBAAoB,MAAM,OAAO;EAC1C,CAAC;;;;;;;;;;;AAsBJ,SAAgB,cACd,QACA,QACA,SACe;CACf,MAAM,yBAAS,IAAI,KAA8B;AACjD,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,OAAO,QAAQ,MAAM,IAAI;EAC/B,MAAM,QAAQ,OAAO,IAAI,KAAK;AAC9B,MAAI,MAAO,OAAM,KAAK,MAAM;MACvB,QAAO,IAAI,MAAM,CAAC,MAAM,CAAC;;CAGhC,MAAM,SAA6B,EAAE;CACrC,MAAM,WAA2B,EAAE;CACnC,MAAM,OAAsB;EAAE,YAAY,EAAE;EAAE,OAAO,EAAE;EAAE;EAAU;AACnE,MAAK,MAAM,CAAC,MAAM,UAAU,QAAQ;AAClC,MAAI,WAAW,CAAC,MAAM,MAAM,UAAU,QAAQ,IAAI,MAAM,CAAC,CAAE;EAC3D,MAAM,SAAS,cACb,QACA,MACA,KAAK,MAAM,KAAK,MAAM,EAAE,KAAK,CAC9B;AACD,MAAI,OAAO,SAAS,YAAY;AAC9B,QAAK,MAAM,SAAS,MAClB,KAAI,CAAC,WAAW,QAAQ,IAAI,MAAM,CAAE,QAAO,KAAK,mBAAmB,OAAO,MAAM,OAAO,CAAC;AAE1F;;AAEF,MAAI;AACF,YAAS,KAAK,GAAG,IAAIC,gCAAc,CAAC,IAAI,OAAO,EAAE,MAAM,QAAQ,CAAC,CAAC,SAAS;WACnE,KAAK;AACZ,OAAI,EAAE,eAAeC,oCAAmB,OAAM;AAC9C,UAAO,KAAK,GAAG,IAAI,OAAO;AAC1B,YAAS,KAAK,GAAG,IAAI,SAAS;AAC9B;;AAEF,MAAI,OAAO,SAAS,MAAO,MAAK,WAAW,KAAK;GAAE,SAAS,OAAO;GAAS;GAAO,CAAC;MAC9E,MAAK,MAAM,KAAK;GAAE;GAAM;GAAO,CAAC;;AAEvC,KAAI,OAAO,SAAS,EAAG,OAAM,IAAIA,mCAAiB,QAAQ,SAAS;AACnE,QAAO;;;;;;;;;;;;AAqBT,SAAgB,gBACd,QACA,QACA,QACiB;CACjB,MAAM,OAAO,cAAc,QAAQ,OAAO;CAC1C,MAAM,UAAqD,EAAE;AAC7D,MAAK,MAAM,EAAE,MAAM,WAAW,KAAK,OAAO;EACxC,MAAM,UAAU,IAAIL,0BAAS;AAC7B,OAAK,MAAM,WAAW,QAAQ,YAAY,OAAO,EAAE,MAAM,QAAQ,CAAC,CAAC,SACjE,QAAO,KAAK,QAAQ,QAAQ;AAE9B,UAAQ,KAAK;GAAE;GAAM;GAAS,CAAC;;AAEjC,MAAK,MAAM,EAAE,MAAM,aAAa,QAAS,eAAc,QAAQ,MAAM,SAAS,EAAE,EAAE,OAAO;AACzF,QAAO;EACL,SAAe;AACb,QAAK,MAAM,EAAE,SAAS,WAAW,KAAK,YAAY;IAChD,MAAM,SAAS,QAAQ,cAAc,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC7D,SAAK,MAAM,WAAW,QAAQ,YAAY,EAAE,CAAE,QAAO,KAAK,QAAQ,QAAQ;;;EAG9E,OAAa;AACX,QAAK,MAAM,EAAE,aAAa,SAAS;IACjC,MAAM,KAAK,OAAO,WAAW,MAAM,EAAE,YAAY,QAAQ;AACzD,QAAI,OAAO,GAAI,QAAO,OAAO,IAAI,EAAE;;;EAGxC"}
@@ -0,0 +1,211 @@
1
+ import { build, msg, quote } from "./message-text.js";
2
+ import { FixtureLoadError } from "./fixture-loader.js";
3
+ import { MCP_FAKES_DEFAULT_MOUNT, McpFakeStore, McpFakesAddError, blockIdOf } from "./mcp-fakes.js";
4
+ import { MCPMock } from "./mcp-mock.js";
5
+
6
+ //#region src/mcp-fakes-mount.ts
7
+ /**
8
+ * The control-API prefix. The server hands every request whose path starts
9
+ * with it to the control API, before any mount, so no mount under it is
10
+ * reachable.
11
+ */
12
+ const CONTROL_PREFIX = "/__aimock";
13
+ /** The handlers an auto-mount created (W3, W6), whichever path created them. */
14
+ const autoMounts = /* @__PURE__ */ new WeakSet();
15
+ /** `handler` was auto-mounted for MCP fakes (at start, after start or by the control API). */
16
+ function isAutoMount(handler) {
17
+ return autoMounts.has(handler);
18
+ }
19
+ /** A mount "is an MCPMock" for fakes if and only if it implements `addMcpFakes`. */
20
+ function isFakeMount(handler) {
21
+ return typeof handler.addMcpFakes === "function";
22
+ }
23
+ /**
24
+ * The mount that serves `path`, in dispatch order: an MCPMock at exactly
25
+ * `path`; a conflict when the path is under the control prefix, when a mount
26
+ * that is not an MCPMock serves it first (it would shadow the fakes), or when
27
+ * an MCPMock at `path` without its trailing "/" answers it as its root; none
28
+ * when no mount serves it. An MCPMock mounted at a prefix of `path` lets any
29
+ * other sub-path fall through, so it is skipped. `pending` are the paths an
30
+ * add will auto-mount before this one, in order (they follow `mounts`).
31
+ */
32
+ function findFakeMount(mounts, path, pending = []) {
33
+ if (path.startsWith(CONTROL_PREFIX)) return {
34
+ kind: "conflict",
35
+ mountPath: CONTROL_PREFIX,
36
+ reason: "control"
37
+ };
38
+ for (const { path: mountPath, handler } of mounts) {
39
+ if (path !== mountPath && !path.startsWith(mountPath + "/")) continue;
40
+ if (!isFakeMount(handler)) return {
41
+ kind: "conflict",
42
+ mountPath,
43
+ reason: "not-mcp"
44
+ };
45
+ if (mountPath === path) return {
46
+ kind: "mcp",
47
+ handler
48
+ };
49
+ if (path === mountPath + "/") return {
50
+ kind: "conflict",
51
+ mountPath,
52
+ reason: "mcp-root"
53
+ };
54
+ }
55
+ for (const mountPath of pending) if (path === mountPath + "/") return {
56
+ kind: "conflict",
57
+ mountPath,
58
+ reason: "mcp-root"
59
+ };
60
+ return { kind: "none" };
61
+ }
62
+ /** Why the fakes for `path` cannot be served there (no location prefix). */
63
+ function mountConflictDetail(path, target) {
64
+ switch (target.reason) {
65
+ case "control": return msg`mount ${quote(path)} is under the control API prefix ${quote(target.mountPath)}, which takes its requests first, so its fakes cannot be served`;
66
+ case "mcp-root": return msg`mount ${quote(path)} is answered by the MCP mock at ${quote(target.mountPath)} as its root, so its fakes cannot be served`;
67
+ case "not-mcp": return msg`mount ${quote(path)} is served by ${quote(target.mountPath)}, which is not an MCP mock, so its fakes cannot be served`;
68
+ }
69
+ }
70
+ /** Wire, push and log (L4, L5) an auto-mounted MCPMock. */
71
+ function pushAutoMount(mounts, path, handler, wiring, logger) {
72
+ if (wiring.journal) handler.setJournal(wiring.journal);
73
+ if (wiring.registry) handler.setRegistry(wiring.registry);
74
+ if (wiring.logger) handler.setLogger(wiring.logger);
75
+ handler.setReplaySpeed(wiring.replaySpeed ?? 1);
76
+ const others = mounts.filter((m) => m.path !== path && isFakeMount(m.handler));
77
+ mounts.push({
78
+ path,
79
+ handler
80
+ });
81
+ autoMounts.add(handler);
82
+ logger.info(build(msg`MCP fakes: auto-mounted an MCP mock at ${quote(path)}`));
83
+ for (const other of others) logger.warn(build(msg`MCP fakes: auto-mounted an MCP mock at ${quote(path)} while an MCP mock is mounted at ${quote(other.path)}; fakes for one path do not reach the other`));
84
+ }
85
+ /**
86
+ * The MCPMock for `path`: the one already mounted there, or a new one pushed
87
+ * onto `mounts` (auto-mount, logs L4 and L5). A conflict is returned, not
88
+ * thrown, so the caller can name the offending block.
89
+ */
90
+ function ensureFakeMount(mounts, path, wiring, logger) {
91
+ const target = findFakeMount(mounts, path);
92
+ if (target.kind === "conflict") return { conflict: target.mountPath };
93
+ if (target.kind === "mcp") return {
94
+ handler: target.handler,
95
+ created: false
96
+ };
97
+ const handler = new MCPMock();
98
+ pushAutoMount(mounts, path, handler, wiring, logger);
99
+ return {
100
+ handler,
101
+ created: true
102
+ };
103
+ }
104
+ /** A block's `mount` path, read without running getters; the add validates it. */
105
+ function mountOf(raw) {
106
+ if (typeof raw !== "object" || raw === null) return MCP_FAKES_DEFAULT_MOUNT;
107
+ const value = Object.getOwnPropertyDescriptor(raw, "mount")?.value;
108
+ return typeof value === "string" ? value : MCP_FAKES_DEFAULT_MOUNT;
109
+ }
110
+ /** The mount-conflict error for one file-origin block. */
111
+ function mountConflictError(block, path, target) {
112
+ return new FixtureLoadError({
113
+ rule: "mcp-fakes/mount-conflict",
114
+ file: block.source,
115
+ blockId: blockIdOf(block.source, "", block.blockIndex),
116
+ entryId: null,
117
+ detail: mountConflictDetail(path, target)
118
+ });
119
+ }
120
+ /**
121
+ * Check file-origin `blocks` against `mounts` without changing anything:
122
+ * group them by `mount` path (default `/mcp`), find each group's mount (a
123
+ * mount conflict is one error per block), and add each group to a throwaway
124
+ * store (bad blocks and id collisions inside the input). Throws one
125
+ * `McpFakesAddError` holding every error. With `checked`, only the groups
126
+ * that hold a checked block are checked, and only checked blocks get a
127
+ * mount-conflict error (the others were checked before).
128
+ */
129
+ function planFileFakes(mounts, blocks, checked) {
130
+ const groups = /* @__PURE__ */ new Map();
131
+ for (const block of blocks) {
132
+ const path = mountOf(block.raw);
133
+ const group = groups.get(path);
134
+ if (group) group.push(block);
135
+ else groups.set(path, [block]);
136
+ }
137
+ const errors = [];
138
+ const warnings = [];
139
+ const plan = {
140
+ onExisting: [],
141
+ onNew: [],
142
+ warnings
143
+ };
144
+ for (const [path, group] of groups) {
145
+ if (checked && !group.some((block) => checked.has(block))) continue;
146
+ const target = findFakeMount(mounts, path, plan.onNew.map((g) => g.path));
147
+ if (target.kind === "conflict") {
148
+ for (const block of group) if (!checked || checked.has(block)) errors.push(mountConflictError(block, path, target));
149
+ continue;
150
+ }
151
+ try {
152
+ warnings.push(...new McpFakeStore().add(group, { kind: "file" }).warnings);
153
+ } catch (err) {
154
+ if (!(err instanceof McpFakesAddError)) throw err;
155
+ errors.push(...err.errors);
156
+ warnings.push(...err.warnings);
157
+ continue;
158
+ }
159
+ if (target.kind === "mcp") plan.onExisting.push({
160
+ handler: target.handler,
161
+ group
162
+ });
163
+ else plan.onNew.push({
164
+ path,
165
+ group
166
+ });
167
+ }
168
+ if (errors.length > 0) throw new McpFakesAddError(errors, warnings);
169
+ return plan;
170
+ }
171
+ /**
172
+ * Start-time hand-off (F2): check every block first (`planFileFakes`; on any
173
+ * error, a bad block, an id collision or a mount conflict, throws one
174
+ * `McpFakesAddError` holding every error and changes nothing). Then the
175
+ * groups for paths no mount serves are added to new MCPMocks, pushed onto
176
+ * `mounts` (auto-mount, L4, L5) so the mount loop wires them. The groups for
177
+ * an MCPMock already mounted are added by `commit`, once the server listens,
178
+ * so a start that fails after the hand-off leaves those mounts untouched;
179
+ * `undo` removes the auto-mounts. The add warnings (L8) are logged at `warn`.
180
+ */
181
+ function handOffMcpFakes(mounts, blocks, logger) {
182
+ const plan = planFileFakes(mounts, blocks);
183
+ const created = [];
184
+ for (const { path, group } of plan.onNew) {
185
+ const handler = new MCPMock();
186
+ for (const warning of handler.addMcpFakes(group, { kind: "file" }).warnings) logger.warn(warning.message);
187
+ created.push({
188
+ path,
189
+ handler
190
+ });
191
+ }
192
+ for (const { path, handler } of created) pushAutoMount(mounts, path, handler, {}, logger);
193
+ return {
194
+ commit() {
195
+ for (const { handler, group } of plan.onExisting) {
196
+ const result = handler.addMcpFakes?.(group, { kind: "file" });
197
+ for (const warning of result?.warnings ?? []) logger.warn(warning.message);
198
+ }
199
+ },
200
+ undo() {
201
+ for (const { handler } of created) {
202
+ const at = mounts.findIndex((m) => m.handler === handler);
203
+ if (at !== -1) mounts.splice(at, 1);
204
+ }
205
+ }
206
+ };
207
+ }
208
+
209
+ //#endregion
210
+ export { ensureFakeMount, findFakeMount, handOffMcpFakes, isAutoMount, isFakeMount, mountConflictDetail, planFileFakes };
211
+ //# sourceMappingURL=mcp-fakes-mount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-fakes-mount.js","names":[],"sources":["../src/mcp-fakes-mount.ts"],"sourcesContent":["/**\n * Where MCP fakes go on a server (spec 5.2): find the mount that serves a\n * block's `mount` path, auto-mount an `MCPMock` when none does (W3, W6), and\n * the start-time hand-off of `ServiceFixtures.mcpFakes` (F2).\n */\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type { McpFakeSource, Mountable } from \"./types.js\";\nimport { FixtureLoadError } from \"./fixture-loader.js\";\nimport { MCPMock } from \"./mcp-mock.js\";\nimport {\n MCP_FAKES_DEFAULT_MOUNT,\n McpFakeStore,\n McpFakesAddError,\n blockIdOf,\n type McpFakeIssue,\n} from \"./mcp-fakes.js\";\nimport { build, msg, quote, type Message } from \"./message-text.js\";\n\nexport type MountList = Array<{ path: string; handler: Mountable }>;\n\n/** Dependencies an auto-mount gets when it is created (after start: W4 wiring). */\nexport interface FakeMountWiring {\n journal?: Journal;\n registry?: MetricsRegistry;\n logger?: Logger;\n /** T1: the server's replay speed for recorded fake timing (default 1). */\n replaySpeed?: number;\n}\n\n/**\n * The control-API prefix. The server hands every request whose path starts\n * with it to the control API, before any mount, so no mount under it is\n * reachable.\n */\nexport const CONTROL_PREFIX = \"/__aimock\";\n\n/**\n * What serves a block's `mount` path. A conflict is a path no fake there can\n * be served at: a mount that is not an MCPMock serves it first (`not-mcp`), an\n * MCPMock mounted at `mountPath` answers it as its root (`mcp-root`, the path\n * is `mountPath` plus a trailing \"/\"), or it is under the control prefix\n * (`control`).\n */\nexport type FakeMountTarget =\n | { kind: \"mcp\"; handler: Mountable }\n | { kind: \"conflict\"; mountPath: string; reason: \"not-mcp\" | \"mcp-root\" | \"control\" }\n | { kind: \"none\" };\n\n/** The handlers an auto-mount created (W3, W6), whichever path created them. */\nconst autoMounts = new WeakSet<Mountable>();\n\n/** `handler` was auto-mounted for MCP fakes (at start, after start or by the control API). */\nexport function isAutoMount(handler: Mountable): boolean {\n return autoMounts.has(handler);\n}\n\n/** A mount \"is an MCPMock\" for fakes if and only if it implements `addMcpFakes`. */\nexport function isFakeMount(handler: Mountable): boolean {\n return typeof handler.addMcpFakes === \"function\";\n}\n\n/**\n * The mount that serves `path`, in dispatch order: an MCPMock at exactly\n * `path`; a conflict when the path is under the control prefix, when a mount\n * that is not an MCPMock serves it first (it would shadow the fakes), or when\n * an MCPMock at `path` without its trailing \"/\" answers it as its root; none\n * when no mount serves it. An MCPMock mounted at a prefix of `path` lets any\n * other sub-path fall through, so it is skipped. `pending` are the paths an\n * add will auto-mount before this one, in order (they follow `mounts`).\n */\nexport function findFakeMount(\n mounts: MountList,\n path: string,\n pending: readonly string[] = [],\n): FakeMountTarget {\n if (path.startsWith(CONTROL_PREFIX)) {\n return { kind: \"conflict\", mountPath: CONTROL_PREFIX, reason: \"control\" };\n }\n for (const { path: mountPath, handler } of mounts) {\n if (path !== mountPath && !path.startsWith(mountPath + \"/\")) continue;\n if (!isFakeMount(handler)) return { kind: \"conflict\", mountPath, reason: \"not-mcp\" };\n if (mountPath === path) return { kind: \"mcp\", handler };\n if (path === mountPath + \"/\") return { kind: \"conflict\", mountPath, reason: \"mcp-root\" };\n }\n for (const mountPath of pending) {\n if (path === mountPath + \"/\") return { kind: \"conflict\", mountPath, reason: \"mcp-root\" };\n }\n return { kind: \"none\" };\n}\n\n/** Why the fakes for `path` cannot be served there (no location prefix). */\nexport function mountConflictDetail(\n path: string,\n target: Extract<FakeMountTarget, { kind: \"conflict\" }>,\n): Message {\n switch (target.reason) {\n case \"control\":\n return msg`mount ${quote(path)} is under the control API prefix ${quote(target.mountPath)}, which takes its requests first, so its fakes cannot be served`;\n case \"mcp-root\":\n return msg`mount ${quote(path)} is answered by the MCP mock at ${quote(target.mountPath)} as its root, so its fakes cannot be served`;\n case \"not-mcp\":\n return msg`mount ${quote(path)} is served by ${quote(target.mountPath)}, which is not an MCP mock, so its fakes cannot be served`;\n }\n}\n\n/** Wire, push and log (L4, L5) an auto-mounted MCPMock. */\nfunction pushAutoMount(\n mounts: MountList,\n path: string,\n handler: MCPMock,\n wiring: FakeMountWiring,\n logger: Logger,\n): void {\n if (wiring.journal) handler.setJournal(wiring.journal);\n if (wiring.registry) handler.setRegistry(wiring.registry);\n if (wiring.logger) handler.setLogger(wiring.logger);\n handler.setReplaySpeed(wiring.replaySpeed ?? 1);\n const others = mounts.filter((m) => m.path !== path && isFakeMount(m.handler));\n mounts.push({ path, handler });\n autoMounts.add(handler);\n logger.info(build(msg`MCP fakes: auto-mounted an MCP mock at ${quote(path)}`));\n for (const other of others) {\n logger.warn(\n build(\n msg`MCP fakes: auto-mounted an MCP mock at ${quote(path)} while an MCP mock is mounted at ${quote(other.path)}; fakes for one path do not reach the other`,\n ),\n );\n }\n}\n\n/**\n * The MCPMock for `path`: the one already mounted there, or a new one pushed\n * onto `mounts` (auto-mount, logs L4 and L5). A conflict is returned, not\n * thrown, so the caller can name the offending block.\n */\nexport function ensureFakeMount(\n mounts: MountList,\n path: string,\n wiring: FakeMountWiring,\n logger: Logger,\n): { handler: Mountable; created: boolean } | { conflict: string } {\n const target = findFakeMount(mounts, path);\n if (target.kind === \"conflict\") return { conflict: target.mountPath };\n if (target.kind === \"mcp\") return { handler: target.handler, created: false };\n const handler = new MCPMock();\n pushAutoMount(mounts, path, handler, wiring, logger);\n return { handler, created: true };\n}\n\n/** A block's `mount` path, read without running getters; the add validates it. */\nfunction mountOf(raw: unknown): string {\n if (typeof raw !== \"object\" || raw === null) return MCP_FAKES_DEFAULT_MOUNT;\n const value: unknown = Object.getOwnPropertyDescriptor(raw, \"mount\")?.value;\n return typeof value === \"string\" ? value : MCP_FAKES_DEFAULT_MOUNT;\n}\n\n/** The mount-conflict error for one file-origin block. */\nexport function mountConflictError(\n block: McpFakeSource,\n path: string,\n target: Extract<FakeMountTarget, { kind: \"conflict\" }>,\n): FixtureLoadError {\n return new FixtureLoadError({\n rule: \"mcp-fakes/mount-conflict\",\n file: block.source,\n blockId: blockIdOf(block.source, \"\", block.blockIndex),\n entryId: null,\n detail: mountConflictDetail(path, target),\n });\n}\n\n/** Where a checked add of file blocks goes: each group by its `mount` path. */\nexport interface FileFakesPlan {\n /** Groups for a path an MCPMock already serves, with that mount. */\n onExisting: Array<{ handler: Mountable; group: McpFakeSource[] }>;\n /** Groups for a path no mount serves, in order: each gets an auto-mount. */\n onNew: Array<{ path: string; group: McpFakeSource[] }>;\n /** The add warnings (L8), as a throwaway store reports them. */\n warnings: McpFakeIssue[];\n}\n\n/**\n * Check file-origin `blocks` against `mounts` without changing anything:\n * group them by `mount` path (default `/mcp`), find each group's mount (a\n * mount conflict is one error per block), and add each group to a throwaway\n * store (bad blocks and id collisions inside the input). Throws one\n * `McpFakesAddError` holding every error. With `checked`, only the groups\n * that hold a checked block are checked, and only checked blocks get a\n * mount-conflict error (the others were checked before).\n */\nexport function planFileFakes(\n mounts: MountList,\n blocks: readonly McpFakeSource[],\n checked?: ReadonlySet<McpFakeSource>,\n): FileFakesPlan {\n const groups = new Map<string, McpFakeSource[]>();\n for (const block of blocks) {\n const path = mountOf(block.raw);\n const group = groups.get(path);\n if (group) group.push(block);\n else groups.set(path, [block]);\n }\n\n const errors: FixtureLoadError[] = [];\n const warnings: McpFakeIssue[] = [];\n const plan: FileFakesPlan = { onExisting: [], onNew: [], warnings };\n for (const [path, group] of groups) {\n if (checked && !group.some((block) => checked.has(block))) continue;\n const target = findFakeMount(\n mounts,\n path,\n plan.onNew.map((g) => g.path),\n );\n if (target.kind === \"conflict\") {\n for (const block of group) {\n if (!checked || checked.has(block)) errors.push(mountConflictError(block, path, target));\n }\n continue;\n }\n try {\n warnings.push(...new McpFakeStore().add(group, { kind: \"file\" }).warnings);\n } catch (err) {\n if (!(err instanceof McpFakesAddError)) throw err;\n errors.push(...err.errors);\n warnings.push(...err.warnings);\n continue;\n }\n if (target.kind === \"mcp\") plan.onExisting.push({ handler: target.handler, group });\n else plan.onNew.push({ path, group });\n }\n if (errors.length > 0) throw new McpFakesAddError(errors, warnings);\n return plan;\n}\n\n/** A start-time hand-off: the auto-mounts are in place, the rest waits for `commit`. */\nexport interface McpFakesHandOff {\n /** Add the groups for existing MCPMocks (after the server listens). */\n commit(): void;\n /** Take the auto-mounts back off the mounts array (start failed). */\n undo(): void;\n}\n\n/**\n * Start-time hand-off (F2): check every block first (`planFileFakes`; on any\n * error, a bad block, an id collision or a mount conflict, throws one\n * `McpFakesAddError` holding every error and changes nothing). Then the\n * groups for paths no mount serves are added to new MCPMocks, pushed onto\n * `mounts` (auto-mount, L4, L5) so the mount loop wires them. The groups for\n * an MCPMock already mounted are added by `commit`, once the server listens,\n * so a start that fails after the hand-off leaves those mounts untouched;\n * `undo` removes the auto-mounts. The add warnings (L8) are logged at `warn`.\n */\nexport function handOffMcpFakes(\n mounts: MountList,\n blocks: McpFakeSource[],\n logger: Logger,\n): McpFakesHandOff {\n const plan = planFileFakes(mounts, blocks);\n const created: Array<{ path: string; handler: MCPMock }> = [];\n for (const { path, group } of plan.onNew) {\n const handler = new MCPMock();\n for (const warning of handler.addMcpFakes(group, { kind: \"file\" }).warnings) {\n logger.warn(warning.message);\n }\n created.push({ path, handler });\n }\n for (const { path, handler } of created) pushAutoMount(mounts, path, handler, {}, logger);\n return {\n commit(): void {\n for (const { handler, group } of plan.onExisting) {\n const result = handler.addMcpFakes?.(group, { kind: \"file\" });\n for (const warning of result?.warnings ?? []) logger.warn(warning.message);\n }\n },\n undo(): void {\n for (const { handler } of created) {\n const at = mounts.findIndex((m) => m.handler === handler);\n if (at !== -1) mounts.splice(at, 1);\n }\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ const require_mcp_fakes_mount = require('./mcp-fakes-mount.cjs');
2
+
3
+ //#region src/mcp-fakes-report.ts
4
+ /** RP7/RP8: thrown by the plugins under "fail" and by assertFakesReport. */
5
+ var AimockFakesReportError = class extends Error {
6
+ report;
7
+ constructor(report, message) {
8
+ super(message);
9
+ this.name = "AimockFakesReportError";
10
+ this.report = report;
11
+ }
12
+ };
13
+ /** TI5: a default test id cannot be inferred safely. */
14
+ var AimockTestIdCollisionError = class extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = "AimockTestIdCollisionError";
18
+ }
19
+ };
20
+ function isReportSource(handler) {
21
+ return typeof handler === "object" && handler !== null && typeof handler.fakesReportPart === "function";
22
+ }
23
+ /** RP1-RP5 over every fake mount of a server. Reads only: no state changes (RP9). */
24
+ function buildFakesReport(mounts, testId, context) {
25
+ let evicted = false;
26
+ const served = [];
27
+ const unconsumed = [];
28
+ const unfaked = [];
29
+ const failures = [];
30
+ const sharedUnconsumed = [];
31
+ for (const { path, handler } of mounts) {
32
+ if (!require_mcp_fakes_mount.isFakeMount(handler) || !isReportSource(handler)) continue;
33
+ const part = handler.fakesReportPart(testId, context, path);
34
+ evicted ||= part.evicted;
35
+ served.push(...part.served);
36
+ unconsumed.push(...part.unconsumed);
37
+ unfaked.push(...part.unfaked);
38
+ failures.push(...part.failures);
39
+ sharedUnconsumed.push(...part.sharedUnconsumed);
40
+ }
41
+ served.sort((a, b) => a.seq - b.seq);
42
+ failures.sort((a, b) => a.seq - b.seq);
43
+ return {
44
+ testId,
45
+ context,
46
+ ok: failures.length === 0 && !evicted && unconsumed.length === 0,
47
+ evicted,
48
+ served: served.map(({ entryId, mount, tool, args }) => ({
49
+ entryId,
50
+ mount,
51
+ tool,
52
+ args
53
+ })),
54
+ unconsumed,
55
+ unfaked,
56
+ failures: failures.map(({ code, mount, tool, args }) => ({
57
+ code,
58
+ mount,
59
+ tool,
60
+ args
61
+ })),
62
+ sharedUnconsumed
63
+ };
64
+ }
65
+ /** `value` as one line of JSON; a value with no JSON text is shown as `String(value)`. */
66
+ function jsonText(value) {
67
+ try {
68
+ return JSON.stringify(value) ?? String(value);
69
+ } catch {
70
+ return String(value);
71
+ }
72
+ }
73
+ /**
74
+ * RP7 message: a header naming the test id (and context), then one line per
75
+ * failure, then per unconsumed entry id, then the evicted line. With
76
+ * `failOnUnfaked`, one line per unfaked call follows.
77
+ */
78
+ function formatFakesReport(report, opts = {}) {
79
+ const lines = [`aimock MCP fakes report failed (${report.testId === null ? "no test id" : `testId ${JSON.stringify(report.testId)}`}${report.context === null ? "" : `, context ${JSON.stringify(report.context)}`}):`];
80
+ for (const f of report.failures) lines.push(` failure ${f.code}: tools/call ${f.tool} on ${f.mount} with ${jsonText(f.args)}`);
81
+ for (const u of report.unconsumed) lines.push(` unconsumed: ${u.entryId} (${u.mount} ${u.tool})`);
82
+ if (report.evicted) lines.push(" evicted: this test id's fake state was evicted by the per-mount test-id cap, or its event log overflowed (1000 events); the report is incomplete");
83
+ if (opts.failOnUnfaked === true) for (const u of report.unfaked) lines.push(` unfaked: tools/call ${u.tool} on ${u.mount} answered by ${u.answeredBy}`);
84
+ return lines.join("\n");
85
+ }
86
+ /** RP8: throw `AimockFakesReportError` when the report fails (RP5, plus unfaked under `failOnUnfaked`). */
87
+ function assertFakesReport(report, opts = {}) {
88
+ const unfakedFails = opts.failOnUnfaked === true && report.unfaked.length > 0;
89
+ if (report.ok && !unfakedFails) return;
90
+ throw new AimockFakesReportError(report, formatFakesReport(report, opts));
91
+ }
92
+ /** The query parameters `GET /__aimock/mcp/fakes/report` accepts. */
93
+ const MCP_FAKES_REPORT_PARAMS = new Set(["testId", "context"]);
94
+ /** RP6: `GET /__aimock/mcp/fakes/report?testId=&context=`. Always answers; returns true. */
95
+ function handleFakesReportRoute(res, searchParams, mounts) {
96
+ const reply = (status, body) => {
97
+ res.writeHead(status, { "Content-Type": "application/json" });
98
+ res.end(JSON.stringify(body));
99
+ return true;
100
+ };
101
+ for (const key of searchParams.keys()) if (!MCP_FAKES_REPORT_PARAMS.has(key)) return reply(400, { error: `Unknown query parameter: '${key}'. Supported: ${[...MCP_FAKES_REPORT_PARAMS].join(", ")}` });
102
+ return reply(200, buildFakesReport(mounts, searchParams.get("testId") || null, searchParams.get("context") || null));
103
+ }
104
+
105
+ //#endregion
106
+ exports.AimockFakesReportError = AimockFakesReportError;
107
+ exports.AimockTestIdCollisionError = AimockTestIdCollisionError;
108
+ exports.assertFakesReport = assertFakesReport;
109
+ exports.formatFakesReport = formatFakesReport;
110
+ exports.handleFakesReportRoute = handleFakesReportRoute;
111
+ //# sourceMappingURL=mcp-fakes-report.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-fakes-report.cjs","names":["isFakeMount"],"sources":["../src/mcp-fakes-report.ts"],"sourcesContent":["/**\n * The per-test MCP fake report (record-replay RP1-RP8): one identity (a test\n * id, plus an optional context) across every MCP mount of one server. Each\n * mount's `MCPMock` gives its part (`fakesReportPart`, from the store's event\n * log); this module merges the parts, decides `ok` (RP5), formats the failure\n * message (RP7) and serves `GET /__aimock/mcp/fakes/report` (RP6).\n */\nimport type * as http from \"node:http\";\nimport type { MountList } from \"./mcp-fakes-mount.js\";\nimport { isFakeMount } from \"./mcp-fakes-mount.js\";\nimport type { McpFakeReportPart } from \"./mcp-fakes.js\";\n\ntype ServedRow = McpFakeReportPart[\"served\"][number];\ntype FailureRow = McpFakeReportPart[\"failures\"][number];\n\n/**\n * RP3: the report. `served` and `failures` are in call order across mounts;\n * their rows have no `seq` (the internal ordering key the parts carry).\n */\nexport interface McpFakesReport {\n testId: string | null;\n context: string | null;\n ok: boolean;\n evicted: boolean;\n served: Omit<ServedRow, \"seq\">[];\n unconsumed: McpFakeReportPart[\"unconsumed\"];\n unfaked: McpFakeReportPart[\"unfaked\"];\n failures: Omit<FailureRow, \"seq\">[];\n sharedUnconsumed: McpFakeReportPart[\"sharedUnconsumed\"];\n}\n\n/** RP7/RP8: thrown by the plugins under \"fail\" and by assertFakesReport. */\nexport class AimockFakesReportError extends Error {\n readonly report: McpFakesReport;\n constructor(report: McpFakesReport, message: string) {\n super(message);\n this.name = \"AimockFakesReportError\";\n this.report = report;\n }\n}\n\n/** TI5: a default test id cannot be inferred safely. */\nexport class AimockTestIdCollisionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AimockTestIdCollisionError\";\n }\n}\n\ninterface ReportSource {\n fakesReportPart(testId: string | null, context: string | null, mount: string): McpFakeReportPart;\n}\n\nfunction isReportSource(handler: unknown): handler is ReportSource {\n return (\n typeof handler === \"object\" &&\n handler !== null &&\n typeof (handler as Partial<ReportSource>).fakesReportPart === \"function\"\n );\n}\n\n/** RP1-RP5 over every fake mount of a server. Reads only: no state changes (RP9). */\nexport function buildFakesReport(\n mounts: MountList,\n testId: string | null,\n context: string | null,\n): McpFakesReport {\n let evicted = false;\n const served: ServedRow[] = [];\n const unconsumed: McpFakesReport[\"unconsumed\"] = [];\n const unfaked: McpFakesReport[\"unfaked\"] = [];\n const failures: FailureRow[] = [];\n const sharedUnconsumed: McpFakesReport[\"sharedUnconsumed\"] = [];\n for (const { path, handler } of mounts) {\n if (!isFakeMount(handler) || !isReportSource(handler)) continue;\n const part = handler.fakesReportPart(testId, context, path);\n evicted ||= part.evicted;\n served.push(...part.served);\n unconsumed.push(...part.unconsumed);\n unfaked.push(...part.unfaked);\n failures.push(...part.failures);\n sharedUnconsumed.push(...part.sharedUnconsumed);\n }\n // `seq` is shared by every store, so it orders calls across mounts.\n served.sort((a, b) => a.seq - b.seq);\n failures.sort((a, b) => a.seq - b.seq);\n const ok = failures.length === 0 && !evicted && unconsumed.length === 0; // RP5\n return {\n testId,\n context,\n ok,\n evicted,\n served: served.map(({ entryId, mount, tool, args }) => ({ entryId, mount, tool, args })),\n unconsumed,\n unfaked,\n failures: failures.map(({ code, mount, tool, args }) => ({ code, mount, tool, args })),\n sharedUnconsumed,\n };\n}\n\n/** `value` as one line of JSON; a value with no JSON text is shown as `String(value)`. */\nfunction jsonText(value: unknown): string {\n try {\n const text = JSON.stringify(value) as string | undefined;\n return text ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/**\n * RP7 message: a header naming the test id (and context), then one line per\n * failure, then per unconsumed entry id, then the evicted line. With\n * `failOnUnfaked`, one line per unfaked call follows.\n */\nexport function formatFakesReport(\n report: McpFakesReport,\n opts: { failOnUnfaked?: boolean } = {},\n): string {\n const who = report.testId === null ? \"no test id\" : `testId ${JSON.stringify(report.testId)}`;\n const ctx = report.context === null ? \"\" : `, context ${JSON.stringify(report.context)}`;\n const lines = [`aimock MCP fakes report failed (${who}${ctx}):`];\n for (const f of report.failures) {\n lines.push(` failure ${f.code}: tools/call ${f.tool} on ${f.mount} with ${jsonText(f.args)}`);\n }\n for (const u of report.unconsumed) {\n lines.push(` unconsumed: ${u.entryId} (${u.mount} ${u.tool})`);\n }\n if (report.evicted) {\n lines.push(\n \" evicted: this test id's fake state was evicted by the per-mount test-id cap, or its event log overflowed (1000 events); the report is incomplete\",\n );\n }\n if (opts.failOnUnfaked === true) {\n for (const u of report.unfaked) {\n lines.push(` unfaked: tools/call ${u.tool} on ${u.mount} answered by ${u.answeredBy}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\n/** RP8: throw `AimockFakesReportError` when the report fails (RP5, plus unfaked under `failOnUnfaked`). */\nexport function assertFakesReport(\n report: McpFakesReport,\n opts: { failOnUnfaked?: boolean } = {},\n): void {\n const unfakedFails = opts.failOnUnfaked === true && report.unfaked.length > 0;\n if (report.ok && !unfakedFails) return;\n throw new AimockFakesReportError(report, formatFakesReport(report, opts));\n}\n\n/** The query parameters `GET /__aimock/mcp/fakes/report` accepts. */\nconst MCP_FAKES_REPORT_PARAMS = new Set([\"testId\", \"context\"]);\n\n/** RP6: `GET /__aimock/mcp/fakes/report?testId=&context=`. Always answers; returns true. */\nexport function handleFakesReportRoute(\n res: http.ServerResponse,\n searchParams: URLSearchParams,\n mounts: MountList,\n): true {\n const reply = (status: number, body: unknown): true => {\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return true;\n };\n for (const key of searchParams.keys()) {\n if (!MCP_FAKES_REPORT_PARAMS.has(key)) {\n return reply(400, {\n error: `Unknown query parameter: '${key}'. Supported: ${[...MCP_FAKES_REPORT_PARAMS].join(\", \")}`,\n });\n }\n }\n // Empty values are not supplied, as on GET /__aimock/mcp/fakes.\n const testId = searchParams.get(\"testId\") || null;\n const context = searchParams.get(\"context\") || null;\n return reply(200, buildFakesReport(mounts, testId, context));\n}\n"],"mappings":"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"}