mock-mcp 0.3.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +212 -124
  2. package/dist/adapter/index.cjs +875 -0
  3. package/dist/adapter/index.d.cts +142 -0
  4. package/dist/adapter/index.d.ts +142 -0
  5. package/dist/adapter/index.js +835 -0
  6. package/dist/client/connect.cjs +991 -0
  7. package/dist/client/connect.d.cts +218 -0
  8. package/dist/client/connect.d.ts +211 -7
  9. package/dist/client/connect.js +941 -20
  10. package/dist/client/index.cjs +992 -0
  11. package/dist/client/index.d.cts +3 -0
  12. package/dist/client/index.d.ts +3 -2
  13. package/dist/client/index.js +951 -2
  14. package/dist/daemon/index.cjs +717 -0
  15. package/dist/daemon/index.d.cts +62 -0
  16. package/dist/daemon/index.d.ts +62 -0
  17. package/dist/daemon/index.js +678 -0
  18. package/dist/index.cjs +2708 -0
  19. package/dist/index.d.cts +602 -0
  20. package/dist/index.d.ts +602 -11
  21. package/dist/index.js +2651 -53
  22. package/dist/shared/index.cjs +506 -0
  23. package/dist/shared/index.d.cts +241 -0
  24. package/dist/shared/index.d.ts +241 -0
  25. package/dist/shared/index.js +423 -0
  26. package/dist/types-bEGXLBF0.d.cts +190 -0
  27. package/dist/types-bEGXLBF0.d.ts +190 -0
  28. package/package.json +45 -4
  29. package/dist/client/batch-mock-collector.d.ts +0 -111
  30. package/dist/client/batch-mock-collector.js +0 -308
  31. package/dist/client/util.d.ts +0 -1
  32. package/dist/client/util.js +0 -3
  33. package/dist/connect.cjs +0 -400
  34. package/dist/connect.d.cts +0 -82
  35. package/dist/server/index.d.ts +0 -1
  36. package/dist/server/index.js +0 -1
  37. package/dist/server/test-mock-mcp-server.d.ts +0 -73
  38. package/dist/server/test-mock-mcp-server.js +0 -419
  39. package/dist/types.d.ts +0 -45
  40. package/dist/types.js +0 -2
@@ -0,0 +1,875 @@
1
+ 'use strict';
2
+
3
+ var index_js = require('@modelcontextprotocol/sdk/server/index.js');
4
+ var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
5
+ var types_js = require('@modelcontextprotocol/sdk/types.js');
6
+ var http = require('http');
7
+ var crypto2 = require('crypto');
8
+ var fs = require('fs/promises');
9
+ require('fs');
10
+ var os = require('os');
11
+ var path = require('path');
12
+ require('child_process');
13
+ var url = require('url');
14
+ require('module');
15
+
16
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
17
+
18
+ var http__default = /*#__PURE__*/_interopDefault(http);
19
+ var crypto2__default = /*#__PURE__*/_interopDefault(crypto2);
20
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
21
+ var os__default = /*#__PURE__*/_interopDefault(os);
22
+ var path__default = /*#__PURE__*/_interopDefault(path);
23
+
24
+ var __importMetaUrl = (function() {
25
+ if (typeof document !== 'undefined') {
26
+ return document.currentScript && document.currentScript.src || new URL('main.js', document.baseURI).href;
27
+ }
28
+ // Node.js CJS context
29
+ // When this bundle is re-bundled by another tool (e.g., esbuild, webpack),
30
+ // __filename may not be defined or may not be a valid file path.
31
+ // We need to handle these cases gracefully.
32
+ try {
33
+ if (typeof __filename !== 'undefined' && __filename) {
34
+ var url = require('url');
35
+ var path = require('path');
36
+ // Check if __filename looks like a valid file path
37
+ if (path.isAbsolute(__filename) || __filename.startsWith('./') || __filename.startsWith('../')) {
38
+ return url.pathToFileURL(__filename).href;
39
+ }
40
+ }
41
+ } catch (e) {
42
+ // Fallback if pathToFileURL fails
43
+ }
44
+ // Fallback: use process.cwd() as the base URL
45
+ // This is not perfect but allows the code to continue working
46
+ try {
47
+ var url = require('url');
48
+ return url.pathToFileURL(require('path').join(process.cwd(), 'index.cjs')).href;
49
+ } catch (e) {
50
+ return 'file:///unknown';
51
+ }
52
+ })();
53
+ function debugLog(_msg) {
54
+ }
55
+ (() => {
56
+ try {
57
+ const metaUrl = __importMetaUrl;
58
+ if (metaUrl && typeof metaUrl === "string" && metaUrl.startsWith("file://")) {
59
+ return path__default.default.dirname(url.fileURLToPath(metaUrl));
60
+ }
61
+ } catch {
62
+ }
63
+ return process.cwd();
64
+ })();
65
+ function getCacheDir(override) {
66
+ if (override) {
67
+ return override;
68
+ }
69
+ const envCacheDir = process.env.MOCK_MCP_CACHE_DIR;
70
+ if (envCacheDir) {
71
+ return envCacheDir;
72
+ }
73
+ const xdg = process.env.XDG_CACHE_HOME;
74
+ if (xdg) {
75
+ return xdg;
76
+ }
77
+ if (process.platform === "win32" && process.env.LOCALAPPDATA) {
78
+ return process.env.LOCALAPPDATA;
79
+ }
80
+ const home = os__default.default.homedir();
81
+ if (home) {
82
+ return path__default.default.join(home, ".cache");
83
+ }
84
+ return os__default.default.tmpdir();
85
+ }
86
+ async function readRegistry(registryPath) {
87
+ try {
88
+ const txt = await fs__default.default.readFile(registryPath, "utf-8");
89
+ return JSON.parse(txt);
90
+ } catch (error) {
91
+ debugLog(`readRegistry error for ${registryPath}: ${error instanceof Error ? error.message : String(error)}`);
92
+ return null;
93
+ }
94
+ }
95
+ async function healthCheck(ipcPath, timeoutMs = 2e3) {
96
+ return new Promise((resolve) => {
97
+ const req = http__default.default.request(
98
+ {
99
+ method: "GET",
100
+ socketPath: ipcPath,
101
+ path: "/health",
102
+ timeout: timeoutMs
103
+ },
104
+ (res) => {
105
+ resolve(res.statusCode === 200);
106
+ }
107
+ );
108
+ req.on("error", () => resolve(false));
109
+ req.on("timeout", () => {
110
+ req.destroy();
111
+ resolve(false);
112
+ });
113
+ req.end();
114
+ });
115
+ }
116
+ function getGlobalIndexPath(cacheDir) {
117
+ const base = path__default.default.join(getCacheDir(cacheDir), "mock-mcp");
118
+ return path__default.default.join(base, "active-daemons.json");
119
+ }
120
+ async function readGlobalIndex(cacheDir) {
121
+ const indexPath = getGlobalIndexPath(cacheDir);
122
+ try {
123
+ const txt = await fs__default.default.readFile(indexPath, "utf-8");
124
+ return JSON.parse(txt);
125
+ } catch {
126
+ return { daemons: [], updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
127
+ }
128
+ }
129
+ async function writeGlobalIndex(index, cacheDir) {
130
+ const indexPath = getGlobalIndexPath(cacheDir);
131
+ const base = path__default.default.dirname(indexPath);
132
+ await fs__default.default.mkdir(base, { recursive: true });
133
+ await fs__default.default.writeFile(indexPath, JSON.stringify(index, null, 2), {
134
+ encoding: "utf-8",
135
+ mode: 384
136
+ });
137
+ }
138
+ async function cleanupGlobalIndex(cacheDir) {
139
+ const index = await readGlobalIndex(cacheDir);
140
+ const validDaemons = [];
141
+ for (const entry of index.daemons) {
142
+ try {
143
+ process.kill(entry.pid, 0);
144
+ const healthy = await healthCheck(entry.ipcPath, 1e3);
145
+ if (healthy) {
146
+ validDaemons.push(entry);
147
+ } else {
148
+ debugLog(`Removing unhealthy daemon ${entry.projectId} (pid ${entry.pid})`);
149
+ }
150
+ } catch {
151
+ debugLog(`Removing dead daemon ${entry.projectId} (pid ${entry.pid})`);
152
+ }
153
+ }
154
+ if (validDaemons.length !== index.daemons.length) {
155
+ index.daemons = validDaemons;
156
+ index.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
157
+ await writeGlobalIndex(index, cacheDir);
158
+ }
159
+ }
160
+ async function discoverAllDaemons(cacheDir) {
161
+ await cleanupGlobalIndex(cacheDir);
162
+ const index = await readGlobalIndex(cacheDir);
163
+ const results = [];
164
+ for (const entry of index.daemons) {
165
+ const registry = await readRegistry(entry.registryPath);
166
+ if (registry) {
167
+ const healthy = await healthCheck(entry.ipcPath, 2e3);
168
+ results.push({ registry, healthy });
169
+ }
170
+ }
171
+ return results;
172
+ }
173
+
174
+ // src/adapter/multi-daemon-client.ts
175
+ var MultiDaemonClient = class {
176
+ logger;
177
+ cacheDir;
178
+ adapterId;
179
+ constructor(opts = {}) {
180
+ this.logger = opts.logger ?? console;
181
+ this.cacheDir = opts.cacheDir;
182
+ this.adapterId = crypto2__default.default.randomUUID();
183
+ }
184
+ // ===========================================================================
185
+ // Discovery
186
+ // ===========================================================================
187
+ /**
188
+ * Discover all active and healthy daemons.
189
+ */
190
+ async discoverDaemons() {
191
+ return discoverAllDaemons(this.cacheDir);
192
+ }
193
+ // ===========================================================================
194
+ // Aggregated RPC Methods
195
+ // ===========================================================================
196
+ /**
197
+ * Get aggregated status from all daemons.
198
+ */
199
+ async getAggregatedStatus() {
200
+ const daemons = await this.discoverDaemons();
201
+ const statuses = [];
202
+ let totalRuns = 0;
203
+ let totalPending = 0;
204
+ let totalClaimed = 0;
205
+ for (const { registry, healthy } of daemons) {
206
+ if (!healthy) {
207
+ statuses.push({
208
+ version: registry.version,
209
+ projectId: registry.projectId,
210
+ projectRoot: registry.projectRoot,
211
+ pid: registry.pid,
212
+ uptime: 0,
213
+ runs: 0,
214
+ pending: 0,
215
+ claimed: 0,
216
+ totalBatches: 0,
217
+ healthy: false
218
+ });
219
+ continue;
220
+ }
221
+ try {
222
+ const status = await this.rpc(registry, "getStatus", {});
223
+ statuses.push({ ...status, healthy: true });
224
+ totalRuns += status.runs;
225
+ totalPending += status.pending;
226
+ totalClaimed += status.claimed;
227
+ } catch (error) {
228
+ this.logger.warn(`Failed to get status from daemon ${registry.projectId}: ${error}`);
229
+ statuses.push({
230
+ version: registry.version,
231
+ projectId: registry.projectId,
232
+ projectRoot: registry.projectRoot,
233
+ pid: registry.pid,
234
+ uptime: 0,
235
+ runs: 0,
236
+ pending: 0,
237
+ claimed: 0,
238
+ totalBatches: 0,
239
+ healthy: false
240
+ });
241
+ }
242
+ }
243
+ return { daemons: statuses, totalRuns, totalPending, totalClaimed };
244
+ }
245
+ /**
246
+ * List all runs across all daemons.
247
+ */
248
+ async listAllRuns() {
249
+ const daemons = await this.discoverDaemons();
250
+ const allRuns = [];
251
+ for (const { registry, healthy } of daemons) {
252
+ if (!healthy) continue;
253
+ try {
254
+ const result = await this.rpc(registry, "listRuns", {});
255
+ for (const run of result.runs) {
256
+ allRuns.push({
257
+ ...run,
258
+ projectId: registry.projectId,
259
+ projectRoot: registry.projectRoot
260
+ });
261
+ }
262
+ } catch (error) {
263
+ this.logger.warn(`Failed to list runs from daemon ${registry.projectId}: ${error}`);
264
+ }
265
+ }
266
+ return allRuns;
267
+ }
268
+ /**
269
+ * Claim the next available batch from any daemon.
270
+ * Searches through all daemons in order until finding one with a pending batch.
271
+ */
272
+ async claimNextBatch(args) {
273
+ const daemons = await this.discoverDaemons();
274
+ for (const { registry, healthy } of daemons) {
275
+ if (!healthy) continue;
276
+ try {
277
+ const result = await this.rpc(registry, "claimNextBatch", {
278
+ adapterId: this.adapterId,
279
+ runId: args.runId,
280
+ leaseMs: args.leaseMs
281
+ });
282
+ if (result) {
283
+ return {
284
+ ...result,
285
+ projectId: registry.projectId,
286
+ projectRoot: registry.projectRoot
287
+ };
288
+ }
289
+ } catch (error) {
290
+ this.logger.warn(`Failed to claim batch from daemon ${registry.projectId}: ${error}`);
291
+ }
292
+ }
293
+ return null;
294
+ }
295
+ /**
296
+ * Provide mock data for a batch.
297
+ * Automatically routes to the correct daemon based on batchId.
298
+ */
299
+ async provideBatch(args) {
300
+ const parts = args.batchId.split(":");
301
+ if (parts.length < 2) {
302
+ return { ok: false, message: `Invalid batchId format: ${args.batchId}` };
303
+ }
304
+ const daemons = await this.discoverDaemons();
305
+ for (const { registry, healthy } of daemons) {
306
+ if (!healthy) continue;
307
+ try {
308
+ const result = await this.rpc(registry, "provideBatch", {
309
+ adapterId: this.adapterId,
310
+ batchId: args.batchId,
311
+ claimToken: args.claimToken,
312
+ mocks: args.mocks
313
+ });
314
+ return result;
315
+ } catch (error) {
316
+ const msg = error instanceof Error ? error.message : String(error);
317
+ if (msg.includes("not found") || msg.includes("Not found")) {
318
+ continue;
319
+ }
320
+ return { ok: false, message: msg };
321
+ }
322
+ }
323
+ return { ok: false, message: `Batch not found: ${args.batchId}` };
324
+ }
325
+ /**
326
+ * Release a batch.
327
+ */
328
+ async releaseBatch(args) {
329
+ const daemons = await this.discoverDaemons();
330
+ for (const { registry, healthy } of daemons) {
331
+ if (!healthy) continue;
332
+ try {
333
+ const result = await this.rpc(registry, "releaseBatch", {
334
+ adapterId: this.adapterId,
335
+ batchId: args.batchId,
336
+ claimToken: args.claimToken,
337
+ reason: args.reason
338
+ });
339
+ return result;
340
+ } catch (error) {
341
+ const msg = error instanceof Error ? error.message : String(error);
342
+ if (msg.includes("not found") || msg.includes("Not found")) {
343
+ continue;
344
+ }
345
+ return { ok: false, message: msg };
346
+ }
347
+ }
348
+ return { ok: false, message: `Batch not found: ${args.batchId}` };
349
+ }
350
+ /**
351
+ * Get a specific batch by ID.
352
+ */
353
+ async getBatch(batchId) {
354
+ const daemons = await this.discoverDaemons();
355
+ for (const { registry, healthy } of daemons) {
356
+ if (!healthy) continue;
357
+ try {
358
+ const result = await this.rpc(registry, "getBatch", { batchId });
359
+ return result;
360
+ } catch (error) {
361
+ const msg = error instanceof Error ? error.message : String(error);
362
+ if (msg.includes("not found") || msg.includes("Not found")) {
363
+ continue;
364
+ }
365
+ throw error;
366
+ }
367
+ }
368
+ return null;
369
+ }
370
+ // ===========================================================================
371
+ // Internal RPC
372
+ // ===========================================================================
373
+ rpc(registry, method, params) {
374
+ const payload = {
375
+ jsonrpc: "2.0",
376
+ id: crypto2__default.default.randomUUID(),
377
+ method,
378
+ params
379
+ };
380
+ return new Promise((resolve, reject) => {
381
+ const req = http__default.default.request(
382
+ {
383
+ method: "POST",
384
+ socketPath: registry.ipcPath,
385
+ path: "/control",
386
+ headers: {
387
+ "content-type": "application/json",
388
+ "x-mock-mcp-token": registry.token
389
+ },
390
+ timeout: 3e4
391
+ },
392
+ (res) => {
393
+ let buf = "";
394
+ res.on("data", (chunk) => buf += chunk);
395
+ res.on("end", () => {
396
+ try {
397
+ const response = JSON.parse(buf);
398
+ if (response.error) {
399
+ reject(new Error(response.error.message));
400
+ } else {
401
+ resolve(response.result);
402
+ }
403
+ } catch (e) {
404
+ reject(e);
405
+ }
406
+ });
407
+ }
408
+ );
409
+ req.on("error", (err) => {
410
+ reject(new Error(`Daemon connection failed: ${err.message}`));
411
+ });
412
+ req.on("timeout", () => {
413
+ req.destroy();
414
+ reject(new Error("Daemon request timeout"));
415
+ });
416
+ req.end(JSON.stringify(payload));
417
+ });
418
+ }
419
+ };
420
+
421
+ // src/adapter/adapter.ts
422
+ var TOOLS = [
423
+ {
424
+ name: "get_status",
425
+ description: "Get the current status of the mock-mcp daemon, including active test runs and pending batches.",
426
+ inputSchema: {
427
+ type: "object",
428
+ properties: {},
429
+ required: []
430
+ }
431
+ },
432
+ {
433
+ name: "list_runs",
434
+ description: "List all active test runs connected to the daemon.",
435
+ inputSchema: {
436
+ type: "object",
437
+ properties: {},
438
+ required: []
439
+ }
440
+ },
441
+ {
442
+ name: "claim_next_batch",
443
+ description: `Claim the next pending mock batch for processing. This acquires a lease on the batch.
444
+
445
+ You MUST call this before provide_batch_mock_data. The batch will be locked for 30 seconds (configurable via leaseMs).
446
+ If you don't provide mock data within the lease time, the batch will be released for another adapter to claim.`,
447
+ inputSchema: {
448
+ type: "object",
449
+ properties: {
450
+ runId: {
451
+ type: "string",
452
+ description: "Optional: Filter to only claim batches from a specific test run."
453
+ },
454
+ leaseMs: {
455
+ type: "number",
456
+ description: "Optional: Lease duration in milliseconds. Default: 30000 (30 seconds)."
457
+ }
458
+ },
459
+ required: []
460
+ }
461
+ },
462
+ {
463
+ name: "get_batch",
464
+ description: "Get details of a specific batch by ID (read-only, does not claim).",
465
+ inputSchema: {
466
+ type: "object",
467
+ properties: {
468
+ batchId: {
469
+ type: "string",
470
+ description: "The batch ID to retrieve."
471
+ }
472
+ },
473
+ required: ["batchId"]
474
+ }
475
+ },
476
+ {
477
+ name: "provide_batch_mock_data",
478
+ description: `Provide mock response data for a claimed batch.
479
+
480
+ You MUST first call claim_next_batch to get the batchId and claimToken.
481
+ The mocks array must contain exactly one mock for each request in the batch.`,
482
+ inputSchema: {
483
+ type: "object",
484
+ properties: {
485
+ batchId: {
486
+ type: "string",
487
+ description: "The batch ID (from claim_next_batch)."
488
+ },
489
+ claimToken: {
490
+ type: "string",
491
+ description: "The claim token (from claim_next_batch)."
492
+ },
493
+ mocks: {
494
+ type: "array",
495
+ description: "Array of mock responses, one for each request in the batch.",
496
+ items: {
497
+ type: "object",
498
+ properties: {
499
+ requestId: {
500
+ type: "string",
501
+ description: "The requestId from the original request."
502
+ },
503
+ data: {
504
+ description: "The mock response data (any JSON value)."
505
+ },
506
+ status: {
507
+ type: "number",
508
+ description: "Optional HTTP status code. Default: 200."
509
+ },
510
+ headers: {
511
+ type: "object",
512
+ description: "Optional response headers."
513
+ },
514
+ delayMs: {
515
+ type: "number",
516
+ description: "Optional delay before returning the mock (ms)."
517
+ }
518
+ },
519
+ required: ["requestId", "data"]
520
+ }
521
+ }
522
+ },
523
+ required: ["batchId", "claimToken", "mocks"]
524
+ }
525
+ },
526
+ {
527
+ name: "release_batch",
528
+ description: "Release a claimed batch without providing mock data. Use this if you cannot generate appropriate mocks.",
529
+ inputSchema: {
530
+ type: "object",
531
+ properties: {
532
+ batchId: {
533
+ type: "string",
534
+ description: "The batch ID to release."
535
+ },
536
+ claimToken: {
537
+ type: "string",
538
+ description: "The claim token."
539
+ },
540
+ reason: {
541
+ type: "string",
542
+ description: "Optional reason for releasing."
543
+ }
544
+ },
545
+ required: ["batchId", "claimToken"]
546
+ }
547
+ }
548
+ ];
549
+ async function runAdapter(opts = {}) {
550
+ const logger = opts.logger ?? console;
551
+ const version = opts.version ?? "0.5.0";
552
+ logger.error("\u{1F50D} Initializing mock-mcp adapter (multi-daemon mode)...");
553
+ const multiDaemon = new MultiDaemonClient({ logger });
554
+ const daemons = await multiDaemon.discoverDaemons();
555
+ if (daemons.length > 0) {
556
+ logger.error(`\u2705 Found ${daemons.length} active daemon(s):`);
557
+ for (const d of daemons) {
558
+ const status = d.healthy ? "healthy" : "unhealthy";
559
+ logger.error(` - ${d.registry.projectId}: ${d.registry.projectRoot} (${status})`);
560
+ }
561
+ } else {
562
+ logger.error("\u2139\uFE0F No active daemons found. Waiting for test processes to start...");
563
+ }
564
+ const server = new index_js.Server(
565
+ {
566
+ name: "mock-mcp-adapter",
567
+ version
568
+ },
569
+ {
570
+ capabilities: { tools: {} }
571
+ }
572
+ );
573
+ server.setRequestHandler(types_js.ListToolsRequestSchema, async () => ({
574
+ tools: [...TOOLS]
575
+ }));
576
+ server.setRequestHandler(types_js.CallToolRequestSchema, async (request) => {
577
+ const { name, arguments: args } = request.params;
578
+ try {
579
+ switch (name) {
580
+ case "get_status": {
581
+ const result = await multiDaemon.getAggregatedStatus();
582
+ return buildToolResponse(formatAggregatedStatus(result));
583
+ }
584
+ case "list_runs": {
585
+ const result = await multiDaemon.listAllRuns();
586
+ return buildToolResponse(formatExtendedRuns(result));
587
+ }
588
+ case "claim_next_batch": {
589
+ const result = await multiDaemon.claimNextBatch({
590
+ runId: args?.runId,
591
+ leaseMs: args?.leaseMs
592
+ });
593
+ return buildToolResponse(formatClaimResult(result));
594
+ }
595
+ case "get_batch": {
596
+ if (!args?.batchId) {
597
+ throw new Error("batchId is required");
598
+ }
599
+ const result = await multiDaemon.getBatch(args.batchId);
600
+ if (!result) {
601
+ throw new Error(`Batch not found: ${args.batchId}`);
602
+ }
603
+ return buildToolResponse(formatBatch(result));
604
+ }
605
+ case "provide_batch_mock_data": {
606
+ if (!args?.batchId || !args?.claimToken || !args?.mocks) {
607
+ throw new Error("batchId, claimToken, and mocks are required");
608
+ }
609
+ const result = await multiDaemon.provideBatch({
610
+ batchId: args.batchId,
611
+ claimToken: args.claimToken,
612
+ mocks: args.mocks
613
+ });
614
+ return buildToolResponse(formatProvideResult(result));
615
+ }
616
+ case "release_batch": {
617
+ if (!args?.batchId || !args?.claimToken) {
618
+ throw new Error("batchId and claimToken are required");
619
+ }
620
+ const result = await multiDaemon.releaseBatch({
621
+ batchId: args.batchId,
622
+ claimToken: args.claimToken,
623
+ reason: args?.reason
624
+ });
625
+ return buildToolResponse(JSON.stringify(result, null, 2));
626
+ }
627
+ default:
628
+ throw new Error(`Unknown tool: ${name}`);
629
+ }
630
+ } catch (error) {
631
+ const message = error instanceof Error ? error.message : String(error);
632
+ logger.error(`Tool error (${name}): ${message}`);
633
+ return buildToolResponse(`Error: ${message}`, true);
634
+ }
635
+ });
636
+ const transport = new stdio_js.StdioServerTransport();
637
+ await server.connect(transport);
638
+ logger.error("\u2705 MCP adapter ready (stdio transport)");
639
+ }
640
+ function buildToolResponse(text, isError = false) {
641
+ return {
642
+ content: [{ type: "text", text }],
643
+ isError
644
+ };
645
+ }
646
+ function formatAggregatedStatus(status) {
647
+ if (status.daemons.length === 0) {
648
+ return `# Mock MCP Status
649
+
650
+ No active daemons found. Start a test with \`MOCK_MCP=1\` to begin.
651
+ `;
652
+ }
653
+ const lines = [
654
+ "# Mock MCP Status\n",
655
+ "## Summary",
656
+ `- **Active Daemons**: ${status.daemons.filter((d) => d.healthy).length}`,
657
+ `- **Total Active Runs**: ${status.totalRuns}`,
658
+ `- **Total Pending Batches**: ${status.totalPending}`,
659
+ `- **Total Claimed Batches**: ${status.totalClaimed}`,
660
+ "",
661
+ "## Daemons\n"
662
+ ];
663
+ for (const daemon of status.daemons) {
664
+ const healthIcon = daemon.healthy ? "\u2705" : "\u274C";
665
+ lines.push(`### ${healthIcon} ${daemon.projectRoot}`);
666
+ lines.push(`- **Project ID**: ${daemon.projectId}`);
667
+ lines.push(`- **Version**: ${daemon.version}`);
668
+ lines.push(`- **PID**: ${daemon.pid}`);
669
+ if (daemon.healthy) {
670
+ lines.push(`- **Uptime**: ${Math.round(daemon.uptime / 1e3)}s`);
671
+ lines.push(`- **Runs**: ${daemon.runs}`);
672
+ lines.push(`- **Pending**: ${daemon.pending}`);
673
+ lines.push(`- **Claimed**: ${daemon.claimed}`);
674
+ } else {
675
+ lines.push(`- **Status**: Not responding`);
676
+ }
677
+ lines.push("");
678
+ }
679
+ return lines.join("\n");
680
+ }
681
+ function formatExtendedRuns(runs) {
682
+ if (runs.length === 0) {
683
+ return "No active test runs.\n\nStart a test with `MOCK_MCP=1` to begin.";
684
+ }
685
+ const lines = ["# Active Test Runs\n"];
686
+ const byProject = /* @__PURE__ */ new Map();
687
+ for (const run of runs) {
688
+ const key = run.projectRoot;
689
+ if (!byProject.has(key)) {
690
+ byProject.set(key, []);
691
+ }
692
+ byProject.get(key).push(run);
693
+ }
694
+ for (const [projectRoot, projectRuns] of byProject) {
695
+ lines.push(`## Project: ${projectRoot}
696
+ `);
697
+ for (const run of projectRuns) {
698
+ lines.push(`### Run: ${run.runId}`);
699
+ lines.push(`- **PID**: ${run.pid}`);
700
+ lines.push(`- **CWD**: ${run.cwd}`);
701
+ lines.push(`- **Started**: ${run.startedAt}`);
702
+ lines.push(`- **Pending Batches**: ${run.pendingBatches}`);
703
+ if (run.testMeta) {
704
+ if (run.testMeta.testFile) {
705
+ lines.push(`- **Test File**: ${run.testMeta.testFile}`);
706
+ }
707
+ if (run.testMeta.testName) {
708
+ lines.push(`- **Test Name**: ${run.testMeta.testName}`);
709
+ }
710
+ }
711
+ lines.push("");
712
+ }
713
+ }
714
+ return lines.join("\n");
715
+ }
716
+ function formatClaimResult(result) {
717
+ if (!result) {
718
+ return "No pending batches available to claim.\n\nMake sure a test is running with `MOCK_MCP=1` and has pending mock requests.";
719
+ }
720
+ const lines = [
721
+ "# Batch Claimed Successfully\n",
722
+ `**Batch ID**: \`${result.batchId}\``,
723
+ `**Claim Token**: \`${result.claimToken}\``,
724
+ `**Run ID**: ${result.runId}`,
725
+ `**Project**: ${result.projectRoot}`,
726
+ `**Lease Until**: ${new Date(result.leaseUntil).toISOString()}`,
727
+ "",
728
+ "## Requests\n"
729
+ ];
730
+ for (const req of result.requests) {
731
+ lines.push(`### ${req.method} ${req.endpoint}`);
732
+ lines.push(`- **Request ID**: \`${req.requestId}\``);
733
+ if (req.body !== void 0) {
734
+ lines.push(`- **Body**: \`\`\`json
735
+ ${JSON.stringify(req.body, null, 2)}
736
+ \`\`\``);
737
+ }
738
+ if (req.headers) {
739
+ lines.push(`- **Headers**: ${JSON.stringify(req.headers)}`);
740
+ }
741
+ if (req.metadata) {
742
+ lines.push(`- **Metadata**: ${JSON.stringify(req.metadata)}`);
743
+ }
744
+ lines.push("");
745
+ }
746
+ lines.push("---");
747
+ lines.push("**Next step**: Call `provide_batch_mock_data` with the batch ID, claim token, and mock data for each request.");
748
+ return lines.join("\n");
749
+ }
750
+ function formatBatch(result) {
751
+ const lines = [
752
+ `# Batch: ${result.batchId}
753
+ `,
754
+ `**Status**: ${result.status}`,
755
+ `**Run ID**: ${result.runId}`,
756
+ `**Created**: ${new Date(result.createdAt).toISOString()}`
757
+ ];
758
+ if (result.claim) {
759
+ lines.push(`**Claimed by**: ${result.claim.adapterId}`);
760
+ lines.push(`**Lease until**: ${new Date(result.claim.leaseUntil).toISOString()}`);
761
+ }
762
+ lines.push("", "## Requests\n");
763
+ for (const req of result.requests) {
764
+ lines.push(`### ${req.method} ${req.endpoint}`);
765
+ lines.push(`- **Request ID**: \`${req.requestId}\``);
766
+ if (req.body !== void 0) {
767
+ lines.push(`- **Body**: \`\`\`json
768
+ ${JSON.stringify(req.body, null, 2)}
769
+ \`\`\``);
770
+ }
771
+ lines.push("");
772
+ }
773
+ return lines.join("\n");
774
+ }
775
+ function formatProvideResult(result) {
776
+ if (result.ok) {
777
+ return `\u2705 ${result.message ?? "Mock data provided successfully."}`;
778
+ }
779
+ return `\u274C Failed to provide mock data: ${result.message}`;
780
+ }
781
+ var DaemonClient = class {
782
+ constructor(ipcPath, token, adapterId) {
783
+ this.ipcPath = ipcPath;
784
+ this.token = token;
785
+ this.adapterId = adapterId;
786
+ }
787
+ // ===========================================================================
788
+ // RPC Methods
789
+ // ===========================================================================
790
+ async getStatus() {
791
+ return this.rpc("getStatus", {});
792
+ }
793
+ async listRuns() {
794
+ return this.rpc("listRuns", {});
795
+ }
796
+ async claimNextBatch(args) {
797
+ return this.rpc("claimNextBatch", {
798
+ adapterId: this.adapterId,
799
+ runId: args.runId,
800
+ leaseMs: args.leaseMs
801
+ });
802
+ }
803
+ async provideBatch(args) {
804
+ return this.rpc("provideBatch", {
805
+ adapterId: this.adapterId,
806
+ batchId: args.batchId,
807
+ claimToken: args.claimToken,
808
+ mocks: args.mocks
809
+ });
810
+ }
811
+ async releaseBatch(args) {
812
+ return this.rpc("releaseBatch", {
813
+ adapterId: this.adapterId,
814
+ batchId: args.batchId,
815
+ claimToken: args.claimToken,
816
+ reason: args.reason
817
+ });
818
+ }
819
+ async getBatch(batchId) {
820
+ return this.rpc("getBatch", { batchId });
821
+ }
822
+ // ===========================================================================
823
+ // Internal
824
+ // ===========================================================================
825
+ rpc(method, params) {
826
+ const payload = {
827
+ jsonrpc: "2.0",
828
+ id: crypto2__default.default.randomUUID(),
829
+ method,
830
+ params
831
+ };
832
+ return new Promise((resolve, reject) => {
833
+ const req = http__default.default.request(
834
+ {
835
+ method: "POST",
836
+ socketPath: this.ipcPath,
837
+ path: "/control",
838
+ headers: {
839
+ "content-type": "application/json",
840
+ "x-mock-mcp-token": this.token
841
+ },
842
+ timeout: 3e4
843
+ },
844
+ (res) => {
845
+ let buf = "";
846
+ res.on("data", (chunk) => buf += chunk);
847
+ res.on("end", () => {
848
+ try {
849
+ const response = JSON.parse(buf);
850
+ if (response.error) {
851
+ reject(new Error(response.error.message));
852
+ } else {
853
+ resolve(response.result);
854
+ }
855
+ } catch (e) {
856
+ reject(e);
857
+ }
858
+ });
859
+ }
860
+ );
861
+ req.on("error", (err) => {
862
+ reject(new Error(`Daemon connection failed: ${err.message}`));
863
+ });
864
+ req.on("timeout", () => {
865
+ req.destroy();
866
+ reject(new Error("Daemon request timeout"));
867
+ });
868
+ req.end(JSON.stringify(payload));
869
+ });
870
+ }
871
+ };
872
+
873
+ exports.DaemonClient = DaemonClient;
874
+ exports.MultiDaemonClient = MultiDaemonClient;
875
+ exports.runAdapter = runAdapter;