@copilotkit/aimock 1.43.0 → 1.44.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 (290) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +8 -2
  5. package/dist/agui-handler.d.ts.map +1 -1
  6. package/dist/agui-types.d.ts.map +1 -1
  7. package/dist/bedrock-converse.cjs +32 -4
  8. package/dist/bedrock-converse.cjs.map +1 -1
  9. package/dist/bedrock-converse.d.cts.map +1 -1
  10. package/dist/bedrock-converse.d.ts.map +1 -1
  11. package/dist/bedrock-converse.js +32 -4
  12. package/dist/bedrock-converse.js.map +1 -1
  13. package/dist/chaos.cjs +8 -5
  14. package/dist/chaos.cjs.map +1 -1
  15. package/dist/chaos.d.cts.map +1 -1
  16. package/dist/chaos.d.ts.map +1 -1
  17. package/dist/chaos.js +9 -6
  18. package/dist/chaos.js.map +1 -1
  19. package/dist/cli.cjs +89 -7
  20. package/dist/cli.cjs.map +1 -1
  21. package/dist/cli.js +91 -9
  22. package/dist/cli.js.map +1 -1
  23. package/dist/cohere.cjs +27 -1
  24. package/dist/cohere.cjs.map +1 -1
  25. package/dist/cohere.js +27 -1
  26. package/dist/cohere.js.map +1 -1
  27. package/dist/config-loader.cjs +1 -1
  28. package/dist/config-loader.d.ts.map +1 -1
  29. package/dist/config-loader.js +1 -1
  30. package/dist/constants.cjs +8 -0
  31. package/dist/constants.cjs.map +1 -1
  32. package/dist/constants.d.cts +6 -2
  33. package/dist/constants.d.ts +6 -2
  34. package/dist/constants.js +8 -1
  35. package/dist/constants.js.map +1 -1
  36. package/dist/echo-text.cjs +65 -0
  37. package/dist/echo-text.cjs.map +1 -0
  38. package/dist/echo-text.d.cts +22 -0
  39. package/dist/echo-text.d.cts.map +1 -0
  40. package/dist/echo-text.d.ts +22 -0
  41. package/dist/echo-text.d.ts.map +1 -0
  42. package/dist/echo-text.js +65 -0
  43. package/dist/echo-text.js.map +1 -0
  44. package/dist/embeddings.cjs +18 -1
  45. package/dist/embeddings.cjs.map +1 -1
  46. package/dist/embeddings.d.cts +1 -1
  47. package/dist/embeddings.d.cts.map +1 -1
  48. package/dist/embeddings.d.ts +1 -1
  49. package/dist/embeddings.d.ts.map +1 -1
  50. package/dist/embeddings.js +18 -1
  51. package/dist/embeddings.js.map +1 -1
  52. package/dist/files.cjs +9 -11
  53. package/dist/files.cjs.map +1 -1
  54. package/dist/files.d.cts.map +1 -1
  55. package/dist/files.d.ts.map +1 -1
  56. package/dist/files.js +10 -13
  57. package/dist/files.js.map +1 -1
  58. package/dist/fine-tuning.cjs +1 -19
  59. package/dist/fine-tuning.cjs.map +1 -1
  60. package/dist/fine-tuning.d.cts.map +1 -1
  61. package/dist/fine-tuning.d.ts.map +1 -1
  62. package/dist/fine-tuning.js +2 -20
  63. package/dist/fine-tuning.js.map +1 -1
  64. package/dist/fixture-loader-services.cjs +116 -0
  65. package/dist/fixture-loader-services.cjs.map +1 -0
  66. package/dist/fixture-loader-services.d.cts +48 -0
  67. package/dist/fixture-loader-services.d.cts.map +1 -0
  68. package/dist/fixture-loader-services.d.ts +48 -0
  69. package/dist/fixture-loader-services.d.ts.map +1 -0
  70. package/dist/fixture-loader-services.js +114 -0
  71. package/dist/fixture-loader-services.js.map +1 -0
  72. package/dist/fixture-loader.cjs +172 -11
  73. package/dist/fixture-loader.cjs.map +1 -1
  74. package/dist/fixture-loader.d.cts +95 -1
  75. package/dist/fixture-loader.d.cts.map +1 -1
  76. package/dist/fixture-loader.d.ts +95 -1
  77. package/dist/fixture-loader.d.ts.map +1 -1
  78. package/dist/fixture-loader.js +168 -12
  79. package/dist/fixture-loader.js.map +1 -1
  80. package/dist/gemini-embeddings.cjs +42 -2
  81. package/dist/gemini-embeddings.cjs.map +1 -1
  82. package/dist/gemini-embeddings.js +43 -3
  83. package/dist/gemini-embeddings.js.map +1 -1
  84. package/dist/gemini-interactions.cjs +38 -0
  85. package/dist/gemini-interactions.cjs.map +1 -1
  86. package/dist/gemini-interactions.d.cts.map +1 -1
  87. package/dist/gemini-interactions.d.ts.map +1 -1
  88. package/dist/gemini-interactions.js +38 -0
  89. package/dist/gemini-interactions.js.map +1 -1
  90. package/dist/gemini.cjs +8 -1
  91. package/dist/gemini.cjs.map +1 -1
  92. package/dist/gemini.d.cts.map +1 -1
  93. package/dist/gemini.d.ts.map +1 -1
  94. package/dist/gemini.js +8 -1
  95. package/dist/gemini.js.map +1 -1
  96. package/dist/helpers.cjs +276 -5
  97. package/dist/helpers.cjs.map +1 -1
  98. package/dist/helpers.d.cts.map +1 -1
  99. package/dist/helpers.d.ts.map +1 -1
  100. package/dist/helpers.js +275 -7
  101. package/dist/helpers.js.map +1 -1
  102. package/dist/images.cjs +3 -2
  103. package/dist/images.cjs.map +1 -1
  104. package/dist/images.js +3 -2
  105. package/dist/images.js.map +1 -1
  106. package/dist/index.cjs +30 -1
  107. package/dist/index.d.cts +8 -3
  108. package/dist/index.d.ts +8 -3
  109. package/dist/index.js +9 -3
  110. package/dist/jest.cjs +15 -10
  111. package/dist/jest.cjs.map +1 -1
  112. package/dist/jest.d.cts +4 -2
  113. package/dist/jest.d.cts.map +1 -1
  114. package/dist/jest.d.ts +4 -2
  115. package/dist/jest.d.ts.map +1 -1
  116. package/dist/jest.js +16 -11
  117. package/dist/jest.js.map +1 -1
  118. package/dist/journal.cjs +20 -0
  119. package/dist/journal.cjs.map +1 -1
  120. package/dist/journal.d.cts +16 -0
  121. package/dist/journal.d.cts.map +1 -1
  122. package/dist/journal.d.ts +16 -0
  123. package/dist/journal.d.ts.map +1 -1
  124. package/dist/journal.js +20 -0
  125. package/dist/journal.js.map +1 -1
  126. package/dist/llmock.cjs +82 -10
  127. package/dist/llmock.cjs.map +1 -1
  128. package/dist/llmock.d.cts +35 -0
  129. package/dist/llmock.d.cts.map +1 -1
  130. package/dist/llmock.d.ts +35 -0
  131. package/dist/llmock.d.ts.map +1 -1
  132. package/dist/llmock.js +83 -11
  133. package/dist/llmock.js.map +1 -1
  134. package/dist/mcp-fakes-mount.cjs +216 -0
  135. package/dist/mcp-fakes-mount.cjs.map +1 -0
  136. package/dist/mcp-fakes-mount.js +210 -0
  137. package/dist/mcp-fakes-mount.js.map +1 -0
  138. package/dist/mcp-fakes.cjs +1619 -0
  139. package/dist/mcp-fakes.cjs.map +1 -0
  140. package/dist/mcp-fakes.d.cts +101 -0
  141. package/dist/mcp-fakes.d.cts.map +1 -0
  142. package/dist/mcp-fakes.d.ts +101 -0
  143. package/dist/mcp-fakes.d.ts.map +1 -0
  144. package/dist/mcp-fakes.js +1614 -0
  145. package/dist/mcp-fakes.js.map +1 -0
  146. package/dist/mcp-handler.cjs +331 -17
  147. package/dist/mcp-handler.cjs.map +1 -1
  148. package/dist/mcp-handler.js +330 -18
  149. package/dist/mcp-handler.js.map +1 -1
  150. package/dist/mcp-mock.cjs +217 -49
  151. package/dist/mcp-mock.cjs.map +1 -1
  152. package/dist/mcp-mock.d.cts +66 -1
  153. package/dist/mcp-mock.d.cts.map +1 -1
  154. package/dist/mcp-mock.d.ts +66 -1
  155. package/dist/mcp-mock.d.ts.map +1 -1
  156. package/dist/mcp-mock.js +218 -50
  157. package/dist/mcp-mock.js.map +1 -1
  158. package/dist/mcp-stub.cjs +5 -1
  159. package/dist/mcp-stub.d.cts +3 -1
  160. package/dist/mcp-stub.d.ts +3 -1
  161. package/dist/mcp-stub.js +3 -1
  162. package/dist/mcp-types.d.cts +6 -0
  163. package/dist/mcp-types.d.ts +6 -0
  164. package/dist/message-text.cjs +192 -0
  165. package/dist/message-text.cjs.map +1 -0
  166. package/dist/message-text.d.cts +31 -0
  167. package/dist/message-text.d.cts.map +1 -0
  168. package/dist/message-text.d.ts +31 -0
  169. package/dist/message-text.d.ts.map +1 -0
  170. package/dist/message-text.js +184 -0
  171. package/dist/message-text.js.map +1 -0
  172. package/dist/messages.cjs +38 -0
  173. package/dist/messages.cjs.map +1 -1
  174. package/dist/messages.js +38 -0
  175. package/dist/messages.js.map +1 -1
  176. package/dist/metrics.cjs +46 -66
  177. package/dist/metrics.cjs.map +1 -1
  178. package/dist/metrics.d.cts +14 -6
  179. package/dist/metrics.d.cts.map +1 -1
  180. package/dist/metrics.d.ts +14 -6
  181. package/dist/metrics.d.ts.map +1 -1
  182. package/dist/metrics.js +32 -40
  183. package/dist/metrics.js.map +1 -1
  184. package/dist/ollama.cjs +5 -1
  185. package/dist/ollama.cjs.map +1 -1
  186. package/dist/ollama.d.cts.map +1 -1
  187. package/dist/ollama.d.ts.map +1 -1
  188. package/dist/ollama.js +5 -1
  189. package/dist/ollama.js.map +1 -1
  190. package/dist/openapi.cjs +3405 -0
  191. package/dist/openapi.cjs.map +1 -0
  192. package/dist/openapi.d.cts +30 -0
  193. package/dist/openapi.d.cts.map +1 -0
  194. package/dist/openapi.d.ts +30 -0
  195. package/dist/openapi.d.ts.map +1 -0
  196. package/dist/openapi.js +3404 -0
  197. package/dist/openapi.js.map +1 -0
  198. package/dist/openrouter-video.cjs +4 -4
  199. package/dist/openrouter-video.cjs.map +1 -1
  200. package/dist/openrouter-video.d.cts.map +1 -1
  201. package/dist/openrouter-video.d.ts.map +1 -1
  202. package/dist/openrouter-video.js +4 -4
  203. package/dist/openrouter-video.js.map +1 -1
  204. package/dist/recorder.cjs +9 -1
  205. package/dist/recorder.cjs.map +1 -1
  206. package/dist/recorder.d.cts.map +1 -1
  207. package/dist/recorder.d.ts.map +1 -1
  208. package/dist/recorder.js +9 -1
  209. package/dist/recorder.js.map +1 -1
  210. package/dist/responses.cjs +43 -4
  211. package/dist/responses.cjs.map +1 -1
  212. package/dist/responses.d.cts.map +1 -1
  213. package/dist/responses.d.ts.map +1 -1
  214. package/dist/responses.js +43 -4
  215. package/dist/responses.js.map +1 -1
  216. package/dist/route-registry.cjs +951 -0
  217. package/dist/route-registry.cjs.map +1 -0
  218. package/dist/route-registry.d.cts +33 -0
  219. package/dist/route-registry.d.cts.map +1 -0
  220. package/dist/route-registry.d.ts +33 -0
  221. package/dist/route-registry.d.ts.map +1 -0
  222. package/dist/route-registry.js +866 -0
  223. package/dist/route-registry.js.map +1 -0
  224. package/dist/router.cjs +1 -1
  225. package/dist/router.cjs.map +1 -1
  226. package/dist/router.js +1 -1
  227. package/dist/router.js.map +1 -1
  228. package/dist/server.cjs +590 -191
  229. package/dist/server.cjs.map +1 -1
  230. package/dist/server.d.cts +4 -1
  231. package/dist/server.d.cts.map +1 -1
  232. package/dist/server.d.ts +4 -1
  233. package/dist/server.d.ts.map +1 -1
  234. package/dist/server.js +498 -99
  235. package/dist/server.js.map +1 -1
  236. package/dist/speech.cjs +4 -2
  237. package/dist/speech.cjs.map +1 -1
  238. package/dist/speech.js +4 -2
  239. package/dist/speech.js.map +1 -1
  240. package/dist/suite.cjs +1 -1
  241. package/dist/suite.js +1 -1
  242. package/dist/transcription.cjs +2 -2
  243. package/dist/transcription.cjs.map +1 -1
  244. package/dist/transcription.d.cts.map +1 -1
  245. package/dist/transcription.d.ts.map +1 -1
  246. package/dist/transcription.js +2 -2
  247. package/dist/transcription.js.map +1 -1
  248. package/dist/types.cjs +18 -0
  249. package/dist/types.cjs.map +1 -0
  250. package/dist/types.d.cts +202 -2
  251. package/dist/types.d.cts.map +1 -1
  252. package/dist/types.d.ts +202 -2
  253. package/dist/types.d.ts.map +1 -1
  254. package/dist/types.js +17 -0
  255. package/dist/types.js.map +1 -0
  256. package/dist/validate-cli.cjs +89 -31
  257. package/dist/validate-cli.cjs.map +1 -1
  258. package/dist/validate-cli.js +91 -33
  259. package/dist/validate-cli.js.map +1 -1
  260. package/dist/vector-stores.cjs +1180 -0
  261. package/dist/vector-stores.cjs.map +1 -0
  262. package/dist/vector-stores.d.cts +106 -0
  263. package/dist/vector-stores.d.cts.map +1 -0
  264. package/dist/vector-stores.d.ts +106 -0
  265. package/dist/vector-stores.d.ts.map +1 -0
  266. package/dist/vector-stores.js +1164 -0
  267. package/dist/vector-stores.js.map +1 -0
  268. package/dist/vector-types.d.cts.map +1 -1
  269. package/dist/vector-types.d.ts.map +1 -1
  270. package/dist/video.cjs +19 -0
  271. package/dist/video.cjs.map +1 -1
  272. package/dist/video.d.cts.map +1 -1
  273. package/dist/video.d.ts.map +1 -1
  274. package/dist/video.js +19 -0
  275. package/dist/video.js.map +1 -1
  276. package/dist/vitest.cjs +15 -10
  277. package/dist/vitest.cjs.map +1 -1
  278. package/dist/vitest.d.cts +4 -2
  279. package/dist/vitest.d.cts.map +1 -1
  280. package/dist/vitest.d.ts +4 -2
  281. package/dist/vitest.d.ts.map +1 -1
  282. package/dist/vitest.js +16 -11
  283. package/dist/vitest.js.map +1 -1
  284. package/dist/ws-gemini-live.d.ts +2 -2
  285. package/dist/ws-realtime.d.ts +2 -2
  286. package/dist/ws-responses.cjs +14 -2
  287. package/dist/ws-responses.cjs.map +1 -1
  288. package/dist/ws-responses.js +14 -2
  289. package/dist/ws-responses.js.map +1 -1
  290. package/package.json +4 -1
package/dist/mcp-mock.cjs CHANGED
@@ -1,6 +1,8 @@
1
1
  const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
+ const require_message_text = require('./message-text.cjs');
2
3
  const require_helpers = require('./helpers.cjs');
3
4
  const require_mcp_handler = require('./mcp-handler.cjs');
5
+ const require_mcp_fakes = require('./mcp-fakes.cjs');
4
6
  let node_http = require("node:http");
5
7
  node_http = require_runtime.__toESM(node_http);
6
8
 
@@ -10,9 +12,21 @@ var MCPMock = class {
10
12
  resources = /* @__PURE__ */ new Map();
11
13
  prompts = /* @__PURE__ */ new Map();
12
14
  sessions = /* @__PURE__ */ new Map();
15
+ /** This mount's fakes; kept across `reset()` so the id counters are never rewound. */
16
+ fakes = new require_mcp_fakes.McpFakeStore();
17
+ /** Fake blocks loaded since the last clear. */
18
+ fakeBlocks = 0;
19
+ /**
20
+ * Decode fallbacks already reported (L2), per live session: one key per
21
+ * field and source, plus one for undecodable query names. Dropped when the
22
+ * session is deleted.
23
+ */
24
+ decodeWarned = /* @__PURE__ */ new Map();
13
25
  server = null;
14
26
  journal = null;
15
27
  registry = null;
28
+ /** Mount logger for L1, L2 and L10; none (lines dropped) until `setLogger`. */
29
+ logger = null;
16
30
  options;
17
31
  requestHandler;
18
32
  constructor(options) {
@@ -46,10 +60,85 @@ var MCPMock = class {
46
60
  });
47
61
  return this;
48
62
  }
63
+ /**
64
+ * Load MCP fakes from code: one `mcpFakes` value (a block, or an array of
65
+ * blocks), in the fixture-file shape, validated here. One run-time addition
66
+ * (`code#<n>` entry ids). Always appends. Returns the shadowed-entry
67
+ * warnings; on any bad block throws one `McpFakesAddError` and adds nothing.
68
+ */
69
+ loadFakes(blocks) {
70
+ const sources = Array.isArray(blocks) ? blocks.map((raw, blockIndex) => ({
71
+ source: "code",
72
+ blockIndex,
73
+ raw
74
+ })) : [{
75
+ source: "code",
76
+ blockIndex: null,
77
+ raw: blocks
78
+ }];
79
+ return this.addMcpFakes(sources, { kind: "code" });
80
+ }
81
+ addMcpFakes(blocks, origin) {
82
+ const result = this.fakes.add(blocks, origin);
83
+ this.fakeBlocks += blocks.length;
84
+ return result;
85
+ }
86
+ /**
87
+ * The blocks on this mount that apply to `testId` and `context` (spec 6.5,
88
+ * I9), with every entry id and its consumed state for that test id. `null`
89
+ * means "none sent". Read-only: the result is deeply frozen.
90
+ */
91
+ fakesSnapshot(testId = null, context = null) {
92
+ return this.fakes.snapshot(testId, context);
93
+ }
94
+ clearMcpFakes() {
95
+ this.fakes.clear();
96
+ this.fakeBlocks = 0;
97
+ }
98
+ resetScenarioState(testId) {
99
+ this.fakes.resetState(testId);
100
+ }
101
+ setLogger(logger) {
102
+ this.logger = logger;
103
+ }
49
104
  async handleRequest(req, res, pathname) {
50
105
  if (pathname !== "/" && pathname !== "") return false;
51
- if (req.method !== "POST" && req.method !== "DELETE") return false;
52
- const body = await require_helpers.readBody(req);
106
+ if (req.method === "GET") {
107
+ this.answerMethodNotAllowed(req, res);
108
+ return true;
109
+ }
110
+ if (!isServedMethod(req.method)) return false;
111
+ await this.serve(req, res, mountPathOf(req.url, pathname), false);
112
+ return true;
113
+ }
114
+ /**
115
+ * Serve one request, in both modes (mounted: a POST or DELETE on the mount
116
+ * root; standalone: any method but GET on any path): count it, run the
117
+ * handler, journal it. A throw from the mount's own code is logged with the
118
+ * identity resolved so far, answered 500 and journaled with its message,
119
+ * never left to a generic catch.
120
+ */
121
+ async serve(req, res, mount, standalone) {
122
+ const record = require_mcp_handler.newMCPRequestRecord();
123
+ try {
124
+ const body = await require_helpers.readBody(req);
125
+ this.countRequest(req, body);
126
+ await this.requestHandler(req, res, body, mount, record);
127
+ this.journalRequest(req, res, record);
128
+ } catch (err) {
129
+ const thrown = err instanceof Error ? err.message : String(err);
130
+ const line = require_message_text.build(require_message_text.msg`MCPMock request error (${record.identity ? require_mcp_handler.describeMcpIdentity(record.identity) : require_message_text.msg`no identity resolved`}, mount ${require_message_text.plainText(mount)}): ${require_message_text.plainText(thrown)}`);
131
+ if (standalone || !this.logger) console.error(line);
132
+ else this.logger.error(line);
133
+ if (!res.headersSent) {
134
+ res.writeHead(500);
135
+ res.end("Internal server error");
136
+ } else if (!res.writableEnded) res.end();
137
+ this.journalRequest(req, res, record, thrown);
138
+ }
139
+ }
140
+ /** `aimock_mcp_requests_total` by JSON-RPC method (`session/delete` for DELETE). */
141
+ countRequest(req, body) {
53
142
  if (this.registry) if (req.method === "DELETE") this.registry.incrementCounter("aimock_mcp_requests_total", { method: "session/delete" });
54
143
  else try {
55
144
  const parsed = JSON.parse(body);
@@ -58,19 +147,19 @@ var MCPMock = class {
58
147
  } catch {
59
148
  this.registry.incrementCounter("aimock_mcp_requests_total", { method: "unknown" });
60
149
  }
61
- await this.requestHandler(req, res, body);
62
- if (this.journal) this.journal.add({
63
- method: req.method ?? "POST",
64
- path: req.url ?? "/",
65
- headers: require_helpers.flattenHeaders(req.headers),
66
- body: null,
67
- service: "mcp",
68
- response: {
69
- status: res.statusCode,
70
- fixture: null
71
- }
150
+ }
151
+ /**
152
+ * B1: answer 405 (`Allow: POST, DELETE`), journaled. Mounted, this is a GET
153
+ * on the mount root; standalone, a GET on any path.
154
+ */
155
+ answerMethodNotAllowed(req, res) {
156
+ res.writeHead(405, {
157
+ "Content-Type": "application/json",
158
+ Allow: "POST, DELETE"
72
159
  });
73
- return true;
160
+ res.end(JSON.stringify({ error: require_message_text.build(require_message_text.msg`Method not allowed: use POST or DELETE`) }));
161
+ req.resume();
162
+ this.journalRequest(req, res, null);
74
163
  }
75
164
  health() {
76
165
  return {
@@ -83,6 +172,7 @@ var MCPMock = class {
83
172
  }
84
173
  setJournal(journal) {
85
174
  this.journal = journal;
175
+ this.fakes.setMaxTestIds(journal.fixtureCountsMaxTestIdsCap);
86
176
  }
87
177
  setRegistry(registry) {
88
178
  this.registry = registry;
@@ -93,40 +183,13 @@ var MCPMock = class {
93
183
  const port = this.options.port ?? 0;
94
184
  return new Promise((resolve, reject) => {
95
185
  const srv = node_http.createServer((req, res) => {
96
- const chunks = [];
97
- req.on("data", (chunk) => chunks.push(chunk));
98
- req.on("end", () => {
99
- const body = Buffer.concat(chunks).toString();
100
- this.requestHandler(req, res, body).then(() => {
101
- if (this.journal) this.journal.add({
102
- method: req.method ?? "POST",
103
- path: req.url ?? "/",
104
- headers: require_helpers.flattenHeaders(req.headers),
105
- body: null,
106
- service: "mcp",
107
- response: {
108
- status: res.statusCode,
109
- fixture: null
110
- }
111
- });
112
- }).catch((err) => {
113
- console.error("MCPMock request error:", err);
114
- if (!res.headersSent) {
115
- res.writeHead(500);
116
- res.end("Internal server error");
117
- } else if (!res.writableEnded) res.end();
118
- if (this.journal) this.journal.add({
119
- method: req.method ?? "POST",
120
- path: req.url ?? "/",
121
- headers: require_helpers.flattenHeaders(req.headers),
122
- body: null,
123
- service: "mcp",
124
- response: {
125
- status: res.statusCode,
126
- fixture: null
127
- }
128
- });
129
- });
186
+ if (req.method === "GET") {
187
+ this.answerMethodNotAllowed(req, res);
188
+ return;
189
+ }
190
+ this.serve(req, res, "/", true).catch((err) => {
191
+ console.error("MCPMock request error:", err);
192
+ if (!res.writableEnded) res.end();
130
193
  });
131
194
  });
132
195
  srv.listen(port, host, () => {
@@ -158,9 +221,83 @@ var MCPMock = class {
158
221
  this.resources.clear();
159
222
  this.prompts.clear();
160
223
  this.sessions.clear();
224
+ this.decodeWarned.clear();
225
+ this.clearMcpFakes();
161
226
  this.requestHandler = this.buildHandler();
162
227
  return this;
163
228
  }
229
+ /**
230
+ * The journal entries of one handled request (B2, B3): one per JSON-RPC
231
+ * message (each element of a batch), or one with no body when the request
232
+ * carried none that was read; none without a journal (W5). `error` is what
233
+ * the mount's own code threw while serving it.
234
+ */
235
+ journalRequest(req, res, record, error) {
236
+ if (!this.journal) return;
237
+ const messages = record?.messages.length ? record.messages : [{
238
+ body: null,
239
+ mcpFake: null,
240
+ error: null
241
+ }];
242
+ for (const message of messages) {
243
+ const failed = message.error ?? error;
244
+ this.journal.add({
245
+ method: req.method ?? "POST",
246
+ path: req.url ?? "/",
247
+ headers: require_helpers.flattenHeaders(req.headers),
248
+ body: message.body,
249
+ service: "mcp",
250
+ ...record?.identity ? {
251
+ testId: record.identity.testId ?? require_mcp_fakes.MCP_FAKES_DEFAULT_TEST_ID,
252
+ context: record.identity.context
253
+ } : {},
254
+ response: {
255
+ status: res.statusCode,
256
+ fixture: null,
257
+ ...message.mcpFake ? { mcpFake: message.mcpFake } : {},
258
+ ...failed !== void 0 && failed !== null ? { error: failed } : {}
259
+ }
260
+ });
261
+ }
262
+ }
263
+ /**
264
+ * Side channels of a fake outcome (9.3): the failure metric and the L1 /
265
+ * L10 line. A claim that threw (`internal_error`) is counted and logged
266
+ * like a failure, under `MCP_FAKE_INTERNAL_ERROR`.
267
+ */
268
+ onFakeEvent(evt) {
269
+ if (evt.kind === "answered") return;
270
+ this.registry?.incrementCounter("aimock_mcp_fake_failures_total", { code: evt.code });
271
+ if (!this.logger) return;
272
+ const where = require_message_text.msg`mount ${require_message_text.plainText(evt.mount)}`;
273
+ const kind = evt.kind.replace("_", " ");
274
+ const line = evt.kind === "evicted" ? require_message_text.msg`MCP-FAKE: evicted testId ${require_message_text.quote(evt.testId)} (cap ${evt.cap}) for tools/call ${require_message_text.plainText(evt.tool)} (${where})` : require_message_text.msg`MCP-FAKE: ${require_message_text.fixed(kind)} for tools/call ${require_message_text.plainText(evt.tool)} (${require_mcp_handler.describeMcpIdentity(evt.identity)}, ${where}): ${require_message_text.plainText(evt.message)}`;
275
+ this.logger.error(require_message_text.build(line));
276
+ }
277
+ /**
278
+ * L2: one warning per session for each identity field and source whose
279
+ * value is not valid percent-encoding (H1), naming that source and value,
280
+ * and one for query names that are not valid percent-encoding.
281
+ */
282
+ onDecodeFallback(sessionId, fallback) {
283
+ let warned = this.decodeWarned.get(sessionId);
284
+ if (!warned) {
285
+ warned = /* @__PURE__ */ new Set();
286
+ this.decodeWarned.set(sessionId, warned);
287
+ }
288
+ const key = fallback.kind === "names" ? "names" : `${fallback.field}:${fallback.source}`;
289
+ if (warned.has(key)) return;
290
+ warned.add(key);
291
+ const session = require_message_text.quote(sessionId);
292
+ if (fallback.kind === "names") {
293
+ const one = fallback.count === 1;
294
+ this.logger?.warn(require_message_text.build(require_message_text.msg`MCP-FAKE: ${fallback.count} query parameter ${require_message_text.fixed(one ? "name" : "names")} on session ${session} ${require_message_text.fixed(one ? "is" : "are")} not valid percent-encoding; ${require_message_text.fixed(one ? "it matches" : "they match")} no identity field`));
295
+ return;
296
+ }
297
+ const label = DECODE_LABELS[fallback.field][fallback.source];
298
+ const fate = fallback.used ? "it is used as sent" : "it is ignored (the header value is used)";
299
+ this.logger?.warn(require_message_text.build(require_message_text.msg`MCP-FAKE: ${require_message_text.fixed(label)} value ${require_message_text.quote(fallback.raw)} on session ${session} is not valid percent-encoding; ${require_message_text.fixed(fate)}`));
300
+ }
164
301
  buildHandler() {
165
302
  return require_mcp_handler.createMCPRequestHandler({
166
303
  serverInfo: this.options.serverInfo ?? {
@@ -170,10 +307,41 @@ var MCPMock = class {
170
307
  tools: this.tools,
171
308
  resources: this.resources,
172
309
  prompts: this.prompts,
173
- sessions: this.sessions
310
+ sessions: this.sessions,
311
+ fakes: this.fakes,
312
+ hasFakes: () => this.fakeBlocks > 0,
313
+ onFakeEvent: (evt) => this.onFakeEvent(evt),
314
+ onDecodeFallback: (sessionId, fallback) => this.onDecodeFallback(sessionId, fallback),
315
+ onSessionClosed: (sessionId) => void this.decodeWarned.delete(sessionId)
174
316
  });
175
317
  }
176
318
  };
319
+ /** B1: the only methods the mounted mount root serves; every other one is 405. */
320
+ function isServedMethod(method) {
321
+ return method === "POST" || method === "DELETE";
322
+ }
323
+ /** The L2 label of each identity field's header and query source. */
324
+ const DECODE_LABELS = {
325
+ testId: {
326
+ header: "X-Test-Id header",
327
+ query: "?testId= query parameter"
328
+ },
329
+ context: {
330
+ header: "X-AIMock-Context header",
331
+ query: "?context= query parameter"
332
+ }
333
+ };
334
+ /**
335
+ * The path a mount is served at (I9): the request path without the
336
+ * sub-path the server handed the mount (`/mcp` for `/mcp` or `/mcp/`), `/`
337
+ * at the root.
338
+ */
339
+ function mountPathOf(url, subPath) {
340
+ const full = new URL(url ?? "/", "http://mount.invalid").pathname;
341
+ const sub = subPath === "" ? "/" : subPath;
342
+ const mount = full.endsWith(sub) ? full.slice(0, full.length - sub.length) : full;
343
+ return mount === "" ? "/" : mount;
344
+ }
177
345
 
178
346
  //#endregion
179
347
  exports.MCPMock = MCPMock;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-mock.cjs","names":["readBody","flattenHeaders","http","createMCPRequestHandler"],"sources":["../src/mcp-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { Mountable } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type {\n MCPMockOptions,\n MCPToolDefinition,\n MCPResourceDefinition,\n MCPResourceContent,\n MCPPromptDefinition,\n MCPPromptResult,\n MCPContent,\n MCPSession,\n} from \"./mcp-types.js\";\nimport { createMCPRequestHandler, type MCPState } from \"./mcp-handler.js\";\nimport { flattenHeaders, readBody } from \"./helpers.js\";\n\nexport class MCPMock implements Mountable {\n private tools: Map<\n string,\n { def: MCPToolDefinition; handler?: (...args: unknown[]) => unknown }\n > = new Map();\n private resources: Map<string, { def: MCPResourceDefinition; content?: MCPResourceContent }> =\n new Map();\n private prompts: Map<\n string,\n {\n def: MCPPromptDefinition;\n handler?: (...args: unknown[]) => MCPPromptResult | Promise<MCPPromptResult>;\n }\n > = new Map();\n private sessions: Map<string, MCPSession> = new Map();\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n private options: MCPMockOptions;\n private requestHandler: ReturnType<typeof createMCPRequestHandler>;\n\n constructor(options?: MCPMockOptions) {\n this.options = options ?? {};\n this.requestHandler = this.buildHandler();\n }\n\n // ---- Configuration: Tools ----\n\n addTool(def: MCPToolDefinition): this {\n this.tools.set(def.name, { def });\n return this;\n }\n\n onToolCall(\n name: string,\n handler: (args: unknown) => MCPContent[] | string | Promise<MCPContent[] | string>,\n ): this {\n const entry = this.tools.get(name);\n if (entry) {\n entry.handler = handler;\n } else {\n this.tools.set(name, { def: { name }, handler });\n }\n return this;\n }\n\n // ---- Configuration: Resources ----\n\n addResource(def: MCPResourceDefinition, content?: MCPResourceContent): this {\n this.resources.set(def.uri, { def, content });\n return this;\n }\n\n // ---- Configuration: Prompts ----\n\n addPrompt(\n def: MCPPromptDefinition,\n handler?: (args: unknown) => MCPPromptResult | Promise<MCPPromptResult>,\n ): this {\n this.prompts.set(def.name, { def, handler });\n return this;\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n // Only handle POST and DELETE to the root of the mount\n if (pathname !== \"/\" && pathname !== \"\") {\n return false;\n }\n if (req.method !== \"POST\" && req.method !== \"DELETE\") {\n return false;\n }\n\n const body = await readBody(req);\n\n // Extract JSON-RPC method for metrics (skip for DELETE — no JSON-RPC body)\n if (this.registry) {\n if (req.method === \"DELETE\") {\n this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method: \"session/delete\" });\n } else {\n try {\n const parsed = JSON.parse(body);\n const method =\n typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n ? String(parsed.method)\n : \"unknown\";\n this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method });\n } catch {\n this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method: \"unknown\" });\n }\n }\n }\n\n await this.requestHandler(req, res, body);\n\n // Journal the request after the handler completes\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"mcp\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n\n return true;\n }\n\n health(): { status: string; [key: string]: unknown } {\n return {\n status: \"ok\",\n tools: this.tools.size,\n resources: this.resources.size,\n prompts: this.prompts.size,\n sessions: this.sessions.size,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"Server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise((resolve, reject) => {\n const srv = http.createServer((req, res) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => {\n const body = Buffer.concat(chunks).toString();\n\n this.requestHandler(req, res, body)\n .then(() => {\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"mcp\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n })\n .catch((err) => {\n console.error(\"MCPMock request error:\", err);\n if (!res.headersSent) {\n res.writeHead(500);\n res.end(\"Internal server error\");\n } else if (!res.writableEnded) {\n res.end();\n }\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"mcp\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n });\n });\n });\n\n srv.listen(port, host, () => {\n this.server = srv;\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n resolve(`http://${host}:${addr.port}`);\n } else {\n resolve(`http://${host}:${port}`);\n }\n });\n\n srv.on(\"error\", reject);\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"Server not started\");\n }\n const srv = this.server;\n this.server = null;\n await new Promise<void>((resolve, reject) => {\n srv.close((err) => (err ? reject(err) : resolve()));\n });\n }\n\n // ---- Inspection ----\n\n getRequests(): unknown[] {\n if (!this.journal) return [];\n return this.journal.getAll().filter((e) => e.service === \"mcp\");\n }\n\n getSessions(): Map<string, MCPSession> {\n return new Map(this.sessions);\n }\n\n reset(): this {\n this.tools.clear();\n this.resources.clear();\n this.prompts.clear();\n this.sessions.clear();\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n // ---- Internal ----\n\n private buildHandler() {\n const state: MCPState = {\n serverInfo: this.options.serverInfo ?? { name: \"mcp-mock\", version: \"1.0.0\" },\n tools: this.tools,\n resources: this.resources,\n prompts: this.prompts,\n sessions: this.sessions,\n };\n return createMCPRequestHandler(state);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mcp-mock.cjs","names":["McpFakeStore","newMCPRequestRecord","readBody","build","describeMcpIdentity","plainText","http","flattenHeaders","MCP_FAKES_DEFAULT_TEST_ID","quote","fixed","createMCPRequestHandler"],"sources":["../src/mcp-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { McpFakeSource, Mountable } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type { Logger } from \"./logger.js\";\nimport type {\n MCPMockOptions,\n MCPToolDefinition,\n MCPResourceDefinition,\n MCPResourceContent,\n MCPPromptDefinition,\n MCPPromptResult,\n MCPContent,\n MCPSession,\n} from \"./mcp-types.js\";\nimport {\n createMCPRequestHandler,\n describeMcpIdentity,\n newMCPRequestRecord,\n type McpDecodeFallback,\n type McpFakeEvent,\n type MCPMessageRecord,\n type MCPRequestRecord,\n type MCPState,\n} from \"./mcp-handler.js\";\nimport {\n MCP_FAKES_DEFAULT_TEST_ID,\n McpFakeStore,\n type McpFakeAddOrigin,\n type McpFakeAddResult,\n type McpFakeBlockSnapshot,\n} from \"./mcp-fakes.js\";\nimport { build, fixed, msg, plainText, quote } from \"./message-text.js\";\nimport { flattenHeaders, readBody } from \"./helpers.js\";\n\nexport class MCPMock implements Mountable {\n private tools: Map<\n string,\n { def: MCPToolDefinition; handler?: (...args: unknown[]) => unknown }\n > = new Map();\n private resources: Map<string, { def: MCPResourceDefinition; content?: MCPResourceContent }> =\n new Map();\n private prompts: Map<\n string,\n {\n def: MCPPromptDefinition;\n handler?: (...args: unknown[]) => MCPPromptResult | Promise<MCPPromptResult>;\n }\n > = new Map();\n private sessions: Map<string, MCPSession> = new Map();\n /** This mount's fakes; kept across `reset()` so the id counters are never rewound. */\n private fakes = new McpFakeStore();\n /** Fake blocks loaded since the last clear. */\n private fakeBlocks = 0;\n /**\n * Decode fallbacks already reported (L2), per live session: one key per\n * field and source, plus one for undecodable query names. Dropped when the\n * session is deleted.\n */\n private decodeWarned = new Map<string, Set<string>>();\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n /** Mount logger for L1, L2 and L10; none (lines dropped) until `setLogger`. */\n private logger: Logger | null = null;\n private options: MCPMockOptions;\n private requestHandler: ReturnType<typeof createMCPRequestHandler>;\n\n constructor(options?: MCPMockOptions) {\n this.options = options ?? {};\n this.requestHandler = this.buildHandler();\n }\n\n // ---- Configuration: Tools ----\n\n addTool(def: MCPToolDefinition): this {\n this.tools.set(def.name, { def });\n return this;\n }\n\n onToolCall(\n name: string,\n handler: (args: unknown) => MCPContent[] | string | Promise<MCPContent[] | string>,\n ): this {\n const entry = this.tools.get(name);\n if (entry) {\n entry.handler = handler;\n } else {\n this.tools.set(name, { def: { name }, handler });\n }\n return this;\n }\n\n // ---- Configuration: Resources ----\n\n addResource(def: MCPResourceDefinition, content?: MCPResourceContent): this {\n this.resources.set(def.uri, { def, content });\n return this;\n }\n\n // ---- Configuration: Prompts ----\n\n addPrompt(\n def: MCPPromptDefinition,\n handler?: (args: unknown) => MCPPromptResult | Promise<MCPPromptResult>,\n ): this {\n this.prompts.set(def.name, { def, handler });\n return this;\n }\n\n // ---- Configuration: MCP fakes ----\n\n /**\n * Load MCP fakes from code: one `mcpFakes` value (a block, or an array of\n * blocks), in the fixture-file shape, validated here. One run-time addition\n * (`code#<n>` entry ids). Always appends. Returns the shadowed-entry\n * warnings; on any bad block throws one `McpFakesAddError` and adds nothing.\n */\n loadFakes(blocks: unknown): McpFakeAddResult {\n const sources: McpFakeSource[] = Array.isArray(blocks)\n ? blocks.map((raw: unknown, blockIndex) => ({ source: \"code\", blockIndex, raw }))\n : [{ source: \"code\", blockIndex: null, raw: blocks }];\n return this.addMcpFakes(sources, { kind: \"code\" });\n }\n\n addMcpFakes(blocks: McpFakeSource[], origin: McpFakeAddOrigin): McpFakeAddResult {\n const result = this.fakes.add(blocks, origin);\n this.fakeBlocks += blocks.length;\n return result;\n }\n\n /**\n * The blocks on this mount that apply to `testId` and `context` (spec 6.5,\n * I9), with every entry id and its consumed state for that test id. `null`\n * means \"none sent\". Read-only: the result is deeply frozen.\n */\n fakesSnapshot(\n testId: string | null = null,\n context: string | null = null,\n ): readonly McpFakeBlockSnapshot[] {\n return this.fakes.snapshot(testId, context);\n }\n\n clearMcpFakes(): void {\n this.fakes.clear();\n this.fakeBlocks = 0;\n }\n\n resetScenarioState(testId?: string): void {\n this.fakes.resetState(testId);\n }\n\n setLogger(logger: Logger): void {\n this.logger = logger;\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n // Off the mount root: not this mount's request (fall through).\n if (pathname !== \"/\" && pathname !== \"\") {\n return false;\n }\n // B1: a GET on the mount root is 405. Any other method but POST and\n // DELETE falls through to the next route, as on origin/main.\n if (req.method === \"GET\") {\n this.answerMethodNotAllowed(req, res);\n return true;\n }\n if (!isServedMethod(req.method)) {\n return false;\n }\n await this.serve(req, res, mountPathOf(req.url, pathname), false);\n return true;\n }\n\n /**\n * Serve one request, in both modes (mounted: a POST or DELETE on the mount\n * root; standalone: any method but GET on any path): count it, run the\n * handler, journal it. A throw from the mount's own code is logged with the\n * identity resolved so far, answered 500 and journaled with its message,\n * never left to a generic catch.\n */\n private async serve(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n mount: string,\n standalone: boolean,\n ): Promise<void> {\n const record = newMCPRequestRecord();\n try {\n const body = await readBody(req);\n this.countRequest(req, body);\n await this.requestHandler(req, res, body, mount, record);\n this.journalRequest(req, res, record);\n } catch (err) {\n const thrown = err instanceof Error ? err.message : String(err);\n const who = record.identity\n ? describeMcpIdentity(record.identity)\n : msg`no identity resolved`;\n const line = build(\n msg`MCPMock request error (${who}, mount ${plainText(mount)}): ${plainText(thrown)}`,\n );\n // W5: the standalone server always prints the error, as on origin/main,\n // even when the mount also has a server's (possibly silent) logger.\n // Mounted, it goes to the server's logger, as origin/main's route error did.\n if (standalone || !this.logger) console.error(line);\n else this.logger.error(line);\n if (!res.headersSent) {\n res.writeHead(500);\n res.end(\"Internal server error\");\n } else if (!res.writableEnded) {\n res.end();\n }\n this.journalRequest(req, res, record, thrown);\n }\n }\n\n /** `aimock_mcp_requests_total` by JSON-RPC method (`session/delete` for DELETE). */\n private countRequest(req: http.IncomingMessage, body: string): void {\n if (this.registry) {\n if (req.method === \"DELETE\") {\n this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method: \"session/delete\" });\n } else {\n try {\n const parsed = JSON.parse(body);\n const method =\n typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n ? String(parsed.method)\n : \"unknown\";\n this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method });\n } catch {\n this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method: \"unknown\" });\n }\n }\n }\n }\n\n /**\n * B1: answer 405 (`Allow: POST, DELETE`), journaled. Mounted, this is a GET\n * on the mount root; standalone, a GET on any path.\n */\n private answerMethodNotAllowed(req: http.IncomingMessage, res: http.ServerResponse): void {\n res.writeHead(405, { \"Content-Type\": \"application/json\", Allow: \"POST, DELETE\" });\n res.end(JSON.stringify({ error: build(msg`Method not allowed: use POST or DELETE`) }));\n req.resume();\n this.journalRequest(req, res, null);\n }\n\n health(): { status: string; [key: string]: unknown } {\n return {\n status: \"ok\",\n tools: this.tools.size,\n resources: this.resources.size,\n prompts: this.prompts.size,\n sessions: this.sessions.size,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n // I5: the fakes' test-id cap follows the journal's (`0` = unbounded).\n this.fakes.setMaxTestIds(journal.fixtureCountsMaxTestIdsCap);\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"Server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise((resolve, reject) => {\n // The standalone server serves every path, as it always has: every\n // method but GET goes to the MCP handler on any path. B1: a GET on any\n // path is 405 (`Allow: POST, DELETE`) and journaled, so the SDK's\n // optional GET SSE stream is declined, not mishandled.\n const srv = http.createServer((req, res) => {\n if (req.method === \"GET\") {\n this.answerMethodNotAllowed(req, res);\n return;\n }\n // serve() catches the request's own errors; this is only a logger\n // that threw while reporting one.\n this.serve(req, res, \"/\", true).catch((err: unknown) => {\n console.error(\"MCPMock request error:\", err);\n if (!res.writableEnded) res.end();\n });\n });\n\n srv.listen(port, host, () => {\n this.server = srv;\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n resolve(`http://${host}:${addr.port}`);\n } else {\n resolve(`http://${host}:${port}`);\n }\n });\n\n srv.on(\"error\", reject);\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"Server not started\");\n }\n const srv = this.server;\n this.server = null;\n await new Promise<void>((resolve, reject) => {\n srv.close((err) => (err ? reject(err) : resolve()));\n });\n }\n\n // ---- Inspection ----\n\n getRequests(): unknown[] {\n if (!this.journal) return [];\n return this.journal.getAll().filter((e) => e.service === \"mcp\");\n }\n\n getSessions(): Map<string, MCPSession> {\n return new Map(this.sessions);\n }\n\n reset(): this {\n this.tools.clear();\n this.resources.clear();\n this.prompts.clear();\n this.sessions.clear();\n this.decodeWarned.clear();\n // R8: fakes and their consumption state go; the id counters stay.\n this.clearMcpFakes();\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n // ---- Internal ----\n\n /**\n * The journal entries of one handled request (B2, B3): one per JSON-RPC\n * message (each element of a batch), or one with no body when the request\n * carried none that was read; none without a journal (W5). `error` is what\n * the mount's own code threw while serving it.\n */\n private journalRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n record: MCPRequestRecord | null,\n error?: string,\n ): void {\n if (!this.journal) return;\n const messages: MCPMessageRecord[] = record?.messages.length\n ? record.messages\n : [{ body: null, mcpFake: null, error: null }];\n for (const message of messages) {\n const failed = message.error ?? error;\n this.journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: message.body,\n service: \"mcp\",\n ...(record?.identity\n ? {\n testId: record.identity.testId ?? MCP_FAKES_DEFAULT_TEST_ID,\n context: record.identity.context,\n }\n : {}),\n response: {\n status: res.statusCode,\n fixture: null,\n ...(message.mcpFake ? { mcpFake: message.mcpFake } : {}),\n ...(failed !== undefined && failed !== null ? { error: failed } : {}),\n },\n });\n }\n }\n\n /**\n * Side channels of a fake outcome (9.3): the failure metric and the L1 /\n * L10 line. A claim that threw (`internal_error`) is counted and logged\n * like a failure, under `MCP_FAKE_INTERNAL_ERROR`.\n */\n private onFakeEvent(evt: McpFakeEvent): void {\n if (evt.kind === \"answered\") return;\n this.registry?.incrementCounter(\"aimock_mcp_fake_failures_total\", { code: evt.code });\n if (!this.logger) return;\n const where = msg`mount ${plainText(evt.mount)}`;\n // \"not declared\", \"internal error\"; the other kinds are one word.\n const kind = evt.kind.replace(\"_\", \" \");\n const line =\n evt.kind === \"evicted\"\n ? msg`MCP-FAKE: evicted testId ${quote(evt.testId)} (cap ${evt.cap}) for tools/call ${plainText(evt.tool)} (${where})`\n : msg`MCP-FAKE: ${fixed(kind)} for tools/call ${plainText(evt.tool)} (${describeMcpIdentity(evt.identity)}, ${where}): ${plainText(evt.message)}`;\n this.logger.error(build(line));\n }\n\n /**\n * L2: one warning per session for each identity field and source whose\n * value is not valid percent-encoding (H1), naming that source and value,\n * and one for query names that are not valid percent-encoding.\n */\n private onDecodeFallback(sessionId: string, fallback: McpDecodeFallback): void {\n let warned = this.decodeWarned.get(sessionId);\n if (!warned) {\n warned = new Set();\n this.decodeWarned.set(sessionId, warned);\n }\n const key = fallback.kind === \"names\" ? \"names\" : `${fallback.field}:${fallback.source}`;\n if (warned.has(key)) return;\n warned.add(key);\n const session = quote(sessionId);\n if (fallback.kind === \"names\") {\n const one = fallback.count === 1;\n this.logger?.warn(\n build(\n msg`MCP-FAKE: ${fallback.count} query parameter ${fixed(one ? \"name\" : \"names\")} on session ${session} ${fixed(one ? \"is\" : \"are\")} not valid percent-encoding; ${fixed(one ? \"it matches\" : \"they match\")} no identity field`,\n ),\n );\n return;\n }\n const label = DECODE_LABELS[fallback.field][fallback.source];\n const fate = fallback.used ? \"it is used as sent\" : \"it is ignored (the header value is used)\";\n this.logger?.warn(\n build(\n msg`MCP-FAKE: ${fixed(label)} value ${quote(fallback.raw)} on session ${session} is not valid percent-encoding; ${fixed(fate)}`,\n ),\n );\n }\n\n private buildHandler() {\n const state: MCPState = {\n serverInfo: this.options.serverInfo ?? { name: \"mcp-mock\", version: \"1.0.0\" },\n tools: this.tools,\n resources: this.resources,\n prompts: this.prompts,\n sessions: this.sessions,\n fakes: this.fakes,\n hasFakes: () => this.fakeBlocks > 0,\n onFakeEvent: (evt) => this.onFakeEvent(evt),\n onDecodeFallback: (sessionId, fallback) => this.onDecodeFallback(sessionId, fallback),\n onSessionClosed: (sessionId) => void this.decodeWarned.delete(sessionId),\n };\n return createMCPRequestHandler(state);\n }\n}\n\n/** B1: the only methods the mounted mount root serves; every other one is 405. */\nfunction isServedMethod(method: string | undefined): boolean {\n return method === \"POST\" || method === \"DELETE\";\n}\n\n/** The L2 label of each identity field's header and query source. */\nconst DECODE_LABELS = {\n testId: { header: \"X-Test-Id header\", query: \"?testId= query parameter\" },\n context: { header: \"X-AIMock-Context header\", query: \"?context= query parameter\" },\n} as const;\n\n/**\n * The path a mount is served at (I9): the request path without the\n * sub-path the server handed the mount (`/mcp` for `/mcp` or `/mcp/`), `/`\n * at the root.\n */\nfunction mountPathOf(url: string | undefined, subPath: string): string {\n const full = new URL(url ?? \"/\", \"http://mount.invalid\").pathname;\n const sub = subPath === \"\" ? \"/\" : subPath;\n const mount = full.endsWith(sub) ? full.slice(0, full.length - sub.length) : full;\n return mount === \"\" ? \"/\" : mount;\n}\n"],"mappings":"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"}
@@ -1,6 +1,8 @@
1
1
  import { Journal } from "./journal.cjs";
2
+ import { Logger } from "./logger.cjs";
2
3
  import { MetricsRegistry } from "./metrics.cjs";
3
- import { Mountable } from "./types.cjs";
4
+ import { McpFakeAddOrigin, McpFakeAddResult, McpFakeBlockSnapshot } from "./mcp-fakes.cjs";
5
+ import { McpFakeSource, Mountable } from "./types.cjs";
4
6
  import { MCPContent, MCPMockOptions, MCPPromptDefinition, MCPPromptResult, MCPResourceContent, MCPResourceDefinition, MCPSession, MCPToolDefinition } from "./mcp-types.cjs";
5
7
  import * as http$1 from "node:http";
6
8
 
@@ -10,9 +12,21 @@ declare class MCPMock implements Mountable {
10
12
  private resources;
11
13
  private prompts;
12
14
  private sessions;
15
+ /** This mount's fakes; kept across `reset()` so the id counters are never rewound. */
16
+ private fakes;
17
+ /** Fake blocks loaded since the last clear. */
18
+ private fakeBlocks;
19
+ /**
20
+ * Decode fallbacks already reported (L2), per live session: one key per
21
+ * field and source, plus one for undecodable query names. Dropped when the
22
+ * session is deleted.
23
+ */
24
+ private decodeWarned;
13
25
  private server;
14
26
  private journal;
15
27
  private registry;
28
+ /** Mount logger for L1, L2 and L10; none (lines dropped) until `setLogger`. */
29
+ private logger;
16
30
  private options;
17
31
  private requestHandler;
18
32
  constructor(options?: MCPMockOptions);
@@ -20,7 +34,39 @@ declare class MCPMock implements Mountable {
20
34
  onToolCall(name: string, handler: (args: unknown) => MCPContent[] | string | Promise<MCPContent[] | string>): this;
21
35
  addResource(def: MCPResourceDefinition, content?: MCPResourceContent): this;
22
36
  addPrompt(def: MCPPromptDefinition, handler?: (args: unknown) => MCPPromptResult | Promise<MCPPromptResult>): this;
37
+ /**
38
+ * Load MCP fakes from code: one `mcpFakes` value (a block, or an array of
39
+ * blocks), in the fixture-file shape, validated here. One run-time addition
40
+ * (`code#<n>` entry ids). Always appends. Returns the shadowed-entry
41
+ * warnings; on any bad block throws one `McpFakesAddError` and adds nothing.
42
+ */
43
+ loadFakes(blocks: unknown): McpFakeAddResult;
44
+ addMcpFakes(blocks: McpFakeSource[], origin: McpFakeAddOrigin): McpFakeAddResult;
45
+ /**
46
+ * The blocks on this mount that apply to `testId` and `context` (spec 6.5,
47
+ * I9), with every entry id and its consumed state for that test id. `null`
48
+ * means "none sent". Read-only: the result is deeply frozen.
49
+ */
50
+ fakesSnapshot(testId?: string | null, context?: string | null): readonly McpFakeBlockSnapshot[];
51
+ clearMcpFakes(): void;
52
+ resetScenarioState(testId?: string): void;
53
+ setLogger(logger: Logger): void;
23
54
  handleRequest(req: http$1.IncomingMessage, res: http$1.ServerResponse, pathname: string): Promise<boolean>;
55
+ /**
56
+ * Serve one request, in both modes (mounted: a POST or DELETE on the mount
57
+ * root; standalone: any method but GET on any path): count it, run the
58
+ * handler, journal it. A throw from the mount's own code is logged with the
59
+ * identity resolved so far, answered 500 and journaled with its message,
60
+ * never left to a generic catch.
61
+ */
62
+ private serve;
63
+ /** `aimock_mcp_requests_total` by JSON-RPC method (`session/delete` for DELETE). */
64
+ private countRequest;
65
+ /**
66
+ * B1: answer 405 (`Allow: POST, DELETE`), journaled. Mounted, this is a GET
67
+ * on the mount root; standalone, a GET on any path.
68
+ */
69
+ private answerMethodNotAllowed;
24
70
  health(): {
25
71
  status: string;
26
72
  [key: string]: unknown;
@@ -32,6 +78,25 @@ declare class MCPMock implements Mountable {
32
78
  getRequests(): unknown[];
33
79
  getSessions(): Map<string, MCPSession>;
34
80
  reset(): this;
81
+ /**
82
+ * The journal entries of one handled request (B2, B3): one per JSON-RPC
83
+ * message (each element of a batch), or one with no body when the request
84
+ * carried none that was read; none without a journal (W5). `error` is what
85
+ * the mount's own code threw while serving it.
86
+ */
87
+ private journalRequest;
88
+ /**
89
+ * Side channels of a fake outcome (9.3): the failure metric and the L1 /
90
+ * L10 line. A claim that threw (`internal_error`) is counted and logged
91
+ * like a failure, under `MCP_FAKE_INTERNAL_ERROR`.
92
+ */
93
+ private onFakeEvent;
94
+ /**
95
+ * L2: one warning per session for each identity field and source whose
96
+ * value is not valid percent-encoding (H1), naming that source and value,
97
+ * and one for query names that are not valid percent-encoding.
98
+ */
99
+ private onDecodeFallback;
35
100
  private buildHandler;
36
101
  }
37
102
  //# sourceMappingURL=mcp-mock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-mock.d.cts","names":[],"sources":["../src/mcp-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;cAiBa,OAAA,YAAmB;;EAAnB,QAAA,SAAQ;EAAA,QAAA,OAAA;UAqBG,QAAA;UAOT,MAAA;UAOiB,OAAA;UAAgC,QAAA;UAAR,OAAA;UAarC,cAAA;aAAiC,CAAA,OAAA,CAAA,EA3B5B,cA2B4B;SAQ3C,CAAA,GAAA,EA5BM,iBA4BN,CAAA,EAAA,IAAA;YACwB,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GAtBD,UAsBC,EAAA,GAAA,MAAA,GAtBuB,OAsBvB,CAtB+B,UAsB/B,EAAA,GAAA,MAAA,CAAA,CAAA,EAAA,IAAA;aAA0B,CAAA,GAAA,EATxC,qBASwC,EAAA,OAAA,CAAA,EATP,kBASO,CAAA,EAAA,IAAA;WAAR,CAAA,GAAA,EAD1C,mBAC0C,EAAA,OAAA,CAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GAAlB,eAAkB,GAAA,OAAA,CAAQ,eAAR,CAAA,CAAA,EAAA,IAAA;eASrC,CAAA,GAAA,EAAL,MAAA,CAAK,eAAA,EAAA,GAAA,EACL,MAAA,CAAK,cADA,EAAA,QAAA,EAAA,MAAA,CAAA,EAGT,OAHS,CAAA,OAAA,CAAA;QACL,CAAA,CAAA,EAAK;IAET,MAAA,EAAA,MAAA;IAwDiB,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,OAAA;;YAUL,CAAA,OAAA,EAVK,OAUL,CAAA,EAAA,IAAA;aAgED,CAAA,QAAA,EAtEQ,eAsER,CAAA,EAAA,IAAA;OAkBa,CAAA,CAAA,EAlFZ,OAkFY,CAAA,MAAA,CAAA;MAAZ,CAAA,CAAA,EAlBD,OAkBC,CAAA,IAAA,CAAA;aAzNe,CAAA,CAAA,EAAA,OAAA,EAAA;EAAS,WAAA,CAAA,CAAA,EAyNxB,GAzNwB,CAAA,MAAA,EAyNZ,UAzNY,CAAA"}
1
+ {"version":3,"file":"mcp-mock.d.cts","names":[],"sources":["../src/mcp-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;;;cAmCa,OAAA,YAAmB;;EAAnB,QAAA,SAAQ;EAAA,QAAA,OAAA;UAiCG,QAAA;;UAcQ,KAAA;;UAAwB,UAAA;;;;;;UAsBL,YAAA;UAcrB,MAAA;UAOR,OAAA;UAAyB,QAAA;;UAcjC,MAAA;UAaM,OAAA;UAOX,cAAK;aACA,CAAA,OAAA,CAAA,EA5FU,cA4FV;SAET,CAAA,GAAA,EAvFU,iBAuFV,CAAA,EAAA,IAAA;YAqGiB,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GArLU,UAqLV,EAAA,GAAA,MAAA,GArLkC,OAqLlC,CArL0C,UAqL1C,EAAA,GAAA,MAAA,CAAA,CAAA,EAAA,IAAA;aAME,CAAA,GAAA,EA9KL,qBA8KK,EAAA,OAAA,CAAA,EA9K4B,kBA8K5B,CAAA,EAAA,IAAA;WAMP,CAAA,GAAA,EA5KR,mBA4KQ,EAAA,OAAA,CAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GA3KgB,eA2KhB,GA3KkC,OA2KlC,CA3K0C,eA2K1C,CAAA,CAAA,EAAA,IAAA;;;;;;;8BA7Ja;sBAOR,yBAAyB,mBAAmB;;;;;;2EAcpD;;;oBAaM;qBAOX,MAAA,CAAK,sBACL,MAAA,CAAK,mCAET;;;;;;;;;;;;;;;;;;;;sBAqGiB;wBAME;WAMP;UAwCD;;iBAkBC,YAAY"}
@@ -1,6 +1,8 @@
1
1
  import { Journal } from "./journal.js";
2
+ import { Logger } from "./logger.js";
2
3
  import { MetricsRegistry } from "./metrics.js";
3
- import { Mountable } from "./types.js";
4
+ import { McpFakeAddOrigin, McpFakeAddResult, McpFakeBlockSnapshot } from "./mcp-fakes.js";
5
+ import { McpFakeSource, Mountable } from "./types.js";
4
6
  import { MCPContent, MCPMockOptions, MCPPromptDefinition, MCPPromptResult, MCPResourceContent, MCPResourceDefinition, MCPSession, MCPToolDefinition } from "./mcp-types.js";
5
7
  import * as http$1 from "node:http";
6
8
 
@@ -10,9 +12,21 @@ declare class MCPMock implements Mountable {
10
12
  private resources;
11
13
  private prompts;
12
14
  private sessions;
15
+ /** This mount's fakes; kept across `reset()` so the id counters are never rewound. */
16
+ private fakes;
17
+ /** Fake blocks loaded since the last clear. */
18
+ private fakeBlocks;
19
+ /**
20
+ * Decode fallbacks already reported (L2), per live session: one key per
21
+ * field and source, plus one for undecodable query names. Dropped when the
22
+ * session is deleted.
23
+ */
24
+ private decodeWarned;
13
25
  private server;
14
26
  private journal;
15
27
  private registry;
28
+ /** Mount logger for L1, L2 and L10; none (lines dropped) until `setLogger`. */
29
+ private logger;
16
30
  private options;
17
31
  private requestHandler;
18
32
  constructor(options?: MCPMockOptions);
@@ -20,7 +34,39 @@ declare class MCPMock implements Mountable {
20
34
  onToolCall(name: string, handler: (args: unknown) => MCPContent[] | string | Promise<MCPContent[] | string>): this;
21
35
  addResource(def: MCPResourceDefinition, content?: MCPResourceContent): this;
22
36
  addPrompt(def: MCPPromptDefinition, handler?: (args: unknown) => MCPPromptResult | Promise<MCPPromptResult>): this;
37
+ /**
38
+ * Load MCP fakes from code: one `mcpFakes` value (a block, or an array of
39
+ * blocks), in the fixture-file shape, validated here. One run-time addition
40
+ * (`code#<n>` entry ids). Always appends. Returns the shadowed-entry
41
+ * warnings; on any bad block throws one `McpFakesAddError` and adds nothing.
42
+ */
43
+ loadFakes(blocks: unknown): McpFakeAddResult;
44
+ addMcpFakes(blocks: McpFakeSource[], origin: McpFakeAddOrigin): McpFakeAddResult;
45
+ /**
46
+ * The blocks on this mount that apply to `testId` and `context` (spec 6.5,
47
+ * I9), with every entry id and its consumed state for that test id. `null`
48
+ * means "none sent". Read-only: the result is deeply frozen.
49
+ */
50
+ fakesSnapshot(testId?: string | null, context?: string | null): readonly McpFakeBlockSnapshot[];
51
+ clearMcpFakes(): void;
52
+ resetScenarioState(testId?: string): void;
53
+ setLogger(logger: Logger): void;
23
54
  handleRequest(req: http$1.IncomingMessage, res: http$1.ServerResponse, pathname: string): Promise<boolean>;
55
+ /**
56
+ * Serve one request, in both modes (mounted: a POST or DELETE on the mount
57
+ * root; standalone: any method but GET on any path): count it, run the
58
+ * handler, journal it. A throw from the mount's own code is logged with the
59
+ * identity resolved so far, answered 500 and journaled with its message,
60
+ * never left to a generic catch.
61
+ */
62
+ private serve;
63
+ /** `aimock_mcp_requests_total` by JSON-RPC method (`session/delete` for DELETE). */
64
+ private countRequest;
65
+ /**
66
+ * B1: answer 405 (`Allow: POST, DELETE`), journaled. Mounted, this is a GET
67
+ * on the mount root; standalone, a GET on any path.
68
+ */
69
+ private answerMethodNotAllowed;
24
70
  health(): {
25
71
  status: string;
26
72
  [key: string]: unknown;
@@ -32,6 +78,25 @@ declare class MCPMock implements Mountable {
32
78
  getRequests(): unknown[];
33
79
  getSessions(): Map<string, MCPSession>;
34
80
  reset(): this;
81
+ /**
82
+ * The journal entries of one handled request (B2, B3): one per JSON-RPC
83
+ * message (each element of a batch), or one with no body when the request
84
+ * carried none that was read; none without a journal (W5). `error` is what
85
+ * the mount's own code threw while serving it.
86
+ */
87
+ private journalRequest;
88
+ /**
89
+ * Side channels of a fake outcome (9.3): the failure metric and the L1 /
90
+ * L10 line. A claim that threw (`internal_error`) is counted and logged
91
+ * like a failure, under `MCP_FAKE_INTERNAL_ERROR`.
92
+ */
93
+ private onFakeEvent;
94
+ /**
95
+ * L2: one warning per session for each identity field and source whose
96
+ * value is not valid percent-encoding (H1), naming that source and value,
97
+ * and one for query names that are not valid percent-encoding.
98
+ */
99
+ private onDecodeFallback;
35
100
  private buildHandler;
36
101
  }
37
102
  //# sourceMappingURL=mcp-mock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-mock.d.ts","names":[],"sources":["../src/mcp-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;cAiBa,OAAA,YAAmB;;EAAnB,QAAA,SAAQ;EAAA,QAAA,OAAA;UAqBG,QAAA;UAOT,MAAA;UAOiB,OAAA;UAAgC,QAAA;UAAR,OAAA;UAarC,cAAA;aAAiC,CAAA,OAAA,CAAA,EA3B5B,cA2B4B;SAQ3C,CAAA,GAAA,EA5BM,iBA4BN,CAAA,EAAA,IAAA;YACwB,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GAtBD,UAsBC,EAAA,GAAA,MAAA,GAtBuB,OAsBvB,CAtB+B,UAsB/B,EAAA,GAAA,MAAA,CAAA,CAAA,EAAA,IAAA;aAA0B,CAAA,GAAA,EATxC,qBASwC,EAAA,OAAA,CAAA,EATP,kBASO,CAAA,EAAA,IAAA;WAAR,CAAA,GAAA,EAD1C,mBAC0C,EAAA,OAAA,CAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GAAlB,eAAkB,GAAA,OAAA,CAAQ,eAAR,CAAA,CAAA,EAAA,IAAA;eASrC,CAAA,GAAA,EAAL,MAAA,CAAK,eAAA,EAAA,GAAA,EACL,MAAA,CAAK,cADA,EAAA,QAAA,EAAA,MAAA,CAAA,EAGT,OAHS,CAAA,OAAA,CAAA;QACL,CAAA,CAAA,EAAK;IAET,MAAA,EAAA,MAAA;IAwDiB,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,OAAA;;YAUL,CAAA,OAAA,EAVK,OAUL,CAAA,EAAA,IAAA;aAgED,CAAA,QAAA,EAtEQ,eAsER,CAAA,EAAA,IAAA;OAkBa,CAAA,CAAA,EAlFZ,OAkFY,CAAA,MAAA,CAAA;MAAZ,CAAA,CAAA,EAlBD,OAkBC,CAAA,IAAA,CAAA;aAzNe,CAAA,CAAA,EAAA,OAAA,EAAA;EAAS,WAAA,CAAA,CAAA,EAyNxB,GAzNwB,CAAA,MAAA,EAyNZ,UAzNY,CAAA"}
1
+ {"version":3,"file":"mcp-mock.d.ts","names":[],"sources":["../src/mcp-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;;;cAmCa,OAAA,YAAmB;;EAAnB,QAAA,SAAQ;EAAA,QAAA,OAAA;UAiCG,QAAA;;UAcQ,KAAA;;UAAwB,UAAA;;;;;;UAsBL,YAAA;UAcrB,MAAA;UAOR,OAAA;UAAyB,QAAA;;UAcjC,MAAA;UAaM,OAAA;UAOX,cAAK;aACA,CAAA,OAAA,CAAA,EA5FU,cA4FV;SAET,CAAA,GAAA,EAvFU,iBAuFV,CAAA,EAAA,IAAA;YAqGiB,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GArLU,UAqLV,EAAA,GAAA,MAAA,GArLkC,OAqLlC,CArL0C,UAqL1C,EAAA,GAAA,MAAA,CAAA,CAAA,EAAA,IAAA;aAME,CAAA,GAAA,EA9KL,qBA8KK,EAAA,OAAA,CAAA,EA9K4B,kBA8K5B,CAAA,EAAA,IAAA;WAMP,CAAA,GAAA,EA5KR,mBA4KQ,EAAA,OAAA,CAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GA3KgB,eA2KhB,GA3KkC,OA2KlC,CA3K0C,eA2K1C,CAAA,CAAA,EAAA,IAAA;;;;;;;8BA7Ja;sBAOR,yBAAyB,mBAAmB;;;;;;2EAcpD;;;oBAaM;qBAOX,MAAA,CAAK,sBACL,MAAA,CAAK,mCAET;;;;;;;;;;;;;;;;;;;;sBAqGiB;wBAME;WAMP;UAwCD;;iBAkBC,YAAY"}