@apicircle/mcp-server 1.0.6 → 1.0.8

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.js CHANGED
@@ -6,10 +6,34 @@ import { z } from "zod";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@apicircle/mcp-server",
9
- version: "1.0.6",
9
+ version: "1.0.8",
10
10
  private: false,
11
11
  type: "module",
12
12
  description: "Model Context Protocol server exposing API Circle Studio's workspace as a tool catalog. Used by Claude Desktop, ChatGPT, Cursor, GitHub Copilot, and any other MCP-compatible AI client.",
13
+ keywords: [
14
+ "apicircle",
15
+ "api",
16
+ "api-circle",
17
+ "mcp",
18
+ "mcp-server",
19
+ "model-context-protocol",
20
+ "ai",
21
+ "llm",
22
+ "agent",
23
+ "tool-catalog",
24
+ "claude",
25
+ "claude-desktop",
26
+ "chatgpt",
27
+ "cursor",
28
+ "copilot",
29
+ "continue",
30
+ "cline",
31
+ "zed",
32
+ "windsurf",
33
+ "openapi",
34
+ "postman",
35
+ "api-tools"
36
+ ],
13
37
  license: "SEE LICENSE IN LICENSE",
14
38
  repository: {
15
39
  type: "git",
@@ -534,6 +558,7 @@ var workspaceListTool = {
534
558
  // src/tools/crud.ts
535
559
  import { z as z5 } from "zod";
536
560
  import { generateId as generateId2 } from "@apicircle/shared";
561
+ import { parseApicircleEnvironmentDoc } from "@apicircle/core";
537
562
  var HTTP_METHOD = z5.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
538
563
  var requestCreateTool = {
539
564
  name: "request.create",
@@ -778,55 +803,136 @@ var environmentSetPriorityTool = {
778
803
  };
779
804
  var environmentExportTool = {
780
805
  name: "environment.export",
781
- 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.",
806
+ description: "Serialize an environment to a portable JSON string (envelope v2). Encrypted variables now carry their ciphertext, slot label, and per-slot salt \u2014 the destination decrypts with its local slot value at request-execute time, matching the Git push/pull contract. The plaintext slot value never leaves the device, but the ciphertext does. v1 envelopes (no ciphertext) still parse on import for back-compat.",
782
807
  inputSchema: z5.object({ name: z5.string() }),
783
808
  async handler(input, ctx) {
784
809
  const state = await ctx.workspace.read();
785
810
  const env = state.synced.environments.items[input.name];
786
811
  if (!env) return { ok: false, error: "environment not found" };
787
812
  const payload = {
788
- apicircleEnvironment: 1,
813
+ apicircleEnvironment: 2,
789
814
  name: env.name,
790
- variables: env.variables.map(
791
- (v) => v.encrypted && v.secretKeyId ? { key: v.key, encrypted: true, secretKeyId: v.secretKeyId } : { key: v.key, value: v.value, encrypted: false }
792
- )
815
+ variables: env.variables.map((v) => {
816
+ if (v.encrypted && v.secretKeyId) {
817
+ const slot = state.synced.secretKeys?.[v.secretKeyId];
818
+ const label = slot?.label ?? v.key;
819
+ const value = typeof v.value === "string" && v.value.startsWith("enc:") ? v.value : "";
820
+ return {
821
+ key: v.key,
822
+ encrypted: true,
823
+ value,
824
+ secretKeyId: v.secretKeyId,
825
+ secret: { label, salt: slot?.salt ?? null }
826
+ };
827
+ }
828
+ return { key: v.key, value: v.value, encrypted: false };
829
+ })
793
830
  };
794
831
  return { ok: true, json: JSON.stringify(payload, null, 2) };
795
832
  }
796
833
  };
797
834
  var environmentImportTool = {
798
835
  name: "environment.import",
799
- 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.",
836
+ description: "Import an environment from the JSON shape produced by `environment.export`. v2 envelopes carry the row ciphertext + per-slot salt, so the destination decrypts at request-execute time with its local slot value (same model as Git push/pull); when no destination slot matches the source's salt, a new slot is minted via `secretKey.upsert` and surfaced in `mintedSlots` so the caller can provide values via `secret.addLocal`. v1 envelopes carry only metadata, so unmatched rows come back as `pendingBindings` for the caller to prompt-and-bind via `secret.addLocal` + `environment.bindSecret`. Pass `overwrite: true` to replace a same-name destination env.",
800
837
  inputSchema: z5.object({
801
838
  json: z5.string().min(1),
802
839
  overwrite: z5.boolean().default(false)
803
840
  }),
804
841
  async handler(input, ctx) {
805
- let parsed;
842
+ let raw;
806
843
  try {
807
- parsed = JSON.parse(input.json);
844
+ raw = JSON.parse(input.json);
808
845
  } catch {
809
846
  return { ok: false, error: "invalid JSON" };
810
847
  }
811
- const obj = parsed;
812
- if (obj.apicircleEnvironment !== 1 || typeof obj.name !== "string" || !Array.isArray(obj.variables)) {
813
- return { ok: false, error: "unsupported export shape" };
848
+ let parsed;
849
+ try {
850
+ parsed = parseApicircleEnvironmentDoc(raw);
851
+ } catch (err) {
852
+ return {
853
+ ok: false,
854
+ error: err instanceof Error ? err.message : "unsupported export shape"
855
+ };
814
856
  }
815
857
  const state = await ctx.workspace.read();
816
- if (state.synced.environments.items[obj.name] && !input.overwrite) {
858
+ if (state.synced.environments.items[parsed.name] && !input.overwrite) {
817
859
  return {
818
860
  ok: false,
819
861
  error: "environment already exists; pass overwrite:true"
820
862
  };
821
863
  }
822
- const env = {
823
- name: obj.name,
824
- variables: obj.variables.map(
825
- (v) => v.encrypted ? { key: v.key, value: "", encrypted: true, secretKeyId: v.secretKeyId } : { key: v.key, value: v.value, encrypted: false }
826
- )
827
- };
864
+ const destSlots = state.synced.secretKeys ?? {};
865
+ const labelToSlotId = /* @__PURE__ */ new Map();
866
+ for (const slot of Object.values(destSlots)) {
867
+ if (!labelToSlotId.has(slot.label)) labelToSlotId.set(slot.label, slot.id);
868
+ }
869
+ const resolvedVariables = [];
870
+ const pendingBindings = [];
871
+ const mintedSlots = [];
872
+ const knownDestIds = new Set(Object.keys(destSlots));
873
+ let hintCursor = 0;
874
+ for (const v of parsed.variables) {
875
+ if (!v.encrypted) {
876
+ resolvedVariables.push(v);
877
+ continue;
878
+ }
879
+ const hint = parsed.encryptedBindingHints[hintCursor];
880
+ hintCursor += 1;
881
+ if (hint && hint.ciphertext && hint.salt) {
882
+ if (hint.originSecretKeyId && destSlots[hint.originSecretKeyId]?.salt === hint.salt) {
883
+ resolvedVariables.push({ ...v, secretKeyId: hint.originSecretKeyId });
884
+ continue;
885
+ }
886
+ const labelMatch = labelToSlotId.get(hint.label);
887
+ if (labelMatch && destSlots[labelMatch]?.salt === hint.salt) {
888
+ resolvedVariables.push({ ...v, secretKeyId: labelMatch });
889
+ continue;
890
+ }
891
+ const mintedId = hint.originSecretKeyId && !knownDestIds.has(hint.originSecretKeyId) ? hint.originSecretKeyId : generateId2();
892
+ knownDestIds.add(mintedId);
893
+ if (!labelToSlotId.has(hint.label)) labelToSlotId.set(hint.label, mintedId);
894
+ mintedSlots.push({
895
+ id: mintedId,
896
+ label: hint.label,
897
+ salt: hint.salt,
898
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
899
+ });
900
+ resolvedVariables.push({ ...v, secretKeyId: mintedId });
901
+ continue;
902
+ }
903
+ if (hint?.originSecretKeyId && destSlots[hint.originSecretKeyId]) {
904
+ resolvedVariables.push({ ...v, secretKeyId: hint.originSecretKeyId });
905
+ continue;
906
+ }
907
+ if (hint?.label) {
908
+ const matchId = labelToSlotId.get(hint.label);
909
+ if (matchId) {
910
+ resolvedVariables.push({ ...v, secretKeyId: matchId });
911
+ continue;
912
+ }
913
+ }
914
+ resolvedVariables.push(v);
915
+ if (hint) {
916
+ pendingBindings.push({
917
+ varKey: hint.varKey,
918
+ label: hint.label,
919
+ labelFromFallback: hint.labelFromFallback
920
+ });
921
+ }
922
+ }
923
+ for (const meta of mintedSlots) {
924
+ await ctx.workspace.apply({ kind: "secretKey.upsert", meta });
925
+ }
926
+ const env = { name: parsed.name, variables: resolvedVariables };
828
927
  const out = await ctx.workspace.apply({ kind: "environment.upsert", environment: env });
829
- return { ok: true, name: env.name, changedIds: out.changedIds };
928
+ return {
929
+ ok: true,
930
+ name: env.name,
931
+ changedIds: out.changedIds,
932
+ pendingBindings,
933
+ mintedSlots: mintedSlots.map((s) => ({ id: s.id, label: s.label })),
934
+ warnings: parsed.warnings
935
+ };
830
936
  }
831
937
  };
832
938
  var PLAN_STEP = z5.object({
@@ -1157,17 +1263,106 @@ var workspaceWriteTool = {
1157
1263
  }
1158
1264
  };
1159
1265
 
1160
- // src/tools/history.ts
1266
+ // src/tools/folderExchange.ts
1161
1267
  import { z as z6 } from "zod";
1268
+ import {
1269
+ collectFolderExport,
1270
+ parseApicircleFolderExport,
1271
+ redactFolderExportCredentials,
1272
+ serializeFolderExport,
1273
+ suggestFolderExportFilename
1274
+ } from "@apicircle/core";
1275
+ var folderExportJsonTool = {
1276
+ name: "folder.export_json",
1277
+ description: "Export an existing folder (and its subtree) to the API Circle exchange JSON format. Embeds JSON Schema + GraphQL dependencies inline. Auth credentials are redacted by default \u2014 pass `includeCredentialIds` to keep specific fields verbatim (the report from `collectFolderExport` enumerates the available ids).",
1278
+ inputSchema: z6.object({
1279
+ folderId: z6.string().min(1, "folderId is required"),
1280
+ /**
1281
+ * Subset of credential ids to KEEP in the output. Anything not in
1282
+ * this list (and every detected credential when this is empty)
1283
+ * gets blanked. Use the report-side ids surfaced by the export
1284
+ * report (`<scope>:<ownerId>.<authType>.<field>`).
1285
+ */
1286
+ includeCredentialIds: z6.array(z6.string()).optional()
1287
+ }),
1288
+ async handler(input, ctx) {
1289
+ const state = await ctx.workspace.read();
1290
+ const collected = collectFolderExport({
1291
+ synced: state.synced,
1292
+ folderId: input.folderId
1293
+ });
1294
+ if (!collected) {
1295
+ return {
1296
+ error: "folder_not_found",
1297
+ message: `No folder with id "${input.folderId}" exists in the active workspace.`
1298
+ };
1299
+ }
1300
+ const includeIds = new Set(input.includeCredentialIds ?? []);
1301
+ const redacted = redactFolderExportCredentials(collected.envelope, includeIds);
1302
+ return {
1303
+ envelope: redacted,
1304
+ json: serializeFolderExport(redacted),
1305
+ filename: suggestFolderExportFilename(redacted),
1306
+ report: collected.report
1307
+ };
1308
+ }
1309
+ };
1310
+ var folderImportJsonTool = {
1311
+ name: "folder.import_json",
1312
+ description: "Import an `apicircle.folder/v1` envelope into the active workspace. Folder + request ids are reminted, dependency references are remapped, and JSON Schema / GraphQL definitions that match an existing global asset (by name + content) are reused.",
1313
+ inputSchema: z6.object({
1314
+ /** Either a JSON string or the already-parsed envelope object. */
1315
+ json: z6.string().min(1).optional(),
1316
+ envelope: z6.record(z6.unknown()).optional(),
1317
+ parentFolderId: z6.string().nullable().optional()
1318
+ }),
1319
+ async handler(input, ctx) {
1320
+ if (!input.json && !input.envelope) {
1321
+ return {
1322
+ error: "invalid_input",
1323
+ message: "Pass either `json` (string) or `envelope` (object)."
1324
+ };
1325
+ }
1326
+ const text = input.json !== void 0 ? input.json : JSON.stringify(input.envelope);
1327
+ let parsed;
1328
+ try {
1329
+ parsed = parseApicircleFolderExport(text);
1330
+ } catch (err) {
1331
+ return {
1332
+ error: "invalid_envelope",
1333
+ message: err instanceof Error ? err.message : String(err)
1334
+ };
1335
+ }
1336
+ const out = await ctx.workspace.apply({
1337
+ kind: "folder.import_apicircle",
1338
+ parsed,
1339
+ parentFolderId: input.parentFolderId ?? null
1340
+ });
1341
+ return {
1342
+ rootFolderId: parsed.rootFolder.id,
1343
+ rootFolderName: parsed.rootFolder.name,
1344
+ counts: {
1345
+ folders: parsed.subfolders.length + 1,
1346
+ requests: parsed.requests.length
1347
+ },
1348
+ filesRequiringReattachment: parsed.dependencies.files.map((f) => f.id),
1349
+ warnings: parsed.warnings,
1350
+ changedIds: out.changedIds
1351
+ };
1352
+ }
1353
+ };
1354
+
1355
+ // src/tools/history.ts
1356
+ import { z as z7 } from "zod";
1162
1357
  var historyListRunsTool = {
1163
1358
  name: "history.list_runs",
1164
1359
  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.",
1165
- inputSchema: z6.object({
1166
- requestId: z6.string().optional(),
1167
- ok: z6.boolean().optional(),
1168
- since: z6.string().optional(),
1169
- until: z6.string().optional(),
1170
- limit: z6.number().int().positive().max(500).default(100)
1360
+ inputSchema: z7.object({
1361
+ requestId: z7.string().optional(),
1362
+ ok: z7.boolean().optional(),
1363
+ since: z7.string().optional(),
1364
+ until: z7.string().optional(),
1365
+ limit: z7.number().int().positive().max(500).default(100)
1171
1366
  }),
1172
1367
  async handler(input, ctx) {
1173
1368
  const state = await ctx.workspace.read();
@@ -1201,7 +1396,7 @@ var historyListRunsTool = {
1201
1396
  var historyGetRunTool = {
1202
1397
  name: "history.get_run",
1203
1398
  description: "Fetch a single history row in full (headers, body preview, assertion results).",
1204
- inputSchema: z6.object({ id: z6.string() }),
1399
+ inputSchema: z7.object({ id: z7.string() }),
1205
1400
  async handler(input, ctx) {
1206
1401
  const state = await ctx.workspace.read();
1207
1402
  const run = state.local.history.requestRuns.find((r) => r.id === input.id);
@@ -1212,7 +1407,7 @@ var historyGetRunTool = {
1212
1407
  var historyDeleteRunTool = {
1213
1408
  name: "history.delete_run",
1214
1409
  description: "Delete a single request-run row by id.",
1215
- inputSchema: z6.object({ id: z6.string() }),
1410
+ inputSchema: z7.object({ id: z7.string() }),
1216
1411
  async handler(input, ctx) {
1217
1412
  const out = await ctx.workspace.apply({ kind: "history.delete_run", runId: input.id });
1218
1413
  return { deleted: out.changedIds.length, changedIds: out.changedIds };
@@ -1221,8 +1416,8 @@ var historyDeleteRunTool = {
1221
1416
  var historyPurgeTool = {
1222
1417
  name: "history.purge_by_age",
1223
1418
  description: "Drop every request-run + plan-run older than `olderThanDays` days. Pass 0 to clear all history.",
1224
- inputSchema: z6.object({
1225
- olderThanDays: z6.number().nonnegative()
1419
+ inputSchema: z7.object({
1420
+ olderThanDays: z7.number().nonnegative()
1226
1421
  }),
1227
1422
  async handler(input, ctx) {
1228
1423
  const olderThanMs = input.olderThanDays * 24 * 60 * 60 * 1e3;
@@ -1232,15 +1427,15 @@ var historyPurgeTool = {
1232
1427
  };
1233
1428
 
1234
1429
  // src/tools/codebase.ts
1235
- import { z as z7 } from "zod";
1430
+ import { z as z8 } from "zod";
1236
1431
  var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "options", "head"];
1237
1432
  var codebaseExtractCollectionTool = {
1238
1433
  name: "codebase.extract_collection",
1239
1434
  description: "Scan source code for HTTP route definitions (Express, FastAPI, NestJS, Spring) and return candidate requests for the user to confirm before import.",
1240
- inputSchema: z7.object({
1241
- source: z7.string().min(1),
1435
+ inputSchema: z8.object({
1436
+ source: z8.string().min(1),
1242
1437
  /** Hint to limit which framework patterns to apply. Empty = try all. */
1243
- frameworks: z7.array(z7.enum(["express", "fastapi", "nest", "spring"])).default([])
1438
+ frameworks: z8.array(z8.enum(["express", "fastapi", "nest", "spring"])).default([])
1244
1439
  }),
1245
1440
  async handler(input) {
1246
1441
  const enabled = new Set(
@@ -1321,18 +1516,18 @@ var codebaseExtractCollectionTool = {
1321
1516
  };
1322
1517
 
1323
1518
  // src/tools/prompt.ts
1324
- import { z as z8 } from "zod";
1519
+ import { z as z9 } from "zod";
1325
1520
  import { generateId as generateId3, makeDefaultMockResponse, makeDefaultRequestSchema } from "@apicircle/shared";
1326
1521
  var promptCreateEnvironmentTool = {
1327
1522
  name: "prompt.create_environment",
1328
1523
  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.",
1329
- inputSchema: z8.object({
1330
- name: z8.string(),
1331
- variables: z8.array(
1332
- z8.object({
1333
- key: z8.string(),
1334
- value: z8.string(),
1335
- encrypted: z8.boolean().default(false)
1524
+ inputSchema: z9.object({
1525
+ name: z9.string(),
1526
+ variables: z9.array(
1527
+ z9.object({
1528
+ key: z9.string(),
1529
+ value: z9.string(),
1530
+ encrypted: z9.boolean().default(false)
1336
1531
  })
1337
1532
  )
1338
1533
  }),
@@ -1345,13 +1540,13 @@ var promptCreateEnvironmentTool = {
1345
1540
  var promptCreateAssertionTool = {
1346
1541
  name: "prompt.create_assertion",
1347
1542
  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".',
1348
- inputSchema: z8.object({
1349
- requestId: z8.string(),
1350
- assertion: z8.object({
1351
- kind: z8.enum(["status", "header", "json-path", "duration"]),
1352
- op: z8.enum(["equals", "not-equals", "contains", "lt", "gt", "matches"]),
1353
- target: z8.string().optional(),
1354
- expected: z8.union([z8.string(), z8.number()])
1543
+ inputSchema: z9.object({
1544
+ requestId: z9.string(),
1545
+ assertion: z9.object({
1546
+ kind: z9.enum(["status", "header", "json-path", "duration"]),
1547
+ op: z9.enum(["equals", "not-equals", "contains", "lt", "gt", "matches"]),
1548
+ target: z9.string().optional(),
1549
+ expected: z9.union([z9.string(), z9.number()])
1355
1550
  })
1356
1551
  }),
1357
1552
  async handler(input, ctx) {
@@ -1370,10 +1565,10 @@ var promptCreateAssertionTool = {
1370
1565
  var promptCreatePlanTool = {
1371
1566
  name: "prompt.create_plan",
1372
1567
  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.",
1373
- inputSchema: z8.object({
1374
- name: z8.string(),
1375
- stepRequestIds: z8.array(z8.string()).default([]),
1376
- envPriorityOrder: z8.array(z8.string()).default([])
1568
+ inputSchema: z9.object({
1569
+ name: z9.string(),
1570
+ stepRequestIds: z9.array(z9.string()).default([]),
1571
+ envPriorityOrder: z9.array(z9.string()).default([])
1377
1572
  }),
1378
1573
  async handler(input, ctx) {
1379
1574
  const state = await ctx.workspace.read();
@@ -1402,51 +1597,51 @@ var promptCreatePlanTool = {
1402
1597
  return { ok: true, id, changedIds: out.changedIds };
1403
1598
  }
1404
1599
  };
1405
- var HTTP_METHOD2 = z8.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
1406
- var HEADER_OR_QUERY = z8.object({
1407
- key: z8.string(),
1408
- value: z8.string(),
1409
- enabled: z8.boolean().default(true)
1600
+ var HTTP_METHOD2 = z9.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
1601
+ var HEADER_OR_QUERY = z9.object({
1602
+ key: z9.string(),
1603
+ value: z9.string(),
1604
+ enabled: z9.boolean().default(true)
1410
1605
  });
1411
- var REQUEST_BODY = z8.object({
1412
- type: z8.enum(["none", "json", "text", "xml", "graphql", "urlencoded"]).default("none"),
1413
- content: z8.string().default(""),
1414
- variables: z8.string().optional()
1606
+ var REQUEST_BODY = z9.object({
1607
+ type: z9.enum(["none", "json", "text", "xml", "graphql", "urlencoded"]).default("none"),
1608
+ content: z9.string().default(""),
1609
+ variables: z9.string().optional()
1415
1610
  });
1416
- var PROMPT_AUTH = z8.discriminatedUnion("type", [
1417
- z8.object({ type: z8.literal("none") }),
1418
- z8.object({ type: z8.literal("inherit") }),
1419
- z8.object({ type: z8.literal("bearer"), token: z8.string().default("") }),
1420
- z8.object({
1421
- type: z8.literal("basic"),
1422
- username: z8.string().default(""),
1423
- password: z8.string().default("")
1611
+ var PROMPT_AUTH = z9.discriminatedUnion("type", [
1612
+ z9.object({ type: z9.literal("none") }),
1613
+ z9.object({ type: z9.literal("inherit") }),
1614
+ z9.object({ type: z9.literal("bearer"), token: z9.string().default("") }),
1615
+ z9.object({
1616
+ type: z9.literal("basic"),
1617
+ username: z9.string().default(""),
1618
+ password: z9.string().default("")
1424
1619
  }),
1425
- z8.object({
1426
- type: z8.literal("api-key"),
1427
- key: z8.string().default(""),
1428
- value: z8.string().default(""),
1429
- addTo: z8.enum(["header", "query", "cookie"]).default("header")
1620
+ z9.object({
1621
+ type: z9.literal("api-key"),
1622
+ key: z9.string().default(""),
1623
+ value: z9.string().default(""),
1624
+ addTo: z9.enum(["header", "query", "cookie"]).default("header")
1430
1625
  }),
1431
- z8.object({
1432
- type: z8.literal("custom-header"),
1433
- key: z8.string().default(""),
1434
- value: z8.string().default("")
1626
+ z9.object({
1627
+ type: z9.literal("custom-header"),
1628
+ key: z9.string().default(""),
1629
+ value: z9.string().default("")
1435
1630
  })
1436
1631
  ]);
1437
- var PROMPT_ASSERTION = z8.object({
1438
- kind: z8.enum(["status", "header", "json-path", "duration"]),
1439
- op: z8.enum(["equals", "not-equals", "contains", "lt", "gt", "matches"]),
1440
- target: z8.string().optional(),
1441
- expected: z8.union([z8.string(), z8.number()])
1632
+ var PROMPT_ASSERTION = z9.object({
1633
+ kind: z9.enum(["status", "header", "json-path", "duration"]),
1634
+ op: z9.enum(["equals", "not-equals", "contains", "lt", "gt", "matches"]),
1635
+ target: z9.string().optional(),
1636
+ expected: z9.union([z9.string(), z9.number()])
1442
1637
  });
1443
- var ENDPOINT_RESPONSE = z8.object({
1444
- status: z8.number().int().min(100).max(599).default(200),
1445
- jsonBody: z8.string().default("{}"),
1446
- contentType: z8.string().default("application/json")
1638
+ var ENDPOINT_RESPONSE = z9.object({
1639
+ status: z9.number().int().min(100).max(599).default(200),
1640
+ jsonBody: z9.string().default("{}"),
1641
+ contentType: z9.string().default("application/json")
1447
1642
  });
1448
- var VALIDATION_RULE_NL = z8.object({
1449
- kind: z8.enum([
1643
+ var VALIDATION_RULE_NL = z9.object({
1644
+ kind: z9.enum([
1450
1645
  "header-required",
1451
1646
  "header-equals",
1452
1647
  "header-matches",
@@ -1457,50 +1652,50 @@ var VALIDATION_RULE_NL = z8.object({
1457
1652
  "body-required",
1458
1653
  "content-type-equals"
1459
1654
  ]),
1460
- target: z8.string().default(""),
1461
- expected: z8.string().optional(),
1462
- message: z8.string().optional(),
1463
- enabled: z8.boolean().default(true),
1464
- failResponse: z8.object({
1465
- status: z8.number().int().min(100).max(599).default(400),
1466
- jsonBody: z8.string().default('{"error":"validation failed"}')
1655
+ target: z9.string().default(""),
1656
+ expected: z9.string().optional(),
1657
+ message: z9.string().optional(),
1658
+ enabled: z9.boolean().default(true),
1659
+ failResponse: z9.object({
1660
+ status: z9.number().int().min(100).max(599).default(400),
1661
+ jsonBody: z9.string().default('{"error":"validation failed"}')
1467
1662
  }).default({})
1468
1663
  });
1469
- var CONDITION_CLAUSE_NL = z8.object({
1470
- scope: z8.enum(["query", "pathParam", "header", "cookie", "body-json-path"]),
1471
- target: z8.string(),
1472
- op: z8.enum(["equals", "not-equals", "matches", "gt", "lt", "gte", "lte", "present", "absent"]),
1473
- value: z8.string().optional()
1664
+ var CONDITION_CLAUSE_NL = z9.object({
1665
+ scope: z9.enum(["query", "pathParam", "header", "cookie", "body-json-path"]),
1666
+ target: z9.string(),
1667
+ op: z9.enum(["equals", "not-equals", "matches", "gt", "lt", "gte", "lte", "present", "absent"]),
1668
+ value: z9.string().optional()
1474
1669
  });
1475
- var RESPONSE_RULE_NL = z8.object({
1476
- name: z8.string(),
1477
- enabled: z8.boolean().default(true),
1478
- when: z8.array(CONDITION_CLAUSE_NL).default([]),
1479
- response: z8.object({
1480
- status: z8.number().int().min(100).max(599).default(200),
1481
- jsonBody: z8.string().default("{}")
1670
+ var RESPONSE_RULE_NL = z9.object({
1671
+ name: z9.string(),
1672
+ enabled: z9.boolean().default(true),
1673
+ when: z9.array(CONDITION_CLAUSE_NL).default([]),
1674
+ response: z9.object({
1675
+ status: z9.number().int().min(100).max(599).default(200),
1676
+ jsonBody: z9.string().default("{}")
1482
1677
  }).default({})
1483
1678
  });
1484
- var MULTIPLIER_NL = z8.object({
1485
- name: z8.string().optional(),
1486
- source: z8.object({
1487
- kind: z8.enum(["query", "pathParam", "header", "body-json-path"]),
1488
- key: z8.string()
1679
+ var MULTIPLIER_NL = z9.object({
1680
+ name: z9.string().optional(),
1681
+ source: z9.object({
1682
+ kind: z9.enum(["query", "pathParam", "header", "body-json-path"]),
1683
+ key: z9.string()
1489
1684
  }),
1490
- targetJsonPath: z8.string(),
1491
- defaultCount: z8.number().int().nonnegative().default(0),
1492
- min: z8.number().int().nonnegative().optional(),
1493
- max: z8.number().int().nonnegative().optional()
1685
+ targetJsonPath: z9.string(),
1686
+ defaultCount: z9.number().int().nonnegative().default(0),
1687
+ min: z9.number().int().nonnegative().optional(),
1688
+ max: z9.number().int().nonnegative().optional()
1494
1689
  });
1495
- var ENDPOINT_INPUT = z8.object({
1690
+ var ENDPOINT_INPUT = z9.object({
1496
1691
  method: HTTP_METHOD2,
1497
- pathPattern: z8.string().min(1),
1498
- name: z8.string().optional(),
1499
- description: z8.string().optional(),
1692
+ pathPattern: z9.string().min(1),
1693
+ name: z9.string().optional(),
1694
+ description: z9.string().optional(),
1500
1695
  response: ENDPOINT_RESPONSE.optional(),
1501
- validationRules: z8.array(VALIDATION_RULE_NL).default([]),
1502
- responseRules: z8.array(RESPONSE_RULE_NL).default([]),
1503
- multipliers: z8.array(MULTIPLIER_NL).default([])
1696
+ validationRules: z9.array(VALIDATION_RULE_NL).default([]),
1697
+ responseRules: z9.array(RESPONSE_RULE_NL).default([]),
1698
+ multipliers: z9.array(MULTIPLIER_NL).default([])
1504
1699
  });
1505
1700
  function buildRequestBody(input) {
1506
1701
  if (!input) return { type: "none", content: "" };
@@ -1593,17 +1788,17 @@ function patchEndpoint(mock, endpointId, patcher) {
1593
1788
  var promptCreateRequestTool = {
1594
1789
  name: "prompt.create_request",
1595
1790
  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.",
1596
- inputSchema: z8.object({
1597
- name: z8.string().default("New request"),
1791
+ inputSchema: z9.object({
1792
+ name: z9.string().default("New request"),
1598
1793
  method: HTTP_METHOD2.default("GET"),
1599
- url: z8.string().default(""),
1600
- folderId: z8.string().nullable().optional(),
1601
- headers: z8.array(HEADER_OR_QUERY).default([]),
1602
- queryParams: z8.array(HEADER_OR_QUERY).default([]),
1603
- pathParams: z8.record(z8.string(), z8.string()).optional(),
1794
+ url: z9.string().default(""),
1795
+ folderId: z9.string().nullable().optional(),
1796
+ headers: z9.array(HEADER_OR_QUERY).default([]),
1797
+ queryParams: z9.array(HEADER_OR_QUERY).default([]),
1798
+ pathParams: z9.record(z9.string(), z9.string()).optional(),
1604
1799
  body: REQUEST_BODY.optional(),
1605
1800
  auth: PROMPT_AUTH.optional(),
1606
- assertions: z8.array(PROMPT_ASSERTION).default([])
1801
+ assertions: z9.array(PROMPT_ASSERTION).default([])
1607
1802
  }),
1608
1803
  async handler(input, ctx) {
1609
1804
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1633,19 +1828,19 @@ var promptCreateRequestTool = {
1633
1828
  var promptUpdateRequestTool = {
1634
1829
  name: "prompt.update_request",
1635
1830
  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.",
1636
- inputSchema: z8.object({
1637
- id: z8.string(),
1638
- patch: z8.object({
1639
- name: z8.string().optional(),
1831
+ inputSchema: z9.object({
1832
+ id: z9.string(),
1833
+ patch: z9.object({
1834
+ name: z9.string().optional(),
1640
1835
  method: HTTP_METHOD2.optional(),
1641
- url: z8.string().optional(),
1642
- folderId: z8.string().nullable().optional(),
1643
- headers: z8.array(HEADER_OR_QUERY).optional(),
1644
- queryParams: z8.array(HEADER_OR_QUERY).optional(),
1645
- pathParams: z8.record(z8.string(), z8.string()).optional(),
1836
+ url: z9.string().optional(),
1837
+ folderId: z9.string().nullable().optional(),
1838
+ headers: z9.array(HEADER_OR_QUERY).optional(),
1839
+ queryParams: z9.array(HEADER_OR_QUERY).optional(),
1840
+ pathParams: z9.record(z9.string(), z9.string()).optional(),
1646
1841
  body: REQUEST_BODY.optional(),
1647
1842
  auth: PROMPT_AUTH.optional(),
1648
- assertions: z8.array(PROMPT_ASSERTION).optional()
1843
+ assertions: z9.array(PROMPT_ASSERTION).optional()
1649
1844
  }).strict()
1650
1845
  }),
1651
1846
  async handler(input, ctx) {
@@ -1673,17 +1868,17 @@ var promptUpdateRequestTool = {
1673
1868
  return { ok: true, changedIds: out.changedIds };
1674
1869
  }
1675
1870
  };
1676
- var FOLDER_TREE_NODE = z8.lazy(
1677
- () => z8.object({
1678
- name: z8.string(),
1679
- children: z8.array(FOLDER_TREE_NODE).optional()
1871
+ var FOLDER_TREE_NODE = z9.lazy(
1872
+ () => z9.object({
1873
+ name: z9.string(),
1874
+ children: z9.array(FOLDER_TREE_NODE).optional()
1680
1875
  })
1681
1876
  );
1682
1877
  var promptCreateFolderTreeTool = {
1683
1878
  name: "prompt.create_folder_tree",
1684
1879
  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.",
1685
- inputSchema: z8.object({
1686
- parentId: z8.string().nullable().optional(),
1880
+ inputSchema: z9.object({
1881
+ parentId: z9.string().nullable().optional(),
1687
1882
  tree: FOLDER_TREE_NODE
1688
1883
  }),
1689
1884
  async handler(input, ctx) {
@@ -1709,9 +1904,9 @@ var promptCreateFolderTreeTool = {
1709
1904
  var promptAddPlanStepsTool = {
1710
1905
  name: "prompt.add_plan_steps",
1711
1906
  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.",
1712
- inputSchema: z8.object({
1713
- planId: z8.string(),
1714
- requestIds: z8.array(z8.string()).min(1)
1907
+ inputSchema: z9.object({
1908
+ planId: z9.string(),
1909
+ requestIds: z9.array(z9.string()).min(1)
1715
1910
  }),
1716
1911
  async handler(input, ctx) {
1717
1912
  const state = await ctx.workspace.read();
@@ -1741,9 +1936,9 @@ var promptAddPlanStepsTool = {
1741
1936
  var promptSetPlanVariablesTool = {
1742
1937
  name: "prompt.set_plan_variables",
1743
1938
  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.",
1744
- inputSchema: z8.object({
1745
- planId: z8.string(),
1746
- variables: z8.array(z8.object({ key: z8.string(), value: z8.string() }))
1939
+ inputSchema: z9.object({
1940
+ planId: z9.string(),
1941
+ variables: z9.array(z9.object({ key: z9.string(), value: z9.string() }))
1747
1942
  }),
1748
1943
  async handler(input, ctx) {
1749
1944
  const state = await ctx.workspace.read();
@@ -1759,10 +1954,10 @@ var promptSetPlanVariablesTool = {
1759
1954
  var promptCreateMockServerTool = {
1760
1955
  name: "prompt.create_mock_server",
1761
1956
  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.",
1762
- inputSchema: z8.object({
1763
- name: z8.string().min(1),
1764
- defaultPort: z8.number().int().positive().nullable().optional(),
1765
- endpoints: z8.array(ENDPOINT_INPUT).default([])
1957
+ inputSchema: z9.object({
1958
+ name: z9.string().min(1),
1959
+ defaultPort: z9.number().int().positive().nullable().optional(),
1960
+ endpoints: z9.array(ENDPOINT_INPUT).default([])
1766
1961
  }),
1767
1962
  async handler(input, ctx) {
1768
1963
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1789,16 +1984,16 @@ var promptCreateMockServerTool = {
1789
1984
  var promptAddMockEndpointTool = {
1790
1985
  name: "prompt.add_mock_endpoint",
1791
1986
  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.",
1792
- inputSchema: z8.object({
1793
- mockId: z8.string(),
1987
+ inputSchema: z9.object({
1988
+ mockId: z9.string(),
1794
1989
  method: HTTP_METHOD2,
1795
- pathPattern: z8.string().min(1),
1796
- name: z8.string().optional(),
1797
- description: z8.string().optional(),
1990
+ pathPattern: z9.string().min(1),
1991
+ name: z9.string().optional(),
1992
+ description: z9.string().optional(),
1798
1993
  response: ENDPOINT_RESPONSE.optional(),
1799
- validationRules: z8.array(VALIDATION_RULE_NL).default([]),
1800
- responseRules: z8.array(RESPONSE_RULE_NL).default([]),
1801
- multipliers: z8.array(MULTIPLIER_NL).default([])
1994
+ validationRules: z9.array(VALIDATION_RULE_NL).default([]),
1995
+ responseRules: z9.array(RESPONSE_RULE_NL).default([]),
1996
+ multipliers: z9.array(MULTIPLIER_NL).default([])
1802
1997
  }),
1803
1998
  async handler(input, ctx) {
1804
1999
  const state = await ctx.workspace.read();
@@ -1820,10 +2015,10 @@ var promptAddMockEndpointTool = {
1820
2015
  var promptSetEndpointValidationRulesTool = {
1821
2016
  name: "prompt.set_endpoint_validation_rules",
1822
2017
  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.",
1823
- inputSchema: z8.object({
1824
- mockId: z8.string(),
1825
- endpointId: z8.string(),
1826
- rules: z8.array(VALIDATION_RULE_NL)
2018
+ inputSchema: z9.object({
2019
+ mockId: z9.string(),
2020
+ endpointId: z9.string(),
2021
+ rules: z9.array(VALIDATION_RULE_NL)
1827
2022
  }),
1828
2023
  async handler(input, ctx) {
1829
2024
  const state = await ctx.workspace.read();
@@ -1854,10 +2049,10 @@ var promptSetEndpointValidationRulesTool = {
1854
2049
  var promptSetEndpointResponseRulesTool = {
1855
2050
  name: "prompt.set_endpoint_response_rules",
1856
2051
  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.",
1857
- inputSchema: z8.object({
1858
- mockId: z8.string(),
1859
- endpointId: z8.string(),
1860
- rules: z8.array(RESPONSE_RULE_NL)
2052
+ inputSchema: z9.object({
2053
+ mockId: z9.string(),
2054
+ endpointId: z9.string(),
2055
+ rules: z9.array(RESPONSE_RULE_NL)
1861
2056
  }),
1862
2057
  async handler(input, ctx) {
1863
2058
  const state = await ctx.workspace.read();
@@ -1892,10 +2087,10 @@ var promptSetEndpointResponseRulesTool = {
1892
2087
  var promptSetEndpointMultipliersTool = {
1893
2088
  name: "prompt.set_endpoint_multipliers",
1894
2089
  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.",
1895
- inputSchema: z8.object({
1896
- mockId: z8.string(),
1897
- endpointId: z8.string(),
1898
- multipliers: z8.array(MULTIPLIER_NL)
2090
+ inputSchema: z9.object({
2091
+ mockId: z9.string(),
2092
+ endpointId: z9.string(),
2093
+ multipliers: z9.array(MULTIPLIER_NL)
1899
2094
  }),
1900
2095
  async handler(input, ctx) {
1901
2096
  const state = await ctx.workspace.read();
@@ -1924,7 +2119,7 @@ var promptSetEndpointMultipliersTool = {
1924
2119
  };
1925
2120
 
1926
2121
  // src/tools/mocks.ts
1927
- import { z as z9 } from "zod";
2122
+ import { z as z10 } from "zod";
1928
2123
  import { generateId as generateId4, makeDefaultMockResponse as makeDefaultMockResponse2, makeDefaultRequestSchema as makeDefaultRequestSchema2 } from "@apicircle/shared";
1929
2124
  import { parseSourceToEndpoints } from "@apicircle/mock-server-core";
1930
2125
  async function ingestSource(source, name) {
@@ -1947,10 +2142,10 @@ async function ingestSource(source, name) {
1947
2142
  var mockCreateFromOpenApiTool = {
1948
2143
  name: "mock.create_from_openapi",
1949
2144
  description: "Create a mock server from an OpenAPI / Swagger spec (YAML or JSON).",
1950
- inputSchema: z9.object({
1951
- name: z9.string(),
1952
- spec: z9.string().min(1),
1953
- format: z9.enum(["json", "yaml"]).default("json")
2145
+ inputSchema: z10.object({
2146
+ name: z10.string(),
2147
+ spec: z10.string().min(1),
2148
+ format: z10.enum(["json", "yaml"]).default("json")
1954
2149
  }),
1955
2150
  async handler(input, ctx) {
1956
2151
  const { mock, warnings } = await ingestSource(
@@ -1969,7 +2164,7 @@ var mockCreateFromOpenApiTool = {
1969
2164
  var mockCreateFromPostmanTool = {
1970
2165
  name: "mock.create_from_postman",
1971
2166
  description: "Create a mock server from a Postman v2/v2.1 collection.",
1972
- inputSchema: z9.object({ name: z9.string(), collection: z9.string().min(1) }),
2167
+ inputSchema: z10.object({ name: z10.string(), collection: z10.string().min(1) }),
1973
2168
  async handler(input, ctx) {
1974
2169
  const { mock, warnings } = await ingestSource(
1975
2170
  { kind: "postman", collection: input.collection },
@@ -1987,7 +2182,7 @@ var mockCreateFromPostmanTool = {
1987
2182
  var mockCreateFromInsomniaTool = {
1988
2183
  name: "mock.create_from_insomnia",
1989
2184
  description: "Create a mock server from an Insomnia v4 export.",
1990
- inputSchema: z9.object({ name: z9.string(), export: z9.string().min(1) }),
2185
+ inputSchema: z10.object({ name: z10.string(), export: z10.string().min(1) }),
1991
2186
  async handler(input, ctx) {
1992
2187
  const { mock, warnings } = await ingestSource(
1993
2188
  { kind: "insomnia", export: input.export },
@@ -2005,7 +2200,7 @@ var mockCreateFromInsomniaTool = {
2005
2200
  var mockImportPostmanMockCollectionTool = {
2006
2201
  name: "mock.import_postman_mock_collection",
2007
2202
  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.",
2008
- inputSchema: z9.object({ name: z9.string(), collection: z9.string().min(1) }),
2203
+ inputSchema: z10.object({ name: z10.string(), collection: z10.string().min(1) }),
2009
2204
  async handler(input, ctx) {
2010
2205
  const { mock, warnings } = await ingestSource(
2011
2206
  { kind: "postman", collection: input.collection },
@@ -2023,7 +2218,7 @@ var mockImportPostmanMockCollectionTool = {
2023
2218
  var mockListTool = {
2024
2219
  name: "mock.list",
2025
2220
  description: "List all mock servers in the workspace plus their runtime status (running / stopped, port).",
2026
- inputSchema: z9.object({}),
2221
+ inputSchema: z10.object({}),
2027
2222
  async handler(_input, ctx) {
2028
2223
  const state = await ctx.workspace.read();
2029
2224
  const running = await ctx.mock.list();
@@ -2045,9 +2240,9 @@ var mockListTool = {
2045
2240
  var mockStartTool = {
2046
2241
  name: "mock.start",
2047
2242
  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.",
2048
- inputSchema: z9.object({
2049
- id: z9.string(),
2050
- port: z9.number().int().positive().optional()
2243
+ inputSchema: z10.object({
2244
+ id: z10.string(),
2245
+ port: z10.number().int().positive().optional()
2051
2246
  }),
2052
2247
  async handler(input, ctx) {
2053
2248
  const state = await ctx.workspace.read();
@@ -2064,7 +2259,7 @@ var mockStartTool = {
2064
2259
  var mockStopTool = {
2065
2260
  name: "mock.stop",
2066
2261
  description: "Stop a running mock server by id (no-op if not running).",
2067
- inputSchema: z9.object({ id: z9.string() }),
2262
+ inputSchema: z10.object({ id: z10.string() }),
2068
2263
  async handler(input, ctx) {
2069
2264
  try {
2070
2265
  await ctx.mock.stop(input.id);
@@ -2077,7 +2272,7 @@ var mockStopTool = {
2077
2272
  var mockDeleteTool = {
2078
2273
  name: "mock.delete",
2079
2274
  description: "Delete a mock server definition. Stops it first if it's running.",
2080
- inputSchema: z9.object({ id: z9.string() }),
2275
+ inputSchema: z10.object({ id: z10.string() }),
2081
2276
  async handler(input, ctx) {
2082
2277
  try {
2083
2278
  await ctx.mock.stop(input.id);
@@ -2087,13 +2282,13 @@ var mockDeleteTool = {
2087
2282
  return { ok: true, changedIds: out.changedIds };
2088
2283
  }
2089
2284
  };
2090
- var HTTP_METHOD3 = z9.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
2285
+ var HTTP_METHOD3 = z10.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
2091
2286
  var mockCreateManualTool = {
2092
2287
  name: "mock.create_manual",
2093
2288
  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.",
2094
- inputSchema: z9.object({
2095
- name: z9.string().min(1),
2096
- defaultPort: z9.number().int().positive().nullable().optional()
2289
+ inputSchema: z10.object({
2290
+ name: z10.string().min(1),
2291
+ defaultPort: z10.number().int().positive().nullable().optional()
2097
2292
  }),
2098
2293
  async handler(input, ctx) {
2099
2294
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -2116,7 +2311,7 @@ var mockCreateManualTool = {
2116
2311
  var mockListEndpointsTool = {
2117
2312
  name: "mock.list_endpoints",
2118
2313
  description: "List endpoints for a mock server (id, method, path, name).",
2119
- inputSchema: z9.object({ mockId: z9.string() }),
2314
+ inputSchema: z10.object({ mockId: z10.string() }),
2120
2315
  async handler(input, ctx) {
2121
2316
  const state = await ctx.workspace.read();
2122
2317
  const mock = state.synced.mockServers[input.mockId];
@@ -2135,10 +2330,10 @@ var mockListEndpointsTool = {
2135
2330
  };
2136
2331
  }
2137
2332
  };
2138
- var ENDPOINT_RESPONSE2 = z9.object({
2139
- status: z9.number().int().min(100).max(599).default(200),
2140
- jsonBody: z9.string().default("{}"),
2141
- contentType: z9.string().default("application/json")
2333
+ var ENDPOINT_RESPONSE2 = z10.object({
2334
+ status: z10.number().int().min(100).max(599).default(200),
2335
+ jsonBody: z10.string().default("{}"),
2336
+ contentType: z10.string().default("application/json")
2142
2337
  });
2143
2338
  function buildDefaultEndpoint(args) {
2144
2339
  const response = args.response ?? {
@@ -2167,12 +2362,12 @@ function buildDefaultEndpoint(args) {
2167
2362
  var mockAddEndpointTool = {
2168
2363
  name: "mock.add_endpoint",
2169
2364
  description: "Append a new endpoint to a mock server. Defaults to a 200 JSON response of `{}`. Returns the new endpoint id.",
2170
- inputSchema: z9.object({
2171
- mockId: z9.string(),
2365
+ inputSchema: z10.object({
2366
+ mockId: z10.string(),
2172
2367
  method: HTTP_METHOD3,
2173
- pathPattern: z9.string().min(1),
2174
- name: z9.string().optional(),
2175
- description: z9.string().optional(),
2368
+ pathPattern: z10.string().min(1),
2369
+ name: z10.string().optional(),
2370
+ description: z10.string().optional(),
2176
2371
  response: ENDPOINT_RESPONSE2.optional()
2177
2372
  }),
2178
2373
  async handler(input, ctx) {
@@ -2195,13 +2390,13 @@ var mockAddEndpointTool = {
2195
2390
  var mockUpdateEndpointTool = {
2196
2391
  name: "mock.update_endpoint",
2197
2392
  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.",
2198
- inputSchema: z9.object({
2199
- mockId: z9.string(),
2200
- endpointId: z9.string(),
2393
+ inputSchema: z10.object({
2394
+ mockId: z10.string(),
2395
+ endpointId: z10.string(),
2201
2396
  method: HTTP_METHOD3.optional(),
2202
- pathPattern: z9.string().optional(),
2203
- name: z9.string().optional(),
2204
- description: z9.string().optional(),
2397
+ pathPattern: z10.string().optional(),
2398
+ name: z10.string().optional(),
2399
+ description: z10.string().optional(),
2205
2400
  response: ENDPOINT_RESPONSE2.partial().optional()
2206
2401
  }),
2207
2402
  async handler(input, ctx) {
@@ -2242,7 +2437,7 @@ var mockUpdateEndpointTool = {
2242
2437
  var mockDeleteEndpointTool = {
2243
2438
  name: "mock.delete_endpoint",
2244
2439
  description: "Remove an endpoint from a mock server.",
2245
- inputSchema: z9.object({ mockId: z9.string(), endpointId: z9.string() }),
2440
+ inputSchema: z10.object({ mockId: z10.string(), endpointId: z10.string() }),
2246
2441
  async handler(input, ctx) {
2247
2442
  const state = await ctx.workspace.read();
2248
2443
  const mock = state.synced.mockServers[input.mockId];
@@ -2262,9 +2457,9 @@ var mockDeleteEndpointTool = {
2262
2457
  return { ok: true, changedIds: out.changedIds };
2263
2458
  }
2264
2459
  };
2265
- var VALIDATION_RULE = z9.object({
2266
- id: z9.string().optional(),
2267
- kind: z9.enum([
2460
+ var VALIDATION_RULE = z10.object({
2461
+ id: z10.string().optional(),
2462
+ kind: z10.enum([
2268
2463
  "header-required",
2269
2464
  "header-equals",
2270
2465
  "header-matches",
@@ -2275,43 +2470,43 @@ var VALIDATION_RULE = z9.object({
2275
2470
  "body-required",
2276
2471
  "content-type-equals"
2277
2472
  ]),
2278
- target: z9.string().default(""),
2279
- expected: z9.string().optional(),
2280
- message: z9.string().optional(),
2281
- enabled: z9.boolean().default(true),
2282
- failResponse: z9.object({
2283
- status: z9.number().int().min(100).max(599).default(400),
2284
- jsonBody: z9.string().default('{"error":"validation failed"}')
2473
+ target: z10.string().default(""),
2474
+ expected: z10.string().optional(),
2475
+ message: z10.string().optional(),
2476
+ enabled: z10.boolean().default(true),
2477
+ failResponse: z10.object({
2478
+ status: z10.number().int().min(100).max(599).default(400),
2479
+ jsonBody: z10.string().default('{"error":"validation failed"}')
2285
2480
  }).default({})
2286
2481
  });
2287
- var CONDITION_CLAUSE = z9.object({
2288
- id: z9.string().optional(),
2289
- scope: z9.enum(["query", "pathParam", "header", "cookie", "body-json-path"]),
2290
- target: z9.string(),
2291
- op: z9.enum(["equals", "not-equals", "matches", "gt", "lt", "gte", "lte", "present", "absent"]),
2292
- value: z9.string().optional()
2482
+ var CONDITION_CLAUSE = z10.object({
2483
+ id: z10.string().optional(),
2484
+ scope: z10.enum(["query", "pathParam", "header", "cookie", "body-json-path"]),
2485
+ target: z10.string(),
2486
+ op: z10.enum(["equals", "not-equals", "matches", "gt", "lt", "gte", "lte", "present", "absent"]),
2487
+ value: z10.string().optional()
2293
2488
  });
2294
- var RESPONSE_RULE = z9.object({
2295
- id: z9.string().optional(),
2296
- name: z9.string(),
2297
- enabled: z9.boolean().default(true),
2298
- when: z9.array(CONDITION_CLAUSE).default([]),
2299
- response: z9.object({
2300
- status: z9.number().int().min(100).max(599).default(200),
2301
- jsonBody: z9.string().default("{}")
2489
+ var RESPONSE_RULE = z10.object({
2490
+ id: z10.string().optional(),
2491
+ name: z10.string(),
2492
+ enabled: z10.boolean().default(true),
2493
+ when: z10.array(CONDITION_CLAUSE).default([]),
2494
+ response: z10.object({
2495
+ status: z10.number().int().min(100).max(599).default(200),
2496
+ jsonBody: z10.string().default("{}")
2302
2497
  }).default({})
2303
2498
  });
2304
- var MULTIPLIER = z9.object({
2305
- id: z9.string().optional(),
2306
- name: z9.string().optional(),
2307
- source: z9.object({
2308
- kind: z9.enum(["query", "pathParam", "header", "body-json-path"]),
2309
- key: z9.string()
2499
+ var MULTIPLIER = z10.object({
2500
+ id: z10.string().optional(),
2501
+ name: z10.string().optional(),
2502
+ source: z10.object({
2503
+ kind: z10.enum(["query", "pathParam", "header", "body-json-path"]),
2504
+ key: z10.string()
2310
2505
  }),
2311
- targetJsonPath: z9.string(),
2312
- defaultCount: z9.number().int().nonnegative().default(0),
2313
- min: z9.number().int().nonnegative().optional(),
2314
- max: z9.number().int().nonnegative().optional()
2506
+ targetJsonPath: z10.string(),
2507
+ defaultCount: z10.number().int().nonnegative().default(0),
2508
+ min: z10.number().int().nonnegative().optional(),
2509
+ max: z10.number().int().nonnegative().optional()
2315
2510
  });
2316
2511
  function defaultJsonResponseConfig(args) {
2317
2512
  return {
@@ -2336,10 +2531,10 @@ function patchEndpoint2(mock, endpointId, patcher) {
2336
2531
  var mockSetValidationRulesTool = {
2337
2532
  name: "mock.set_validation_rules",
2338
2533
  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.",
2339
- inputSchema: z9.object({
2340
- mockId: z9.string(),
2341
- endpointId: z9.string(),
2342
- rules: z9.array(VALIDATION_RULE)
2534
+ inputSchema: z10.object({
2535
+ mockId: z10.string(),
2536
+ endpointId: z10.string(),
2537
+ rules: z10.array(VALIDATION_RULE)
2343
2538
  }),
2344
2539
  async handler(input, ctx) {
2345
2540
  const state = await ctx.workspace.read();
@@ -2366,10 +2561,10 @@ var mockSetValidationRulesTool = {
2366
2561
  var mockSetResponseRulesTool = {
2367
2562
  name: "mock.set_response_rules",
2368
2563
  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.",
2369
- inputSchema: z9.object({
2370
- mockId: z9.string(),
2371
- endpointId: z9.string(),
2372
- rules: z9.array(RESPONSE_RULE)
2564
+ inputSchema: z10.object({
2565
+ mockId: z10.string(),
2566
+ endpointId: z10.string(),
2567
+ rules: z10.array(RESPONSE_RULE)
2373
2568
  }),
2374
2569
  async handler(input, ctx) {
2375
2570
  const state = await ctx.workspace.read();
@@ -2400,10 +2595,10 @@ var mockSetResponseRulesTool = {
2400
2595
  var mockSetMultipliersTool = {
2401
2596
  name: "mock.set_multipliers",
2402
2597
  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.",
2403
- inputSchema: z9.object({
2404
- mockId: z9.string(),
2405
- endpointId: z9.string(),
2406
- multipliers: z9.array(MULTIPLIER)
2598
+ inputSchema: z10.object({
2599
+ mockId: z10.string(),
2600
+ endpointId: z10.string(),
2601
+ multipliers: z10.array(MULTIPLIER)
2407
2602
  }),
2408
2603
  async handler(input, ctx) {
2409
2604
  const state = await ctx.workspace.read();
@@ -2450,6 +2645,8 @@ var TOOL_REGISTRY = [
2450
2645
  folderReadTool,
2451
2646
  folderUpdateTool,
2452
2647
  folderDeleteTool,
2648
+ folderExportJsonTool,
2649
+ folderImportJsonTool,
2453
2650
  environmentCreateTool,
2454
2651
  environmentReadTool,
2455
2652
  environmentUpdateTool,
@@ -2635,17 +2832,58 @@ import {
2635
2832
  setActiveWorkspace as setActiveWorkspaceOnDisk,
2636
2833
  workspaceDirFor
2637
2834
  } from "@apicircle/core/workspace/registry";
2835
+ var LazyActiveWorkspaceProvider = class {
2836
+ constructor(registryRoot, onActiveResolved) {
2837
+ this.registryRoot = registryRoot;
2838
+ this.onActiveResolved = onActiveResolved;
2839
+ }
2840
+ registryRoot;
2841
+ onActiveResolved;
2842
+ async resolveActive() {
2843
+ const registry = await loadRegistry(this.registryRoot);
2844
+ const activeId = registry?.activeWorkspaceId ?? null;
2845
+ if (!activeId) {
2846
+ throw new Error(
2847
+ "No active workspace. Open the desktop app at least once, or run `apicircle workspaces create <name>`."
2848
+ );
2849
+ }
2850
+ this.onActiveResolved(activeId);
2851
+ return new FileBackedWorkspaceProvider(workspaceDirFor(this.registryRoot, activeId));
2852
+ }
2853
+ async read() {
2854
+ const provider = await this.resolveActive();
2855
+ return provider.read();
2856
+ }
2857
+ async apply(patch) {
2858
+ const provider = await this.resolveActive();
2859
+ return provider.apply(patch);
2860
+ }
2861
+ async write(next) {
2862
+ const provider = await this.resolveActive();
2863
+ return provider.write(next);
2864
+ }
2865
+ };
2638
2866
  var MultiWorkspaceProvider = class {
2639
2867
  constructor(registryRoot) {
2640
2868
  this.registryRoot = registryRoot;
2869
+ this.lazyProvider = new LazyActiveWorkspaceProvider(this.registryRoot, (id) => {
2870
+ this.activeWorkspaceId = id;
2871
+ });
2641
2872
  }
2642
2873
  registryRoot;
2643
- active = null;
2874
+ /** Last-known active workspace id. Refreshed every time the lazy
2875
+ * provider resolves; reflects what the most recent operation saw on
2876
+ * disk, not a stale boot-time snapshot. */
2644
2877
  activeWorkspaceId = null;
2878
+ /** The lazy provider tool handlers consume as `ctx.workspace`. Holds a
2879
+ * reference back to this instance so each call updates
2880
+ * `activeWorkspaceId` for `activeId()` callers + diagnostic logs. */
2881
+ lazyProvider;
2645
2882
  /**
2646
- * Hydrate the active provider from disk. Must be called once before the
2647
- * MCP host boots so `ctx.workspace.read()` doesn't race the first
2648
- * registry-load. Returns the registry the boot can log.
2883
+ * Read the registry from disk so the host can log a boot banner. Does
2884
+ * NOT cache a per-id provider — each `activeProvider()` call re-reads
2885
+ * the registry, so a workspace switch in the desktop is picked up by
2886
+ * the next tool call without restarting the MCP server.
2649
2887
  */
2650
2888
  async init() {
2651
2889
  const registry = await loadRegistry(this.registryRoot) ?? {
@@ -2653,22 +2891,18 @@ var MultiWorkspaceProvider = class {
2653
2891
  activeWorkspaceId: null,
2654
2892
  workspaces: []
2655
2893
  };
2656
- if (registry.activeWorkspaceId) {
2657
- this.activeWorkspaceId = registry.activeWorkspaceId;
2658
- this.active = new FileBackedWorkspaceProvider(
2659
- workspaceDirFor(this.registryRoot, registry.activeWorkspaceId)
2660
- );
2661
- }
2894
+ this.activeWorkspaceId = registry.activeWorkspaceId;
2662
2895
  return registry;
2663
2896
  }
2664
- /** The provider tool handlers see as `ctx.workspace`. */
2897
+ /**
2898
+ * The provider tool handlers see as `ctx.workspace`. Returns a lazy
2899
+ * provider whose `read` / `apply` / `write` calls re-read
2900
+ * `registry.json` so the right active workspace is always targeted
2901
+ * even if the desktop switched workspaces since this MCP process
2902
+ * started.
2903
+ */
2665
2904
  activeProvider() {
2666
- if (!this.active) {
2667
- throw new Error(
2668
- "No active workspace. Open the desktop app at least once, or run `apicircle workspaces create <name>`."
2669
- );
2670
- }
2671
- return this.active;
2905
+ return this.lazyProvider;
2672
2906
  }
2673
2907
  // ─── Workspaces interface ──────────────────────────────────────────────────
2674
2908
  async list() {
@@ -2716,23 +2950,17 @@ var MultiWorkspaceProvider = class {
2716
2950
  if (!registry || !registry.workspaces.some((w) => w.id === workspaceId)) {
2717
2951
  throw new WorkspaceNotFoundError(workspaceId);
2718
2952
  }
2719
- const next = await setActiveWorkspaceOnDisk(this.registryRoot, workspaceId);
2720
- void next;
2953
+ await setActiveWorkspaceOnDisk(this.registryRoot, workspaceId);
2721
2954
  this.activeWorkspaceId = workspaceId;
2722
- this.active = new FileBackedWorkspaceProvider(workspaceDirFor(this.registryRoot, workspaceId));
2723
2955
  }
2724
2956
  /**
2725
2957
  * Idempotent registry write — used by tests / tools that need to
2726
- * persist registry updates that didn't go through `setActive`.
2958
+ * persist registry updates that didn't go through `setActive`. The
2959
+ * lazy active provider picks the new id up on its next operation.
2727
2960
  */
2728
2961
  async writeRegistry(registry) {
2729
2962
  await saveRegistry(this.registryRoot, registry);
2730
2963
  this.activeWorkspaceId = registry.activeWorkspaceId;
2731
- if (registry.activeWorkspaceId) {
2732
- this.active = new FileBackedWorkspaceProvider(
2733
- workspaceDirFor(this.registryRoot, registry.activeWorkspaceId)
2734
- );
2735
- }
2736
2964
  }
2737
2965
  };
2738
2966