@apicircle/mcp-server 1.0.7 → 1.0.9

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,7 +6,7 @@ import { z } from "zod";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@apicircle/mcp-server",
9
- version: "1.0.7",
9
+ version: "1.0.9",
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.",
@@ -2118,15 +2118,143 @@ var promptSetEndpointMultipliersTool = {
2118
2118
  }
2119
2119
  };
2120
2120
 
2121
- // src/tools/mocks.ts
2121
+ // src/tools/globalAssets.ts
2122
2122
  import { z as z10 } from "zod";
2123
- import { generateId as generateId4, makeDefaultMockResponse as makeDefaultMockResponse2, makeDefaultRequestSchema as makeDefaultRequestSchema2 } from "@apicircle/shared";
2123
+ import { generateId as generateId4 } from "@apicircle/shared";
2124
+ function deriveState(asset, hasPendingUpload) {
2125
+ const w = asset.workingBranchRef ?? null;
2126
+ const b = asset.baseBranchRef ?? null;
2127
+ if (w && b) {
2128
+ if (w.blobSha && b.blobSha && w.blobSha !== b.blobSha) return "diverged";
2129
+ return "merged";
2130
+ }
2131
+ if (w && !b) return "workingOnly";
2132
+ if (!w && b) return "baseOnly";
2133
+ if (hasPendingUpload) return "uploading";
2134
+ return "missing";
2135
+ }
2136
+ var globalAssetsFilesListTool = {
2137
+ name: "assets.list_files",
2138
+ description: "List every Global File Asset with its provenance state and reference count. Each entry includes id, name, filename, size, mimeType, sha256, state (uploading | workingOnly | merged | baseOnly | missing | diverged), workingBranchRef, baseBranchRef, and usage { requests, mockEndpoints, total }.",
2139
+ inputSchema: z10.object({}).strict(),
2140
+ async handler(_input, ctx) {
2141
+ const state = await ctx.workspace.read();
2142
+ const files = state.synced.globalAssets.files ?? {};
2143
+ const pending = state.local.pendingFileUploads ?? {};
2144
+ const usage = state.local.assetUsageIndex ?? {};
2145
+ const items = Object.values(files).map((asset) => {
2146
+ const hasPending = Boolean(pending[asset.id]);
2147
+ const u = usage[asset.id] ?? { requests: [], mockEndpoints: [], total: 0 };
2148
+ return {
2149
+ id: asset.id,
2150
+ name: asset.name,
2151
+ description: asset.description ?? null,
2152
+ filename: asset.filename,
2153
+ size: asset.size,
2154
+ mimeType: asset.mimeType,
2155
+ sha256: asset.sha256 ?? null,
2156
+ state: deriveState(asset, hasPending),
2157
+ workingBranchRef: asset.workingBranchRef ?? null,
2158
+ baseBranchRef: asset.baseBranchRef ?? null,
2159
+ usage: { ...u }
2160
+ };
2161
+ });
2162
+ return { count: items.length, files: items };
2163
+ }
2164
+ };
2165
+ var globalAssetsFilesCreateTool = {
2166
+ name: "assets.create_file",
2167
+ description: 'Register a Global File Asset entry. Bytes are NOT carried \u2014 MCP returns the new asset id and the asset enters the "missing" state. The user fills the bytes from the Global Assets panel (a "Fill bytes" button surfaces on missing-state assets) which preserves the slot id and queues the bytes for the next push. Use this when an AI client wants to claim an asset slot for a file the user will provide later.',
2168
+ inputSchema: z10.object({
2169
+ name: z10.string().min(1, "name is required"),
2170
+ description: z10.string().optional(),
2171
+ filename: z10.string().min(1, "filename is required"),
2172
+ size: z10.number().int().nonnegative(),
2173
+ mimeType: z10.string().default("application/octet-stream"),
2174
+ sha256: z10.string().optional()
2175
+ }).strict(),
2176
+ async handler(input, ctx) {
2177
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2178
+ const file = {
2179
+ id: generateId4(),
2180
+ name: input.name,
2181
+ description: input.description,
2182
+ slotId: generateId4(),
2183
+ filename: input.filename,
2184
+ size: input.size,
2185
+ mimeType: input.mimeType,
2186
+ sha256: input.sha256,
2187
+ createdAt: now,
2188
+ updatedAt: now
2189
+ };
2190
+ const out = await ctx.workspace.apply({ kind: "globalAsset.upsertFile", file });
2191
+ return { id: file.id, slotId: file.slotId, changedIds: out.changedIds };
2192
+ }
2193
+ };
2194
+ var globalAssetsFilesUpdateTool = {
2195
+ name: "assets.update_file",
2196
+ description: "Rename or re-describe a Global File Asset. Provenance refs (workingBranchRef, baseBranchRef) and binary metadata (slotId, sha256, size, mimeType) are preserved verbatim.",
2197
+ inputSchema: z10.object({
2198
+ id: z10.string().min(1),
2199
+ patch: z10.object({
2200
+ name: z10.string().optional(),
2201
+ description: z10.string().nullable().optional()
2202
+ }).strict()
2203
+ }).strict(),
2204
+ async handler(input, ctx) {
2205
+ const state = await ctx.workspace.read();
2206
+ const existing = state.synced.globalAssets.files?.[input.id];
2207
+ if (!existing) {
2208
+ return { found: false, changedIds: [] };
2209
+ }
2210
+ const next = {
2211
+ ...existing,
2212
+ name: input.patch.name ?? existing.name,
2213
+ description: input.patch.description === null ? void 0 : input.patch.description ?? existing.description
2214
+ };
2215
+ const out = await ctx.workspace.apply({ kind: "globalAsset.upsertFile", file: next });
2216
+ return { found: true, id: next.id, changedIds: out.changedIds };
2217
+ }
2218
+ };
2219
+ var globalAssetsFilesDeleteTool = {
2220
+ name: "assets.delete_file",
2221
+ description: "Delete a Global File Asset. Cascades \u2014 every request body and mock-response body that referenced the asset is unbound in the same mutation. The result envelope includes the consumer list that was cleared so the caller can report what changed.",
2222
+ inputSchema: z10.object({ id: z10.string().min(1) }).strict(),
2223
+ async handler(input, ctx) {
2224
+ const before = await ctx.workspace.read();
2225
+ const usage = before.local.assetUsageIndex?.[input.id] ?? {
2226
+ requests: [],
2227
+ mockEndpoints: [],
2228
+ total: 0
2229
+ };
2230
+ const existing = before.synced.globalAssets.files?.[input.id];
2231
+ if (!existing) {
2232
+ return { found: false, changedIds: [] };
2233
+ }
2234
+ const out = await ctx.workspace.apply({ kind: "globalAsset.removeFile", id: input.id });
2235
+ return {
2236
+ found: true,
2237
+ id: input.id,
2238
+ filename: existing.filename,
2239
+ unbound: {
2240
+ requests: usage.requests,
2241
+ mockEndpoints: usage.mockEndpoints,
2242
+ total: usage.total
2243
+ },
2244
+ changedIds: out.changedIds
2245
+ };
2246
+ }
2247
+ };
2248
+
2249
+ // src/tools/mocks.ts
2250
+ import { z as z11 } from "zod";
2251
+ import { generateId as generateId5, makeDefaultMockResponse as makeDefaultMockResponse2, makeDefaultRequestSchema as makeDefaultRequestSchema2 } from "@apicircle/shared";
2124
2252
  import { parseSourceToEndpoints } from "@apicircle/mock-server-core";
2125
2253
  async function ingestSource(source, name) {
2126
2254
  const { endpoints, warnings } = await parseSourceToEndpoints(source);
2127
2255
  const now = (/* @__PURE__ */ new Date()).toISOString();
2128
2256
  const mock = {
2129
- id: generateId4(),
2257
+ id: generateId5(),
2130
2258
  name,
2131
2259
  source,
2132
2260
  endpoints,
@@ -2142,10 +2270,10 @@ async function ingestSource(source, name) {
2142
2270
  var mockCreateFromOpenApiTool = {
2143
2271
  name: "mock.create_from_openapi",
2144
2272
  description: "Create a mock server from an OpenAPI / Swagger spec (YAML or JSON).",
2145
- inputSchema: z10.object({
2146
- name: z10.string(),
2147
- spec: z10.string().min(1),
2148
- format: z10.enum(["json", "yaml"]).default("json")
2273
+ inputSchema: z11.object({
2274
+ name: z11.string(),
2275
+ spec: z11.string().min(1),
2276
+ format: z11.enum(["json", "yaml"]).default("json")
2149
2277
  }),
2150
2278
  async handler(input, ctx) {
2151
2279
  const { mock, warnings } = await ingestSource(
@@ -2164,7 +2292,7 @@ var mockCreateFromOpenApiTool = {
2164
2292
  var mockCreateFromPostmanTool = {
2165
2293
  name: "mock.create_from_postman",
2166
2294
  description: "Create a mock server from a Postman v2/v2.1 collection.",
2167
- inputSchema: z10.object({ name: z10.string(), collection: z10.string().min(1) }),
2295
+ inputSchema: z11.object({ name: z11.string(), collection: z11.string().min(1) }),
2168
2296
  async handler(input, ctx) {
2169
2297
  const { mock, warnings } = await ingestSource(
2170
2298
  { kind: "postman", collection: input.collection },
@@ -2182,7 +2310,7 @@ var mockCreateFromPostmanTool = {
2182
2310
  var mockCreateFromInsomniaTool = {
2183
2311
  name: "mock.create_from_insomnia",
2184
2312
  description: "Create a mock server from an Insomnia v4 export.",
2185
- inputSchema: z10.object({ name: z10.string(), export: z10.string().min(1) }),
2313
+ inputSchema: z11.object({ name: z11.string(), export: z11.string().min(1) }),
2186
2314
  async handler(input, ctx) {
2187
2315
  const { mock, warnings } = await ingestSource(
2188
2316
  { kind: "insomnia", export: input.export },
@@ -2200,7 +2328,7 @@ var mockCreateFromInsomniaTool = {
2200
2328
  var mockImportPostmanMockCollectionTool = {
2201
2329
  name: "mock.import_postman_mock_collection",
2202
2330
  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.",
2203
- inputSchema: z10.object({ name: z10.string(), collection: z10.string().min(1) }),
2331
+ inputSchema: z11.object({ name: z11.string(), collection: z11.string().min(1) }),
2204
2332
  async handler(input, ctx) {
2205
2333
  const { mock, warnings } = await ingestSource(
2206
2334
  { kind: "postman", collection: input.collection },
@@ -2218,7 +2346,7 @@ var mockImportPostmanMockCollectionTool = {
2218
2346
  var mockListTool = {
2219
2347
  name: "mock.list",
2220
2348
  description: "List all mock servers in the workspace plus their runtime status (running / stopped, port).",
2221
- inputSchema: z10.object({}),
2349
+ inputSchema: z11.object({}),
2222
2350
  async handler(_input, ctx) {
2223
2351
  const state = await ctx.workspace.read();
2224
2352
  const running = await ctx.mock.list();
@@ -2240,9 +2368,9 @@ var mockListTool = {
2240
2368
  var mockStartTool = {
2241
2369
  name: "mock.start",
2242
2370
  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.",
2243
- inputSchema: z10.object({
2244
- id: z10.string(),
2245
- port: z10.number().int().positive().optional()
2371
+ inputSchema: z11.object({
2372
+ id: z11.string(),
2373
+ port: z11.number().int().positive().optional()
2246
2374
  }),
2247
2375
  async handler(input, ctx) {
2248
2376
  const state = await ctx.workspace.read();
@@ -2259,7 +2387,7 @@ var mockStartTool = {
2259
2387
  var mockStopTool = {
2260
2388
  name: "mock.stop",
2261
2389
  description: "Stop a running mock server by id (no-op if not running).",
2262
- inputSchema: z10.object({ id: z10.string() }),
2390
+ inputSchema: z11.object({ id: z11.string() }),
2263
2391
  async handler(input, ctx) {
2264
2392
  try {
2265
2393
  await ctx.mock.stop(input.id);
@@ -2272,7 +2400,7 @@ var mockStopTool = {
2272
2400
  var mockDeleteTool = {
2273
2401
  name: "mock.delete",
2274
2402
  description: "Delete a mock server definition. Stops it first if it's running.",
2275
- inputSchema: z10.object({ id: z10.string() }),
2403
+ inputSchema: z11.object({ id: z11.string() }),
2276
2404
  async handler(input, ctx) {
2277
2405
  try {
2278
2406
  await ctx.mock.stop(input.id);
@@ -2282,18 +2410,18 @@ var mockDeleteTool = {
2282
2410
  return { ok: true, changedIds: out.changedIds };
2283
2411
  }
2284
2412
  };
2285
- var HTTP_METHOD3 = z10.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
2413
+ var HTTP_METHOD3 = z11.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
2286
2414
  var mockCreateManualTool = {
2287
2415
  name: "mock.create_manual",
2288
2416
  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.",
2289
- inputSchema: z10.object({
2290
- name: z10.string().min(1),
2291
- defaultPort: z10.number().int().positive().nullable().optional()
2417
+ inputSchema: z11.object({
2418
+ name: z11.string().min(1),
2419
+ defaultPort: z11.number().int().positive().nullable().optional()
2292
2420
  }),
2293
2421
  async handler(input, ctx) {
2294
2422
  const now = (/* @__PURE__ */ new Date()).toISOString();
2295
2423
  const mock = {
2296
- id: generateId4(),
2424
+ id: generateId5(),
2297
2425
  name: input.name,
2298
2426
  source: { kind: "manual", endpoints: [] },
2299
2427
  endpoints: [],
@@ -2311,7 +2439,7 @@ var mockCreateManualTool = {
2311
2439
  var mockListEndpointsTool = {
2312
2440
  name: "mock.list_endpoints",
2313
2441
  description: "List endpoints for a mock server (id, method, path, name).",
2314
- inputSchema: z10.object({ mockId: z10.string() }),
2442
+ inputSchema: z11.object({ mockId: z11.string() }),
2315
2443
  async handler(input, ctx) {
2316
2444
  const state = await ctx.workspace.read();
2317
2445
  const mock = state.synced.mockServers[input.mockId];
@@ -2330,10 +2458,10 @@ var mockListEndpointsTool = {
2330
2458
  };
2331
2459
  }
2332
2460
  };
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")
2461
+ var ENDPOINT_RESPONSE2 = z11.object({
2462
+ status: z11.number().int().min(100).max(599).default(200),
2463
+ jsonBody: z11.string().default("{}"),
2464
+ contentType: z11.string().default("application/json")
2337
2465
  });
2338
2466
  function buildDefaultEndpoint(args) {
2339
2467
  const response = args.response ?? {
@@ -2343,7 +2471,7 @@ function buildDefaultEndpoint(args) {
2343
2471
  };
2344
2472
  const headers = [{ key: "Content-Type", value: response.contentType, enabled: true }];
2345
2473
  return {
2346
- id: generateId4(),
2474
+ id: generateId5(),
2347
2475
  name: args.name ?? `${args.method} ${args.pathPattern}`,
2348
2476
  method: args.method,
2349
2477
  pathPattern: args.pathPattern,
@@ -2362,12 +2490,12 @@ function buildDefaultEndpoint(args) {
2362
2490
  var mockAddEndpointTool = {
2363
2491
  name: "mock.add_endpoint",
2364
2492
  description: "Append a new endpoint to a mock server. Defaults to a 200 JSON response of `{}`. Returns the new endpoint id.",
2365
- inputSchema: z10.object({
2366
- mockId: z10.string(),
2493
+ inputSchema: z11.object({
2494
+ mockId: z11.string(),
2367
2495
  method: HTTP_METHOD3,
2368
- pathPattern: z10.string().min(1),
2369
- name: z10.string().optional(),
2370
- description: z10.string().optional(),
2496
+ pathPattern: z11.string().min(1),
2497
+ name: z11.string().optional(),
2498
+ description: z11.string().optional(),
2371
2499
  response: ENDPOINT_RESPONSE2.optional()
2372
2500
  }),
2373
2501
  async handler(input, ctx) {
@@ -2390,13 +2518,13 @@ var mockAddEndpointTool = {
2390
2518
  var mockUpdateEndpointTool = {
2391
2519
  name: "mock.update_endpoint",
2392
2520
  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.",
2393
- inputSchema: z10.object({
2394
- mockId: z10.string(),
2395
- endpointId: z10.string(),
2521
+ inputSchema: z11.object({
2522
+ mockId: z11.string(),
2523
+ endpointId: z11.string(),
2396
2524
  method: HTTP_METHOD3.optional(),
2397
- pathPattern: z10.string().optional(),
2398
- name: z10.string().optional(),
2399
- description: z10.string().optional(),
2525
+ pathPattern: z11.string().optional(),
2526
+ name: z11.string().optional(),
2527
+ description: z11.string().optional(),
2400
2528
  response: ENDPOINT_RESPONSE2.partial().optional()
2401
2529
  }),
2402
2530
  async handler(input, ctx) {
@@ -2437,7 +2565,7 @@ var mockUpdateEndpointTool = {
2437
2565
  var mockDeleteEndpointTool = {
2438
2566
  name: "mock.delete_endpoint",
2439
2567
  description: "Remove an endpoint from a mock server.",
2440
- inputSchema: z10.object({ mockId: z10.string(), endpointId: z10.string() }),
2568
+ inputSchema: z11.object({ mockId: z11.string(), endpointId: z11.string() }),
2441
2569
  async handler(input, ctx) {
2442
2570
  const state = await ctx.workspace.read();
2443
2571
  const mock = state.synced.mockServers[input.mockId];
@@ -2457,9 +2585,9 @@ var mockDeleteEndpointTool = {
2457
2585
  return { ok: true, changedIds: out.changedIds };
2458
2586
  }
2459
2587
  };
2460
- var VALIDATION_RULE = z10.object({
2461
- id: z10.string().optional(),
2462
- kind: z10.enum([
2588
+ var VALIDATION_RULE = z11.object({
2589
+ id: z11.string().optional(),
2590
+ kind: z11.enum([
2463
2591
  "header-required",
2464
2592
  "header-equals",
2465
2593
  "header-matches",
@@ -2470,43 +2598,43 @@ var VALIDATION_RULE = z10.object({
2470
2598
  "body-required",
2471
2599
  "content-type-equals"
2472
2600
  ]),
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"}')
2601
+ target: z11.string().default(""),
2602
+ expected: z11.string().optional(),
2603
+ message: z11.string().optional(),
2604
+ enabled: z11.boolean().default(true),
2605
+ failResponse: z11.object({
2606
+ status: z11.number().int().min(100).max(599).default(400),
2607
+ jsonBody: z11.string().default('{"error":"validation failed"}')
2480
2608
  }).default({})
2481
2609
  });
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()
2610
+ var CONDITION_CLAUSE = z11.object({
2611
+ id: z11.string().optional(),
2612
+ scope: z11.enum(["query", "pathParam", "header", "cookie", "body-json-path"]),
2613
+ target: z11.string(),
2614
+ op: z11.enum(["equals", "not-equals", "matches", "gt", "lt", "gte", "lte", "present", "absent"]),
2615
+ value: z11.string().optional()
2488
2616
  });
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("{}")
2617
+ var RESPONSE_RULE = z11.object({
2618
+ id: z11.string().optional(),
2619
+ name: z11.string(),
2620
+ enabled: z11.boolean().default(true),
2621
+ when: z11.array(CONDITION_CLAUSE).default([]),
2622
+ response: z11.object({
2623
+ status: z11.number().int().min(100).max(599).default(200),
2624
+ jsonBody: z11.string().default("{}")
2497
2625
  }).default({})
2498
2626
  });
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()
2627
+ var MULTIPLIER = z11.object({
2628
+ id: z11.string().optional(),
2629
+ name: z11.string().optional(),
2630
+ source: z11.object({
2631
+ kind: z11.enum(["query", "pathParam", "header", "body-json-path"]),
2632
+ key: z11.string()
2505
2633
  }),
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()
2634
+ targetJsonPath: z11.string(),
2635
+ defaultCount: z11.number().int().nonnegative().default(0),
2636
+ min: z11.number().int().nonnegative().optional(),
2637
+ max: z11.number().int().nonnegative().optional()
2510
2638
  });
2511
2639
  function defaultJsonResponseConfig(args) {
2512
2640
  return {
@@ -2531,10 +2659,10 @@ function patchEndpoint2(mock, endpointId, patcher) {
2531
2659
  var mockSetValidationRulesTool = {
2532
2660
  name: "mock.set_validation_rules",
2533
2661
  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.",
2534
- inputSchema: z10.object({
2535
- mockId: z10.string(),
2536
- endpointId: z10.string(),
2537
- rules: z10.array(VALIDATION_RULE)
2662
+ inputSchema: z11.object({
2663
+ mockId: z11.string(),
2664
+ endpointId: z11.string(),
2665
+ rules: z11.array(VALIDATION_RULE)
2538
2666
  }),
2539
2667
  async handler(input, ctx) {
2540
2668
  const state = await ctx.workspace.read();
@@ -2544,7 +2672,7 @@ var mockSetValidationRulesTool = {
2544
2672
  const next = patchEndpoint2(mock, input.endpointId, (e) => ({
2545
2673
  ...e,
2546
2674
  requestValidation: rules.map((r) => ({
2547
- id: r.id ?? generateId4(),
2675
+ id: r.id ?? generateId5(),
2548
2676
  kind: r.kind,
2549
2677
  target: r.target,
2550
2678
  expected: r.expected,
@@ -2561,10 +2689,10 @@ var mockSetValidationRulesTool = {
2561
2689
  var mockSetResponseRulesTool = {
2562
2690
  name: "mock.set_response_rules",
2563
2691
  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.",
2564
- inputSchema: z10.object({
2565
- mockId: z10.string(),
2566
- endpointId: z10.string(),
2567
- rules: z10.array(RESPONSE_RULE)
2692
+ inputSchema: z11.object({
2693
+ mockId: z11.string(),
2694
+ endpointId: z11.string(),
2695
+ rules: z11.array(RESPONSE_RULE)
2568
2696
  }),
2569
2697
  async handler(input, ctx) {
2570
2698
  const state = await ctx.workspace.read();
@@ -2574,11 +2702,11 @@ var mockSetResponseRulesTool = {
2574
2702
  const next = patchEndpoint2(mock, input.endpointId, (e) => ({
2575
2703
  ...e,
2576
2704
  responseRules: rules.map((r) => ({
2577
- id: r.id ?? generateId4(),
2705
+ id: r.id ?? generateId5(),
2578
2706
  name: r.name,
2579
2707
  enabled: r.enabled,
2580
2708
  when: r.when.map((c) => ({
2581
- id: c.id ?? generateId4(),
2709
+ id: c.id ?? generateId5(),
2582
2710
  scope: c.scope,
2583
2711
  target: c.target,
2584
2712
  op: c.op,
@@ -2595,10 +2723,10 @@ var mockSetResponseRulesTool = {
2595
2723
  var mockSetMultipliersTool = {
2596
2724
  name: "mock.set_multipliers",
2597
2725
  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.",
2598
- inputSchema: z10.object({
2599
- mockId: z10.string(),
2600
- endpointId: z10.string(),
2601
- multipliers: z10.array(MULTIPLIER)
2726
+ inputSchema: z11.object({
2727
+ mockId: z11.string(),
2728
+ endpointId: z11.string(),
2729
+ multipliers: z11.array(MULTIPLIER)
2602
2730
  }),
2603
2731
  async handler(input, ctx) {
2604
2732
  const state = await ctx.workspace.read();
@@ -2610,7 +2738,7 @@ var mockSetMultipliersTool = {
2610
2738
  defaultResponse: {
2611
2739
  ...e.defaultResponse,
2612
2740
  multipliers: multipliers.length === 0 ? void 0 : multipliers.map((m) => ({
2613
- id: m.id ?? generateId4(),
2741
+ id: m.id ?? generateId5(),
2614
2742
  name: m.name,
2615
2743
  source: { kind: m.source.kind, key: m.source.key },
2616
2744
  targetJsonPath: m.targetJsonPath,
@@ -2686,6 +2814,10 @@ var TOOL_REGISTRY = [
2686
2814
  promptSetEndpointValidationRulesTool,
2687
2815
  promptSetEndpointResponseRulesTool,
2688
2816
  promptSetEndpointMultipliersTool,
2817
+ globalAssetsFilesListTool,
2818
+ globalAssetsFilesCreateTool,
2819
+ globalAssetsFilesUpdateTool,
2820
+ globalAssetsFilesDeleteTool,
2689
2821
  mockCreateFromOpenApiTool,
2690
2822
  mockCreateFromPostmanTool,
2691
2823
  mockCreateFromInsomniaTool,
@@ -2832,17 +2964,58 @@ import {
2832
2964
  setActiveWorkspace as setActiveWorkspaceOnDisk,
2833
2965
  workspaceDirFor
2834
2966
  } from "@apicircle/core/workspace/registry";
2967
+ var LazyActiveWorkspaceProvider = class {
2968
+ constructor(registryRoot, onActiveResolved) {
2969
+ this.registryRoot = registryRoot;
2970
+ this.onActiveResolved = onActiveResolved;
2971
+ }
2972
+ registryRoot;
2973
+ onActiveResolved;
2974
+ async resolveActive() {
2975
+ const registry = await loadRegistry(this.registryRoot);
2976
+ const activeId = registry?.activeWorkspaceId ?? null;
2977
+ if (!activeId) {
2978
+ throw new Error(
2979
+ "No active workspace. Open the desktop app at least once, or run `apicircle workspaces create <name>`."
2980
+ );
2981
+ }
2982
+ this.onActiveResolved(activeId);
2983
+ return new FileBackedWorkspaceProvider(workspaceDirFor(this.registryRoot, activeId));
2984
+ }
2985
+ async read() {
2986
+ const provider = await this.resolveActive();
2987
+ return provider.read();
2988
+ }
2989
+ async apply(patch) {
2990
+ const provider = await this.resolveActive();
2991
+ return provider.apply(patch);
2992
+ }
2993
+ async write(next) {
2994
+ const provider = await this.resolveActive();
2995
+ return provider.write(next);
2996
+ }
2997
+ };
2835
2998
  var MultiWorkspaceProvider = class {
2836
2999
  constructor(registryRoot) {
2837
3000
  this.registryRoot = registryRoot;
3001
+ this.lazyProvider = new LazyActiveWorkspaceProvider(this.registryRoot, (id) => {
3002
+ this.activeWorkspaceId = id;
3003
+ });
2838
3004
  }
2839
3005
  registryRoot;
2840
- active = null;
3006
+ /** Last-known active workspace id. Refreshed every time the lazy
3007
+ * provider resolves; reflects what the most recent operation saw on
3008
+ * disk, not a stale boot-time snapshot. */
2841
3009
  activeWorkspaceId = null;
3010
+ /** The lazy provider tool handlers consume as `ctx.workspace`. Holds a
3011
+ * reference back to this instance so each call updates
3012
+ * `activeWorkspaceId` for `activeId()` callers + diagnostic logs. */
3013
+ lazyProvider;
2842
3014
  /**
2843
- * Hydrate the active provider from disk. Must be called once before the
2844
- * MCP host boots so `ctx.workspace.read()` doesn't race the first
2845
- * registry-load. Returns the registry the boot can log.
3015
+ * Read the registry from disk so the host can log a boot banner. Does
3016
+ * NOT cache a per-id provider — each `activeProvider()` call re-reads
3017
+ * the registry, so a workspace switch in the desktop is picked up by
3018
+ * the next tool call without restarting the MCP server.
2846
3019
  */
2847
3020
  async init() {
2848
3021
  const registry = await loadRegistry(this.registryRoot) ?? {
@@ -2850,22 +3023,18 @@ var MultiWorkspaceProvider = class {
2850
3023
  activeWorkspaceId: null,
2851
3024
  workspaces: []
2852
3025
  };
2853
- if (registry.activeWorkspaceId) {
2854
- this.activeWorkspaceId = registry.activeWorkspaceId;
2855
- this.active = new FileBackedWorkspaceProvider(
2856
- workspaceDirFor(this.registryRoot, registry.activeWorkspaceId)
2857
- );
2858
- }
3026
+ this.activeWorkspaceId = registry.activeWorkspaceId;
2859
3027
  return registry;
2860
3028
  }
2861
- /** The provider tool handlers see as `ctx.workspace`. */
3029
+ /**
3030
+ * The provider tool handlers see as `ctx.workspace`. Returns a lazy
3031
+ * provider whose `read` / `apply` / `write` calls re-read
3032
+ * `registry.json` so the right active workspace is always targeted
3033
+ * even if the desktop switched workspaces since this MCP process
3034
+ * started.
3035
+ */
2862
3036
  activeProvider() {
2863
- if (!this.active) {
2864
- throw new Error(
2865
- "No active workspace. Open the desktop app at least once, or run `apicircle workspaces create <name>`."
2866
- );
2867
- }
2868
- return this.active;
3037
+ return this.lazyProvider;
2869
3038
  }
2870
3039
  // ─── Workspaces interface ──────────────────────────────────────────────────
2871
3040
  async list() {
@@ -2913,23 +3082,17 @@ var MultiWorkspaceProvider = class {
2913
3082
  if (!registry || !registry.workspaces.some((w) => w.id === workspaceId)) {
2914
3083
  throw new WorkspaceNotFoundError(workspaceId);
2915
3084
  }
2916
- const next = await setActiveWorkspaceOnDisk(this.registryRoot, workspaceId);
2917
- void next;
3085
+ await setActiveWorkspaceOnDisk(this.registryRoot, workspaceId);
2918
3086
  this.activeWorkspaceId = workspaceId;
2919
- this.active = new FileBackedWorkspaceProvider(workspaceDirFor(this.registryRoot, workspaceId));
2920
3087
  }
2921
3088
  /**
2922
3089
  * Idempotent registry write — used by tests / tools that need to
2923
- * persist registry updates that didn't go through `setActive`.
3090
+ * persist registry updates that didn't go through `setActive`. The
3091
+ * lazy active provider picks the new id up on its next operation.
2924
3092
  */
2925
3093
  async writeRegistry(registry) {
2926
3094
  await saveRegistry(this.registryRoot, registry);
2927
3095
  this.activeWorkspaceId = registry.activeWorkspaceId;
2928
- if (registry.activeWorkspaceId) {
2929
- this.active = new FileBackedWorkspaceProvider(
2930
- workspaceDirFor(this.registryRoot, registry.activeWorkspaceId)
2931
- );
2932
- }
2933
3096
  }
2934
3097
  };
2935
3098