callman-mcp 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +18 -4
  3. package/dist/context.d.ts +2 -0
  4. package/dist/context.d.ts.map +1 -1
  5. package/dist/context.js +7 -2
  6. package/dist/context.js.map +1 -1
  7. package/dist/resources/docs/overview.d.ts +1 -1
  8. package/dist/resources/docs/overview.d.ts.map +1 -1
  9. package/dist/resources/docs/overview.js +15 -2
  10. package/dist/resources/docs/overview.js.map +1 -1
  11. package/dist/resources/index.d.ts.map +1 -1
  12. package/dist/resources/index.js +10 -13
  13. package/dist/resources/index.js.map +1 -1
  14. package/dist/server.d.ts +1 -1
  15. package/dist/server.d.ts.map +1 -1
  16. package/dist/server.js +1 -1
  17. package/dist/server.js.map +1 -1
  18. package/dist/tools/catalog.d.ts.map +1 -1
  19. package/dist/tools/catalog.js +1 -0
  20. package/dist/tools/catalog.js.map +1 -1
  21. package/dist/tools/collections.d.ts.map +1 -1
  22. package/dist/tools/collections.js +27 -30
  23. package/dist/tools/collections.js.map +1 -1
  24. package/dist/tools/contracts.d.ts.map +1 -1
  25. package/dist/tools/contracts.js +35 -26
  26. package/dist/tools/contracts.js.map +1 -1
  27. package/dist/tools/environments.d.ts.map +1 -1
  28. package/dist/tools/environments.js +32 -31
  29. package/dist/tools/environments.js.map +1 -1
  30. package/dist/tools/import.d.ts.map +1 -1
  31. package/dist/tools/import.js +9 -5
  32. package/dist/tools/import.js.map +1 -1
  33. package/dist/tools/lookups.d.ts +26 -1
  34. package/dist/tools/lookups.d.ts.map +1 -1
  35. package/dist/tools/lookups.js +95 -2
  36. package/dist/tools/lookups.js.map +1 -1
  37. package/dist/tools/meta.d.ts.map +1 -1
  38. package/dist/tools/meta.js +9 -4
  39. package/dist/tools/meta.js.map +1 -1
  40. package/dist/tools/requests.d.ts.map +1 -1
  41. package/dist/tools/requests.js +49 -29
  42. package/dist/tools/requests.js.map +1 -1
  43. package/dist/tools/run.d.ts.map +1 -1
  44. package/dist/tools/run.js +9 -6
  45. package/dist/tools/run.js.map +1 -1
  46. package/dist/tools/scenarios.d.ts.map +1 -1
  47. package/dist/tools/scenarios.js +8 -3
  48. package/dist/tools/scenarios.js.map +1 -1
  49. package/dist/tools/shared.d.ts.map +1 -1
  50. package/dist/tools/shared.js +1 -1
  51. package/dist/tools/shared.js.map +1 -1
  52. package/dist/tools/workspaces.d.ts.map +1 -1
  53. package/dist/tools/workspaces.js +18 -2
  54. package/dist/tools/workspaces.js.map +1 -1
  55. package/dist-cjs/context.js +7 -2
  56. package/dist-cjs/resources/docs/overview.js +15 -2
  57. package/dist-cjs/resources/index.js +9 -12
  58. package/dist-cjs/server.js +1 -1
  59. package/dist-cjs/tools/catalog.js +1 -0
  60. package/dist-cjs/tools/collections.js +26 -29
  61. package/dist-cjs/tools/contracts.js +34 -25
  62. package/dist-cjs/tools/environments.js +31 -30
  63. package/dist-cjs/tools/import.js +8 -4
  64. package/dist-cjs/tools/lookups.js +103 -3
  65. package/dist-cjs/tools/meta.js +9 -4
  66. package/dist-cjs/tools/requests.js +48 -28
  67. package/dist-cjs/tools/run.js +8 -5
  68. package/dist-cjs/tools/scenarios.js +8 -3
  69. package/dist-cjs/tools/shared.js +1 -1
  70. package/dist-cjs/tools/workspaces.js +18 -2
  71. package/package.json +1 -1
@@ -86,22 +86,19 @@ const registerResources = (server, ctx) => {
86
86
  const { items } = await ctx.api.listEnvironments(ws.id);
87
87
  return json(uri, { workspace: { id: ws.id, name: ws.name }, environments: items.map((environment) => ({ id: environment.id, name: environment.name, variableKeys: environment.variables.map((entry) => entry.key) })) });
88
88
  });
89
- server.registerResource("collection-tree", new mcp_js_1.ResourceTemplate("callman://collection/{collection}/tree", { list: undefined }), { title: "A collection's folders and requests", description: "Folders and requests of a collection (id or name) in the default workspace.", mimeType: "application/json" }, async (uri, variables) => {
90
- const ws = await ctx.resolveWorkspace();
91
- const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, variable(variables.collection));
89
+ server.registerResource("collection-tree", new mcp_js_1.ResourceTemplate("callman://collection/{collection}/tree", { list: undefined }), { title: "A collection's folders and requests", description: "Folders and requests of a collection (id or name) in any of your workspaces.", mimeType: "application/json" }, async (uri, variables) => {
90
+ const { ws, value: resolved } = await (0, lookups_js_1.findCollection)(ctx, null, variable(variables.collection));
92
91
  const tree = await (0, lookups_js_1.loadTree)(ctx, ws.id, resolved.item.id);
93
- return json(uri, { collection: { id: resolved.item.id, name: resolved.item.name }, folders: tree.folders, requests: tree.requests.map(requestShape_js_1.requestSummaryView) });
92
+ return json(uri, { collection: { id: resolved.item.id, name: resolved.item.name }, workspace: (0, lookups_js_1.workspaceTag)(ws), folders: tree.folders, requests: tree.requests.map(requestShape_js_1.requestSummaryView) });
94
93
  });
95
- server.registerResource("request", new mcp_js_1.ResourceTemplate("callman://request/{request}", { list: undefined }), { title: "A saved request", description: "One request (id or unique name) in the default workspace, secrets masked.", mimeType: "application/json" }, async (uri, variables) => {
96
- const ws = await ctx.resolveWorkspace();
97
- const { request } = await (0, lookups_js_1.resolveRequest)(ctx, ws.id, variable(variables.request));
98
- return json(uri, { request: (0, requestShape_js_1.compactRequestView)(request, { full: true }) });
94
+ server.registerResource("request", new mcp_js_1.ResourceTemplate("callman://request/{request}", { list: undefined }), { title: "A saved request", description: "One request (id or unique name) in any of your workspaces, secrets masked.", mimeType: "application/json" }, async (uri, variables) => {
95
+ const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, null, variable(variables.request));
96
+ return json(uri, { request: (0, requestShape_js_1.compactRequestView)(value.request, { full: true }), workspace: (0, lookups_js_1.workspaceTag)(ws) });
99
97
  });
100
- server.registerResource("environment", new mcp_js_1.ResourceTemplate("callman://environment/{environment}", { list: undefined }), { title: "An environment", description: "One environment (id or name) in the default workspace, values masked by the server.", mimeType: "application/json" }, async (uri, variables) => {
101
- const ws = await ctx.resolveWorkspace();
102
- const resolved = await (0, lookups_js_1.resolveEnvironment)(ctx, ws.id, variable(variables.environment));
98
+ server.registerResource("environment", new mcp_js_1.ResourceTemplate("callman://environment/{environment}", { list: undefined }), { title: "An environment", description: "One environment (id or name) in any of your workspaces, values masked by the server.", mimeType: "application/json" }, async (uri, variables) => {
99
+ const { ws, value: resolved } = await (0, lookups_js_1.findEnvironment)(ctx, null, variable(variables.environment));
103
100
  const fresh = await ctx.api.getEnvironment(ws.id, resolved.item.id);
104
- return json(uri, { environment: { id: fresh.id, name: fresh.name, variables: fresh.variables } });
101
+ return json(uri, { environment: { id: fresh.id, name: fresh.name, variables: fresh.variables }, workspace: (0, lookups_js_1.workspaceTag)(ws) });
105
102
  });
106
103
  server.registerResource("scenario", new mcp_js_1.ResourceTemplate("callman://scenario/{scenario}", { list: undefined }), { title: "A scenario", description: "One scenario (id or name): nodes with configs (clipped, secrets masked) and edges.", mimeType: "application/json" }, async (uri, variables) => {
107
104
  const ws = await ctx.resolveWorkspaceOrNull();
@@ -8,7 +8,7 @@ const index_js_2 = require("./resources/index.js");
8
8
  const index_js_3 = require("./tools/index.js");
9
9
  exports.SERVER_NAME = "callman";
10
10
  exports.SERVER_INSTRUCTIONS = `Callman is an API client and test platform: collections of HTTP requests, environments with variables, multi-step test scenarios, and system-design diagrams. You act as the user whose token is configured, within that token's permissions and the user's workspace role.
11
- Start with whoami. Names work as references (collection, folder, request, environment, scenario, diagram, workspace); ambiguous names return candidates. List before creating — duplicate collection names are refused. Preview large writes with dryRun: true; deletions need confirm: true and the user's agreement. Environment values are masked unless the token holds environments:reveal and you pass reveal: true; never write a masked value back. Arguments are strict — an unknown key is an error listing the valid ones. Read callman://docs/overview for the conventions, callman://docs/request-schema before creating or editing requests, callman://docs/contracts before contract checks, callman://docs/scenario-nodes before scenarios, and callman://docs/diagram-schema + callman://diagram/components before diagrams.`;
11
+ Start with whoami. The token works in every workspace the user belongs to: lists and searches cover all of them unless you pass \`workspace\`, names are looked up in the default workspace first and then everywhere, and results say which workspace they come from; creating something needs a target (\`workspace\`, the collection it goes into, or the session default — use_workspace). Names work as references (collection, folder, request, environment, scenario, diagram, workspace); ambiguous names return candidates. List before creating — duplicate collection names are refused. Preview large writes with dryRun: true; deletions need confirm: true and the user's agreement. Environment values are masked unless the token holds environments:reveal and you pass reveal: true; never write a masked value back. Arguments are strict — an unknown key is an error listing the valid ones. Read callman://docs/overview for the conventions, callman://docs/request-schema before creating or editing requests, callman://docs/contracts before contract checks, callman://docs/scenario-nodes before scenarios, and callman://docs/diagram-schema + callman://diagram/components before diagrams.`;
12
12
  const createCallmanMcpServer = (options) => {
13
13
  const version = options.version ?? "0.0.0";
14
14
  const context = (0, context_js_1.createToolContext)({
@@ -8,6 +8,7 @@ exports.toolAvailability = exports.TOOL_CATALOG = void 0;
8
8
  exports.TOOL_CATALOG = [
9
9
  { name: "whoami", group: "meta", scopes: [] },
10
10
  { name: "list_workspaces", group: "workspaces", scopes: ["workspaces:read"] },
11
+ { name: "use_workspace", group: "workspaces", scopes: ["workspaces:read"] },
11
12
  { name: "list_workspace_members", group: "workspaces", scopes: ["workspaces:read"] },
12
13
  { name: "add_workspace_member", group: "workspaces", scopes: ["members:manage"] },
13
14
  { name: "update_workspace_member", group: "workspaces", scopes: ["members:manage"] },
@@ -20,20 +20,22 @@ const runnerSettingsArg = (0, strict_js_1.strictObject)({
20
20
  const registerCollectionTools = (server, ctx) => {
21
21
  server.registerTool("list_collections", {
22
22
  title: "List collections",
23
- description: "List the collections in a workspace with request/folder counts. Optional `query` filters by name (substring). Use get_collection_tree to see what is inside one.",
23
+ description: "List collections with request/folder counts — across ALL your workspaces unless `workspace` narrows it (the default workspace comes first; every row says its workspace). Optional `query` filters by name (substring). Use get_collection_tree to see what is inside one.",
24
24
  inputSchema: { workspace: shared_js_1.workspaceArg, query: zod_1.z.string().max(200).optional(), ...shared_js_1.cursorArgs },
25
25
  annotations: shared_js_1.READ,
26
26
  }, (0, shared_js_1.guarded)(async ({ workspace, query, cursor, limit }) => {
27
- const ws = await ctx.resolveWorkspace(workspace);
28
- const all = await (0, lookups_js_1.loadCollections)(ctx, ws.id);
27
+ const workspaces = await (0, lookups_js_1.workspacesForListing)(ctx, workspace);
29
28
  const needle = query?.trim().toLowerCase();
30
- const filtered = needle ? all.filter((collection) => collection.name.toLowerCase().includes(needle)) : all;
31
- const page = (0, pagination_js_1.sliceWithCursor)(filtered, cursor, limit, needle ?? "");
29
+ const rows = (await Promise.all(workspaces.map(async (ws) => (await (0, lookups_js_1.loadCollections)(ctx, ws.id)).map((collection) => ({ ws, collection })))))
30
+ .flat()
31
+ .filter(({ collection }) => !needle || collection.name.toLowerCase().includes(needle));
32
+ const page = (0, pagination_js_1.sliceWithCursor)(rows, cursor, limit, `${workspaces.map((ws) => ws.id).join(",")}|${needle ?? ""}`);
32
33
  return (0, shared_js_1.ok)({
33
- workspace: { id: ws.id, name: ws.name },
34
- collections: page.items.map((collection) => ({
34
+ ...(workspaces.length === 1 ? { workspace: (0, lookups_js_1.workspaceTag)(workspaces[0]) } : { workspaces: workspaces.map(lookups_js_1.workspaceTag) }),
35
+ collections: page.items.map(({ ws, collection }) => ({
35
36
  id: collection.id,
36
37
  name: collection.name,
38
+ ...(workspaces.length > 1 ? { workspace: (0, lookups_js_1.workspaceTag)(ws) } : {}),
37
39
  requestCount: collection.requestCount ?? null,
38
40
  folderCount: collection.folderCount ?? null,
39
41
  updatedAt: collection.updatedAt ?? null,
@@ -44,17 +46,17 @@ const registerCollectionTools = (server, ctx) => {
44
46
  }));
45
47
  server.registerTool("get_collection_tree", {
46
48
  title: "Get a collection's folders and requests",
47
- description: "The folders (with paths like \"Auth/Login\") and requests (id, name, method, url, folder) of one collection — the map you need before creating, moving or running requests.",
49
+ description: "The folders (with paths like \"Auth/Login\") and requests (id, name, method, url, folder) of one collection, in whichever workspace it lives — the map you need before creating, moving or running requests.",
48
50
  inputSchema: { workspace: shared_js_1.workspaceArg, collection: (0, shared_js_1.refArg)("Collection") },
49
51
  annotations: shared_js_1.READ,
50
52
  }, (0, shared_js_1.guarded)(async ({ workspace, collection }) => {
51
- const ws = await ctx.resolveWorkspace(workspace);
52
- const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection);
53
+ const { ws, value: resolved, note } = await (0, lookups_js_1.findCollection)(ctx, workspace, collection);
53
54
  const tree = await (0, lookups_js_1.loadTree)(ctx, ws.id, resolved.item.id);
54
55
  const truncated = tree.requests.length > truncate_js_1.LIMITS.TREE_ITEMS;
55
56
  return (0, shared_js_1.ok)({
56
57
  collection: { id: resolved.item.id, name: resolved.item.name },
57
- resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved)],
58
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
59
+ resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), ...(note ? [{ input: collection, note }] : [])],
58
60
  folders: tree.folders.map((folder) => ({ id: folder.id, name: folder.name, parentId: folder.parentId, path: folder.path })),
59
61
  requests: tree.requests.slice(0, truncate_js_1.LIMITS.TREE_ITEMS).map(requestShape_js_1.requestSummaryView),
60
62
  ...(truncated ? { truncated: true, totalRequests: tree.requests.length } : {}),
@@ -66,18 +68,17 @@ const registerCollectionTools = (server, ctx) => {
66
68
  inputSchema: { workspace: shared_js_1.workspaceArg, collection: (0, shared_js_1.refArg)("Collection"), format: zod_1.z.enum(["internal", "postman"]).default("internal") },
67
69
  annotations: shared_js_1.READ,
68
70
  }, (0, shared_js_1.guarded)(async ({ workspace, collection, format }) => {
69
- const ws = await ctx.resolveWorkspace(workspace);
70
- const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection);
71
+ const { ws, value: resolved } = await (0, lookups_js_1.findCollection)(ctx, workspace, collection);
71
72
  const document = await ctx.api.exportCollection(ws.id, resolved.item.id, format);
72
73
  const bytes = (0, truncate_js_1.byteLength)(document);
73
74
  if (bytes > EXPORT_MAX_BYTES) {
74
75
  throw new errors_js_1.ToolInputError("EXPORT_TOO_LARGE", `The export is ${bytes} bytes (limit ${EXPORT_MAX_BYTES}). Export it from the desktop app instead.`, { bytes });
75
76
  }
76
- return (0, shared_js_1.ok)({ collection: { id: resolved.item.id, name: resolved.item.name }, format, bytes, document: document });
77
+ return (0, shared_js_1.ok)({ collection: { id: resolved.item.id, name: resolved.item.name }, workspace: (0, lookups_js_1.workspaceTag)(ws), format, bytes, document: document });
77
78
  }));
78
79
  server.registerTool("create_collection", {
79
80
  title: "Create a collection",
80
- description: "Create an empty collection. Refuses a name that already exists in the workspace unless allowDuplicateName is true — list_collections first and reuse an existing one when it fits.",
81
+ description: "Create an empty collection in `workspace` (or the session default — use_workspace; with one workspace, that one). Refuses a name that already exists in that workspace unless allowDuplicateName is true — list_collections first and reuse an existing one when it fits.",
81
82
  inputSchema: {
82
83
  workspace: shared_js_1.workspaceArg,
83
84
  name: zod_1.z.string().min(1).max(truncate_js_1.LIMITS.NAME),
@@ -95,7 +96,7 @@ const registerCollectionTools = (server, ctx) => {
95
96
  }
96
97
  const created = await ctx.api.createCollection(ws.id, { name: name.trim(), ...(runnerSettings ? { runnerSettings: compactSettings(runnerSettings) } : {}) });
97
98
  ctx.invalidate(`collections:${ws.id}`);
98
- return (0, shared_js_1.ok)({ collection: { id: created.id, name: created.name }, note: "Created. Add requests with create_request / create_requests_bulk / import_content." });
99
+ return (0, shared_js_1.ok)({ collection: { id: created.id, name: created.name }, workspace: (0, lookups_js_1.workspaceTag)(ws), note: "Created. Add requests with create_request / create_requests_bulk / import_content." });
99
100
  }));
100
101
  server.registerTool("update_collection", {
101
102
  title: "Rename a collection / change runner settings",
@@ -110,14 +111,13 @@ const registerCollectionTools = (server, ctx) => {
110
111
  }, (0, shared_js_1.guarded)(async ({ workspace, collection, name, runnerSettings }) => {
111
112
  if (name === undefined && runnerSettings === undefined)
112
113
  throw new errors_js_1.ToolInputError("NOTHING_TO_UPDATE", "Pass name and/or runnerSettings.");
113
- const ws = await ctx.resolveWorkspace(workspace);
114
- const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection);
114
+ const { ws, value: resolved, note } = await (0, lookups_js_1.findCollection)(ctx, workspace, collection);
115
115
  const updated = await ctx.api.updateCollection(ws.id, resolved.item.id, {
116
116
  ...(name !== undefined ? { name: name.trim() } : {}),
117
117
  ...(runnerSettings ? { runnerSettings: compactSettings(runnerSettings) } : {}),
118
118
  });
119
119
  ctx.invalidate(`collections:${ws.id}`);
120
- return (0, shared_js_1.ok)({ collection: { id: updated.id, name: updated.name, runnerSettings: updated.runnerSettings ?? null }, resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved)] });
120
+ return (0, shared_js_1.ok)({ collection: { id: updated.id, name: updated.name, runnerSettings: updated.runnerSettings ?? null }, workspace: (0, lookups_js_1.workspaceTag)(ws), resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), ...(note ? [{ input: collection, note }] : [])] });
121
121
  }));
122
122
  server.registerTool("create_folder", {
123
123
  title: "Create a folder",
@@ -130,8 +130,7 @@ const registerCollectionTools = (server, ctx) => {
130
130
  },
131
131
  annotations: shared_js_1.WRITE,
132
132
  }, (0, shared_js_1.guarded)(async ({ workspace, collection, name, parent }) => {
133
- const ws = await ctx.resolveWorkspace(workspace);
134
- const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection);
133
+ const { ws, value: resolved } = await (0, lookups_js_1.findCollection)(ctx, workspace, collection);
135
134
  const parentFolder = await (0, lookups_js_1.resolveFolder)(ctx, ws.id, resolved.item.id, parent);
136
135
  const tree = await (0, lookups_js_1.loadTree)(ctx, ws.id, resolved.item.id);
137
136
  const sibling = tree.folders.find((folder) => folder.parentId === (parentFolder?.item.id ?? null) && (0, lookups_js_1.sameName)(folder.name, name));
@@ -143,6 +142,7 @@ const registerCollectionTools = (server, ctx) => {
143
142
  ctx.invalidate(`collections:${ws.id}`);
144
143
  return (0, shared_js_1.ok)({
145
144
  folder: { id: folder.id, name: folder.name, parentId: folder.parentId, path: parentFolder ? `${parentFolder.item.path}/${folder.name}` : folder.name },
145
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
146
146
  resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), ...(parentFolder ? [(0, resolve_js_1.resolvedSummary)(parent, parentFolder)] : [])],
147
147
  });
148
148
  }));
@@ -161,8 +161,7 @@ const registerCollectionTools = (server, ctx) => {
161
161
  }, (0, shared_js_1.guarded)(async ({ workspace, collection, folder, name, parent, targetCollection }) => {
162
162
  if (name === undefined && parent === undefined && targetCollection === undefined)
163
163
  throw new errors_js_1.ToolInputError("NOTHING_TO_UPDATE", "Pass name, parent and/or targetCollection.");
164
- const ws = await ctx.resolveWorkspace(workspace);
165
- const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection);
164
+ const { ws, value: resolved } = await (0, lookups_js_1.findCollection)(ctx, workspace, collection);
166
165
  const target = await (0, lookups_js_1.resolveFolder)(ctx, ws.id, resolved.item.id, folder);
167
166
  if (!target)
168
167
  throw new errors_js_1.ToolInputError("REF_REQUIRED", "folder is required.");
@@ -190,12 +189,11 @@ const registerCollectionTools = (server, ctx) => {
190
189
  annotations: shared_js_1.DESTRUCTIVE,
191
190
  }, (0, shared_js_1.guarded)(async ({ workspace, collection, confirm }) => {
192
191
  (0, shared_js_1.requireConfirm)(confirm, "a collection");
193
- const ws = await ctx.resolveWorkspace(workspace);
194
- const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection, { exact: true });
192
+ const { ws, value: resolved } = await (0, lookups_js_1.findCollection)(ctx, workspace, collection, { exact: true });
195
193
  await ctx.api.deleteCollection(ws.id, resolved.item.id);
196
194
  ctx.invalidate(`collections:${ws.id}`);
197
195
  ctx.invalidate(`tree:${ws.id}:${resolved.item.id}`);
198
- return (0, shared_js_1.ok)({ deleted: { id: resolved.item.id, name: resolved.item.name }, resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved)] });
196
+ return (0, shared_js_1.ok)({ deleted: { id: resolved.item.id, name: resolved.item.name }, workspace: (0, lookups_js_1.workspaceTag)(ws), resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved)] });
199
197
  }));
200
198
  server.registerTool("delete_folder", {
201
199
  title: "Delete a folder",
@@ -204,15 +202,14 @@ const registerCollectionTools = (server, ctx) => {
204
202
  annotations: shared_js_1.DESTRUCTIVE,
205
203
  }, (0, shared_js_1.guarded)(async ({ workspace, collection, folder, confirm }) => {
206
204
  (0, shared_js_1.requireConfirm)(confirm, "a folder");
207
- const ws = await ctx.resolveWorkspace(workspace);
208
- const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection);
205
+ const { ws, value: resolved } = await (0, lookups_js_1.findCollection)(ctx, workspace, collection);
209
206
  const target = await (0, lookups_js_1.resolveFolder)(ctx, ws.id, resolved.item.id, folder, { exact: true });
210
207
  if (!target)
211
208
  throw new errors_js_1.ToolInputError("REF_REQUIRED", "folder is required.");
212
209
  await ctx.api.deleteFolder(ws.id, target.item.id);
213
210
  ctx.invalidate(`tree:${ws.id}:${resolved.item.id}`);
214
211
  ctx.invalidate(`collections:${ws.id}`);
215
- return (0, shared_js_1.ok)({ deleted: { id: target.item.id, name: target.item.name, path: target.item.path }, resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), (0, resolve_js_1.resolvedSummary)(folder, target)] });
212
+ return (0, shared_js_1.ok)({ deleted: { id: target.item.id, name: target.item.name, path: target.item.path }, workspace: (0, lookups_js_1.workspaceTag)(ws), resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), (0, resolve_js_1.resolvedSummary)(folder, target)] });
216
213
  }));
217
214
  };
218
215
  exports.registerCollectionTools = registerCollectionTools;
@@ -24,14 +24,15 @@ const registerContractTools = (server, ctx) => {
24
24
  inputSchema: requestArgs,
25
25
  annotations: shared_js_1.READ,
26
26
  }, (0, shared_js_1.guarded)(async ({ workspace, request, collection }) => {
27
- const ws = await ctx.resolveWorkspace(workspace);
28
- const { request: found, annotation } = await (0, lookups_js_1.resolveRequest)(ctx, ws.id, request, collection);
27
+ const { ws, value, note } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
28
+ const { request: found, annotation } = value;
29
29
  const [rules, dataContract] = await Promise.all([ctx.api.listContractRules(ws.id, found.id), ctx.api.getDataContract(ws.id, found.id)]);
30
30
  return (0, shared_js_1.ok)({
31
31
  request: (0, requestShape_js_1.requestSummaryView)(found),
32
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
32
33
  rules: rules.items.map(ruleView),
33
34
  dataContract: dataContract.rows.length > 0 || dataContract.columns.length > 0 ? { columns: dataContract.columns, rows: dataContract.rows } : null,
34
- ...(annotation ? { resolved: [{ input: request, matched: found.name, id: found.id, note: annotation }] } : {}),
35
+ ...(annotation || note ? { resolved: [{ input: request, matched: found.name, id: found.id, note: [annotation, note].filter(Boolean).join("; ") }] } : {}),
35
36
  });
36
37
  }));
37
38
  server.registerTool("add_contract_rules", {
@@ -45,13 +46,14 @@ const registerContractTools = (server, ctx) => {
45
46
  },
46
47
  annotations: shared_js_1.WRITE,
47
48
  }, (0, shared_js_1.guarded)(async ({ workspace, request, collection, rules, replace, dryRun }) => {
48
- const ws = await ctx.resolveWorkspace(workspace);
49
- const { request: found } = await (0, lookups_js_1.resolveRequest)(ctx, ws.id, request, collection);
49
+ const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
50
+ const { request: found } = value;
50
51
  const existing = (await ctx.api.listContractRules(ws.id, found.id)).items;
51
52
  const duplicates = replace ? [] : rules.filter((rule) => existing.some((entry) => entry.path === rule.path)).map((rule) => rule.path);
52
53
  const warnings = duplicates.length > 0 ? [`These paths already had rules and now have another one each: ${duplicates.join(", ")} — use update_contract_rule to change an existing rule instead.`] : [];
53
54
  if (dryRun) {
54
- return (0, shared_js_1.ok)({ dryRun: true, request: (0, requestShape_js_1.requestSummaryView)(found), adding: rules.length, ...(replace ? { removing: existing.length } : {}), warnings });
55
+ return (0, shared_js_1.ok)({ dryRun: true, request: (0, requestShape_js_1.requestSummaryView)(found),
56
+ workspace: (0, lookups_js_1.workspaceTag)(ws), adding: rules.length, ...(replace ? { removing: existing.length } : {}), warnings });
55
57
  }
56
58
  if (replace) {
57
59
  for (const rule of existing)
@@ -60,6 +62,7 @@ const registerContractTools = (server, ctx) => {
60
62
  const created = await ctx.api.createContractRules(ws.id, { requestId: found.id, rules: rules.map((rule) => ({ ...rule, enabled: rule.enabled ?? true })) });
61
63
  return (0, shared_js_1.ok)({
62
64
  request: (0, requestShape_js_1.requestSummaryView)(found),
65
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
63
66
  added: created.items.map(ruleView),
64
67
  ...(replace ? { removed: existing.length } : {}),
65
68
  ...(warnings.length > 0 ? { warnings } : {}),
@@ -79,9 +82,10 @@ const registerContractTools = (server, ctx) => {
79
82
  }, (0, shared_js_1.guarded)(async ({ workspace, rule, path, rules, enabled }) => {
80
83
  if (path === undefined && rules === undefined && enabled === undefined)
81
84
  throw new errors_js_1.ToolInputError("NOTHING_TO_UPDATE", "Pass path, rules and/or enabled.");
82
- const ws = await ctx.resolveWorkspace(workspace);
83
- const updated = await ctx.api.updateContractRule(ws.id, rule, { ...(path !== undefined ? { path } : {}), ...(rules !== undefined ? { rules } : {}), ...(enabled !== undefined ? { enabled } : {}) });
84
- return (0, shared_js_1.ok)({ rule: ruleView(updated) });
85
+ const body = { ...(path !== undefined ? { path } : {}), ...(rules !== undefined ? { rules } : {}), ...(enabled !== undefined ? { enabled } : {}) };
86
+ // A rule id lives in exactly one workspace; the others answer 404.
87
+ const { ws, value: updated } = await (0, lookups_js_1.findInWorkspaces)(ctx, workspace, "contract rule", rule, (candidate) => ctx.api.updateContractRule(candidate.id, rule, body), { matchOf: () => "id" });
88
+ return (0, shared_js_1.ok)({ rule: ruleView(updated), workspace: (0, lookups_js_1.workspaceTag)(ws) });
85
89
  }));
86
90
  server.registerTool("delete_contract_rules", {
87
91
  title: "Delete contract rules",
@@ -94,8 +98,8 @@ const registerContractTools = (server, ctx) => {
94
98
  annotations: shared_js_1.DESTRUCTIVE,
95
99
  }, (0, shared_js_1.guarded)(async ({ workspace, request, collection, rules, confirm }) => {
96
100
  (0, shared_js_1.requireConfirm)(confirm, "contract rules");
97
- const ws = await ctx.resolveWorkspace(workspace);
98
- const { request: found } = await (0, lookups_js_1.resolveRequest)(ctx, ws.id, request, collection, { exact: true });
101
+ const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection, { exact: true });
102
+ const { request: found } = value;
99
103
  const existing = (await ctx.api.listContractRules(ws.id, found.id)).items;
100
104
  const ids = rules ?? existing.map((rule) => rule.id);
101
105
  const unknown = ids.filter((id) => !existing.some((rule) => rule.id === id));
@@ -113,7 +117,8 @@ const registerContractTools = (server, ctx) => {
113
117
  failed.push({ rule: id, error: (0, shared_js_1.failureOf)(error) });
114
118
  }
115
119
  }
116
- return (0, shared_js_1.ok)({ request: (0, requestShape_js_1.requestSummaryView)(found), deleted, failed });
120
+ return (0, shared_js_1.ok)({ request: (0, requestShape_js_1.requestSummaryView)(found),
121
+ workspace: (0, lookups_js_1.workspaceTag)(ws), deleted, failed });
117
122
  }));
118
123
  server.registerTool("test_contract_rules", {
119
124
  title: "Test contract rules against a response body",
@@ -124,10 +129,11 @@ const registerContractTools = (server, ctx) => {
124
129
  },
125
130
  annotations: shared_js_1.READ,
126
131
  }, (0, shared_js_1.guarded)(async ({ workspace, request, collection, responseBody }) => {
127
- const ws = await ctx.resolveWorkspace(workspace);
128
- const { request: found } = await (0, lookups_js_1.resolveRequest)(ctx, ws.id, request, collection);
132
+ const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
133
+ const { request: found } = value;
129
134
  const result = await ctx.api.validateContractRules(ws.id, { requestId: found.id, responseBody: responseBody ?? null });
130
- return (0, shared_js_1.ok)({ request: (0, requestShape_js_1.requestSummaryView)(found), summary: result.summary, results: result.results });
135
+ return (0, shared_js_1.ok)({ request: (0, requestShape_js_1.requestSummaryView)(found),
136
+ workspace: (0, lookups_js_1.workspaceTag)(ws), summary: result.summary, results: result.results });
131
137
  }));
132
138
  server.registerTool("set_data_contract", {
133
139
  title: "Set a request's data-driven contract",
@@ -143,13 +149,14 @@ const registerContractTools = (server, ctx) => {
143
149
  if (undeclared.length > 0) {
144
150
  throw new errors_js_1.ToolInputError("DATA_COLUMN_UNDECLARED", `Rows use column(s) not in columns: ${undeclared.join(", ")}.`);
145
151
  }
146
- const ws = await ctx.resolveWorkspace(workspace);
147
- const { request: found } = await (0, lookups_js_1.resolveRequest)(ctx, ws.id, request, collection);
152
+ const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
153
+ const { request: found } = value;
148
154
  const saved = await ctx.api.putDataContract(ws.id, { requestId: found.id, columns, rows });
149
155
  const text = JSON.stringify([found.url, found.headers, found.body, found.query]);
150
156
  const unused = columns.filter((column) => !text.includes(`{{data.${column}}}`));
151
157
  return (0, shared_js_1.ok)({
152
158
  request: (0, requestShape_js_1.requestSummaryView)(found),
159
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
153
160
  dataContract: { columns: saved.columns, rows: saved.rows.length },
154
161
  ...(unused.length > 0 ? { warnings: [`The request does not reference {{data.${unused.join("}}, {{data.")}}} yet — add them to its url, query, headers or body with update_request.`] } : {}),
155
162
  });
@@ -161,27 +168,29 @@ const registerContractTools = (server, ctx) => {
161
168
  annotations: shared_js_1.DESTRUCTIVE,
162
169
  }, (0, shared_js_1.guarded)(async ({ workspace, request, collection, confirm }) => {
163
170
  (0, shared_js_1.requireConfirm)(confirm, "a data contract");
164
- const ws = await ctx.resolveWorkspace(workspace);
165
- const { request: found } = await (0, lookups_js_1.resolveRequest)(ctx, ws.id, request, collection, { exact: true });
171
+ const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection, { exact: true });
172
+ const { request: found } = value;
166
173
  await ctx.api.deleteDataContract(ws.id, found.id);
167
- return (0, shared_js_1.ok)({ request: (0, requestShape_js_1.requestSummaryView)(found), deleted: true });
174
+ return (0, shared_js_1.ok)({ request: (0, requestShape_js_1.requestSummaryView)(found),
175
+ workspace: (0, lookups_js_1.workspaceTag)(ws), deleted: true });
168
176
  }));
169
177
  server.registerTool("list_connections", {
170
178
  title: "List workspace connections",
171
- description: "List the workspace's database / Redis / Kafka / notification / AI connections (id, name, type, provider — never credentials). Request database/Kafka/Redis steps and scenario db/redis/kafka/notification/ai nodes reference a connection by this id (or name). Connections are created and tested in the desktop app.",
179
+ description: "List database / Redis / Kafka / notification / AI connections (id, name, type, provider, workspace — never credentials) across all your workspaces, or one `workspace`. A connection belongs to one workspace: use one from the same workspace as the request or scenario. Request database/Kafka/Redis steps and scenario db/redis/kafka/notification/ai nodes reference a connection by this id (or name). Connections are created and tested in the desktop app.",
172
180
  inputSchema: {
173
181
  workspace: shared_js_1.workspaceArg,
174
182
  type: zod_1.z.enum(["database", "redis", "kafka", "notification", "ai"]).optional(),
175
183
  },
176
184
  annotations: shared_js_1.READ,
177
185
  }, (0, shared_js_1.guarded)(async ({ workspace, type }) => {
178
- const ws = await ctx.resolveWorkspace(workspace);
179
- const connections = await ctx.api.listConnections(ws.id, type);
186
+ const workspaces = await (0, lookups_js_1.workspacesForListing)(ctx, workspace);
187
+ const perWorkspace = await Promise.all(workspaces.map(async (ws) => (await ctx.api.listConnections(ws.id, type)).map((connection) => ({ ws, connection }))));
180
188
  return (0, shared_js_1.ok)({
181
- workspace: { id: ws.id, name: ws.name },
182
- connections: connections.map((connection) => ({
189
+ ...(workspaces.length === 1 ? { workspace: (0, lookups_js_1.workspaceTag)(workspaces[0]) } : { workspaces: workspaces.map(lookups_js_1.workspaceTag) }),
190
+ connections: perWorkspace.flat().map(({ ws, connection }) => ({
183
191
  id: connection.id,
184
192
  name: connection.name,
193
+ ...(workspaces.length > 1 ? { workspace: (0, lookups_js_1.workspaceTag)(ws) } : {}),
185
194
  type: connection.type,
186
195
  provider: connection.provider,
187
196
  ...(connection.description ? { description: connection.description } : {}),
@@ -47,24 +47,28 @@ const loadGlobals = async (ctx, ws, reveal) => {
47
47
  const registerEnvironmentTools = (server, ctx) => {
48
48
  server.registerTool("list_environments", {
49
49
  title: "List environments",
50
- description: "List a workspace's environments with their variable KEYS (values are not included). Global variables (shared by every environment) are listed too.",
50
+ description: "List environments with their variable KEYS (values are not included) — across ALL your workspaces unless `workspace` narrows it, each workspace with its global variables (shared by that workspace's environments).",
51
51
  inputSchema: { workspace: shared_js_1.workspaceArg },
52
52
  annotations: shared_js_1.READ,
53
53
  }, (0, shared_js_1.guarded)(async ({ workspace }) => {
54
- const ws = await ctx.resolveWorkspace(workspace);
55
- const [environments, globals] = await Promise.all([(0, lookups_js_1.loadEnvironments)(ctx, ws.id), loadGlobals(ctx, ws.id, false)]);
56
- return (0, shared_js_1.ok)({
57
- workspace: { id: ws.id, name: ws.name },
58
- environments: environments.map((environment) => ({
59
- id: environment.id,
60
- name: environment.name,
61
- variableCount: environment.variables.length,
62
- variableKeys: environment.variables.map((variable) => variable.key).slice(0, 200),
63
- ...(environment.variables.length > 200 ? { variableKeysTruncated: true } : {}),
64
- })),
65
- globalVariableKeys: globals.globals.map((variable) => variable.key),
66
- ...(globals.globalVariablesError ? { globalVariablesError: globals.globalVariablesError } : {}),
67
- });
54
+ const workspaces = await (0, lookups_js_1.workspacesForListing)(ctx, workspace);
55
+ const perWorkspace = await Promise.all(workspaces.map(async (ws) => {
56
+ const [environments, globals] = await Promise.all([(0, lookups_js_1.loadEnvironments)(ctx, ws.id), loadGlobals(ctx, ws.id, false)]);
57
+ return {
58
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
59
+ environments: environments.map((environment) => ({
60
+ id: environment.id,
61
+ name: environment.name,
62
+ variableCount: environment.variables.length,
63
+ variableKeys: environment.variables.map((variable) => variable.key).slice(0, 200),
64
+ ...(environment.variables.length > 200 ? { variableKeysTruncated: true } : {}),
65
+ })),
66
+ globalVariableKeys: globals.globals.map((variable) => variable.key),
67
+ ...(globals.globalVariablesError ? { globalVariablesError: globals.globalVariablesError } : {}),
68
+ };
69
+ }));
70
+ // One workspace keeps the flat shape; several come back per workspace.
71
+ return (0, shared_js_1.ok)(perWorkspace.length === 1 ? perWorkspace[0] : { workspaces: perWorkspace });
68
72
  }));
69
73
  server.registerTool("get_environment", {
70
74
  title: "Get an environment",
@@ -72,22 +76,22 @@ const registerEnvironmentTools = (server, ctx) => {
72
76
  inputSchema: { workspace: shared_js_1.workspaceArg, environment: (0, shared_js_1.refArg)("Environment"), reveal: zod_1.z.boolean().optional() },
73
77
  annotations: shared_js_1.READ,
74
78
  }, (0, shared_js_1.guarded)(async ({ workspace, environment, reveal }) => {
75
- const ws = await ctx.resolveWorkspace(workspace);
76
- const resolved = await (0, lookups_js_1.resolveEnvironment)(ctx, ws.id, environment);
79
+ const { ws, value: resolved, note } = await (0, lookups_js_1.findEnvironment)(ctx, workspace, environment);
77
80
  const [fresh, globals] = await Promise.all([ctx.api.getEnvironment(ws.id, resolved.item.id, reveal ?? false), loadGlobals(ctx, ws.id, reveal ?? false)]);
78
81
  const masked = fresh.variables.some((variable) => variable.valueMasked);
79
82
  return (0, shared_js_1.ok)({
80
83
  environment: { id: fresh.id, name: fresh.name, variables: fresh.variables.map((variable) => ({ key: variable.key, value: variable.value })) },
84
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
81
85
  globalVariables: globals.globals.map((variable) => ({ key: variable.key, value: variable.value })),
82
86
  ...(globals.globalVariablesError ? { globalVariablesError: globals.globalVariablesError } : {}),
83
87
  valuesMasked: masked,
84
88
  ...(masked && reveal ? { note: "Values stayed masked: the token lacks environments:reveal." } : {}),
85
- resolved: [(0, resolve_js_1.resolvedSummary)(environment, resolved)],
89
+ resolved: [(0, resolve_js_1.resolvedSummary)(environment, resolved), ...(note ? [{ input: environment, note }] : [])],
86
90
  });
87
91
  }));
88
92
  server.registerTool("create_environment", {
89
93
  title: "Create an environment",
90
- description: `Create an environment with variables ({ key, value }, up to ${VARIABLES_MAX}). Use it for hosts ({{baseUrl}}) and credentials so requests never hard-code them. Names must be unique in the workspace.`,
94
+ description: `Create an environment with variables ({ key, value }, up to ${VARIABLES_MAX}) in \`workspace\` (or the session default — use_workspace). Use it for hosts ({{baseUrl}}) and credentials so requests never hard-code them; requests use environments of their own workspace. Names must be unique in the workspace.`,
91
95
  inputSchema: {
92
96
  workspace: shared_js_1.workspaceArg,
93
97
  name: zod_1.z.string().trim().min(1).max(truncate_js_1.LIMITS.NAME),
@@ -107,7 +111,7 @@ const registerEnvironmentTools = (server, ctx) => {
107
111
  }
108
112
  const created = await ctx.api.createEnvironment(ws.id, { name: name.trim(), variables: rows });
109
113
  ctx.invalidate(`environments:${ws.id}`);
110
- return (0, shared_js_1.ok)({ environment: { id: created.id, name: created.name, variableKeys: created.variables.map((variable) => variable.key) } });
114
+ return (0, shared_js_1.ok)({ environment: { id: created.id, name: created.name, variableKeys: created.variables.map((variable) => variable.key) }, workspace: (0, lookups_js_1.workspaceTag)(ws) });
111
115
  }));
112
116
  server.registerTool("update_environment", {
113
117
  title: "Rename an environment",
@@ -115,14 +119,13 @@ const registerEnvironmentTools = (server, ctx) => {
115
119
  inputSchema: { workspace: shared_js_1.workspaceArg, environment: (0, shared_js_1.refArg)("Environment"), name: zod_1.z.string().trim().min(1).max(truncate_js_1.LIMITS.NAME) },
116
120
  annotations: shared_js_1.IDEMPOTENT_WRITE,
117
121
  }, (0, shared_js_1.guarded)(async ({ workspace, environment, name }) => {
118
- const ws = await ctx.resolveWorkspace(workspace);
119
- const resolved = await (0, lookups_js_1.resolveEnvironment)(ctx, ws.id, environment);
122
+ const { ws, value: resolved } = await (0, lookups_js_1.findEnvironment)(ctx, workspace, environment);
120
123
  const clash = (await (0, lookups_js_1.loadEnvironments)(ctx, ws.id)).find((entry) => entry.id !== resolved.item.id && (0, lookups_js_1.sameName)(entry.name, name));
121
124
  if (clash)
122
125
  throw new errors_js_1.ToolInputError("ENVIRONMENT_NAME_EXISTS", `Another environment is already named "${clash.name}".`);
123
126
  const updated = await ctx.api.updateEnvironment(ws.id, resolved.item.id, { name: name.trim() });
124
127
  ctx.invalidate(`environments:${ws.id}`);
125
- return (0, shared_js_1.ok)({ environment: { id: updated.id, name: updated.name, previousName: resolved.item.name }, resolved: [(0, resolve_js_1.resolvedSummary)(environment, resolved)] });
128
+ return (0, shared_js_1.ok)({ environment: { id: updated.id, name: updated.name, previousName: resolved.item.name }, workspace: (0, lookups_js_1.workspaceTag)(ws), resolved: [(0, resolve_js_1.resolvedSummary)(environment, resolved)] });
126
129
  }));
127
130
  server.registerTool("set_environment_variables", {
128
131
  title: "Set / rename / unset environment variables",
@@ -145,8 +148,7 @@ const registerEnvironmentTools = (server, ctx) => {
145
148
  const duplicates = duplicateKeys(rows);
146
149
  if (duplicates.length > 0)
147
150
  throw new errors_js_1.ToolInputError("DUPLICATE_KEYS", `Each key once in set: ${duplicates.join(", ")}.`);
148
- const ws = await ctx.resolveWorkspace(workspace);
149
- const resolved = await (0, lookups_js_1.resolveEnvironment)(ctx, ws.id, environment);
151
+ const { ws, value: resolved } = await (0, lookups_js_1.findEnvironment)(ctx, workspace, environment);
150
152
  const existingKeys = new Set(resolved.item.variables.map((variable) => variable.key));
151
153
  const updated = [];
152
154
  const added = [];
@@ -200,7 +202,7 @@ const registerEnvironmentTools = (server, ctx) => {
200
202
  }
201
203
  ctx.invalidate(`environments:${ws.id}`);
202
204
  const changed = updated.length + added.length + renamed.length + removed.length;
203
- const body = { environment: { id: resolved.item.id, name: resolved.item.name }, updated, added, renamed, removed, notFound, failed, resolved: [(0, resolve_js_1.resolvedSummary)(environment, resolved)] };
205
+ const body = { environment: { id: resolved.item.id, name: resolved.item.name }, workspace: (0, lookups_js_1.workspaceTag)(ws), updated, added, renamed, removed, notFound, failed, resolved: [(0, resolve_js_1.resolvedSummary)(environment, resolved)] };
204
206
  if (failed.length > 0 && changed === 0) {
205
207
  return { ...(0, shared_js_1.toolFailure)(new errors_js_1.ToolInputError("NOTHING_CHANGED", `No variable was changed — every write failed (first: ${String(failed[0].error.code ?? failed[0].error.message)}).`, body)) };
206
208
  }
@@ -208,7 +210,7 @@ const registerEnvironmentTools = (server, ctx) => {
208
210
  }));
209
211
  server.registerTool("set_global_variables", {
210
212
  title: "Set / unset global variables",
211
- description: "Upsert (`set`: { key, value }) and/or remove (`unset`, needs environments:delete) workspace GLOBAL variables — shared by every environment and resolved as {{key}} when no environment defines it, or explicitly as {{global.key}}. A masked value (••••…) is refused.",
213
+ description: "Upsert (`set`: { key, value }) and/or remove (`unset`, needs environments:delete) the GLOBAL variables of `workspace` (or the session default) — shared by that workspace's environments and resolved as {{key}} when no environment defines it, or explicitly as {{global.key}}. A masked value (••••…) is refused.",
212
214
  inputSchema: {
213
215
  workspace: shared_js_1.workspaceArg,
214
216
  set: zod_1.z.array(variableSchema).max(VARIABLES_MAX).optional(),
@@ -262,11 +264,10 @@ const registerEnvironmentTools = (server, ctx) => {
262
264
  annotations: shared_js_1.DESTRUCTIVE,
263
265
  }, (0, shared_js_1.guarded)(async ({ workspace, environment, confirm }) => {
264
266
  (0, shared_js_1.requireConfirm)(confirm, "an environment");
265
- const ws = await ctx.resolveWorkspace(workspace);
266
- const resolved = await (0, lookups_js_1.resolveEnvironment)(ctx, ws.id, environment, { exact: true });
267
+ const { ws, value: resolved } = await (0, lookups_js_1.findEnvironment)(ctx, workspace, environment, { exact: true });
267
268
  await ctx.api.deleteEnvironment(ws.id, resolved.item.id);
268
269
  ctx.invalidate(`environments:${ws.id}`);
269
- return (0, shared_js_1.ok)({ deleted: { id: resolved.item.id, name: resolved.item.name }, resolved: [(0, resolve_js_1.resolvedSummary)(environment, resolved)] });
270
+ return (0, shared_js_1.ok)({ deleted: { id: resolved.item.id, name: resolved.item.name }, workspace: (0, lookups_js_1.workspaceTag)(ws), resolved: [(0, resolve_js_1.resolvedSummary)(environment, resolved)] });
270
271
  }));
271
272
  };
272
273
  exports.registerEnvironmentTools = registerEnvironmentTools;
@@ -29,7 +29,10 @@ const registerImportTools = (server, ctx) => {
29
29
  },
30
30
  annotations: shared_js_1.WRITE,
31
31
  }, (0, shared_js_1.guarded)(async ({ workspace, content, fileName, format, collectionName, targetCollection, folder, environment, dryRun }) => {
32
- const ws = await ctx.resolveWorkspace(workspace);
32
+ // Into an existing collection → that collection's workspace; otherwise
33
+ // the one named, the session default or the only one.
34
+ const target = targetCollection ? await (0, lookups_js_1.findCollection)(ctx, workspace, targetCollection) : null;
35
+ const ws = target ? target.ws : await ctx.resolveWorkspace(workspace);
33
36
  const detection = (0, importDetect_js_1.detectImport)(fileName, content);
34
37
  const resolvedFormat = format === "auto" ? detection.format : format;
35
38
  const targetEnvironment = environment ? await (0, lookups_js_1.resolveEnvironment)(ctx, ws.id, environment) : null;
@@ -76,7 +79,7 @@ const registerImportTools = (server, ctx) => {
76
79
  return { environment: { id: targetEnvironment.item.id, name: targetEnvironment.item.name, variablesWritten: written } };
77
80
  };
78
81
  if (targetCollection) {
79
- const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, targetCollection);
82
+ const resolved = target.value;
80
83
  const targetFolder = await (0, lookups_js_1.resolveFolder)(ctx, ws.id, resolved.item.id, folder);
81
84
  if (dryRun) {
82
85
  return (0, shared_js_1.ok)({ dryRun: true, detected: detection, format: resolvedFormat, mode: "existing-collection", collection: { id: resolved.item.id, name: resolved.item.name }, requestCount: payload.requests.length, sample });
@@ -94,7 +97,7 @@ const registerImportTools = (server, ctx) => {
94
97
  }
95
98
  ctx.invalidate(`tree:${ws.id}:${resolved.item.id}`);
96
99
  ctx.invalidate(`collections:${ws.id}`);
97
- return (0, shared_js_1.ok)({ format: resolvedFormat, collection: { id: resolved.item.id, name: resolved.item.name }, created: created.length, failed, requests: created, ...(await applyFileVariables()), resolved: [(0, resolve_js_1.resolvedSummary)(targetCollection, resolved)] });
100
+ return (0, shared_js_1.ok)({ format: resolvedFormat, collection: { id: resolved.item.id, name: resolved.item.name }, workspace: (0, lookups_js_1.workspaceTag)(ws), created: created.length, failed, requests: created, ...(await applyFileVariables()), resolved: [(0, resolve_js_1.resolvedSummary)(targetCollection, resolved)] });
98
101
  }
99
102
  const existing = (await (0, lookups_js_1.loadCollections)(ctx, ws.id)).find((entry) => (0, lookups_js_1.sameName)(entry.name, payload.collectionName));
100
103
  if (existing) {
@@ -107,7 +110,7 @@ const registerImportTools = (server, ctx) => {
107
110
  }
108
111
  const result = await ctx.api.importJson(ws.id, { format: "requests", payload });
109
112
  ctx.invalidate(`collections:${ws.id}`);
110
- return (0, shared_js_1.ok)({ format: resolvedFormat, collection: { id: result.collection.id, name: result.collection.name }, created: result.importedCounts, ...(await applyFileVariables()) });
113
+ return (0, shared_js_1.ok)({ format: resolvedFormat, collection: { id: result.collection.id, name: result.collection.name }, workspace: (0, lookups_js_1.workspaceTag)(ws), created: result.importedCounts, ...(await applyFileVariables()) });
111
114
  }
112
115
  if (targetCollection) {
113
116
  throw new errors_js_1.ToolInputError("IMPORT_TARGET_UNSUPPORTED", `A ${resolvedFormat} document always creates a new collection — targetCollection only applies to cURL/.http. Import it, then use move_requests to move requests into "${targetCollection}".`);
@@ -131,6 +134,7 @@ const registerImportTools = (server, ctx) => {
131
134
  return (0, shared_js_1.ok)({
132
135
  format: resolvedFormat,
133
136
  collection: { id: result.collection.id, name: result.collection.name },
137
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
134
138
  created: result.importedCounts,
135
139
  // Keys only — the import response carries the environment's values.
136
140
  ...(result.environment ? { environment: { id: result.environment.id, name: result.environment.name, variableKeys: (result.environment.variables ?? []).map((variable) => variable.key) } } : {}),