@apicircle/mcp-server 1.0.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,2527 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ FileBackedWorkspaceProvider: () => FileBackedWorkspaceProvider,
24
+ InMemoryWorkspaceProvider: () => InMemoryWorkspaceProvider,
25
+ InProcessMockController: () => InProcessMockController,
26
+ McpHost: () => McpHost,
27
+ TOOL_REGISTRY: () => TOOL_REGISTRY,
28
+ createMcpServer: () => createMcpServer,
29
+ getTool: () => getTool
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/host/McpHost.ts
34
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
35
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
36
+ var import_zod = require("zod");
37
+ var PACKAGE_NAME = "apicircle-mcp";
38
+ var PACKAGE_VERSION = "1.0.0";
39
+ var McpHost = class {
40
+ server;
41
+ tools;
42
+ context;
43
+ constructor(options) {
44
+ this.server = new import_mcp.McpServer({
45
+ name: options.serverInfo?.name ?? PACKAGE_NAME,
46
+ version: options.serverInfo?.version ?? PACKAGE_VERSION
47
+ });
48
+ this.tools = options.tools;
49
+ this.context = options.context;
50
+ this.registerAll();
51
+ }
52
+ registerAll() {
53
+ for (const tool of this.tools) {
54
+ const shape = isZodObject(tool.inputSchema) ? tool.inputSchema.shape : void 0;
55
+ this.server.registerTool(
56
+ tool.name,
57
+ {
58
+ description: tool.description,
59
+ ...shape ? { inputSchema: shape } : {}
60
+ },
61
+ async (args) => {
62
+ try {
63
+ const parsed = tool.inputSchema.parse(args ?? {});
64
+ const result = await tool.handler(parsed, this.context);
65
+ return {
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: JSON.stringify(result, null, 2)
70
+ }
71
+ ]
72
+ };
73
+ } catch (err) {
74
+ return {
75
+ isError: true,
76
+ content: [
77
+ {
78
+ type: "text",
79
+ text: formatError(err)
80
+ }
81
+ ]
82
+ };
83
+ }
84
+ }
85
+ );
86
+ }
87
+ }
88
+ /** Connect the underlying server to a transport (defaults to stdio). */
89
+ async connect(transport) {
90
+ await this.server.connect(transport ?? new import_stdio.StdioServerTransport());
91
+ }
92
+ async close() {
93
+ await this.server.close();
94
+ }
95
+ };
96
+ function isZodObject(schema) {
97
+ return schema instanceof import_zod.z.ZodObject;
98
+ }
99
+ function formatError(err) {
100
+ if (err instanceof import_zod.z.ZodError) {
101
+ return `Validation failed: ${err.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`).join("; ")}`;
102
+ }
103
+ if (err instanceof Error) return err.message;
104
+ return String(err);
105
+ }
106
+
107
+ // src/tools/imports.ts
108
+ var import_zod2 = require("zod");
109
+ var import_shared = require("@apicircle/shared");
110
+ var import_core = require("@apicircle/core");
111
+ var import_mock_server_core = require("@apicircle/mock-server-core");
112
+ var FALLBACK_NAME = (idx, fallback) => fallback || `Imported request ${idx + 1}`;
113
+ function blankRequest() {
114
+ return {
115
+ name: "Imported request",
116
+ folderId: null,
117
+ method: "GET",
118
+ url: "",
119
+ headers: [],
120
+ query: [],
121
+ body: { type: "none", content: "" },
122
+ auth: { type: "none" },
123
+ contextVars: [],
124
+ extractions: [],
125
+ assertions: []
126
+ };
127
+ }
128
+ var importCurlTool = {
129
+ name: "import.curl",
130
+ description: "Parse a `curl ...` command into a Request and persist it to the workspace.",
131
+ inputSchema: import_zod2.z.object({
132
+ curl: import_zod2.z.string().min(1, "curl command is required"),
133
+ name: import_zod2.z.string().optional(),
134
+ folderId: import_zod2.z.string().nullable().optional()
135
+ }),
136
+ async handler(input, ctx) {
137
+ const parsed = (0, import_core.parseCurl)(input.curl);
138
+ const now = (/* @__PURE__ */ new Date()).toISOString();
139
+ const request = {
140
+ ...blankRequest(),
141
+ id: (0, import_shared.generateId)(),
142
+ name: input.name?.trim() || `cURL ${parsed.method} ${parsed.url}`.slice(0, 80),
143
+ method: parsed.method,
144
+ url: parsed.url,
145
+ headers: parsed.headers,
146
+ query: parsed.query,
147
+ body: parsed.body,
148
+ auth: parsed.auth,
149
+ folderId: input.folderId ?? null,
150
+ createdAt: now,
151
+ updatedAt: now
152
+ };
153
+ const out = await ctx.workspace.apply({ kind: "request.create", request });
154
+ return {
155
+ id: request.id,
156
+ warnings: parsed.warnings,
157
+ changedIds: out.changedIds
158
+ };
159
+ }
160
+ };
161
+ var importOpenApiTool = {
162
+ name: "import.openapi",
163
+ description: "Parse an OpenAPI / Swagger spec (YAML or JSON) and create one Request per operation. Returns the list of created request ids.",
164
+ inputSchema: import_zod2.z.object({
165
+ spec: import_zod2.z.string().min(1),
166
+ format: import_zod2.z.enum(["json", "yaml"]).default("json"),
167
+ folderId: import_zod2.z.string().nullable().optional()
168
+ }),
169
+ async handler(input, ctx) {
170
+ const parsed = await (0, import_mock_server_core.parseOpenApiToEndpoints)(input.spec, input.format);
171
+ const ids = [];
172
+ for (let i = 0; i < parsed.endpoints.length; i++) {
173
+ const ep = parsed.endpoints[i];
174
+ const now = (/* @__PURE__ */ new Date()).toISOString();
175
+ const req = {
176
+ ...blankRequest(),
177
+ id: (0, import_shared.generateId)(),
178
+ name: FALLBACK_NAME(i, ep.example ?? `${ep.method} ${ep.pathPattern}`),
179
+ method: ep.method,
180
+ url: ep.pathPattern,
181
+ folderId: input.folderId ?? null,
182
+ createdAt: now,
183
+ updatedAt: now
184
+ };
185
+ await ctx.workspace.apply({ kind: "request.create", request: req });
186
+ ids.push(req.id);
187
+ }
188
+ return { createdIds: ids, warnings: parsed.warnings };
189
+ }
190
+ };
191
+ var importPostmanTool = {
192
+ name: "import.postman",
193
+ description: "Parse a Postman v2/v2.1 collection JSON and create one Request per item.",
194
+ inputSchema: import_zod2.z.object({
195
+ collection: import_zod2.z.string().min(1),
196
+ folderId: import_zod2.z.string().nullable().optional()
197
+ }),
198
+ async handler(input, ctx) {
199
+ const parsed = (0, import_mock_server_core.parsePostmanToEndpoints)(input.collection);
200
+ const ids = [];
201
+ for (let i = 0; i < parsed.endpoints.length; i++) {
202
+ const ep = parsed.endpoints[i];
203
+ const now = (/* @__PURE__ */ new Date()).toISOString();
204
+ const req = {
205
+ ...blankRequest(),
206
+ id: (0, import_shared.generateId)(),
207
+ name: FALLBACK_NAME(i, ep.example ?? `${ep.method} ${ep.pathPattern}`),
208
+ method: ep.method,
209
+ url: ep.pathPattern,
210
+ folderId: input.folderId ?? null,
211
+ createdAt: now,
212
+ updatedAt: now
213
+ };
214
+ await ctx.workspace.apply({ kind: "request.create", request: req });
215
+ ids.push(req.id);
216
+ }
217
+ return { createdIds: ids, warnings: parsed.warnings };
218
+ }
219
+ };
220
+ var importInsomniaTool = {
221
+ name: "import.insomnia",
222
+ description: 'Parse an Insomnia v4 export and create one Request per resource of type "request".',
223
+ inputSchema: import_zod2.z.object({
224
+ export: import_zod2.z.string().min(1),
225
+ folderId: import_zod2.z.string().nullable().optional()
226
+ }),
227
+ async handler(input, ctx) {
228
+ const parsed = (0, import_mock_server_core.parseInsomniaToEndpoints)(input.export);
229
+ const ids = [];
230
+ for (let i = 0; i < parsed.endpoints.length; i++) {
231
+ const ep = parsed.endpoints[i];
232
+ const now = (/* @__PURE__ */ new Date()).toISOString();
233
+ const req = {
234
+ ...blankRequest(),
235
+ id: (0, import_shared.generateId)(),
236
+ name: FALLBACK_NAME(i, ep.example ?? `${ep.method} ${ep.pathPattern}`),
237
+ method: ep.method,
238
+ url: ep.pathPattern,
239
+ folderId: input.folderId ?? null,
240
+ createdAt: now,
241
+ updatedAt: now
242
+ };
243
+ await ctx.workspace.apply({ kind: "request.create", request: req });
244
+ ids.push(req.id);
245
+ }
246
+ return { createdIds: ids, warnings: parsed.warnings };
247
+ }
248
+ };
249
+ var importHarTool = {
250
+ name: "import.har",
251
+ description: "Parse an HTTP Archive (.har) JSON and create one Request per recorded entry. Cookies, response, and timings are dropped.",
252
+ inputSchema: import_zod2.z.object({
253
+ har: import_zod2.z.string().min(1),
254
+ folderId: import_zod2.z.string().nullable().optional()
255
+ }),
256
+ async handler(input, ctx) {
257
+ let parsed;
258
+ try {
259
+ parsed = JSON.parse(input.har);
260
+ } catch (err) {
261
+ return { createdIds: [], warnings: [`HAR parse error: ${err.message}`] };
262
+ }
263
+ const entries = parsed.log?.entries ?? [];
264
+ const ids = [];
265
+ const warnings = [];
266
+ for (let i = 0; i < entries.length; i++) {
267
+ const e = entries[i].request;
268
+ if (!e || !e.url || !e.method) {
269
+ warnings.push(`Entry #${i} missing method/url; skipped`);
270
+ continue;
271
+ }
272
+ const url = new URL(e.url);
273
+ const now = (/* @__PURE__ */ new Date()).toISOString();
274
+ const req = {
275
+ ...blankRequest(),
276
+ id: (0, import_shared.generateId)(),
277
+ name: `${e.method} ${url.pathname}`,
278
+ method: e.method,
279
+ url: e.url,
280
+ headers: (e.headers ?? []).map((h) => ({
281
+ key: h.name,
282
+ value: h.value,
283
+ enabled: true
284
+ })),
285
+ query: (e.queryString ?? []).map((q) => ({
286
+ key: q.name,
287
+ value: q.value,
288
+ enabled: true
289
+ })),
290
+ body: e.postData ? { type: "text", content: e.postData.text ?? "" } : { type: "none", content: "" },
291
+ folderId: input.folderId ?? null,
292
+ createdAt: now,
293
+ updatedAt: now
294
+ };
295
+ await ctx.workspace.apply({ kind: "request.create", request: req });
296
+ ids.push(req.id);
297
+ }
298
+ return { createdIds: ids, warnings };
299
+ }
300
+ };
301
+
302
+ // src/tools/codegen.ts
303
+ var import_zod3 = require("zod");
304
+ var TARGET = import_zod3.z.enum(["curl", "fetch", "node-axios", "python-requests", "go", "rust"]);
305
+ var generateCodeTool = {
306
+ name: "generate.code",
307
+ description: "Generate runnable code (curl / JavaScript fetch / Node Axios / Python requests / Go net/http / Rust reqwest) that reproduces a workspace request.",
308
+ inputSchema: import_zod3.z.object({
309
+ requestId: import_zod3.z.string(),
310
+ target: TARGET
311
+ }),
312
+ async handler(input, ctx) {
313
+ const state = await ctx.workspace.read();
314
+ const req = state.synced.collections.requests[input.requestId];
315
+ if (!req) return { ok: false, error: "request not found" };
316
+ const code = renderCode(req, input.target);
317
+ return { ok: true, target: input.target, code };
318
+ }
319
+ };
320
+ function renderCode(req, target) {
321
+ switch (target) {
322
+ case "curl":
323
+ return renderCurl(req);
324
+ case "fetch":
325
+ return renderFetch(req);
326
+ case "node-axios":
327
+ return renderAxios(req);
328
+ case "python-requests":
329
+ return renderPython(req);
330
+ case "go":
331
+ return renderGo(req);
332
+ case "rust":
333
+ return renderRust(req);
334
+ }
335
+ }
336
+ function fullUrl(req) {
337
+ if (!req.query.length) return req.url;
338
+ const enabled = req.query.filter((q) => q.enabled !== false);
339
+ if (!enabled.length) return req.url;
340
+ const sep = req.url.includes("?") ? "&" : "?";
341
+ return req.url + sep + enabled.map((q) => `${encodeURIComponent(q.key)}=${encodeURIComponent(q.value)}`).join("&");
342
+ }
343
+ function bodyContent(req) {
344
+ if (req.body.type === "none") return null;
345
+ if (req.body.type === "json" || req.body.type === "text" || req.body.type === "xml") {
346
+ return req.body.content;
347
+ }
348
+ if (req.body.type === "graphql") {
349
+ return JSON.stringify({ query: req.body.content, variables: req.body.variables ?? null });
350
+ }
351
+ if (req.body.type === "urlencoded") return req.body.content;
352
+ return null;
353
+ }
354
+ function renderCurl(req) {
355
+ const parts = [`curl -X ${req.method} '${fullUrl(req)}'`];
356
+ for (const h of req.headers.filter((x) => x.enabled !== false)) {
357
+ parts.push(`-H '${h.key}: ${h.value}'`);
358
+ }
359
+ const body = bodyContent(req);
360
+ if (body !== null) {
361
+ parts.push(`--data-raw '${body.replace(/'/g, "'\\''")}'`);
362
+ }
363
+ return parts.join(" \\\n ");
364
+ }
365
+ function renderFetch(req) {
366
+ const headers = Object.fromEntries(
367
+ req.headers.filter((h) => h.enabled !== false).map((h) => [h.key, h.value])
368
+ );
369
+ const body = bodyContent(req);
370
+ const init = { method: req.method };
371
+ if (Object.keys(headers).length) init.headers = headers;
372
+ if (body !== null) init.body = body;
373
+ return `await fetch(${JSON.stringify(fullUrl(req))}, ${JSON.stringify(init, null, 2)})`;
374
+ }
375
+ function renderAxios(req) {
376
+ const config = {
377
+ method: req.method.toLowerCase(),
378
+ url: fullUrl(req)
379
+ };
380
+ const headers = req.headers.filter((h) => h.enabled !== false);
381
+ if (headers.length) config.headers = Object.fromEntries(headers.map((h) => [h.key, h.value]));
382
+ const body = bodyContent(req);
383
+ if (body !== null) config.data = body;
384
+ return `import axios from 'axios';
385
+
386
+ const response = await axios(${JSON.stringify(
387
+ config,
388
+ null,
389
+ 2
390
+ )});`;
391
+ }
392
+ function renderPython(req) {
393
+ const headers = req.headers.filter((h) => h.enabled !== false);
394
+ const body = bodyContent(req);
395
+ const lines = ["import requests", ""];
396
+ lines.push(`response = requests.request(`);
397
+ lines.push(` method=${JSON.stringify(req.method)},`);
398
+ lines.push(` url=${JSON.stringify(fullUrl(req))},`);
399
+ if (headers.length) {
400
+ lines.push(
401
+ ` headers=${JSON.stringify(Object.fromEntries(headers.map((h) => [h.key, h.value])))},`
402
+ );
403
+ }
404
+ if (body !== null) lines.push(` data=${JSON.stringify(body)},`);
405
+ lines.push(")");
406
+ return lines.join("\n");
407
+ }
408
+ function renderGo(req) {
409
+ const headers = req.headers.filter((h) => h.enabled !== false);
410
+ const body = bodyContent(req);
411
+ const lines = [
412
+ "package main",
413
+ "",
414
+ "import (",
415
+ ' "io"',
416
+ ' "net/http"',
417
+ ' "strings"',
418
+ ")",
419
+ "",
420
+ "func main() {"
421
+ ];
422
+ if (body !== null) {
423
+ lines.push(` body := strings.NewReader(${JSON.stringify(body)})`);
424
+ lines.push(
425
+ ` req, _ := http.NewRequest(${JSON.stringify(req.method)}, ${JSON.stringify(fullUrl(req))}, body)`
426
+ );
427
+ } else {
428
+ lines.push(
429
+ ` req, _ := http.NewRequest(${JSON.stringify(req.method)}, ${JSON.stringify(fullUrl(req))}, nil)`
430
+ );
431
+ }
432
+ for (const h of headers) {
433
+ lines.push(` req.Header.Set(${JSON.stringify(h.key)}, ${JSON.stringify(h.value)})`);
434
+ }
435
+ lines.push(" resp, _ := http.DefaultClient.Do(req)");
436
+ lines.push(" defer resp.Body.Close()");
437
+ lines.push(" out, _ := io.ReadAll(resp.Body)");
438
+ lines.push(" _ = out");
439
+ lines.push("}");
440
+ return lines.join("\n");
441
+ }
442
+ function renderRust(req) {
443
+ const headers = req.headers.filter((h) => h.enabled !== false);
444
+ const body = bodyContent(req);
445
+ const lines = [
446
+ "use reqwest::Client;",
447
+ "",
448
+ "#[tokio::main]",
449
+ "async fn main() -> Result<(), Box<dyn std::error::Error>> {",
450
+ ` let client = Client::new();`,
451
+ ` let mut req = client.request(reqwest::Method::${req.method}, ${JSON.stringify(
452
+ fullUrl(req)
453
+ )});`
454
+ ];
455
+ for (const h of headers) {
456
+ lines.push(` req = req.header(${JSON.stringify(h.key)}, ${JSON.stringify(h.value)});`);
457
+ }
458
+ if (body !== null) lines.push(` req = req.body(${JSON.stringify(body)});`);
459
+ lines.push(" let _resp = req.send().await?;");
460
+ lines.push(" Ok(())");
461
+ lines.push("}");
462
+ return lines.join("\n");
463
+ }
464
+
465
+ // src/tools/crud.ts
466
+ var import_zod4 = require("zod");
467
+ var import_shared2 = require("@apicircle/shared");
468
+ var HTTP_METHOD = import_zod4.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
469
+ var requestCreateTool = {
470
+ name: "request.create",
471
+ description: "Create a new request from explicit fields and persist it.",
472
+ inputSchema: import_zod4.z.object({
473
+ name: import_zod4.z.string().default("New request"),
474
+ method: HTTP_METHOD.default("GET"),
475
+ url: import_zod4.z.string().default(""),
476
+ folderId: import_zod4.z.string().nullable().optional()
477
+ }),
478
+ async handler(input, ctx) {
479
+ const now = (/* @__PURE__ */ new Date()).toISOString();
480
+ const request = {
481
+ id: (0, import_shared2.generateId)(),
482
+ name: input.name,
483
+ folderId: input.folderId ?? null,
484
+ method: input.method,
485
+ url: input.url,
486
+ headers: [],
487
+ query: [],
488
+ body: { type: "none", content: "" },
489
+ // Default to `inherit` so requests created via MCP inside a folder
490
+ // pick up folder auth automatically. Mirrors editorActions.createRequest.
491
+ auth: { type: "inherit" },
492
+ contextVars: [],
493
+ extractions: [],
494
+ assertions: [],
495
+ createdAt: now,
496
+ updatedAt: now
497
+ };
498
+ const out = await ctx.workspace.apply({ kind: "request.create", request });
499
+ return { id: request.id, changedIds: out.changedIds };
500
+ }
501
+ };
502
+ var requestReadTool = {
503
+ name: "request.read",
504
+ description: "Read a request by id, or list summaries (id, name, method, url) when no id is provided.",
505
+ inputSchema: import_zod4.z.object({ id: import_zod4.z.string().optional() }),
506
+ async handler(input, ctx) {
507
+ const state = await ctx.workspace.read();
508
+ if (input.id) {
509
+ const req = state.synced.collections.requests[input.id];
510
+ if (!req) return { found: false };
511
+ return { found: true, request: req };
512
+ }
513
+ const list = Object.values(state.synced.collections.requests).map((r) => ({
514
+ id: r.id,
515
+ name: r.name,
516
+ method: r.method,
517
+ url: r.url,
518
+ folderId: r.folderId
519
+ }));
520
+ return { count: list.length, requests: list };
521
+ }
522
+ };
523
+ var requestUpdateTool = {
524
+ name: "request.update",
525
+ description: "Patch fields on an existing request.",
526
+ inputSchema: import_zod4.z.object({
527
+ id: import_zod4.z.string(),
528
+ patch: import_zod4.z.object({
529
+ name: import_zod4.z.string().optional(),
530
+ method: HTTP_METHOD.optional(),
531
+ url: import_zod4.z.string().optional(),
532
+ folderId: import_zod4.z.string().nullable().optional()
533
+ }).strict()
534
+ }),
535
+ async handler(input, ctx) {
536
+ const out = await ctx.workspace.apply({
537
+ kind: "request.update",
538
+ id: input.id,
539
+ patch: input.patch
540
+ });
541
+ return { changedIds: out.changedIds };
542
+ }
543
+ };
544
+ var requestDeleteTool = {
545
+ name: "request.delete",
546
+ description: "Delete a request by id.",
547
+ inputSchema: import_zod4.z.object({ id: import_zod4.z.string() }),
548
+ async handler(input, ctx) {
549
+ const out = await ctx.workspace.apply({ kind: "request.delete", id: input.id });
550
+ return { changedIds: out.changedIds };
551
+ }
552
+ };
553
+ var folderCreateTool = {
554
+ name: "folder.create",
555
+ description: "Create a folder under an optional parent folder.",
556
+ inputSchema: import_zod4.z.object({
557
+ name: import_zod4.z.string().default("New folder"),
558
+ parentId: import_zod4.z.string().nullable().optional()
559
+ }),
560
+ async handler(input, ctx) {
561
+ const folder = {
562
+ id: (0, import_shared2.generateId)(),
563
+ name: input.name,
564
+ parentId: input.parentId ?? null
565
+ };
566
+ const out = await ctx.workspace.apply({ kind: "folder.create", folder });
567
+ return { id: folder.id, changedIds: out.changedIds };
568
+ }
569
+ };
570
+ var folderReadTool = {
571
+ name: "folder.read",
572
+ description: "Read a folder by id, or list all folders when no id is provided.",
573
+ inputSchema: import_zod4.z.object({ id: import_zod4.z.string().optional() }),
574
+ async handler(input, ctx) {
575
+ const state = await ctx.workspace.read();
576
+ if (input.id) {
577
+ const folder = state.synced.collections.folders[input.id];
578
+ return folder ? { found: true, folder } : { found: false };
579
+ }
580
+ return {
581
+ count: Object.keys(state.synced.collections.folders).length,
582
+ folders: Object.values(state.synced.collections.folders)
583
+ };
584
+ }
585
+ };
586
+ var folderUpdateTool = {
587
+ name: "folder.update",
588
+ description: "Move a folder to a new parent (or to root with parentId: null).",
589
+ inputSchema: import_zod4.z.object({
590
+ id: import_zod4.z.string(),
591
+ parentId: import_zod4.z.string().nullable()
592
+ }),
593
+ async handler(input, ctx) {
594
+ const out = await ctx.workspace.apply({
595
+ kind: "folder.move",
596
+ id: input.id,
597
+ newParentId: input.parentId
598
+ });
599
+ return { changedIds: out.changedIds };
600
+ }
601
+ };
602
+ var folderDeleteTool = {
603
+ name: "folder.delete",
604
+ description: "Delete a folder. Direct children (sub-folders + requests) are reparented to the deleted folder's parent.",
605
+ inputSchema: import_zod4.z.object({ id: import_zod4.z.string() }),
606
+ async handler(input, ctx) {
607
+ const out = await ctx.workspace.apply({ kind: "folder.delete", id: input.id });
608
+ return { changedIds: out.changedIds };
609
+ }
610
+ };
611
+ var VARIABLE = import_zod4.z.object({
612
+ key: import_zod4.z.string(),
613
+ value: import_zod4.z.string(),
614
+ encrypted: import_zod4.z.boolean().default(false)
615
+ });
616
+ var environmentCreateTool = {
617
+ name: "environment.create",
618
+ description: "Create a new environment (or upsert one with the same name).",
619
+ inputSchema: import_zod4.z.object({
620
+ name: import_zod4.z.string(),
621
+ variables: import_zod4.z.array(VARIABLE).default([])
622
+ }),
623
+ async handler(input, ctx) {
624
+ const env = { name: input.name, variables: input.variables };
625
+ const out = await ctx.workspace.apply({ kind: "environment.upsert", environment: env });
626
+ return { name: env.name, changedIds: out.changedIds };
627
+ }
628
+ };
629
+ var environmentReadTool = {
630
+ name: "environment.read",
631
+ description: "Read environments \u2014 pass `name` for one, or omit for the full list.",
632
+ inputSchema: import_zod4.z.object({ name: import_zod4.z.string().optional() }),
633
+ async handler(input, ctx) {
634
+ const state = await ctx.workspace.read();
635
+ if (input.name) {
636
+ const env = state.synced.environments.items[input.name];
637
+ return env ? { found: true, environment: env } : { found: false };
638
+ }
639
+ return {
640
+ activeName: state.synced.environments.activeName,
641
+ priorityOrder: state.synced.environments.priorityOrder,
642
+ environments: Object.values(state.synced.environments.items)
643
+ };
644
+ }
645
+ };
646
+ var environmentUpdateTool = {
647
+ name: "environment.update",
648
+ description: "Replace the variables list of an environment.",
649
+ inputSchema: import_zod4.z.object({
650
+ name: import_zod4.z.string(),
651
+ variables: import_zod4.z.array(VARIABLE)
652
+ }),
653
+ async handler(input, ctx) {
654
+ const out = await ctx.workspace.apply({
655
+ kind: "environment.upsert",
656
+ environment: { name: input.name, variables: input.variables }
657
+ });
658
+ return { changedIds: out.changedIds };
659
+ }
660
+ };
661
+ var environmentDeleteTool = {
662
+ name: "environment.delete",
663
+ description: "Delete an environment by name.",
664
+ inputSchema: import_zod4.z.object({ name: import_zod4.z.string() }),
665
+ async handler(input, ctx) {
666
+ const out = await ctx.workspace.apply({ kind: "environment.delete", name: input.name });
667
+ return { changedIds: out.changedIds };
668
+ }
669
+ };
670
+ var environmentSetActiveTool = {
671
+ name: "environment.set_active",
672
+ description: "Set (or clear) the active environment. Pass `name: null` to deactivate the current environment.",
673
+ inputSchema: import_zod4.z.object({ name: import_zod4.z.string().nullable() }),
674
+ async handler(input, ctx) {
675
+ const out = await ctx.workspace.apply({
676
+ kind: "environment.setActive",
677
+ name: input.name
678
+ });
679
+ return { changedIds: out.changedIds };
680
+ }
681
+ };
682
+ var environmentSetPriorityTool = {
683
+ name: "environment.set_priority",
684
+ description: 'Replace the global environment priority order (highest priority first). Strings are interpreted as local env names. To target a linked env, pass `{ kind: "linked", linkedWorkspaceId, envName }` instead.',
685
+ inputSchema: import_zod4.z.object({
686
+ order: import_zod4.z.array(
687
+ import_zod4.z.union([
688
+ import_zod4.z.string(),
689
+ import_zod4.z.object({
690
+ kind: import_zod4.z.literal("local"),
691
+ name: import_zod4.z.string()
692
+ }),
693
+ import_zod4.z.object({
694
+ kind: import_zod4.z.literal("linked"),
695
+ linkedWorkspaceId: import_zod4.z.string(),
696
+ envName: import_zod4.z.string()
697
+ })
698
+ ])
699
+ )
700
+ }),
701
+ async handler(input, ctx) {
702
+ const order = input.order.map((entry) => typeof entry === "string" ? { kind: "local", name: entry } : entry);
703
+ const out = await ctx.workspace.apply({
704
+ kind: "environment.setPriority",
705
+ order
706
+ });
707
+ return { changedIds: out.changedIds };
708
+ }
709
+ };
710
+ var environmentExportTool = {
711
+ name: "environment.export",
712
+ description: "Serialize an environment to a portable JSON string. Encrypted variables drop their value (only `secretKeyId` survives) so the export can be safely pasted elsewhere \u2014 re-attach secrets locally on the receiving side.",
713
+ inputSchema: import_zod4.z.object({ name: import_zod4.z.string() }),
714
+ async handler(input, ctx) {
715
+ const state = await ctx.workspace.read();
716
+ const env = state.synced.environments.items[input.name];
717
+ if (!env) return { ok: false, error: "environment not found" };
718
+ const payload = {
719
+ apicircleEnvironment: 1,
720
+ name: env.name,
721
+ variables: env.variables.map(
722
+ (v) => v.encrypted && v.secretKeyId ? { key: v.key, encrypted: true, secretKeyId: v.secretKeyId } : { key: v.key, value: v.value, encrypted: false }
723
+ )
724
+ };
725
+ return { ok: true, json: JSON.stringify(payload, null, 2) };
726
+ }
727
+ };
728
+ var environmentImportTool = {
729
+ name: "environment.import",
730
+ description: "Import an environment from the JSON shape produced by `environment.export`. When a target with the same name exists, pass `overwrite: true` to replace it, otherwise the import is rejected.",
731
+ inputSchema: import_zod4.z.object({
732
+ json: import_zod4.z.string().min(1),
733
+ overwrite: import_zod4.z.boolean().default(false)
734
+ }),
735
+ async handler(input, ctx) {
736
+ let parsed;
737
+ try {
738
+ parsed = JSON.parse(input.json);
739
+ } catch {
740
+ return { ok: false, error: "invalid JSON" };
741
+ }
742
+ const obj = parsed;
743
+ if (obj.apicircleEnvironment !== 1 || typeof obj.name !== "string" || !Array.isArray(obj.variables)) {
744
+ return { ok: false, error: "unsupported export shape" };
745
+ }
746
+ const state = await ctx.workspace.read();
747
+ if (state.synced.environments.items[obj.name] && !input.overwrite) {
748
+ return {
749
+ ok: false,
750
+ error: "environment already exists; pass overwrite:true"
751
+ };
752
+ }
753
+ const env = {
754
+ name: obj.name,
755
+ variables: obj.variables.map(
756
+ (v) => v.encrypted ? { key: v.key, value: "", encrypted: true, secretKeyId: v.secretKeyId } : { key: v.key, value: v.value, encrypted: false }
757
+ )
758
+ };
759
+ const out = await ctx.workspace.apply({ kind: "environment.upsert", environment: env });
760
+ return { ok: true, name: env.name, changedIds: out.changedIds };
761
+ }
762
+ };
763
+ var PLAN_STEP = import_zod4.z.object({
764
+ requestId: import_zod4.z.string(),
765
+ linkedWorkspaceId: import_zod4.z.string().optional()
766
+ });
767
+ var planCreateTool = {
768
+ name: "plan.create",
769
+ description: "Create a new execution plan (sequence of request steps).",
770
+ inputSchema: import_zod4.z.object({
771
+ name: import_zod4.z.string().default("New plan"),
772
+ steps: import_zod4.z.array(PLAN_STEP).default([]),
773
+ envPriorityOrder: import_zod4.z.array(import_zod4.z.string()).default([])
774
+ }),
775
+ async handler(input, ctx) {
776
+ const id = (0, import_shared2.generateId)();
777
+ const now = (/* @__PURE__ */ new Date()).toISOString();
778
+ const plan = {
779
+ id,
780
+ name: input.name,
781
+ steps: input.steps,
782
+ envPriorityOrder: input.envPriorityOrder,
783
+ createdAt: now,
784
+ updatedAt: now
785
+ };
786
+ const out = await ctx.workspace.apply({ kind: "plan.upsert", plan });
787
+ return { id, changedIds: out.changedIds };
788
+ }
789
+ };
790
+ var planReadTool = {
791
+ name: "plan.read",
792
+ description: "Read a plan by id, or list all plans when no id is provided.",
793
+ inputSchema: import_zod4.z.object({ id: import_zod4.z.string().optional() }),
794
+ async handler(input, ctx) {
795
+ const state = await ctx.workspace.read();
796
+ if (input.id) {
797
+ const plan = state.local.executionPlans[input.id];
798
+ return plan ? { found: true, plan } : { found: false };
799
+ }
800
+ return {
801
+ count: Object.keys(state.local.executionPlans).length,
802
+ plans: Object.values(state.local.executionPlans)
803
+ };
804
+ }
805
+ };
806
+ var planUpdateTool = {
807
+ name: "plan.update",
808
+ description: "Patch fields on an existing plan.",
809
+ inputSchema: import_zod4.z.object({
810
+ id: import_zod4.z.string(),
811
+ patch: import_zod4.z.object({
812
+ name: import_zod4.z.string().optional(),
813
+ steps: import_zod4.z.array(PLAN_STEP).optional(),
814
+ envPriorityOrder: import_zod4.z.array(import_zod4.z.string()).optional()
815
+ }).strict()
816
+ }),
817
+ async handler(input, ctx) {
818
+ const state = await ctx.workspace.read();
819
+ const existing = state.local.executionPlans[input.id];
820
+ if (!existing) return { changedIds: [] };
821
+ const merged = {
822
+ ...existing,
823
+ ...input.patch,
824
+ id: existing.id,
825
+ createdAt: existing.createdAt,
826
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
827
+ };
828
+ const out = await ctx.workspace.apply({ kind: "plan.upsert", plan: merged });
829
+ return { changedIds: out.changedIds };
830
+ }
831
+ };
832
+ var planDeleteTool = {
833
+ name: "plan.delete",
834
+ description: "Delete a plan by id. Drops history rows referencing this plan.",
835
+ inputSchema: import_zod4.z.object({ id: import_zod4.z.string() }),
836
+ async handler(input, ctx) {
837
+ const out = await ctx.workspace.apply({ kind: "plan.delete", id: input.id });
838
+ return { changedIds: out.changedIds };
839
+ }
840
+ };
841
+ var planAddStepTool = {
842
+ name: "plan.add_step",
843
+ description: "Append a step to an execution plan. Optional `position` (0-based) inserts at that index instead.",
844
+ inputSchema: import_zod4.z.object({
845
+ planId: import_zod4.z.string(),
846
+ requestId: import_zod4.z.string(),
847
+ linkedWorkspaceId: import_zod4.z.string().optional(),
848
+ position: import_zod4.z.number().int().nonnegative().optional()
849
+ }),
850
+ async handler(input, ctx) {
851
+ const state = await ctx.workspace.read();
852
+ const plan = state.local.executionPlans[input.planId];
853
+ if (!plan) return { ok: false, error: "plan not found" };
854
+ const step = {
855
+ requestId: input.requestId,
856
+ ...input.linkedWorkspaceId ? { linkedWorkspaceId: input.linkedWorkspaceId } : {}
857
+ };
858
+ const steps = [...plan.steps];
859
+ if (input.position !== void 0 && input.position <= steps.length) {
860
+ steps.splice(input.position, 0, step);
861
+ } else {
862
+ steps.push(step);
863
+ }
864
+ const out = await ctx.workspace.apply({
865
+ kind: "plan.upsert",
866
+ plan: { ...plan, steps, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
867
+ });
868
+ return { ok: true, changedIds: out.changedIds };
869
+ }
870
+ };
871
+ var planRemoveStepTool = {
872
+ name: "plan.remove_step",
873
+ description: "Remove a step from a plan by 0-based index.",
874
+ inputSchema: import_zod4.z.object({
875
+ planId: import_zod4.z.string(),
876
+ index: import_zod4.z.number().int().nonnegative()
877
+ }),
878
+ async handler(input, ctx) {
879
+ const state = await ctx.workspace.read();
880
+ const plan = state.local.executionPlans[input.planId];
881
+ if (!plan) return { ok: false, error: "plan not found" };
882
+ if (input.index >= plan.steps.length) {
883
+ return { ok: false, error: "index out of range" };
884
+ }
885
+ const steps = plan.steps.filter((_, i) => i !== input.index);
886
+ const out = await ctx.workspace.apply({
887
+ kind: "plan.upsert",
888
+ plan: { ...plan, steps, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
889
+ });
890
+ return { ok: true, changedIds: out.changedIds };
891
+ }
892
+ };
893
+ var planReorderStepsTool = {
894
+ name: "plan.reorder_steps",
895
+ description: "Replace the plan steps with a new permutation. The supplied indices must reference valid current step indices.",
896
+ inputSchema: import_zod4.z.object({
897
+ planId: import_zod4.z.string(),
898
+ order: import_zod4.z.array(import_zod4.z.number().int().nonnegative())
899
+ }),
900
+ async handler(input, ctx) {
901
+ const state = await ctx.workspace.read();
902
+ const plan = state.local.executionPlans[input.planId];
903
+ if (!plan) return { ok: false, error: "plan not found" };
904
+ if (input.order.length !== plan.steps.length) {
905
+ return { ok: false, error: "order length must equal step count" };
906
+ }
907
+ const order = input.order;
908
+ const seen = new Set(order);
909
+ if (seen.size !== order.length || order.some((i) => i >= plan.steps.length)) {
910
+ return { ok: false, error: "order must be a permutation of step indices" };
911
+ }
912
+ const steps = order.map((i) => plan.steps[i]);
913
+ const out = await ctx.workspace.apply({
914
+ kind: "plan.upsert",
915
+ plan: { ...plan, steps, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
916
+ });
917
+ return { ok: true, changedIds: out.changedIds };
918
+ }
919
+ };
920
+ var PLAN_VARIABLE = import_zod4.z.object({ key: import_zod4.z.string(), value: import_zod4.z.string() });
921
+ var planSetVariablesTool = {
922
+ name: "plan.set_variables",
923
+ description: "Replace the plan-scoped variables. These live highest-priority during plan runs (above environment vars, below context vars).",
924
+ inputSchema: import_zod4.z.object({
925
+ planId: import_zod4.z.string(),
926
+ variables: import_zod4.z.array(PLAN_VARIABLE)
927
+ }),
928
+ async handler(input, ctx) {
929
+ const state = await ctx.workspace.read();
930
+ const plan = state.local.executionPlans[input.planId];
931
+ if (!plan) return { ok: false, error: "plan not found" };
932
+ const out = await ctx.workspace.apply({
933
+ kind: "plan.upsert",
934
+ plan: { ...plan, variables: input.variables, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
935
+ });
936
+ return { ok: true, changedIds: out.changedIds };
937
+ }
938
+ };
939
+ var planRunTool = {
940
+ name: "plan.run",
941
+ description: "Run a plan headlessly (server-side). Currently returns a not-implemented marker \u2014 full execution requires the Desktop or browser runtime which the MCP host does not own. The Desktop integration overrides this tool with a real runner.",
942
+ inputSchema: import_zod4.z.object({
943
+ id: import_zod4.z.string(),
944
+ withAssertions: import_zod4.z.boolean().default(true)
945
+ }),
946
+ async handler(input, ctx) {
947
+ const state = await ctx.workspace.read();
948
+ const plan = state.local.executionPlans[input.id];
949
+ if (!plan) return { ok: false, error: "plan not found" };
950
+ return {
951
+ ok: false,
952
+ error: "Plan execution is only available in the Desktop app (or once a hosted runtime is wired). The plan exists and is ready to run from the UI.",
953
+ planId: plan.id,
954
+ stepCount: plan.steps.length
955
+ };
956
+ }
957
+ };
958
+ var ASSERTION = import_zod4.z.object({
959
+ id: import_zod4.z.string().optional(),
960
+ kind: import_zod4.z.enum(["status", "header", "json-path", "duration"]),
961
+ op: import_zod4.z.enum(["equals", "not-equals", "contains", "lt", "gt", "matches"]),
962
+ target: import_zod4.z.string().optional(),
963
+ expected: import_zod4.z.union([import_zod4.z.string(), import_zod4.z.number()])
964
+ });
965
+ var assertionCreateTool = {
966
+ name: "assertion.create",
967
+ description: "Add an assertion to a request.",
968
+ inputSchema: import_zod4.z.object({
969
+ requestId: import_zod4.z.string(),
970
+ assertion: ASSERTION
971
+ }),
972
+ async handler(input, ctx) {
973
+ const assertion = {
974
+ ...input.assertion,
975
+ id: input.assertion.id ?? (0, import_shared2.generateId)()
976
+ };
977
+ const out = await ctx.workspace.apply({
978
+ kind: "assertion.upsert",
979
+ requestId: input.requestId,
980
+ assertion
981
+ });
982
+ return { id: assertion.id, changedIds: out.changedIds };
983
+ }
984
+ };
985
+ var assertionReadTool = {
986
+ name: "assertion.read",
987
+ description: "List assertions for a request, or fetch a single assertion by id.",
988
+ inputSchema: import_zod4.z.object({
989
+ requestId: import_zod4.z.string(),
990
+ assertionId: import_zod4.z.string().optional()
991
+ }),
992
+ async handler(input, ctx) {
993
+ const state = await ctx.workspace.read();
994
+ const req = state.synced.collections.requests[input.requestId];
995
+ if (!req) return { found: false };
996
+ if (input.assertionId) {
997
+ const a = req.assertions.find((x) => x.id === input.assertionId);
998
+ return a ? { found: true, assertion: a } : { found: false };
999
+ }
1000
+ return { count: req.assertions.length, assertions: req.assertions };
1001
+ }
1002
+ };
1003
+ var assertionUpdateTool = {
1004
+ name: "assertion.update",
1005
+ description: "Replace an existing assertion (matched by `assertion.id`).",
1006
+ inputSchema: import_zod4.z.object({
1007
+ requestId: import_zod4.z.string(),
1008
+ assertion: ASSERTION.required({ id: true })
1009
+ }),
1010
+ async handler(input, ctx) {
1011
+ const out = await ctx.workspace.apply({
1012
+ kind: "assertion.upsert",
1013
+ requestId: input.requestId,
1014
+ assertion: input.assertion
1015
+ });
1016
+ return { changedIds: out.changedIds };
1017
+ }
1018
+ };
1019
+ var assertionDeleteTool = {
1020
+ name: "assertion.delete",
1021
+ description: "Remove an assertion from a request.",
1022
+ inputSchema: import_zod4.z.object({
1023
+ requestId: import_zod4.z.string(),
1024
+ assertionId: import_zod4.z.string()
1025
+ }),
1026
+ async handler(input, ctx) {
1027
+ const out = await ctx.workspace.apply({
1028
+ kind: "assertion.delete",
1029
+ requestId: input.requestId,
1030
+ assertionId: input.assertionId
1031
+ });
1032
+ return { changedIds: out.changedIds };
1033
+ }
1034
+ };
1035
+ var workspaceReadTool = {
1036
+ name: "workspace.read",
1037
+ description: "Return the full `{ synced, local }` workspace pair. Use sparingly \u2014 entity-specific tools are more efficient for small reads.",
1038
+ inputSchema: import_zod4.z.object({}),
1039
+ async handler(_input, ctx) {
1040
+ return await ctx.workspace.read();
1041
+ }
1042
+ };
1043
+ var workspaceWriteTool = {
1044
+ name: "workspace.write",
1045
+ description: "Bulk-replace the workspace. Pass `synced` and/or `local` to overwrite either side. Mutating tools are preferred \u2014 this is for full-doc imports/exports.",
1046
+ inputSchema: import_zod4.z.object({
1047
+ synced: import_zod4.z.unknown().optional(),
1048
+ local: import_zod4.z.unknown().optional()
1049
+ }),
1050
+ async handler(input, ctx) {
1051
+ const next = await ctx.workspace.write({
1052
+ synced: input.synced,
1053
+ local: input.local
1054
+ });
1055
+ return { workspaceId: next.synced.workspaceId, ok: true };
1056
+ }
1057
+ };
1058
+
1059
+ // src/tools/history.ts
1060
+ var import_zod5 = require("zod");
1061
+ var historyListRunsTool = {
1062
+ name: "history.list_runs",
1063
+ description: "List request-run history rows in reverse-chronological order. Filter by `requestId`, `ok` (success/failure), or `since`/`until` ISO timestamps. `limit` caps the result set; default 100.",
1064
+ inputSchema: import_zod5.z.object({
1065
+ requestId: import_zod5.z.string().optional(),
1066
+ ok: import_zod5.z.boolean().optional(),
1067
+ since: import_zod5.z.string().optional(),
1068
+ until: import_zod5.z.string().optional(),
1069
+ limit: import_zod5.z.number().int().positive().max(500).default(100)
1070
+ }),
1071
+ async handler(input, ctx) {
1072
+ const state = await ctx.workspace.read();
1073
+ const sinceMs = input.since ? Date.parse(input.since) : -Infinity;
1074
+ const untilMs = input.until ? Date.parse(input.until) : Infinity;
1075
+ const filtered = state.local.history.requestRuns.filter((r) => {
1076
+ if (input.requestId && r.requestId !== input.requestId) return false;
1077
+ if (input.ok !== void 0 && r.ok !== input.ok) return false;
1078
+ const t = Date.parse(r.startedAt);
1079
+ if (!Number.isFinite(t)) return true;
1080
+ return t >= sinceMs && t <= untilMs;
1081
+ });
1082
+ const sorted = [...filtered].sort((a, b) => b.startedAt.localeCompare(a.startedAt));
1083
+ const limited = sorted.slice(0, input.limit);
1084
+ return {
1085
+ total: filtered.length,
1086
+ returned: limited.length,
1087
+ runs: limited.map((r) => ({
1088
+ id: r.id,
1089
+ requestId: r.requestId,
1090
+ method: r.method,
1091
+ url: r.url,
1092
+ status: r.status,
1093
+ ok: r.ok,
1094
+ startedAt: r.startedAt,
1095
+ durationMs: r.durationMs
1096
+ }))
1097
+ };
1098
+ }
1099
+ };
1100
+ var historyGetRunTool = {
1101
+ name: "history.get_run",
1102
+ description: "Fetch a single history row in full (headers, body preview, assertion results).",
1103
+ inputSchema: import_zod5.z.object({ id: import_zod5.z.string() }),
1104
+ async handler(input, ctx) {
1105
+ const state = await ctx.workspace.read();
1106
+ const run = state.local.history.requestRuns.find((r) => r.id === input.id);
1107
+ if (!run) return { found: false };
1108
+ return { found: true, run };
1109
+ }
1110
+ };
1111
+ var historyDeleteRunTool = {
1112
+ name: "history.delete_run",
1113
+ description: "Delete a single request-run row by id.",
1114
+ inputSchema: import_zod5.z.object({ id: import_zod5.z.string() }),
1115
+ async handler(input, ctx) {
1116
+ const out = await ctx.workspace.apply({ kind: "history.delete_run", runId: input.id });
1117
+ return { deleted: out.changedIds.length, changedIds: out.changedIds };
1118
+ }
1119
+ };
1120
+ var historyPurgeTool = {
1121
+ name: "history.purge_by_age",
1122
+ description: "Drop every request-run + plan-run older than `olderThanDays` days. Pass 0 to clear all history.",
1123
+ inputSchema: import_zod5.z.object({
1124
+ olderThanDays: import_zod5.z.number().nonnegative()
1125
+ }),
1126
+ async handler(input, ctx) {
1127
+ const olderThanMs = input.olderThanDays * 24 * 60 * 60 * 1e3;
1128
+ const out = await ctx.workspace.apply({ kind: "history.purge", olderThanMs });
1129
+ return { purgedCount: out.changedIds.length, changedIds: out.changedIds };
1130
+ }
1131
+ };
1132
+
1133
+ // src/tools/codebase.ts
1134
+ var import_zod6 = require("zod");
1135
+ var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "options", "head"];
1136
+ var codebaseExtractCollectionTool = {
1137
+ name: "codebase.extract_collection",
1138
+ description: "Scan source code for HTTP route definitions (Express, FastAPI, NestJS, Spring) and return candidate requests for the user to confirm before import.",
1139
+ inputSchema: import_zod6.z.object({
1140
+ source: import_zod6.z.string().min(1),
1141
+ /** Hint to limit which framework patterns to apply. Empty = try all. */
1142
+ frameworks: import_zod6.z.array(import_zod6.z.enum(["express", "fastapi", "nest", "spring"])).default([])
1143
+ }),
1144
+ async handler(input) {
1145
+ const enabled = new Set(
1146
+ input.frameworks.length ? input.frameworks : ["express", "fastapi", "nest", "spring"]
1147
+ );
1148
+ const candidates = [];
1149
+ const lines = input.source.split(/\r?\n/);
1150
+ for (let i = 0; i < lines.length; i++) {
1151
+ const line = lines[i];
1152
+ if (enabled.has("express")) {
1153
+ const m = /(?:^|[\s\b])(?:app|router)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/i.exec(
1154
+ line
1155
+ );
1156
+ if (m) {
1157
+ candidates.push({
1158
+ method: m[1].toUpperCase(),
1159
+ path: m[2],
1160
+ framework: "express",
1161
+ line: i + 1
1162
+ });
1163
+ continue;
1164
+ }
1165
+ }
1166
+ if (enabled.has("fastapi")) {
1167
+ const m = /@(?:app|router)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/i.exec(
1168
+ line
1169
+ );
1170
+ if (m) {
1171
+ candidates.push({
1172
+ method: m[1].toUpperCase(),
1173
+ path: m[2],
1174
+ framework: "fastapi",
1175
+ line: i + 1
1176
+ });
1177
+ continue;
1178
+ }
1179
+ }
1180
+ if (enabled.has("nest")) {
1181
+ const m = /@(Get|Post|Put|Patch|Delete|Options|Head)\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/i.exec(
1182
+ line
1183
+ );
1184
+ if (m) {
1185
+ candidates.push({
1186
+ method: m[1].toUpperCase(),
1187
+ path: m[2] || "/",
1188
+ framework: "nest",
1189
+ line: i + 1
1190
+ });
1191
+ continue;
1192
+ }
1193
+ }
1194
+ if (enabled.has("spring")) {
1195
+ const verb = /@(Get|Post|Put|Patch|Delete)Mapping\s*\(?\s*['"`]?([^'"`)\s]*)/i.exec(line);
1196
+ if (verb && HTTP_METHODS.includes(verb[1].toLowerCase())) {
1197
+ candidates.push({
1198
+ method: verb[1].toUpperCase(),
1199
+ path: verb[2] || "/",
1200
+ framework: "spring",
1201
+ line: i + 1
1202
+ });
1203
+ continue;
1204
+ }
1205
+ const generic = /@RequestMapping\s*\(\s*[^)]*method\s*=\s*RequestMethod\.(GET|POST|PUT|PATCH|DELETE)[^)]*?(?:value|path)?\s*=?\s*['"`]?([^'"`)\s,]*)/i.exec(
1206
+ line
1207
+ );
1208
+ if (generic) {
1209
+ candidates.push({
1210
+ method: generic[1].toUpperCase(),
1211
+ path: generic[2] || "/",
1212
+ framework: "spring",
1213
+ line: i + 1
1214
+ });
1215
+ }
1216
+ }
1217
+ }
1218
+ return { count: candidates.length, candidates };
1219
+ }
1220
+ };
1221
+
1222
+ // src/tools/prompt.ts
1223
+ var import_zod7 = require("zod");
1224
+ var import_shared3 = require("@apicircle/shared");
1225
+ var promptCreateEnvironmentTool = {
1226
+ name: "prompt.create_environment",
1227
+ description: "Create a new environment from an LLM-shaped JSON envelope. The model produces { name, variables: [{ key, value, encrypted }] }; this tool validates and persists it.",
1228
+ inputSchema: import_zod7.z.object({
1229
+ name: import_zod7.z.string(),
1230
+ variables: import_zod7.z.array(
1231
+ import_zod7.z.object({
1232
+ key: import_zod7.z.string(),
1233
+ value: import_zod7.z.string(),
1234
+ encrypted: import_zod7.z.boolean().default(false)
1235
+ })
1236
+ )
1237
+ }),
1238
+ async handler(input, ctx) {
1239
+ const env = { name: input.name, variables: input.variables };
1240
+ const out = await ctx.workspace.apply({ kind: "environment.upsert", environment: env });
1241
+ return { name: env.name, changedIds: out.changedIds };
1242
+ }
1243
+ };
1244
+ var promptCreateAssertionTool = {
1245
+ name: "prompt.create_assertion",
1246
+ description: 'Add an assertion to a request from an LLM-shaped JSON envelope. Useful when the user asks "assert that the response status is 200 and body.id matches".',
1247
+ inputSchema: import_zod7.z.object({
1248
+ requestId: import_zod7.z.string(),
1249
+ assertion: import_zod7.z.object({
1250
+ kind: import_zod7.z.enum(["status", "header", "json-path", "duration"]),
1251
+ op: import_zod7.z.enum(["equals", "not-equals", "contains", "lt", "gt", "matches"]),
1252
+ target: import_zod7.z.string().optional(),
1253
+ expected: import_zod7.z.union([import_zod7.z.string(), import_zod7.z.number()])
1254
+ })
1255
+ }),
1256
+ async handler(input, ctx) {
1257
+ const assertion = {
1258
+ ...input.assertion,
1259
+ id: (0, import_shared3.generateId)()
1260
+ };
1261
+ const out = await ctx.workspace.apply({
1262
+ kind: "assertion.upsert",
1263
+ requestId: input.requestId,
1264
+ assertion
1265
+ });
1266
+ return { id: assertion.id, changedIds: out.changedIds };
1267
+ }
1268
+ };
1269
+ var promptCreatePlanTool = {
1270
+ name: "prompt.create_plan",
1271
+ description: "Create an execution plan from an LLM-shaped JSON envelope. The model produces { name, stepRequestIds: [...] } and the tool validates that each id exists in the workspace before persisting.",
1272
+ inputSchema: import_zod7.z.object({
1273
+ name: import_zod7.z.string(),
1274
+ stepRequestIds: import_zod7.z.array(import_zod7.z.string()).default([]),
1275
+ envPriorityOrder: import_zod7.z.array(import_zod7.z.string()).default([])
1276
+ }),
1277
+ async handler(input, ctx) {
1278
+ const state = await ctx.workspace.read();
1279
+ const missing = [];
1280
+ for (const rid of input.stepRequestIds) {
1281
+ if (!state.synced.collections.requests[rid]) missing.push(rid);
1282
+ }
1283
+ if (missing.length) {
1284
+ return {
1285
+ ok: false,
1286
+ error: `Unknown request ids: ${missing.join(", ")}`,
1287
+ missing
1288
+ };
1289
+ }
1290
+ const id = (0, import_shared3.generateId)();
1291
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1292
+ const plan = {
1293
+ id,
1294
+ name: input.name,
1295
+ steps: input.stepRequestIds.map((requestId) => ({ requestId })),
1296
+ envPriorityOrder: input.envPriorityOrder,
1297
+ createdAt: now,
1298
+ updatedAt: now
1299
+ };
1300
+ const out = await ctx.workspace.apply({ kind: "plan.upsert", plan });
1301
+ return { ok: true, id, changedIds: out.changedIds };
1302
+ }
1303
+ };
1304
+ var HTTP_METHOD2 = import_zod7.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
1305
+ var HEADER_OR_QUERY = import_zod7.z.object({
1306
+ key: import_zod7.z.string(),
1307
+ value: import_zod7.z.string(),
1308
+ enabled: import_zod7.z.boolean().default(true)
1309
+ });
1310
+ var REQUEST_BODY = import_zod7.z.object({
1311
+ type: import_zod7.z.enum(["none", "json", "text", "xml", "graphql", "urlencoded"]).default("none"),
1312
+ content: import_zod7.z.string().default(""),
1313
+ variables: import_zod7.z.string().optional()
1314
+ });
1315
+ var PROMPT_AUTH = import_zod7.z.discriminatedUnion("type", [
1316
+ import_zod7.z.object({ type: import_zod7.z.literal("none") }),
1317
+ import_zod7.z.object({ type: import_zod7.z.literal("inherit") }),
1318
+ import_zod7.z.object({ type: import_zod7.z.literal("bearer"), token: import_zod7.z.string().default("") }),
1319
+ import_zod7.z.object({
1320
+ type: import_zod7.z.literal("basic"),
1321
+ username: import_zod7.z.string().default(""),
1322
+ password: import_zod7.z.string().default("")
1323
+ }),
1324
+ import_zod7.z.object({
1325
+ type: import_zod7.z.literal("api-key"),
1326
+ key: import_zod7.z.string().default(""),
1327
+ value: import_zod7.z.string().default(""),
1328
+ addTo: import_zod7.z.enum(["header", "query", "cookie"]).default("header")
1329
+ }),
1330
+ import_zod7.z.object({
1331
+ type: import_zod7.z.literal("custom-header"),
1332
+ key: import_zod7.z.string().default(""),
1333
+ value: import_zod7.z.string().default("")
1334
+ })
1335
+ ]);
1336
+ var PROMPT_ASSERTION = import_zod7.z.object({
1337
+ kind: import_zod7.z.enum(["status", "header", "json-path", "duration"]),
1338
+ op: import_zod7.z.enum(["equals", "not-equals", "contains", "lt", "gt", "matches"]),
1339
+ target: import_zod7.z.string().optional(),
1340
+ expected: import_zod7.z.union([import_zod7.z.string(), import_zod7.z.number()])
1341
+ });
1342
+ var ENDPOINT_RESPONSE = import_zod7.z.object({
1343
+ status: import_zod7.z.number().int().min(100).max(599).default(200),
1344
+ jsonBody: import_zod7.z.string().default("{}"),
1345
+ contentType: import_zod7.z.string().default("application/json")
1346
+ });
1347
+ var VALIDATION_RULE_NL = import_zod7.z.object({
1348
+ kind: import_zod7.z.enum([
1349
+ "header-required",
1350
+ "header-equals",
1351
+ "header-matches",
1352
+ "query-required",
1353
+ "query-equals",
1354
+ "query-matches",
1355
+ "cookie-required",
1356
+ "body-required",
1357
+ "content-type-equals"
1358
+ ]),
1359
+ target: import_zod7.z.string().default(""),
1360
+ expected: import_zod7.z.string().optional(),
1361
+ message: import_zod7.z.string().optional(),
1362
+ enabled: import_zod7.z.boolean().default(true),
1363
+ failResponse: import_zod7.z.object({
1364
+ status: import_zod7.z.number().int().min(100).max(599).default(400),
1365
+ jsonBody: import_zod7.z.string().default('{"error":"validation failed"}')
1366
+ }).default({})
1367
+ });
1368
+ var CONDITION_CLAUSE_NL = import_zod7.z.object({
1369
+ scope: import_zod7.z.enum(["query", "pathParam", "header", "cookie", "body-json-path"]),
1370
+ target: import_zod7.z.string(),
1371
+ op: import_zod7.z.enum(["equals", "not-equals", "matches", "gt", "lt", "gte", "lte", "present", "absent"]),
1372
+ value: import_zod7.z.string().optional()
1373
+ });
1374
+ var RESPONSE_RULE_NL = import_zod7.z.object({
1375
+ name: import_zod7.z.string(),
1376
+ enabled: import_zod7.z.boolean().default(true),
1377
+ when: import_zod7.z.array(CONDITION_CLAUSE_NL).default([]),
1378
+ response: import_zod7.z.object({
1379
+ status: import_zod7.z.number().int().min(100).max(599).default(200),
1380
+ jsonBody: import_zod7.z.string().default("{}")
1381
+ }).default({})
1382
+ });
1383
+ var MULTIPLIER_NL = import_zod7.z.object({
1384
+ name: import_zod7.z.string().optional(),
1385
+ source: import_zod7.z.object({
1386
+ kind: import_zod7.z.enum(["query", "pathParam", "header", "body-json-path"]),
1387
+ key: import_zod7.z.string()
1388
+ }),
1389
+ targetJsonPath: import_zod7.z.string(),
1390
+ defaultCount: import_zod7.z.number().int().nonnegative().default(0),
1391
+ min: import_zod7.z.number().int().nonnegative().optional(),
1392
+ max: import_zod7.z.number().int().nonnegative().optional()
1393
+ });
1394
+ var ENDPOINT_INPUT = import_zod7.z.object({
1395
+ method: HTTP_METHOD2,
1396
+ pathPattern: import_zod7.z.string().min(1),
1397
+ name: import_zod7.z.string().optional(),
1398
+ description: import_zod7.z.string().optional(),
1399
+ response: ENDPOINT_RESPONSE.optional(),
1400
+ validationRules: import_zod7.z.array(VALIDATION_RULE_NL).default([]),
1401
+ responseRules: import_zod7.z.array(RESPONSE_RULE_NL).default([]),
1402
+ multipliers: import_zod7.z.array(MULTIPLIER_NL).default([])
1403
+ });
1404
+ function buildRequestBody(input) {
1405
+ if (!input) return { type: "none", content: "" };
1406
+ const body = { type: input.type, content: input.content };
1407
+ if (input.variables !== void 0 && input.type === "graphql") {
1408
+ body.variables = input.variables;
1409
+ }
1410
+ return body;
1411
+ }
1412
+ function buildEndpoint(input) {
1413
+ const response = input.response ?? {
1414
+ status: 200,
1415
+ jsonBody: "{}",
1416
+ contentType: "application/json"
1417
+ };
1418
+ const headers = [{ key: "Content-Type", value: response.contentType, enabled: true }];
1419
+ const defaultResponse = {
1420
+ ...(0, import_shared3.makeDefaultMockResponse)(),
1421
+ status: response.status,
1422
+ headers,
1423
+ body: { type: "json", content: response.jsonBody }
1424
+ };
1425
+ const validationRules = input.validationRules ?? [];
1426
+ const responseRules = input.responseRules ?? [];
1427
+ const multipliers = input.multipliers ?? [];
1428
+ if (multipliers.length > 0) {
1429
+ defaultResponse.multipliers = multipliers.map((m) => ({
1430
+ id: (0, import_shared3.generateId)(),
1431
+ name: m.name,
1432
+ source: { kind: m.source.kind, key: m.source.key },
1433
+ targetJsonPath: m.targetJsonPath,
1434
+ defaultCount: m.defaultCount,
1435
+ min: m.min,
1436
+ max: m.max
1437
+ }));
1438
+ }
1439
+ return {
1440
+ id: (0, import_shared3.generateId)(),
1441
+ name: input.name ?? `${input.method} ${input.pathPattern}`,
1442
+ method: input.method,
1443
+ pathPattern: input.pathPattern,
1444
+ description: input.description,
1445
+ requestSchema: (0, import_shared3.makeDefaultRequestSchema)(),
1446
+ requestValidation: validationRules.map((r) => ({
1447
+ id: (0, import_shared3.generateId)(),
1448
+ kind: r.kind,
1449
+ target: r.target,
1450
+ expected: r.expected,
1451
+ message: r.message,
1452
+ enabled: r.enabled,
1453
+ failResponse: {
1454
+ status: r.failResponse.status,
1455
+ headers: [{ key: "Content-Type", value: "application/json", enabled: true }],
1456
+ body: { type: "json", content: r.failResponse.jsonBody }
1457
+ }
1458
+ })),
1459
+ responseRules: responseRules.map((r) => ({
1460
+ id: (0, import_shared3.generateId)(),
1461
+ name: r.name,
1462
+ enabled: r.enabled,
1463
+ when: (r.when ?? []).map((c) => ({
1464
+ id: (0, import_shared3.generateId)(),
1465
+ scope: c.scope,
1466
+ target: c.target,
1467
+ op: c.op,
1468
+ value: c.value
1469
+ })),
1470
+ response: {
1471
+ status: r.response.status,
1472
+ headers: [{ key: "Content-Type", value: "application/json", enabled: true }],
1473
+ body: { type: "json", content: r.response.jsonBody }
1474
+ }
1475
+ })),
1476
+ defaultResponse
1477
+ };
1478
+ }
1479
+ function patchEndpoint(mock, endpointId, patcher) {
1480
+ const idx = mock.endpoints.findIndex((e) => e.id === endpointId);
1481
+ if (idx === -1) return null;
1482
+ const nextEndpoints = [...mock.endpoints];
1483
+ nextEndpoints[idx] = patcher(mock.endpoints[idx]);
1484
+ const source = mock.source.kind === "manual" ? { kind: "manual", endpoints: nextEndpoints } : mock.source;
1485
+ return {
1486
+ ...mock,
1487
+ source,
1488
+ endpoints: nextEndpoints,
1489
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1490
+ };
1491
+ }
1492
+ var promptCreateRequestTool = {
1493
+ name: "prompt.create_request",
1494
+ description: "Create a fully-shaped request from an LLM-shaped JSON envelope: method, url, headers, query params, body, auth, and inline assertions. The model produces a flat object; this tool generates the request id, normalizes auth (defaults to `inherit` so folder auth wins), and persists.",
1495
+ inputSchema: import_zod7.z.object({
1496
+ name: import_zod7.z.string().default("New request"),
1497
+ method: HTTP_METHOD2.default("GET"),
1498
+ url: import_zod7.z.string().default(""),
1499
+ folderId: import_zod7.z.string().nullable().optional(),
1500
+ headers: import_zod7.z.array(HEADER_OR_QUERY).default([]),
1501
+ queryParams: import_zod7.z.array(HEADER_OR_QUERY).default([]),
1502
+ pathParams: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.string()).optional(),
1503
+ body: REQUEST_BODY.optional(),
1504
+ auth: PROMPT_AUTH.optional(),
1505
+ assertions: import_zod7.z.array(PROMPT_ASSERTION).default([])
1506
+ }),
1507
+ async handler(input, ctx) {
1508
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1509
+ const auth = input.auth ?? { type: "inherit" };
1510
+ const assertions = input.assertions ?? [];
1511
+ const request = {
1512
+ id: (0, import_shared3.generateId)(),
1513
+ name: input.name ?? "New request",
1514
+ folderId: input.folderId ?? null,
1515
+ method: input.method ?? "GET",
1516
+ url: input.url ?? "",
1517
+ headers: input.headers ?? [],
1518
+ query: input.queryParams ?? [],
1519
+ pathParams: input.pathParams,
1520
+ body: buildRequestBody(input.body),
1521
+ auth,
1522
+ contextVars: [],
1523
+ extractions: [],
1524
+ assertions: assertions.map((a) => ({ ...a, id: (0, import_shared3.generateId)() })),
1525
+ createdAt: now,
1526
+ updatedAt: now
1527
+ };
1528
+ const out = await ctx.workspace.apply({ kind: "request.create", request });
1529
+ return { id: request.id, changedIds: out.changedIds };
1530
+ }
1531
+ };
1532
+ var promptUpdateRequestTool = {
1533
+ name: "prompt.update_request",
1534
+ description: "Patch an existing request from an LLM-shaped JSON envelope. Provided fields replace the existing values; omitted fields are left untouched. Arrays (headers, queryParams, assertions) are full replacements when supplied. Returns `{ ok: false, error }` when the id does not resolve.",
1535
+ inputSchema: import_zod7.z.object({
1536
+ id: import_zod7.z.string(),
1537
+ patch: import_zod7.z.object({
1538
+ name: import_zod7.z.string().optional(),
1539
+ method: HTTP_METHOD2.optional(),
1540
+ url: import_zod7.z.string().optional(),
1541
+ folderId: import_zod7.z.string().nullable().optional(),
1542
+ headers: import_zod7.z.array(HEADER_OR_QUERY).optional(),
1543
+ queryParams: import_zod7.z.array(HEADER_OR_QUERY).optional(),
1544
+ pathParams: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.string()).optional(),
1545
+ body: REQUEST_BODY.optional(),
1546
+ auth: PROMPT_AUTH.optional(),
1547
+ assertions: import_zod7.z.array(PROMPT_ASSERTION).optional()
1548
+ }).strict()
1549
+ }),
1550
+ async handler(input, ctx) {
1551
+ const state = await ctx.workspace.read();
1552
+ if (!state.synced.collections.requests[input.id]) {
1553
+ return { ok: false, error: "request not found" };
1554
+ }
1555
+ const patch = {};
1556
+ if (input.patch.name !== void 0) patch.name = input.patch.name;
1557
+ if (input.patch.method !== void 0) patch.method = input.patch.method;
1558
+ if (input.patch.url !== void 0) patch.url = input.patch.url;
1559
+ if (input.patch.folderId !== void 0) patch.folderId = input.patch.folderId ?? null;
1560
+ if (input.patch.headers !== void 0) patch.headers = input.patch.headers;
1561
+ if (input.patch.queryParams !== void 0) patch.query = input.patch.queryParams;
1562
+ if (input.patch.pathParams !== void 0) patch.pathParams = input.patch.pathParams;
1563
+ if (input.patch.body !== void 0) patch.body = buildRequestBody(input.patch.body);
1564
+ if (input.patch.auth !== void 0) patch.auth = input.patch.auth;
1565
+ if (input.patch.assertions !== void 0) {
1566
+ patch.assertions = input.patch.assertions.map((a) => ({
1567
+ ...a,
1568
+ id: (0, import_shared3.generateId)()
1569
+ }));
1570
+ }
1571
+ const out = await ctx.workspace.apply({ kind: "request.update", id: input.id, patch });
1572
+ return { ok: true, changedIds: out.changedIds };
1573
+ }
1574
+ };
1575
+ var FOLDER_TREE_NODE = import_zod7.z.lazy(
1576
+ () => import_zod7.z.object({
1577
+ name: import_zod7.z.string(),
1578
+ children: import_zod7.z.array(FOLDER_TREE_NODE).optional()
1579
+ })
1580
+ );
1581
+ var promptCreateFolderTreeTool = {
1582
+ name: "prompt.create_folder_tree",
1583
+ description: "Create a recursive folder hierarchy from an LLM-shaped JSON envelope. The model produces `{ parentId?, tree: { name, children?: [...] } }` and this tool walks the tree, generating ids and persisting one folder per node. Returns the list of created ids in pre-order.",
1584
+ inputSchema: import_zod7.z.object({
1585
+ parentId: import_zod7.z.string().nullable().optional(),
1586
+ tree: FOLDER_TREE_NODE
1587
+ }),
1588
+ async handler(input, ctx) {
1589
+ const createdIds = [];
1590
+ const allChangedIds = [];
1591
+ const walk = async (node, parentId) => {
1592
+ const folder = {
1593
+ id: (0, import_shared3.generateId)(),
1594
+ name: node.name,
1595
+ parentId
1596
+ };
1597
+ const out = await ctx.workspace.apply({ kind: "folder.create", folder });
1598
+ createdIds.push(folder.id);
1599
+ allChangedIds.push(...out.changedIds);
1600
+ for (const child of node.children ?? []) {
1601
+ await walk(child, folder.id);
1602
+ }
1603
+ };
1604
+ await walk(input.tree, input.parentId ?? null);
1605
+ return { createdIds, changedIds: allChangedIds };
1606
+ }
1607
+ };
1608
+ var promptAddPlanStepsTool = {
1609
+ name: "prompt.add_plan_steps",
1610
+ description: "Append one or more steps to an existing execution plan from an LLM-shaped JSON envelope. The model produces `{ planId, requestIds: [...] }`; each id is validated against the workspace before any step is appended. Order in the input list is preserved.",
1611
+ inputSchema: import_zod7.z.object({
1612
+ planId: import_zod7.z.string(),
1613
+ requestIds: import_zod7.z.array(import_zod7.z.string()).min(1)
1614
+ }),
1615
+ async handler(input, ctx) {
1616
+ const state = await ctx.workspace.read();
1617
+ const plan = state.local.executionPlans[input.planId];
1618
+ if (!plan) return { ok: false, error: "plan not found" };
1619
+ const requestIds = input.requestIds;
1620
+ const missing = requestIds.filter((rid) => !state.synced.collections.requests[rid]);
1621
+ if (missing.length) {
1622
+ return {
1623
+ ok: false,
1624
+ error: `Unknown request ids: ${missing.join(", ")}`,
1625
+ missing
1626
+ };
1627
+ }
1628
+ const newSteps = requestIds.map((requestId) => ({ requestId }));
1629
+ const out = await ctx.workspace.apply({
1630
+ kind: "plan.upsert",
1631
+ plan: {
1632
+ ...plan,
1633
+ steps: [...plan.steps, ...newSteps],
1634
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1635
+ }
1636
+ });
1637
+ return { ok: true, addedCount: newSteps.length, changedIds: out.changedIds };
1638
+ }
1639
+ };
1640
+ var promptSetPlanVariablesTool = {
1641
+ name: "prompt.set_plan_variables",
1642
+ description: "Replace the plan-scoped variables on an execution plan from an LLM-shaped JSON envelope. The model produces `{ planId, variables: [{ key, value }] }`. Empty array clears all plan variables.",
1643
+ inputSchema: import_zod7.z.object({
1644
+ planId: import_zod7.z.string(),
1645
+ variables: import_zod7.z.array(import_zod7.z.object({ key: import_zod7.z.string(), value: import_zod7.z.string() }))
1646
+ }),
1647
+ async handler(input, ctx) {
1648
+ const state = await ctx.workspace.read();
1649
+ const plan = state.local.executionPlans[input.planId];
1650
+ if (!plan) return { ok: false, error: "plan not found" };
1651
+ const out = await ctx.workspace.apply({
1652
+ kind: "plan.upsert",
1653
+ plan: { ...plan, variables: input.variables, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
1654
+ });
1655
+ return { ok: true, changedIds: out.changedIds };
1656
+ }
1657
+ };
1658
+ var promptCreateMockServerTool = {
1659
+ name: "prompt.create_mock_server",
1660
+ description: "Create a manual-mode mock server with optional inline endpoints from an LLM-shaped JSON envelope. The model produces `{ name, defaultPort?, endpoints: [{ method, pathPattern, name?, response?, validationRules?, responseRules?, multipliers? }] }`; this tool generates ids for the server and every endpoint / rule, then persists in one shot.",
1661
+ inputSchema: import_zod7.z.object({
1662
+ name: import_zod7.z.string().min(1),
1663
+ defaultPort: import_zod7.z.number().int().positive().nullable().optional(),
1664
+ endpoints: import_zod7.z.array(ENDPOINT_INPUT).default([])
1665
+ }),
1666
+ async handler(input, ctx) {
1667
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1668
+ const endpointInputs = input.endpoints ?? [];
1669
+ const endpoints = endpointInputs.map((e) => buildEndpoint(e));
1670
+ const mock = {
1671
+ id: (0, import_shared3.generateId)(),
1672
+ name: input.name,
1673
+ source: { kind: "manual", endpoints },
1674
+ endpoints,
1675
+ defaultPort: input.defaultPort ?? null,
1676
+ cors: { enabled: true, origins: ["*"] },
1677
+ createdAt: now,
1678
+ updatedAt: now
1679
+ };
1680
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock });
1681
+ return {
1682
+ id: mock.id,
1683
+ endpointIds: endpoints.map((e) => e.id),
1684
+ changedIds: out.changedIds
1685
+ };
1686
+ }
1687
+ };
1688
+ var promptAddMockEndpointTool = {
1689
+ name: "prompt.add_mock_endpoint",
1690
+ description: "Append a new endpoint (with optional inline validation rules, response rules, and multipliers) to an existing mock server from an LLM-shaped JSON envelope. All ids are auto-generated; the existing endpoints stay in place.",
1691
+ inputSchema: import_zod7.z.object({
1692
+ mockId: import_zod7.z.string(),
1693
+ method: HTTP_METHOD2,
1694
+ pathPattern: import_zod7.z.string().min(1),
1695
+ name: import_zod7.z.string().optional(),
1696
+ description: import_zod7.z.string().optional(),
1697
+ response: ENDPOINT_RESPONSE.optional(),
1698
+ validationRules: import_zod7.z.array(VALIDATION_RULE_NL).default([]),
1699
+ responseRules: import_zod7.z.array(RESPONSE_RULE_NL).default([]),
1700
+ multipliers: import_zod7.z.array(MULTIPLIER_NL).default([])
1701
+ }),
1702
+ async handler(input, ctx) {
1703
+ const state = await ctx.workspace.read();
1704
+ const mock = state.synced.mockServers[input.mockId];
1705
+ if (!mock) return { ok: false, error: "mock not found" };
1706
+ const endpoint = buildEndpoint(input);
1707
+ const nextEndpoints = [...mock.endpoints, endpoint];
1708
+ const source = mock.source.kind === "manual" ? { kind: "manual", endpoints: nextEndpoints } : mock.source;
1709
+ const next = {
1710
+ ...mock,
1711
+ source,
1712
+ endpoints: nextEndpoints,
1713
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1714
+ };
1715
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock: next });
1716
+ return { ok: true, endpointId: endpoint.id, changedIds: out.changedIds };
1717
+ }
1718
+ };
1719
+ var promptSetEndpointValidationRulesTool = {
1720
+ name: "prompt.set_endpoint_validation_rules",
1721
+ description: "Replace an endpoint's validation rules with an LLM-shaped list. Every rule gets a fresh id; the existing rules are dropped. Empty array clears all validation rules.",
1722
+ inputSchema: import_zod7.z.object({
1723
+ mockId: import_zod7.z.string(),
1724
+ endpointId: import_zod7.z.string(),
1725
+ rules: import_zod7.z.array(VALIDATION_RULE_NL)
1726
+ }),
1727
+ async handler(input, ctx) {
1728
+ const state = await ctx.workspace.read();
1729
+ const mock = state.synced.mockServers[input.mockId];
1730
+ if (!mock) return { ok: false, error: "mock not found" };
1731
+ const rules = input.rules;
1732
+ const next = patchEndpoint(mock, input.endpointId, (e) => ({
1733
+ ...e,
1734
+ requestValidation: rules.map((r) => ({
1735
+ id: (0, import_shared3.generateId)(),
1736
+ kind: r.kind,
1737
+ target: r.target,
1738
+ expected: r.expected,
1739
+ message: r.message,
1740
+ enabled: r.enabled,
1741
+ failResponse: {
1742
+ status: r.failResponse.status,
1743
+ headers: [{ key: "Content-Type", value: "application/json", enabled: true }],
1744
+ body: { type: "json", content: r.failResponse.jsonBody }
1745
+ }
1746
+ }))
1747
+ }));
1748
+ if (!next) return { ok: false, error: "endpoint not found" };
1749
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock: next });
1750
+ return { ok: true, changedIds: out.changedIds };
1751
+ }
1752
+ };
1753
+ var promptSetEndpointResponseRulesTool = {
1754
+ name: "prompt.set_endpoint_response_rules",
1755
+ description: "Replace an endpoint's conditional response rules with an LLM-shaped list. Rules fire in order, first match wins. Every rule + clause gets a fresh id. Empty array falls back to defaultResponse.",
1756
+ inputSchema: import_zod7.z.object({
1757
+ mockId: import_zod7.z.string(),
1758
+ endpointId: import_zod7.z.string(),
1759
+ rules: import_zod7.z.array(RESPONSE_RULE_NL)
1760
+ }),
1761
+ async handler(input, ctx) {
1762
+ const state = await ctx.workspace.read();
1763
+ const mock = state.synced.mockServers[input.mockId];
1764
+ if (!mock) return { ok: false, error: "mock not found" };
1765
+ const rules = input.rules;
1766
+ const next = patchEndpoint(mock, input.endpointId, (e) => ({
1767
+ ...e,
1768
+ responseRules: rules.map((r) => ({
1769
+ id: (0, import_shared3.generateId)(),
1770
+ name: r.name,
1771
+ enabled: r.enabled,
1772
+ when: r.when.map((c) => ({
1773
+ id: (0, import_shared3.generateId)(),
1774
+ scope: c.scope,
1775
+ target: c.target,
1776
+ op: c.op,
1777
+ value: c.value
1778
+ })),
1779
+ response: {
1780
+ status: r.response.status,
1781
+ headers: [{ key: "Content-Type", value: "application/json", enabled: true }],
1782
+ body: { type: "json", content: r.response.jsonBody }
1783
+ }
1784
+ }))
1785
+ }));
1786
+ if (!next) return { ok: false, error: "endpoint not found" };
1787
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock: next });
1788
+ return { ok: true, changedIds: out.changedIds };
1789
+ }
1790
+ };
1791
+ var promptSetEndpointMultipliersTool = {
1792
+ name: "prompt.set_endpoint_multipliers",
1793
+ description: "Replace the response multipliers on an endpoint's defaultResponse with an LLM-shaped list. Multipliers expand an array at `targetJsonPath` to a count derived from a request value. Every multiplier gets a fresh id. Empty array clears all multipliers.",
1794
+ inputSchema: import_zod7.z.object({
1795
+ mockId: import_zod7.z.string(),
1796
+ endpointId: import_zod7.z.string(),
1797
+ multipliers: import_zod7.z.array(MULTIPLIER_NL)
1798
+ }),
1799
+ async handler(input, ctx) {
1800
+ const state = await ctx.workspace.read();
1801
+ const mock = state.synced.mockServers[input.mockId];
1802
+ if (!mock) return { ok: false, error: "mock not found" };
1803
+ const multipliers = input.multipliers;
1804
+ const next = patchEndpoint(mock, input.endpointId, (e) => ({
1805
+ ...e,
1806
+ defaultResponse: {
1807
+ ...e.defaultResponse,
1808
+ multipliers: multipliers.length === 0 ? void 0 : multipliers.map((m) => ({
1809
+ id: (0, import_shared3.generateId)(),
1810
+ name: m.name,
1811
+ source: { kind: m.source.kind, key: m.source.key },
1812
+ targetJsonPath: m.targetJsonPath,
1813
+ defaultCount: m.defaultCount,
1814
+ min: m.min,
1815
+ max: m.max
1816
+ }))
1817
+ }
1818
+ }));
1819
+ if (!next) return { ok: false, error: "endpoint not found" };
1820
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock: next });
1821
+ return { ok: true, changedIds: out.changedIds };
1822
+ }
1823
+ };
1824
+
1825
+ // src/tools/mocks.ts
1826
+ var import_zod8 = require("zod");
1827
+ var import_shared4 = require("@apicircle/shared");
1828
+ var import_mock_server_core2 = require("@apicircle/mock-server-core");
1829
+ async function ingestSource(source, name) {
1830
+ const { endpoints, warnings } = await (0, import_mock_server_core2.parseSourceToEndpoints)(source);
1831
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1832
+ const mock = {
1833
+ id: (0, import_shared4.generateId)(),
1834
+ name,
1835
+ source,
1836
+ endpoints,
1837
+ defaultPort: null,
1838
+ // Off by default — match UI-created mocks. User opts in via cors.enabled=true
1839
+ // after explicitly listing origins (see CorsSection in MockServersPanel).
1840
+ cors: { enabled: false, origins: [] },
1841
+ createdAt: now,
1842
+ updatedAt: now
1843
+ };
1844
+ return { mock, warnings };
1845
+ }
1846
+ var mockCreateFromOpenApiTool = {
1847
+ name: "mock.create_from_openapi",
1848
+ description: "Create a mock server from an OpenAPI / Swagger spec (YAML or JSON).",
1849
+ inputSchema: import_zod8.z.object({
1850
+ name: import_zod8.z.string(),
1851
+ spec: import_zod8.z.string().min(1),
1852
+ format: import_zod8.z.enum(["json", "yaml"]).default("json")
1853
+ }),
1854
+ async handler(input, ctx) {
1855
+ const { mock, warnings } = await ingestSource(
1856
+ { kind: "openapi", spec: input.spec, format: input.format },
1857
+ input.name
1858
+ );
1859
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock });
1860
+ return {
1861
+ id: mock.id,
1862
+ endpointCount: mock.endpoints.length,
1863
+ changedIds: out.changedIds,
1864
+ warnings
1865
+ };
1866
+ }
1867
+ };
1868
+ var mockCreateFromPostmanTool = {
1869
+ name: "mock.create_from_postman",
1870
+ description: "Create a mock server from a Postman v2/v2.1 collection.",
1871
+ inputSchema: import_zod8.z.object({ name: import_zod8.z.string(), collection: import_zod8.z.string().min(1) }),
1872
+ async handler(input, ctx) {
1873
+ const { mock, warnings } = await ingestSource(
1874
+ { kind: "postman", collection: input.collection },
1875
+ input.name
1876
+ );
1877
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock });
1878
+ return {
1879
+ id: mock.id,
1880
+ endpointCount: mock.endpoints.length,
1881
+ changedIds: out.changedIds,
1882
+ warnings
1883
+ };
1884
+ }
1885
+ };
1886
+ var mockCreateFromInsomniaTool = {
1887
+ name: "mock.create_from_insomnia",
1888
+ description: "Create a mock server from an Insomnia v4 export.",
1889
+ inputSchema: import_zod8.z.object({ name: import_zod8.z.string(), export: import_zod8.z.string().min(1) }),
1890
+ async handler(input, ctx) {
1891
+ const { mock, warnings } = await ingestSource(
1892
+ { kind: "insomnia", export: input.export },
1893
+ input.name
1894
+ );
1895
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock });
1896
+ return {
1897
+ id: mock.id,
1898
+ endpointCount: mock.endpoints.length,
1899
+ changedIds: out.changedIds,
1900
+ warnings
1901
+ };
1902
+ }
1903
+ };
1904
+ var mockImportPostmanMockCollectionTool = {
1905
+ name: "mock.import_postman_mock_collection",
1906
+ description: "Import a Postman Mock Collection (collections previously hosted on Postman's mock service). Same parser as a regular Postman collection but marked as a mock import.",
1907
+ inputSchema: import_zod8.z.object({ name: import_zod8.z.string(), collection: import_zod8.z.string().min(1) }),
1908
+ async handler(input, ctx) {
1909
+ const { mock, warnings } = await ingestSource(
1910
+ { kind: "postman", collection: input.collection },
1911
+ input.name
1912
+ );
1913
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock });
1914
+ return {
1915
+ id: mock.id,
1916
+ endpointCount: mock.endpoints.length,
1917
+ changedIds: out.changedIds,
1918
+ warnings
1919
+ };
1920
+ }
1921
+ };
1922
+ var mockListTool = {
1923
+ name: "mock.list",
1924
+ description: "List all mock servers in the workspace plus their runtime status (running / stopped, port).",
1925
+ inputSchema: import_zod8.z.object({}),
1926
+ async handler(_input, ctx) {
1927
+ const state = await ctx.workspace.read();
1928
+ const running = await ctx.mock.list();
1929
+ const runningById = new Map(running.map((r) => [r.serverId, r.runtime]));
1930
+ const items = Object.values(state.synced.mockServers).map((m) => {
1931
+ const runtime = runningById.get(m.id);
1932
+ return {
1933
+ id: m.id,
1934
+ name: m.name,
1935
+ endpointCount: m.endpoints.length,
1936
+ defaultPort: m.defaultPort,
1937
+ running: !!runtime,
1938
+ port: runtime?.port ?? null
1939
+ };
1940
+ });
1941
+ return { count: items.length, mocks: items };
1942
+ }
1943
+ };
1944
+ var mockStartTool = {
1945
+ name: "mock.start",
1946
+ description: "Start a mock server by id. Returns the bound port. Errors if the mock is already running or the requested port is in use.",
1947
+ inputSchema: import_zod8.z.object({
1948
+ id: import_zod8.z.string(),
1949
+ port: import_zod8.z.number().int().positive().optional()
1950
+ }),
1951
+ async handler(input, ctx) {
1952
+ const state = await ctx.workspace.read();
1953
+ const mock = state.synced.mockServers[input.id];
1954
+ if (!mock) return { ok: false, error: "mock not found" };
1955
+ try {
1956
+ const result = await ctx.mock.start(mock, { port: input.port });
1957
+ return { ok: true, port: result.port, pid: result.pid, startedAt: result.startedAt };
1958
+ } catch (err) {
1959
+ return { ok: false, error: err instanceof Error ? err.message : "mock.start failed" };
1960
+ }
1961
+ }
1962
+ };
1963
+ var mockStopTool = {
1964
+ name: "mock.stop",
1965
+ description: "Stop a running mock server by id (no-op if not running).",
1966
+ inputSchema: import_zod8.z.object({ id: import_zod8.z.string() }),
1967
+ async handler(input, ctx) {
1968
+ try {
1969
+ await ctx.mock.stop(input.id);
1970
+ return { ok: true };
1971
+ } catch (err) {
1972
+ return { ok: false, error: err instanceof Error ? err.message : "mock.stop failed" };
1973
+ }
1974
+ }
1975
+ };
1976
+ var mockDeleteTool = {
1977
+ name: "mock.delete",
1978
+ description: "Delete a mock server definition. Stops it first if it's running.",
1979
+ inputSchema: import_zod8.z.object({ id: import_zod8.z.string() }),
1980
+ async handler(input, ctx) {
1981
+ try {
1982
+ await ctx.mock.stop(input.id);
1983
+ } catch {
1984
+ }
1985
+ const out = await ctx.workspace.apply({ kind: "mock.delete", id: input.id });
1986
+ return { ok: true, changedIds: out.changedIds };
1987
+ }
1988
+ };
1989
+ var HTTP_METHOD3 = import_zod8.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
1990
+ var mockCreateManualTool = {
1991
+ name: "mock.create_manual",
1992
+ description: "Create an empty manual-mode mock server. Use `mock.add_endpoint` afterward to populate it. CORS defaults to off (same-origin only); enable + list explicit origins via `mock.update_cors` if cross-origin access is needed.",
1993
+ inputSchema: import_zod8.z.object({
1994
+ name: import_zod8.z.string().min(1),
1995
+ defaultPort: import_zod8.z.number().int().positive().nullable().optional()
1996
+ }),
1997
+ async handler(input, ctx) {
1998
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1999
+ const mock = {
2000
+ id: (0, import_shared4.generateId)(),
2001
+ name: input.name,
2002
+ source: { kind: "manual", endpoints: [] },
2003
+ endpoints: [],
2004
+ defaultPort: input.defaultPort ?? null,
2005
+ // Off by default — match UI-created mocks. Caller opts in via
2006
+ // explicit origins; we never silently wildcard.
2007
+ cors: { enabled: false, origins: [] },
2008
+ createdAt: now,
2009
+ updatedAt: now
2010
+ };
2011
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock });
2012
+ return { id: mock.id, changedIds: out.changedIds };
2013
+ }
2014
+ };
2015
+ var mockListEndpointsTool = {
2016
+ name: "mock.list_endpoints",
2017
+ description: "List endpoints for a mock server (id, method, path, name).",
2018
+ inputSchema: import_zod8.z.object({ mockId: import_zod8.z.string() }),
2019
+ async handler(input, ctx) {
2020
+ const state = await ctx.workspace.read();
2021
+ const mock = state.synced.mockServers[input.mockId];
2022
+ if (!mock) return { ok: false, error: "mock not found" };
2023
+ return {
2024
+ ok: true,
2025
+ count: mock.endpoints.length,
2026
+ endpoints: mock.endpoints.map((e) => ({
2027
+ id: e.id,
2028
+ method: e.method,
2029
+ pathPattern: e.pathPattern,
2030
+ name: e.name,
2031
+ validationCount: e.requestValidation.length,
2032
+ responseRuleCount: e.responseRules.length
2033
+ }))
2034
+ };
2035
+ }
2036
+ };
2037
+ var ENDPOINT_RESPONSE2 = import_zod8.z.object({
2038
+ status: import_zod8.z.number().int().min(100).max(599).default(200),
2039
+ jsonBody: import_zod8.z.string().default("{}"),
2040
+ contentType: import_zod8.z.string().default("application/json")
2041
+ });
2042
+ function buildDefaultEndpoint(args) {
2043
+ const response = args.response ?? {
2044
+ status: 200,
2045
+ jsonBody: "{}",
2046
+ contentType: "application/json"
2047
+ };
2048
+ const headers = [{ key: "Content-Type", value: response.contentType, enabled: true }];
2049
+ return {
2050
+ id: (0, import_shared4.generateId)(),
2051
+ name: args.name ?? `${args.method} ${args.pathPattern}`,
2052
+ method: args.method,
2053
+ pathPattern: args.pathPattern,
2054
+ description: args.description,
2055
+ requestSchema: (0, import_shared4.makeDefaultRequestSchema)(),
2056
+ requestValidation: [],
2057
+ responseRules: [],
2058
+ defaultResponse: {
2059
+ ...(0, import_shared4.makeDefaultMockResponse)(),
2060
+ status: response.status,
2061
+ headers,
2062
+ body: { type: "json", content: response.jsonBody }
2063
+ }
2064
+ };
2065
+ }
2066
+ var mockAddEndpointTool = {
2067
+ name: "mock.add_endpoint",
2068
+ description: "Append a new endpoint to a mock server. Defaults to a 200 JSON response of `{}`. Returns the new endpoint id.",
2069
+ inputSchema: import_zod8.z.object({
2070
+ mockId: import_zod8.z.string(),
2071
+ method: HTTP_METHOD3,
2072
+ pathPattern: import_zod8.z.string().min(1),
2073
+ name: import_zod8.z.string().optional(),
2074
+ description: import_zod8.z.string().optional(),
2075
+ response: ENDPOINT_RESPONSE2.optional()
2076
+ }),
2077
+ async handler(input, ctx) {
2078
+ const state = await ctx.workspace.read();
2079
+ const mock = state.synced.mockServers[input.mockId];
2080
+ if (!mock) return { ok: false, error: "mock not found" };
2081
+ const endpoint = buildDefaultEndpoint(input);
2082
+ const nextEndpoints = [...mock.endpoints, endpoint];
2083
+ const source = mock.source.kind === "manual" ? { kind: "manual", endpoints: nextEndpoints } : mock.source;
2084
+ const next = {
2085
+ ...mock,
2086
+ source,
2087
+ endpoints: nextEndpoints,
2088
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2089
+ };
2090
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock: next });
2091
+ return { ok: true, endpointId: endpoint.id, changedIds: out.changedIds };
2092
+ }
2093
+ };
2094
+ var mockUpdateEndpointTool = {
2095
+ name: "mock.update_endpoint",
2096
+ description: "Patch fields on a single mock endpoint (method, pathPattern, name, description, defaultResponse status / contentType / json body). Pass only the fields you want to change.",
2097
+ inputSchema: import_zod8.z.object({
2098
+ mockId: import_zod8.z.string(),
2099
+ endpointId: import_zod8.z.string(),
2100
+ method: HTTP_METHOD3.optional(),
2101
+ pathPattern: import_zod8.z.string().optional(),
2102
+ name: import_zod8.z.string().optional(),
2103
+ description: import_zod8.z.string().optional(),
2104
+ response: ENDPOINT_RESPONSE2.partial().optional()
2105
+ }),
2106
+ async handler(input, ctx) {
2107
+ const state = await ctx.workspace.read();
2108
+ const mock = state.synced.mockServers[input.mockId];
2109
+ if (!mock) return { ok: false, error: "mock not found" };
2110
+ const idx = mock.endpoints.findIndex((e) => e.id === input.endpointId);
2111
+ if (idx === -1) return { ok: false, error: "endpoint not found" };
2112
+ const existing = mock.endpoints[idx];
2113
+ const nextEndpoint = {
2114
+ ...existing,
2115
+ method: input.method ?? existing.method,
2116
+ pathPattern: input.pathPattern ?? existing.pathPattern,
2117
+ name: input.name ?? existing.name,
2118
+ description: input.description ?? existing.description,
2119
+ defaultResponse: input.response ? {
2120
+ ...existing.defaultResponse,
2121
+ status: input.response.status ?? existing.defaultResponse.status,
2122
+ headers: input.response.contentType ? existing.defaultResponse.headers.map(
2123
+ (h) => h.key.toLowerCase() === "content-type" ? { ...h, value: input.response.contentType } : h
2124
+ ) : existing.defaultResponse.headers,
2125
+ body: input.response.jsonBody !== void 0 ? { type: "json", content: input.response.jsonBody } : existing.defaultResponse.body
2126
+ } : existing.defaultResponse
2127
+ };
2128
+ const nextEndpoints = [...mock.endpoints];
2129
+ nextEndpoints[idx] = nextEndpoint;
2130
+ const source = mock.source.kind === "manual" ? { kind: "manual", endpoints: nextEndpoints } : mock.source;
2131
+ const next = {
2132
+ ...mock,
2133
+ source,
2134
+ endpoints: nextEndpoints,
2135
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2136
+ };
2137
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock: next });
2138
+ return { ok: true, changedIds: out.changedIds };
2139
+ }
2140
+ };
2141
+ var mockDeleteEndpointTool = {
2142
+ name: "mock.delete_endpoint",
2143
+ description: "Remove an endpoint from a mock server.",
2144
+ inputSchema: import_zod8.z.object({ mockId: import_zod8.z.string(), endpointId: import_zod8.z.string() }),
2145
+ async handler(input, ctx) {
2146
+ const state = await ctx.workspace.read();
2147
+ const mock = state.synced.mockServers[input.mockId];
2148
+ if (!mock) return { ok: false, error: "mock not found" };
2149
+ const nextEndpoints = mock.endpoints.filter((e) => e.id !== input.endpointId);
2150
+ if (nextEndpoints.length === mock.endpoints.length) {
2151
+ return { ok: false, error: "endpoint not found" };
2152
+ }
2153
+ const source = mock.source.kind === "manual" ? { kind: "manual", endpoints: nextEndpoints } : mock.source;
2154
+ const next = {
2155
+ ...mock,
2156
+ source,
2157
+ endpoints: nextEndpoints,
2158
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2159
+ };
2160
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock: next });
2161
+ return { ok: true, changedIds: out.changedIds };
2162
+ }
2163
+ };
2164
+ var VALIDATION_RULE = import_zod8.z.object({
2165
+ id: import_zod8.z.string().optional(),
2166
+ kind: import_zod8.z.enum([
2167
+ "header-required",
2168
+ "header-equals",
2169
+ "header-matches",
2170
+ "query-required",
2171
+ "query-equals",
2172
+ "query-matches",
2173
+ "cookie-required",
2174
+ "body-required",
2175
+ "content-type-equals"
2176
+ ]),
2177
+ target: import_zod8.z.string().default(""),
2178
+ expected: import_zod8.z.string().optional(),
2179
+ message: import_zod8.z.string().optional(),
2180
+ enabled: import_zod8.z.boolean().default(true),
2181
+ failResponse: import_zod8.z.object({
2182
+ status: import_zod8.z.number().int().min(100).max(599).default(400),
2183
+ jsonBody: import_zod8.z.string().default('{"error":"validation failed"}')
2184
+ }).default({})
2185
+ });
2186
+ var CONDITION_CLAUSE = import_zod8.z.object({
2187
+ id: import_zod8.z.string().optional(),
2188
+ scope: import_zod8.z.enum(["query", "pathParam", "header", "cookie", "body-json-path"]),
2189
+ target: import_zod8.z.string(),
2190
+ op: import_zod8.z.enum(["equals", "not-equals", "matches", "gt", "lt", "gte", "lte", "present", "absent"]),
2191
+ value: import_zod8.z.string().optional()
2192
+ });
2193
+ var RESPONSE_RULE = import_zod8.z.object({
2194
+ id: import_zod8.z.string().optional(),
2195
+ name: import_zod8.z.string(),
2196
+ enabled: import_zod8.z.boolean().default(true),
2197
+ when: import_zod8.z.array(CONDITION_CLAUSE).default([]),
2198
+ response: import_zod8.z.object({
2199
+ status: import_zod8.z.number().int().min(100).max(599).default(200),
2200
+ jsonBody: import_zod8.z.string().default("{}")
2201
+ }).default({})
2202
+ });
2203
+ var MULTIPLIER = import_zod8.z.object({
2204
+ id: import_zod8.z.string().optional(),
2205
+ name: import_zod8.z.string().optional(),
2206
+ source: import_zod8.z.object({
2207
+ kind: import_zod8.z.enum(["query", "pathParam", "header", "body-json-path"]),
2208
+ key: import_zod8.z.string()
2209
+ }),
2210
+ targetJsonPath: import_zod8.z.string(),
2211
+ defaultCount: import_zod8.z.number().int().nonnegative().default(0),
2212
+ min: import_zod8.z.number().int().nonnegative().optional(),
2213
+ max: import_zod8.z.number().int().nonnegative().optional()
2214
+ });
2215
+ function defaultJsonResponseConfig(args) {
2216
+ return {
2217
+ status: args.status,
2218
+ headers: [{ key: "Content-Type", value: "application/json", enabled: true }],
2219
+ body: { type: "json", content: args.jsonBody }
2220
+ };
2221
+ }
2222
+ function patchEndpoint2(mock, endpointId, patcher) {
2223
+ const idx = mock.endpoints.findIndex((e) => e.id === endpointId);
2224
+ if (idx === -1) return null;
2225
+ const nextEndpoints = [...mock.endpoints];
2226
+ nextEndpoints[idx] = patcher(mock.endpoints[idx]);
2227
+ const source = mock.source.kind === "manual" ? { kind: "manual", endpoints: nextEndpoints } : mock.source;
2228
+ return {
2229
+ ...mock,
2230
+ source,
2231
+ endpoints: nextEndpoints,
2232
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2233
+ };
2234
+ }
2235
+ var mockSetValidationRulesTool = {
2236
+ name: "mock.set_validation_rules",
2237
+ description: "Replace an endpoint's validation rules. Rules without an `id` get a fresh one; existing rules can keep theirs to preserve client-side selection state. Empty array clears all rules.",
2238
+ inputSchema: import_zod8.z.object({
2239
+ mockId: import_zod8.z.string(),
2240
+ endpointId: import_zod8.z.string(),
2241
+ rules: import_zod8.z.array(VALIDATION_RULE)
2242
+ }),
2243
+ async handler(input, ctx) {
2244
+ const state = await ctx.workspace.read();
2245
+ const mock = state.synced.mockServers[input.mockId];
2246
+ if (!mock) return { ok: false, error: "mock not found" };
2247
+ const rules = input.rules;
2248
+ const next = patchEndpoint2(mock, input.endpointId, (e) => ({
2249
+ ...e,
2250
+ requestValidation: rules.map((r) => ({
2251
+ id: r.id ?? (0, import_shared4.generateId)(),
2252
+ kind: r.kind,
2253
+ target: r.target,
2254
+ expected: r.expected,
2255
+ message: r.message,
2256
+ enabled: r.enabled,
2257
+ failResponse: defaultJsonResponseConfig(r.failResponse)
2258
+ }))
2259
+ }));
2260
+ if (!next) return { ok: false, error: "endpoint not found" };
2261
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock: next });
2262
+ return { ok: true, changedIds: out.changedIds };
2263
+ }
2264
+ };
2265
+ var mockSetResponseRulesTool = {
2266
+ name: "mock.set_response_rules",
2267
+ description: "Replace an endpoint's conditional response rules. Rules fire in order; the first whose every clause matches wins. Disabled rules are skipped. Empty array falls back to defaultResponse.",
2268
+ inputSchema: import_zod8.z.object({
2269
+ mockId: import_zod8.z.string(),
2270
+ endpointId: import_zod8.z.string(),
2271
+ rules: import_zod8.z.array(RESPONSE_RULE)
2272
+ }),
2273
+ async handler(input, ctx) {
2274
+ const state = await ctx.workspace.read();
2275
+ const mock = state.synced.mockServers[input.mockId];
2276
+ if (!mock) return { ok: false, error: "mock not found" };
2277
+ const rules = input.rules;
2278
+ const next = patchEndpoint2(mock, input.endpointId, (e) => ({
2279
+ ...e,
2280
+ responseRules: rules.map((r) => ({
2281
+ id: r.id ?? (0, import_shared4.generateId)(),
2282
+ name: r.name,
2283
+ enabled: r.enabled,
2284
+ when: r.when.map((c) => ({
2285
+ id: c.id ?? (0, import_shared4.generateId)(),
2286
+ scope: c.scope,
2287
+ target: c.target,
2288
+ op: c.op,
2289
+ value: c.value
2290
+ })),
2291
+ response: defaultJsonResponseConfig(r.response)
2292
+ }))
2293
+ }));
2294
+ if (!next) return { ok: false, error: "endpoint not found" };
2295
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock: next });
2296
+ return { ok: true, changedIds: out.changedIds };
2297
+ }
2298
+ };
2299
+ var mockSetMultipliersTool = {
2300
+ name: "mock.set_multipliers",
2301
+ description: "Replace the response multipliers on an endpoint's defaultResponse. Multipliers expand an array at `targetJsonPath` to a count derived from a request value. Empty array clears all multipliers.",
2302
+ inputSchema: import_zod8.z.object({
2303
+ mockId: import_zod8.z.string(),
2304
+ endpointId: import_zod8.z.string(),
2305
+ multipliers: import_zod8.z.array(MULTIPLIER)
2306
+ }),
2307
+ async handler(input, ctx) {
2308
+ const state = await ctx.workspace.read();
2309
+ const mock = state.synced.mockServers[input.mockId];
2310
+ if (!mock) return { ok: false, error: "mock not found" };
2311
+ const multipliers = input.multipliers;
2312
+ const next = patchEndpoint2(mock, input.endpointId, (e) => ({
2313
+ ...e,
2314
+ defaultResponse: {
2315
+ ...e.defaultResponse,
2316
+ multipliers: multipliers.length === 0 ? void 0 : multipliers.map((m) => ({
2317
+ id: m.id ?? (0, import_shared4.generateId)(),
2318
+ name: m.name,
2319
+ source: { kind: m.source.kind, key: m.source.key },
2320
+ targetJsonPath: m.targetJsonPath,
2321
+ defaultCount: m.defaultCount,
2322
+ min: m.min,
2323
+ max: m.max
2324
+ }))
2325
+ }
2326
+ }));
2327
+ if (!next) return { ok: false, error: "endpoint not found" };
2328
+ const out = await ctx.workspace.apply({ kind: "mock.upsert", mock: next });
2329
+ return { ok: true, changedIds: out.changedIds };
2330
+ }
2331
+ };
2332
+
2333
+ // src/tools/registry.ts
2334
+ var TOOL_REGISTRY = [
2335
+ importCurlTool,
2336
+ importOpenApiTool,
2337
+ importPostmanTool,
2338
+ importInsomniaTool,
2339
+ importHarTool,
2340
+ generateCodeTool,
2341
+ workspaceReadTool,
2342
+ workspaceWriteTool,
2343
+ requestCreateTool,
2344
+ requestReadTool,
2345
+ requestUpdateTool,
2346
+ requestDeleteTool,
2347
+ folderCreateTool,
2348
+ folderReadTool,
2349
+ folderUpdateTool,
2350
+ folderDeleteTool,
2351
+ environmentCreateTool,
2352
+ environmentReadTool,
2353
+ environmentUpdateTool,
2354
+ environmentDeleteTool,
2355
+ environmentSetActiveTool,
2356
+ environmentSetPriorityTool,
2357
+ environmentExportTool,
2358
+ environmentImportTool,
2359
+ planCreateTool,
2360
+ planRunTool,
2361
+ planReadTool,
2362
+ planUpdateTool,
2363
+ planDeleteTool,
2364
+ planAddStepTool,
2365
+ planRemoveStepTool,
2366
+ planReorderStepsTool,
2367
+ planSetVariablesTool,
2368
+ assertionCreateTool,
2369
+ assertionReadTool,
2370
+ assertionUpdateTool,
2371
+ assertionDeleteTool,
2372
+ historyListRunsTool,
2373
+ historyGetRunTool,
2374
+ historyDeleteRunTool,
2375
+ historyPurgeTool,
2376
+ codebaseExtractCollectionTool,
2377
+ promptCreateEnvironmentTool,
2378
+ promptCreateAssertionTool,
2379
+ promptCreatePlanTool,
2380
+ promptCreateRequestTool,
2381
+ promptUpdateRequestTool,
2382
+ promptCreateFolderTreeTool,
2383
+ promptAddPlanStepsTool,
2384
+ promptSetPlanVariablesTool,
2385
+ promptCreateMockServerTool,
2386
+ promptAddMockEndpointTool,
2387
+ promptSetEndpointValidationRulesTool,
2388
+ promptSetEndpointResponseRulesTool,
2389
+ promptSetEndpointMultipliersTool,
2390
+ mockCreateFromOpenApiTool,
2391
+ mockCreateFromPostmanTool,
2392
+ mockCreateFromInsomniaTool,
2393
+ mockCreateManualTool,
2394
+ mockListTool,
2395
+ mockListEndpointsTool,
2396
+ mockStartTool,
2397
+ mockStopTool,
2398
+ mockDeleteTool,
2399
+ mockAddEndpointTool,
2400
+ mockUpdateEndpointTool,
2401
+ mockDeleteEndpointTool,
2402
+ mockSetValidationRulesTool,
2403
+ mockSetResponseRulesTool,
2404
+ mockSetMultipliersTool,
2405
+ mockImportPostmanMockCollectionTool
2406
+ ];
2407
+ function getTool(name) {
2408
+ return TOOL_REGISTRY.find((t) => t.name === name);
2409
+ }
2410
+
2411
+ // src/providers/InMemoryWorkspaceProvider.ts
2412
+ var import_core2 = require("@apicircle/core");
2413
+ var InMemoryWorkspaceProvider = class {
2414
+ state;
2415
+ constructor(initial) {
2416
+ this.state = initial;
2417
+ }
2418
+ async read() {
2419
+ return this.state;
2420
+ }
2421
+ async apply(patch) {
2422
+ const out = (0, import_core2.applyMutation)(this.state, patch);
2423
+ this.state = out.next;
2424
+ return { state: this.state, changedIds: out.changedIds };
2425
+ }
2426
+ async write(next) {
2427
+ this.state = {
2428
+ synced: next.synced ?? this.state.synced,
2429
+ local: next.local ?? this.state.local
2430
+ };
2431
+ return this.state;
2432
+ }
2433
+ };
2434
+
2435
+ // src/providers/FileBackedWorkspaceProvider.ts
2436
+ var import_core3 = require("@apicircle/core");
2437
+ var import_file_backed = require("@apicircle/core/workspace/file-backed");
2438
+ var FileBackedWorkspaceProvider = class {
2439
+ constructor(dir) {
2440
+ this.dir = dir;
2441
+ }
2442
+ dir;
2443
+ async read() {
2444
+ const out = await (0, import_file_backed.loadFromFile)(this.dir);
2445
+ if (!out) {
2446
+ throw new Error(`No workspace found at ${this.dir}`);
2447
+ }
2448
+ return out;
2449
+ }
2450
+ async apply(patch) {
2451
+ let captured = null;
2452
+ await (0, import_file_backed.withWorkspace)(this.dir, async (state) => {
2453
+ const result = (0, import_core3.applyMutation)(state, patch);
2454
+ captured = { state: result.next, changedIds: result.changedIds };
2455
+ return { next: result.next };
2456
+ });
2457
+ if (!captured) throw new Error("apply did not run");
2458
+ return captured;
2459
+ }
2460
+ async write(next) {
2461
+ const current = await this.read();
2462
+ const merged = {
2463
+ synced: next.synced ?? current.synced,
2464
+ local: next.local ?? current.local
2465
+ };
2466
+ await (0, import_file_backed.saveToFile)(this.dir, merged);
2467
+ return merged;
2468
+ }
2469
+ };
2470
+
2471
+ // src/providers/InProcessMockController.ts
2472
+ var import_mock_server_core3 = require("@apicircle/mock-server-core");
2473
+ var InProcessMockController = class {
2474
+ handles = /* @__PURE__ */ new Map();
2475
+ meta = /* @__PURE__ */ new Map();
2476
+ async start(server, opts = {}) {
2477
+ if (this.handles.has(server.id)) {
2478
+ throw new Error(`Mock '${server.id}' is already running`);
2479
+ }
2480
+ const handle = await (0, import_mock_server_core3.startMockServer)(server, {
2481
+ port: opts.port ?? server.defaultPort ?? void 0
2482
+ });
2483
+ const runtime = {
2484
+ port: handle.port,
2485
+ pid: process.pid,
2486
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2487
+ lastError: null,
2488
+ requestCount: 0
2489
+ };
2490
+ this.handles.set(server.id, handle);
2491
+ this.meta.set(server.id, runtime);
2492
+ return { port: handle.port, pid: runtime.pid, startedAt: runtime.startedAt };
2493
+ }
2494
+ async stop(serverId) {
2495
+ const handle = this.handles.get(serverId);
2496
+ if (!handle) return;
2497
+ await (0, import_mock_server_core3.stopMockServer)(handle);
2498
+ this.handles.delete(serverId);
2499
+ this.meta.delete(serverId);
2500
+ }
2501
+ async list() {
2502
+ return Array.from(this.meta.entries()).map(([serverId, runtime]) => ({
2503
+ serverId,
2504
+ runtime
2505
+ }));
2506
+ }
2507
+ };
2508
+
2509
+ // src/index.ts
2510
+ function createMcpServer(options) {
2511
+ return new McpHost({
2512
+ serverInfo: options.serverInfo,
2513
+ tools: options.tools ?? TOOL_REGISTRY,
2514
+ context: { workspace: options.workspace, mock: options.mock }
2515
+ });
2516
+ }
2517
+ // Annotate the CommonJS export names for ESM import in node:
2518
+ 0 && (module.exports = {
2519
+ FileBackedWorkspaceProvider,
2520
+ InMemoryWorkspaceProvider,
2521
+ InProcessMockController,
2522
+ McpHost,
2523
+ TOOL_REGISTRY,
2524
+ createMcpServer,
2525
+ getTool
2526
+ });
2527
+ //# sourceMappingURL=index.cjs.map