callman-mcp 0.1.0 → 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 (204) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +45 -16
  3. package/dist/client/api.d.ts +64 -3
  4. package/dist/client/api.d.ts.map +1 -1
  5. package/dist/client/api.js +67 -2
  6. package/dist/client/api.js.map +1 -1
  7. package/dist/client/errors.d.ts.map +1 -1
  8. package/dist/client/errors.js +33 -7
  9. package/dist/client/errors.js.map +1 -1
  10. package/dist/client/types.d.ts +91 -2
  11. package/dist/client/types.d.ts.map +1 -1
  12. package/dist/context.d.ts +2 -0
  13. package/dist/context.d.ts.map +1 -1
  14. package/dist/context.js +7 -2
  15. package/dist/context.js.map +1 -1
  16. package/dist/domain/contracts.d.ts +893 -0
  17. package/dist/domain/contracts.d.ts.map +1 -0
  18. package/dist/domain/contracts.js +90 -0
  19. package/dist/domain/contracts.js.map +1 -0
  20. package/dist/domain/diagram/build.d.ts +520 -18
  21. package/dist/domain/diagram/build.d.ts.map +1 -1
  22. package/dist/domain/diagram/build.js +65 -19
  23. package/dist/domain/diagram/build.js.map +1 -1
  24. package/dist/domain/diagram/patch.d.ts +624 -51
  25. package/dist/domain/diagram/patch.d.ts.map +1 -1
  26. package/dist/domain/diagram/patch.js +180 -40
  27. package/dist/domain/diagram/patch.js.map +1 -1
  28. package/dist/domain/diagram/style.d.ts +315 -0
  29. package/dist/domain/diagram/style.d.ts.map +1 -0
  30. package/dist/domain/diagram/style.js +124 -0
  31. package/dist/domain/diagram/style.js.map +1 -0
  32. package/dist/domain/diagram/summarize.d.ts +8 -0
  33. package/dist/domain/diagram/summarize.d.ts.map +1 -1
  34. package/dist/domain/diagram/summarize.js +28 -13
  35. package/dist/domain/diagram/summarize.js.map +1 -1
  36. package/dist/domain/requestIntegrations.d.ts +719 -0
  37. package/dist/domain/requestIntegrations.d.ts.map +1 -0
  38. package/dist/domain/requestIntegrations.js +222 -0
  39. package/dist/domain/requestIntegrations.js.map +1 -0
  40. package/dist/domain/requestShape.d.ts +1350 -152
  41. package/dist/domain/requestShape.d.ts.map +1 -1
  42. package/dist/domain/requestShape.js +336 -121
  43. package/dist/domain/requestShape.js.map +1 -1
  44. package/dist/domain/resolve.d.ts +2 -1
  45. package/dist/domain/resolve.d.ts.map +1 -1
  46. package/dist/domain/resolve.js +5 -1
  47. package/dist/domain/resolve.js.map +1 -1
  48. package/dist/domain/scenario/build.d.ts +82 -26
  49. package/dist/domain/scenario/build.d.ts.map +1 -1
  50. package/dist/domain/scenario/build.js +196 -65
  51. package/dist/domain/scenario/build.js.map +1 -1
  52. package/dist/domain/scenario/nodeConfig.d.ts +11 -0
  53. package/dist/domain/scenario/nodeConfig.d.ts.map +1 -0
  54. package/dist/domain/scenario/nodeConfig.js +251 -0
  55. package/dist/domain/scenario/nodeConfig.js.map +1 -0
  56. package/dist/domain/scenario/summarize.d.ts +145 -25
  57. package/dist/domain/scenario/summarize.d.ts.map +1 -1
  58. package/dist/domain/scenario/summarize.js +84 -29
  59. package/dist/domain/scenario/summarize.js.map +1 -1
  60. package/dist/lib/mask.d.ts +0 -18
  61. package/dist/lib/mask.d.ts.map +1 -1
  62. package/dist/lib/mask.js +2 -44
  63. package/dist/lib/mask.js.map +1 -1
  64. package/dist/lib/pagination.d.ts +3 -2
  65. package/dist/lib/pagination.d.ts.map +1 -1
  66. package/dist/lib/pagination.js +13 -8
  67. package/dist/lib/pagination.js.map +1 -1
  68. package/dist/lib/redact.d.ts +12 -0
  69. package/dist/lib/redact.d.ts.map +1 -0
  70. package/dist/lib/redact.js +50 -0
  71. package/dist/lib/redact.js.map +1 -0
  72. package/dist/lib/strict.d.ts +4 -0
  73. package/dist/lib/strict.d.ts.map +1 -0
  74. package/dist/lib/strict.js +14 -0
  75. package/dist/lib/strict.js.map +1 -0
  76. package/dist/prompts/index.js +7 -7
  77. package/dist/prompts/index.js.map +1 -1
  78. package/dist/resources/docs/contracts.d.ts +2 -0
  79. package/dist/resources/docs/contracts.d.ts.map +1 -0
  80. package/dist/resources/docs/contracts.js +74 -0
  81. package/dist/resources/docs/contracts.js.map +1 -0
  82. package/dist/resources/docs/diagramSchema.d.ts +1 -1
  83. package/dist/resources/docs/diagramSchema.d.ts.map +1 -1
  84. package/dist/resources/docs/diagramSchema.js +95 -28
  85. package/dist/resources/docs/diagramSchema.js.map +1 -1
  86. package/dist/resources/docs/overview.d.ts +1 -1
  87. package/dist/resources/docs/overview.d.ts.map +1 -1
  88. package/dist/resources/docs/overview.js +29 -7
  89. package/dist/resources/docs/overview.js.map +1 -1
  90. package/dist/resources/docs/requestSchema.d.ts +1 -1
  91. package/dist/resources/docs/requestSchema.d.ts.map +1 -1
  92. package/dist/resources/docs/requestSchema.js +85 -16
  93. package/dist/resources/docs/requestSchema.js.map +1 -1
  94. package/dist/resources/generated/aiPacks.d.ts +1 -1
  95. package/dist/resources/generated/aiPacks.d.ts.map +1 -1
  96. package/dist/resources/generated/aiPacks.js +3 -2
  97. package/dist/resources/generated/aiPacks.js.map +1 -1
  98. package/dist/resources/index.d.ts.map +1 -1
  99. package/dist/resources/index.js +19 -19
  100. package/dist/resources/index.js.map +1 -1
  101. package/dist/run/executeCollection.d.ts +3 -1
  102. package/dist/run/executeCollection.d.ts.map +1 -1
  103. package/dist/run/executeCollection.js +16 -4
  104. package/dist/run/executeCollection.js.map +1 -1
  105. package/dist/run/summarize.d.ts +17 -6
  106. package/dist/run/summarize.d.ts.map +1 -1
  107. package/dist/run/summarize.js +48 -41
  108. package/dist/run/summarize.js.map +1 -1
  109. package/dist/run/types.d.ts +1 -0
  110. package/dist/run/types.d.ts.map +1 -1
  111. package/dist/server.d.ts +1 -1
  112. package/dist/server.d.ts.map +1 -1
  113. package/dist/server.js +1 -1
  114. package/dist/server.js.map +1 -1
  115. package/dist/tools/catalog.d.ts +1 -1
  116. package/dist/tools/catalog.d.ts.map +1 -1
  117. package/dist/tools/catalog.js +18 -0
  118. package/dist/tools/catalog.js.map +1 -1
  119. package/dist/tools/collections.d.ts.map +1 -1
  120. package/dist/tools/collections.js +53 -41
  121. package/dist/tools/collections.js.map +1 -1
  122. package/dist/tools/contracts.d.ts +4 -0
  123. package/dist/tools/contracts.d.ts.map +1 -0
  124. package/dist/tools/contracts.js +201 -0
  125. package/dist/tools/contracts.js.map +1 -0
  126. package/dist/tools/diagrams.d.ts.map +1 -1
  127. package/dist/tools/diagrams.js +19 -10
  128. package/dist/tools/diagrams.js.map +1 -1
  129. package/dist/tools/environments.d.ts.map +1 -1
  130. package/dist/tools/environments.js +184 -64
  131. package/dist/tools/environments.js.map +1 -1
  132. package/dist/tools/import.d.ts.map +1 -1
  133. package/dist/tools/import.js +111 -20
  134. package/dist/tools/import.js.map +1 -1
  135. package/dist/tools/index.d.ts +2 -1
  136. package/dist/tools/index.d.ts.map +1 -1
  137. package/dist/tools/index.js +19 -1
  138. package/dist/tools/index.js.map +1 -1
  139. package/dist/tools/lookups.d.ts +38 -7
  140. package/dist/tools/lookups.d.ts.map +1 -1
  141. package/dist/tools/lookups.js +113 -10
  142. package/dist/tools/lookups.js.map +1 -1
  143. package/dist/tools/meta.d.ts.map +1 -1
  144. package/dist/tools/meta.js +9 -4
  145. package/dist/tools/meta.js.map +1 -1
  146. package/dist/tools/requests.d.ts.map +1 -1
  147. package/dist/tools/requests.js +138 -50
  148. package/dist/tools/requests.js.map +1 -1
  149. package/dist/tools/run.d.ts.map +1 -1
  150. package/dist/tools/run.js +84 -28
  151. package/dist/tools/run.js.map +1 -1
  152. package/dist/tools/scenarios.d.ts.map +1 -1
  153. package/dist/tools/scenarios.js +238 -75
  154. package/dist/tools/scenarios.js.map +1 -1
  155. package/dist/tools/shared.d.ts +1 -0
  156. package/dist/tools/shared.d.ts.map +1 -1
  157. package/dist/tools/shared.js +9 -1
  158. package/dist/tools/shared.js.map +1 -1
  159. package/dist/tools/workspaces.d.ts.map +1 -1
  160. package/dist/tools/workspaces.js +44 -7
  161. package/dist/tools/workspaces.js.map +1 -1
  162. package/dist-cjs/client/api.js +67 -2
  163. package/dist-cjs/client/errors.js +33 -7
  164. package/dist-cjs/context.js +7 -2
  165. package/dist-cjs/domain/contracts.js +93 -0
  166. package/dist-cjs/domain/diagram/build.js +71 -20
  167. package/dist-cjs/domain/diagram/patch.js +179 -39
  168. package/dist-cjs/domain/diagram/style.js +130 -0
  169. package/dist-cjs/domain/diagram/summarize.js +28 -13
  170. package/dist-cjs/domain/requestIntegrations.js +228 -0
  171. package/dist-cjs/domain/requestShape.js +342 -121
  172. package/dist-cjs/domain/resolve.js +5 -1
  173. package/dist-cjs/domain/scenario/build.js +197 -65
  174. package/dist-cjs/domain/scenario/nodeConfig.js +260 -0
  175. package/dist-cjs/domain/scenario/summarize.js +86 -29
  176. package/dist-cjs/lib/mask.js +3 -48
  177. package/dist-cjs/lib/pagination.js +13 -8
  178. package/dist-cjs/lib/redact.js +53 -0
  179. package/dist-cjs/lib/strict.js +18 -0
  180. package/dist-cjs/prompts/index.js +7 -7
  181. package/dist-cjs/resources/docs/contracts.js +76 -0
  182. package/dist-cjs/resources/docs/diagramSchema.js +95 -28
  183. package/dist-cjs/resources/docs/overview.js +29 -7
  184. package/dist-cjs/resources/docs/requestSchema.js +85 -16
  185. package/dist-cjs/resources/generated/aiPacks.js +3 -2
  186. package/dist-cjs/resources/index.js +17 -17
  187. package/dist-cjs/run/executeCollection.js +16 -4
  188. package/dist-cjs/run/summarize.js +48 -41
  189. package/dist-cjs/server.js +1 -1
  190. package/dist-cjs/tools/catalog.js +18 -0
  191. package/dist-cjs/tools/collections.js +52 -40
  192. package/dist-cjs/tools/contracts.js +204 -0
  193. package/dist-cjs/tools/diagrams.js +19 -10
  194. package/dist-cjs/tools/environments.js +182 -62
  195. package/dist-cjs/tools/import.js +107 -16
  196. package/dist-cjs/tools/index.js +21 -2
  197. package/dist-cjs/tools/lookups.js +123 -11
  198. package/dist-cjs/tools/meta.js +9 -4
  199. package/dist-cjs/tools/requests.js +134 -46
  200. package/dist-cjs/tools/run.js +82 -26
  201. package/dist-cjs/tools/scenarios.js +235 -72
  202. package/dist-cjs/tools/shared.js +11 -2
  203. package/dist-cjs/tools/workspaces.js +43 -6
  204. package/package.json +2 -2
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sameName = exports.resolveDiagram = exports.loadDiagrams = exports.resolveScenarioFolder = exports.resolveScenario = exports.loadScenarioCatalog = exports.resolveEnvironment = exports.loadEnvironments = exports.resolveRequest = exports.searchRequests = exports.requestIdsInFolder = exports.resolveFolder = exports.loadTree = exports.resolveCollection = exports.loadCollections = void 0;
3
+ exports.sameName = exports.connectionResolverFor = exports.loadConnections = exports.resolveDiagram = exports.loadDiagrams = exports.resolveScenarioFolder = exports.resolveScenario = exports.loadScenarioCatalog = exports.resolveEnvironment = exports.loadEnvironments = exports.resolveRequest = exports.searchRequests = exports.requestIdsInFolder = exports.resolveFolder = exports.loadTree = exports.resolveCollection = exports.findEnvironment = exports.findRequest = exports.findCollection = exports.findInWorkspaces = exports.workspaceTag = exports.workspacesForListing = exports.isAllWorkspaces = exports.loadCollections = void 0;
4
+ const errors_js_1 = require("../client/errors.js");
5
+ const requestIntegrations_js_1 = require("../domain/requestIntegrations.js");
4
6
  const resolve_js_1 = require("../domain/resolve.js");
5
7
  const ids_js_1 = require("../lib/ids.js");
6
8
  const truncate_js_1 = require("../lib/truncate.js");
@@ -9,7 +11,106 @@ const truncate_js_1 = require("../lib/truncate.js");
9
11
  // prefixes it touches so the next read is fresh.
10
12
  const loadCollections = (ctx, ws) => ctx.cache.getOrSet(`collections:${ws}`, () => ctx.api.listAllCollections(ws));
11
13
  exports.loadCollections = loadCollections;
12
- const resolveCollection = async (ctx, ws, ref) => {
14
+ // ── across workspaces ──────────────────────────────────────────────────
15
+ // A personal token acts in EVERY workspace the user belongs to. Reads span
16
+ // all of them; the session default only orders them and wins ties.
17
+ // `workspace: "all"` (or "*") asks a listing for every workspace explicitly.
18
+ const isAllWorkspaces = (ref) => Boolean(ref && ["all", "*", "any"].includes(ref.trim().toLowerCase()));
19
+ exports.isAllWorkspaces = isAllWorkspaces;
20
+ const workspacesForListing = async (ctx, explicit) => {
21
+ if (explicit && explicit.trim() && !(0, exports.isAllWorkspaces)(explicit))
22
+ return [await ctx.resolveWorkspace(explicit)];
23
+ const workspaces = await ctx.listWorkspaces();
24
+ const preferred = ctx.getDefaultWorkspaceId();
25
+ return [...workspaces].sort((a, b) => Number(b.id === preferred) - Number(a.id === preferred));
26
+ };
27
+ exports.workspacesForListing = workspacesForListing;
28
+ const workspaceTag = (ws) => ({ id: ws.id, name: ws.name });
29
+ exports.workspaceTag = workspaceTag;
30
+ // Find something the model named (a collection, request, environment…).
31
+ // `workspace` given → only there. Otherwise the default workspace first — an
32
+ // exact or id match there wins at once; then every other workspace: a unique
33
+ // match anywhere is used, the same name in several workspaces comes back as
34
+ // candidates tagged with their workspace. A workspace where the thing is
35
+ // missing (404 / REF_NOT_FOUND) is simply skipped.
36
+ const findInWorkspaces = async (ctx, explicit, kind, ref, find, options = {}) => {
37
+ if (explicit && explicit.trim() && !(0, exports.isAllWorkspaces)(explicit)) {
38
+ const ws = await ctx.resolveWorkspace(explicit);
39
+ return { ws, value: await find(ws) };
40
+ }
41
+ const order = await (0, exports.workspacesForListing)(ctx, null);
42
+ if (order.length === 0)
43
+ throw new errors_js_1.ToolInputError("WORKSPACE_NONE", "You are not a member of any workspace.");
44
+ const preferred = ctx.getDefaultWorkspaceId();
45
+ const hits = [];
46
+ const ambiguous = [];
47
+ for (const ws of order) {
48
+ let value;
49
+ try {
50
+ value = await find(ws);
51
+ }
52
+ catch (error) {
53
+ if (error instanceof errors_js_1.ToolInputError && error.code === "REF_NOT_FOUND")
54
+ continue;
55
+ if (error instanceof errors_js_1.ToolInputError && error.code === "REF_AMBIGUOUS") {
56
+ ambiguous.push({ ws, error });
57
+ continue;
58
+ }
59
+ if (error instanceof errors_js_1.CallmanApiError && (error.status === 404 || error.status === 403))
60
+ continue;
61
+ throw error;
62
+ }
63
+ const matched = options.matchOf?.(value);
64
+ if (matched === "id" || (ws.id === preferred && matched !== "partial")) {
65
+ return { ws, value, ...(ws.id !== preferred && order.length > 1 ? { note: `found in workspace "${ws.name}"` } : {}) };
66
+ }
67
+ hits.push({ ws, value, matched });
68
+ }
69
+ const exact = hits.filter((hit) => hit.matched !== "partial");
70
+ const pool = exact.length > 0 ? exact : hits;
71
+ // Ambiguous inside ONE workspace and nothing elsewhere: that workspace's
72
+ // own candidate list, tagged with where it is.
73
+ if (pool.length === 0 && ambiguous.length === 1) {
74
+ const [{ ws, error }] = ambiguous;
75
+ throw new errors_js_1.ToolInputError(error.code, order.length > 1 ? `${error.message} (in workspace "${ws.name}")` : error.message, { ...(error.data ?? {}), workspace: (0, exports.workspaceTag)(ws) });
76
+ }
77
+ if (pool.length === 1 && ambiguous.length === 0) {
78
+ const [hit] = pool;
79
+ return { ws: hit.ws, value: hit.value, ...(hit.ws.id !== preferred && order.length > 1 ? { note: `found in workspace "${hit.ws.name}"` } : {}) };
80
+ }
81
+ if (pool.length > 1 || ambiguous.length > 0) {
82
+ throw new errors_js_1.ToolInputError("REF_AMBIGUOUS", `"${ref}" matches ${kind}s in several places — pass \`workspace\` (name or id), or use the id.`, {
83
+ kind,
84
+ ref,
85
+ candidates: [
86
+ ...pool.map((hit) => ({ workspace: (0, exports.workspaceTag)(hit.ws), ...(options.describe?.(hit.value) ?? {}) })),
87
+ ...ambiguous.map((entry) => ({ workspace: (0, exports.workspaceTag)(entry.ws), candidates: entry.error.data?.candidates })),
88
+ ],
89
+ });
90
+ }
91
+ throw new errors_js_1.ToolInputError("REF_NOT_FOUND", `No ${kind} matches "${ref}" in ${order.length === 1 ? "your workspace" : `any of your ${order.length} workspaces`}.`, {
92
+ kind,
93
+ ref,
94
+ searchedWorkspaces: order.map(exports.workspaceTag),
95
+ });
96
+ };
97
+ exports.findInWorkspaces = findInWorkspaces;
98
+ const findCollection = (ctx, workspace, ref, options = {}) => (0, exports.findInWorkspaces)(ctx, workspace, "collection", ref, (ws) => (0, exports.resolveCollection)(ctx, ws.id, ref, options), {
99
+ matchOf: (resolved) => resolved.matched,
100
+ describe: (resolved) => ({ id: resolved.item.id, name: resolved.item.name }),
101
+ });
102
+ exports.findCollection = findCollection;
103
+ const findRequest = (ctx, workspace, ref, collectionRef, options = {}) => (0, exports.findInWorkspaces)(ctx, workspace, "request", ref, (ws) => (0, exports.resolveRequest)(ctx, ws.id, ref, collectionRef, options), {
104
+ matchOf: (resolved) => resolved.matched,
105
+ describe: (resolved) => ({ id: resolved.request.id, name: resolved.request.name, method: resolved.request.method, url: resolved.request.url }),
106
+ });
107
+ exports.findRequest = findRequest;
108
+ const findEnvironment = (ctx, workspace, ref, options = {}) => (0, exports.findInWorkspaces)(ctx, workspace, "environment", ref, (ws) => (0, exports.resolveEnvironment)(ctx, ws.id, ref, options), {
109
+ matchOf: (resolved) => resolved.matched,
110
+ describe: (resolved) => ({ id: resolved.item.id, name: resolved.item.name }),
111
+ });
112
+ exports.findEnvironment = findEnvironment;
113
+ const resolveCollection = async (ctx, ws, ref, options = {}) => {
13
114
  const collections = await (0, exports.loadCollections)(ctx, ws);
14
115
  if ((0, ids_js_1.isObjectId)(ref) && !collections.some((collection) => collection.id === ref)) {
15
116
  // Not in the cached list (just created?) — let the server decide.
@@ -23,6 +124,7 @@ const resolveCollection = async (ctx, ws, ref) => {
23
124
  }
24
125
  return (0, resolve_js_1.resolveRef)("collection", ref, collections, {
25
126
  describe: (collection) => ({ requestCount: collection.requestCount ?? null }),
127
+ exact: options.exact,
26
128
  });
27
129
  };
28
130
  exports.resolveCollection = resolveCollection;
@@ -31,11 +133,11 @@ const loadTree = (ctx, ws, collectionId) => ctx.cache.getOrSet(`tree:${ws}:${col
31
133
  return { folders: (0, resolve_js_1.flattenFolderTree)(folderTree.tree ?? []), requests };
32
134
  });
33
135
  exports.loadTree = loadTree;
34
- const resolveFolder = async (ctx, ws, collectionId, ref) => {
136
+ const resolveFolder = async (ctx, ws, collectionId, ref, options = {}) => {
35
137
  if (!ref || !ref.trim())
36
138
  return null;
37
139
  const tree = await (0, exports.loadTree)(ctx, ws, collectionId);
38
- return (0, resolve_js_1.resolveFolderRef)(ref, tree.folders);
140
+ return (0, resolve_js_1.resolveFolderRef)(ref, tree.folders, options.exact);
39
141
  };
40
142
  exports.resolveFolder = resolveFolder;
41
143
  // Request ids inside a folder, subfolders included.
@@ -85,9 +187,9 @@ const searchRequests = async (ctx, ws, query, collectionId, limit = truncate_js_
85
187
  exports.searchRequests = searchRequests;
86
188
  // A request reference: an id (fetched directly) or a name — unique across the
87
189
  // given collection, or across the workspace when no collection is given.
88
- const resolveRequest = async (ctx, ws, ref, collectionRef) => {
190
+ const resolveRequest = async (ctx, ws, ref, collectionRef, options = {}) => {
89
191
  if ((0, ids_js_1.isObjectId)(ref)) {
90
- return { request: await ctx.api.getRequest(ws, ref) };
192
+ return { request: await ctx.api.getRequest(ws, ref), matched: "id" };
91
193
  }
92
194
  const collectionId = collectionRef ? (await (0, exports.resolveCollection)(ctx, ws, collectionRef)).item.id : null;
93
195
  const collections = await (0, exports.loadCollections)(ctx, ws);
@@ -101,14 +203,16 @@ const resolveRequest = async (ctx, ws, ref, collectionRef) => {
101
203
  const resolved = (0, resolve_js_1.resolveRef)("request", ref, candidates, {
102
204
  describe: (request) => ({ method: request.method, url: request.url, collection: request.collectionName }),
103
205
  aliases: (request) => [`${request.method} ${request.url}`, request.url],
206
+ exact: options.exact,
104
207
  });
105
- return { request: await ctx.api.getRequest(ws, resolved.item.id), annotation: resolved.annotation };
208
+ return { request: await ctx.api.getRequest(ws, resolved.item.id), annotation: resolved.annotation, matched: resolved.matched };
106
209
  };
107
210
  exports.resolveRequest = resolveRequest;
108
- const loadEnvironments = (ctx, ws) => ctx.cache.getOrSet(`environments:${ws}`, async () => (await ctx.api.listEnvironments(ws)).items);
211
+ const loadEnvironments = (ctx, ws) => ctx.cache.getOrSet(`environments:${ws}`, () => ctx.api.listAllEnvironments(ws));
109
212
  exports.loadEnvironments = loadEnvironments;
110
- const resolveEnvironment = async (ctx, ws, ref) => (0, resolve_js_1.resolveRef)("environment", ref, await (0, exports.loadEnvironments)(ctx, ws), {
213
+ const resolveEnvironment = async (ctx, ws, ref, options = {}) => (0, resolve_js_1.resolveRef)("environment", ref, await (0, exports.loadEnvironments)(ctx, ws), {
111
214
  describe: (environment) => ({ variables: environment.variables.length }),
215
+ exact: options.exact,
112
216
  });
113
217
  exports.resolveEnvironment = resolveEnvironment;
114
218
  const loadScenarioCatalog = (ctx, ws) => ctx.cache.getOrSet(`scenarios:${ws ?? "-"}`, async () => {
@@ -120,7 +224,7 @@ const loadScenarioCatalog = (ctx, ws) => ctx.cache.getOrSet(`scenarios:${ws ?? "
120
224
  return { scenarios, folders };
121
225
  });
122
226
  exports.loadScenarioCatalog = loadScenarioCatalog;
123
- const resolveScenario = async (ctx, ws, ref) => {
227
+ const resolveScenario = async (ctx, ws, ref, options = {}) => {
124
228
  if ((0, ids_js_1.isObjectId)(ref)) {
125
229
  return { item: await ctx.api.getScenario(ws, ref), matched: "id" };
126
230
  }
@@ -128,6 +232,7 @@ const resolveScenario = async (ctx, ws, ref) => {
128
232
  const folderName = new Map(folders.map((folder) => [folder.id, folder.name]));
129
233
  return (0, resolve_js_1.resolveRef)("scenario", ref, scenarios, {
130
234
  describe: (scenario) => ({ folder: scenario.folderId ? (folderName.get(scenario.folderId) ?? null) : null, reviewStatus: scenario.reviewStatus }),
235
+ exact: options.exact,
131
236
  });
132
237
  };
133
238
  exports.resolveScenario = resolveScenario;
@@ -135,15 +240,22 @@ const resolveScenarioFolder = async (ctx, ws, ref) => (0, resolve_js_1.resolveRe
135
240
  exports.resolveScenarioFolder = resolveScenarioFolder;
136
241
  const loadDiagrams = (ctx) => ctx.cache.getOrSet("diagrams", () => ctx.api.listDiagrams());
137
242
  exports.loadDiagrams = loadDiagrams;
138
- const resolveDiagram = async (ctx, ref) => {
243
+ const resolveDiagram = async (ctx, ref, options = {}) => {
139
244
  if ((0, ids_js_1.isObjectId)(ref))
140
245
  return { item: await ctx.api.getDiagram(ref), matched: "id" };
141
246
  const resolved = (0, resolve_js_1.resolveRef)("diagram", ref, await (0, exports.loadDiagrams)(ctx), {
142
247
  describe: (diagram) => ({ canEdit: diagram.canEdit }),
248
+ exact: options.exact,
143
249
  });
144
250
  // The list carries the document too, but fetch by id for the freshest copy.
145
251
  return { ...resolved, item: await ctx.api.getDiagram(resolved.item.id) };
146
252
  };
147
253
  exports.resolveDiagram = resolveDiagram;
254
+ const loadConnections = (ctx, ws) => ctx.cache.getOrSet(`connections:${ws}`, () => ctx.api.listConnections(ws));
255
+ exports.loadConnections = loadConnections;
256
+ // Connection resolver for request integration steps — loaded only when a
257
+ // request actually names a connection.
258
+ const connectionResolverFor = async (ctx, ws, needed) => needed ? (0, requestIntegrations_js_1.connectionResolver)(await (0, exports.loadConnections)(ctx, ws)) : undefined;
259
+ exports.connectionResolverFor = connectionResolverFor;
148
260
  const sameName = (a, b) => (0, ids_js_1.normalizeName)(a) === (0, ids_js_1.normalizeName)(b);
149
261
  exports.sameName = sameName;
@@ -46,8 +46,9 @@ const registerMetaTools = (server, ctx) => {
46
46
  const me = meResult.value;
47
47
  const workspaces = workspacesResult.status === "fulfilled" ? workspacesResult.value : [];
48
48
  const features = featuresResult.status === "fulfilled" ? featuresResult.value.features : {};
49
- const defaultWorkspace = ctx.config.defaultWorkspaceId
50
- ? (workspaces.find((workspace) => workspace.id === ctx.config.defaultWorkspaceId) ?? null)
49
+ const preferred = ctx.getDefaultWorkspaceId();
50
+ const defaultWorkspace = preferred
51
+ ? (workspaces.find((workspace) => workspace.id === preferred) ?? null)
51
52
  : workspaces.length === 1
52
53
  ? workspaces[0]
53
54
  : null;
@@ -69,8 +70,12 @@ const registerMetaTools = (server, ctx) => {
69
70
  isDefault: workspace.id === defaultWorkspace?.id,
70
71
  })),
71
72
  defaultWorkspaceId: defaultWorkspace?.id ?? null,
72
- ...(workspaces.length > 1 && !defaultWorkspace
73
- ? { note: "Several workspaces and no default: pass `workspace` to tools or set CALLMAN_WORKSPACE_ID." }
73
+ ...(workspaces.length > 1
74
+ ? {
75
+ workspaceScope: `This token works in all ${workspaces.length} of your workspaces. Lists and searches cover every workspace unless you pass \`workspace\`; a name is looked up in the default workspace first, then in all others. ${defaultWorkspace
76
+ ? `The default ("${defaultWorkspace.name}") only decides where NEW things go and wins a name that exists in several workspaces`
77
+ : "There is no default, so creating something needs `workspace`"} — change it for this session with use_workspace.`,
78
+ }
74
79
  : {}),
75
80
  features,
76
81
  localRun: ctx.config.allowLocalRun,
@@ -4,8 +4,10 @@ exports.registerRequestTools = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const errors_js_1 = require("../client/errors.js");
6
6
  const requestShape_js_1 = require("../domain/requestShape.js");
7
+ const strict_js_1 = require("../lib/strict.js");
7
8
  const resolve_js_1 = require("../domain/resolve.js");
8
9
  const truncate_js_1 = require("../lib/truncate.js");
10
+ const requestIntegrations_js_1 = require("../domain/requestIntegrations.js");
9
11
  const lookups_js_1 = require("./lookups.js");
10
12
  const shared_js_1 = require("./shared.js");
11
13
  const BULK_MAX = 5000;
@@ -13,7 +15,7 @@ const MOVE_MAX = 200;
13
15
  const registerRequestTools = (server, ctx) => {
14
16
  server.registerTool("search_requests", {
15
17
  title: "Search requests",
16
- description: "Find saved requests by words in their name, method or URL (all words must match), across the workspace or inside one collection. Returns up to 50 matches with ids you can pass to get_request / run_request.",
18
+ description: "Find saved requests by words in their name, method or URL (all words must match) — across ALL your workspaces, one `workspace`, or inside one collection. Returns up to 50 matches (each with its collection and workspace) with ids you can pass to get_request / run_request.",
17
19
  inputSchema: {
18
20
  workspace: shared_js_1.workspaceArg,
19
21
  query: zod_1.z.string().min(1).max(200).describe("e.g. \"POST login\", \"users\", \"GET /orders\""),
@@ -22,19 +24,28 @@ const registerRequestTools = (server, ctx) => {
22
24
  },
23
25
  annotations: shared_js_1.READ,
24
26
  }, (0, shared_js_1.guarded)(async ({ workspace, query, collection, limit }) => {
25
- const ws = await ctx.resolveWorkspace(workspace);
26
- const scope = collection ? await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection) : null;
27
- const matches = await (0, lookups_js_1.searchRequests)(ctx, ws.id, query, scope?.item.id ?? null, limit ?? truncate_js_1.LIMITS.SEARCH_RESULTS);
27
+ const max = limit ?? truncate_js_1.LIMITS.SEARCH_RESULTS;
28
+ const scoped = collection ? await (0, lookups_js_1.findCollection)(ctx, workspace, collection) : null;
29
+ const workspaces = scoped ? [scoped.ws] : await (0, lookups_js_1.workspacesForListing)(ctx, workspace);
30
+ const matches = [];
31
+ for (const ws of workspaces) {
32
+ if (matches.length >= max)
33
+ break;
34
+ for (const match of await (0, lookups_js_1.searchRequests)(ctx, ws.id, query, scoped?.value.item.id ?? null, max - matches.length))
35
+ matches.push({ ...match, ws });
36
+ }
37
+ const scope = scoped?.value ?? null;
28
38
  return (0, shared_js_1.ok)({
29
39
  query,
30
40
  ...(scope ? { collection: { id: scope.item.id, name: scope.item.name } } : {}),
31
- matches: matches.map((request) => ({ ...(0, requestShape_js_1.requestSummaryView)(request), collection: request.collectionName })),
32
- ...(matches.length >= (limit ?? truncate_js_1.LIMITS.SEARCH_RESULTS) ? { truncated: true } : {}),
41
+ ...(workspaces.length > 1 ? { searchedWorkspaces: workspaces.length } : { workspace: (0, lookups_js_1.workspaceTag)(workspaces[0]) }),
42
+ matches: matches.map((request) => ({ ...(0, requestShape_js_1.requestSummaryView)(request), collection: request.collectionName, ...(workspaces.length > 1 ? { workspace: (0, lookups_js_1.workspaceTag)(request.ws) } : {}) })),
43
+ ...(matches.length >= max ? { truncated: true } : {}),
33
44
  });
34
45
  }));
35
46
  server.registerTool("get_request", {
36
47
  title: "Get a request",
37
- description: "Read one saved request: url, headers, query, body, auth (secrets masked unless reveal:true and the token holds requests:read + the request's own permissions), scripts. `full: true` returns untruncated body and scripts.",
48
+ description: "Read one saved request in the SAME shape create_request / update_request accept: url, headers, query, pathVariables, body (a JSON value or { mode, … }), auth ({ type, token | username/password | key/value/location | oauth2 fields }), scripts, isEnabledInRunner, plus which database / Kafka / Redis steps it has. Secrets in auth and sensitive headers are masked (••••ab) unless reveal: true — sending a masked value back unchanged keeps the stored secret. A long body comes back as bodyPreview (body: null) unless full: true.",
38
49
  inputSchema: {
39
50
  workspace: shared_js_1.workspaceArg,
40
51
  request: (0, shared_js_1.refArg)("Request", "(name, \"METHOD url\" or id)"),
@@ -44,38 +55,52 @@ const registerRequestTools = (server, ctx) => {
44
55
  },
45
56
  annotations: shared_js_1.READ,
46
57
  }, (0, shared_js_1.guarded)(async ({ workspace, request, collection, full, reveal }) => {
47
- const ws = await ctx.resolveWorkspace(workspace);
48
- const { request: found, annotation } = await (0, lookups_js_1.resolveRequest)(ctx, ws.id, request, collection);
49
- return (0, shared_js_1.ok)({ request: (0, requestShape_js_1.compactRequestView)(found, { full, reveal }), ...(annotation ? { resolved: [{ input: request, matched: found.name, id: found.id, note: annotation }] } : {}) });
58
+ const { ws, value, note } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
59
+ const { request: found, annotation } = value;
60
+ return (0, shared_js_1.ok)({
61
+ request: (0, requestShape_js_1.compactRequestView)(found, { full, reveal }),
62
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
63
+ ...(annotation || note ? { resolved: [{ input: request, matched: found.name, id: found.id, note: [annotation, note].filter(Boolean).join("; ") }] } : {}),
64
+ });
50
65
  }));
51
66
  server.registerTool("create_request", {
52
67
  title: "Create a request",
53
- description: "Create one saved request in a collection (optionally inside a folder by id, name or path). Body may be a JSON value, a raw string, or { mode: raw|formdata|urlencoded }. Use {{variable}} for hosts and secrets and put the values in an environment. For many requests use create_requests_bulk (one call, atomic).",
68
+ description: "Create one saved request in a collection (optionally inside a folder by id, name or path): method, url, headers, query, path variables, body (a JSON value, a raw string, or { mode: raw|formdata|urlencoded|json }), auth (bearer, basic, apiKey, oauth2), pre-request / post-response / tests scripts, and optional database / Kafka / Redis steps and DB assertions (connections by id or name — list_connections). Use {{variable}} for hosts and secrets and put the values in an environment. Contract checks are added with add_contract_rules / set_data_contract. For many requests use create_requests_bulk (one call, atomic). Reference: callman://docs/request-schema.",
54
69
  inputSchema: { workspace: shared_js_1.workspaceArg, collection: (0, shared_js_1.refArg)("Collection"), ...requestShape_js_1.requestInputSchema.omit({ folderKey: true }).shape },
55
70
  annotations: shared_js_1.WRITE,
56
71
  }, (0, shared_js_1.guarded)(async ({ workspace, collection, ...item }) => {
57
- const ws = await ctx.resolveWorkspace(workspace);
58
- const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection);
72
+ const { ws, value: resolved, note } = await (0, lookups_js_1.findCollection)(ctx, workspace, collection);
59
73
  const folder = await (0, lookups_js_1.resolveFolder)(ctx, ws.id, resolved.item.id, item.folder);
60
- const created = await ctx.api.createRequest(ws.id, (0, requestShape_js_1.toCreateRequestPayload)(item, { collectionId: resolved.item.id, folderId: folder?.item.id ?? null }));
74
+ const input = (0, requestShape_js_1.resolveMaskedValues)(item);
75
+ const resolve = await (0, lookups_js_1.connectionResolverFor)(ctx, ws.id, (0, requestIntegrations_js_1.hasIntegrations)(input));
76
+ const { payload, warnings } = (0, requestShape_js_1.toCreateRequestPayload)(input, { collectionId: resolved.item.id, folderId: folder?.item.id ?? null }, resolve);
77
+ let created = await ctx.api.createRequest(ws.id, payload);
78
+ if (input.isEnabledInRunner === false)
79
+ created = await ctx.api.setRequestRunState(ws.id, created.id, false);
61
80
  ctx.invalidate(`tree:${ws.id}:${resolved.item.id}`);
62
81
  ctx.invalidate(`collections:${ws.id}`);
63
82
  return (0, shared_js_1.ok)({
64
- request: (0, requestShape_js_1.requestSummaryView)(created),
83
+ request: { ...(0, requestShape_js_1.requestSummaryView)(created), isEnabledInRunner: created.isEnabledInRunner !== false },
65
84
  collection: { id: resolved.item.id, name: resolved.item.name },
85
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
66
86
  ...(folder ? { folder: { id: folder.item.id, path: folder.item.path } } : {}),
67
- resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), ...(folder && item.folder ? [(0, resolve_js_1.resolvedSummary)(item.folder, folder)] : [])],
87
+ ...(warnings.length > 0 ? { warnings } : {}),
88
+ resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), ...(note ? [{ input: collection, note }] : []), ...(folder && item.folder ? [(0, resolve_js_1.resolvedSummary)(item.folder, folder)] : [])],
68
89
  });
69
90
  }));
70
91
  server.registerTool("create_requests_bulk", {
71
92
  title: "Create many requests",
72
- description: "Create many requests at once. Either `collectionName` (creates a NEW collection with an optional folder skeleton — atomic: all or nothing) or `collection` (adds to an EXISTING one; each request may name a `folder`). Requests reference folders by `folderKey` (new collection) or `folder` (existing). Use dryRun: true first for large sets.",
93
+ description: "Create many requests at once. Either `collectionName` (creates a NEW collection with an optional `folders` skeleton — atomic: all or nothing; requests pick a folder with `folderKey`) or `collection` (adds to an EXISTING one, one request at a time; requests pick a folder with `folder`). Mixing the two folder styles is an error. Each request takes the same fields as create_request. Use dryRun: true first for large sets.",
73
94
  inputSchema: {
74
95
  workspace: shared_js_1.workspaceArg,
75
96
  collectionName: zod_1.z.string().min(1).max(truncate_js_1.LIMITS.NAME).optional().describe("Create a new collection with this name"),
76
97
  collection: zod_1.z.string().max(200).optional().describe("Add to this existing collection (id or name)"),
77
98
  folders: zod_1.z
78
- .array(zod_1.z.object({ key: zod_1.z.string().min(1).max(120), name: zod_1.z.string().min(1).max(truncate_js_1.LIMITS.NAME), parentKey: zod_1.z.string().max(120).optional() }))
99
+ .array((0, strict_js_1.strictObject)({
100
+ key: zod_1.z.string().min(1).max(120).describe("Your own key, referenced by requests[].folderKey and folders[].parentKey"),
101
+ name: zod_1.z.string().min(1).max(truncate_js_1.LIMITS.NAME),
102
+ parentKey: zod_1.z.string().max(120).optional().describe("key of the parent folder (declared in this list)"),
103
+ }, "folder field"))
79
104
  .max(200)
80
105
  .optional()
81
106
  .describe("New-collection mode only: folder skeleton; requests use folderKey"),
@@ -87,13 +112,32 @@ const registerRequestTools = (server, ctx) => {
87
112
  if ((collectionName ? 1 : 0) + (collection ? 1 : 0) !== 1) {
88
113
  throw new errors_js_1.ToolInputError("TARGET_REQUIRED", "Pass exactly one of collectionName (new collection) or collection (existing).");
89
114
  }
90
- const ws = await ctx.resolveWorkspace(workspace);
115
+ // An existing collection decides the workspace; a new one needs a target.
116
+ const target = collection ? await (0, lookups_js_1.findCollection)(ctx, workspace, collection) : null;
117
+ const ws = target ? target.ws : await ctx.resolveWorkspace(workspace);
91
118
  const folderKeys = new Set((folders ?? []).map((folder) => folder.key));
119
+ for (const folder of folders ?? []) {
120
+ if (folder.parentKey && !folderKeys.has(folder.parentKey)) {
121
+ throw new errors_js_1.ToolInputError("FOLDER_KEY_UNKNOWN", `Folder "${folder.name}" has parentKey "${folder.parentKey}", which is not a key in folders.`);
122
+ }
123
+ }
124
+ if (collection && folders && folders.length > 0) {
125
+ throw new errors_js_1.ToolInputError("FOLDERS_NEED_NEW_COLLECTION", "`folders` only applies with collectionName (a new collection). To file requests into an existing collection's folders, give each request a `folder` (id, name or path); create missing folders with create_folder first.");
126
+ }
92
127
  for (const [index, request] of requests.entries()) {
128
+ if (collectionName && request.folder) {
129
+ throw new errors_js_1.ToolInputError("FOLDER_NEEDS_EXISTING_COLLECTION", `requests[${index}].folder only applies with \`collection\` (an existing collection). In a new collection declare \`folders\` and use folderKey.`);
130
+ }
131
+ if (collection && request.folderKey) {
132
+ throw new errors_js_1.ToolInputError("FOLDER_KEY_NEEDS_NEW_COLLECTION", `requests[${index}].folderKey only applies with collectionName. For an existing collection use \`folder\` (id, name or path).`);
133
+ }
93
134
  if (request.folderKey && !folderKeys.has(request.folderKey)) {
94
135
  throw new errors_js_1.ToolInputError("FOLDER_KEY_UNKNOWN", `requests[${index}] references folderKey "${request.folderKey}" which is not declared in folders.`);
95
136
  }
96
137
  }
138
+ const inputs = requests.map((request) => (0, requestShape_js_1.resolveMaskedValues)(request));
139
+ const disabled = inputs.filter((request) => request.isEnabledInRunner === false);
140
+ const resolve = await (0, lookups_js_1.connectionResolverFor)(ctx, ws.id, inputs.some(requestIntegrations_js_1.hasIntegrations));
97
141
  if (collectionName) {
98
142
  const existing = (await (0, lookups_js_1.loadCollections)(ctx, ws.id)).find((entry) => (0, lookups_js_1.sameName)(entry.name, collectionName));
99
143
  if (existing) {
@@ -104,20 +148,44 @@ const registerRequestTools = (server, ctx) => {
104
148
  const payload = {
105
149
  collectionName: collectionName.trim(),
106
150
  ...(folders && folders.length > 0 ? { folders } : {}),
107
- requests: requests.map(requestShape_js_1.toFlatImportRequest),
151
+ requests: inputs.map((request) => (0, requestShape_js_1.toFlatImportRequest)(request, resolve)),
108
152
  };
109
153
  if (dryRun) {
110
154
  return (0, shared_js_1.ok)({ dryRun: true, mode: "new-collection", collectionName, folderCount: folders?.length ?? 0, requestCount: requests.length, sample: requests.slice(0, 5).map((request) => `${request.method} ${request.name}`) });
111
155
  }
112
156
  const result = await ctx.api.importJson(ws.id, { format: "requests", payload });
113
157
  ctx.invalidate(`collections:${ws.id}`);
158
+ // The import route always stores isEnabledInRunner: true — apply the
159
+ // requested "off" switches afterwards, matched by name + folder.
160
+ const runnerOff = [];
161
+ const runnerWarnings = [];
162
+ if (disabled.length > 0) {
163
+ ctx.invalidate(`tree:${ws.id}:${result.collection.id}`);
164
+ const tree = await (0, lookups_js_1.loadTree)(ctx, ws.id, result.collection.id);
165
+ const folderIdByKey = new Map();
166
+ for (const folder of folders ?? [])
167
+ folderIdByKey.set(folder.key, tree.folders.find((entry) => (0, lookups_js_1.sameName)(entry.name, folder.name))?.id ?? null);
168
+ for (const request of disabled) {
169
+ const folderId = request.folderKey ? (folderIdByKey.get(request.folderKey) ?? null) : null;
170
+ const row = tree.requests.find((entry) => (0, lookups_js_1.sameName)(entry.name, request.name) && (entry.folderId ?? null) === folderId);
171
+ if (!row) {
172
+ runnerWarnings.push(`Could not find "${request.name}" after import to turn it off in the runner — use update_request isEnabledInRunner: false.`);
173
+ continue;
174
+ }
175
+ await ctx.api.setRequestRunState(ws.id, row.id, false);
176
+ runnerOff.push(request.name);
177
+ }
178
+ }
114
179
  return (0, shared_js_1.ok)({
115
180
  collection: { id: result.collection.id, name: result.collection.name },
181
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
116
182
  created: result.importedCounts,
183
+ ...(runnerOff.length > 0 ? { disabledInRunner: runnerOff } : {}),
184
+ ...(runnerWarnings.length > 0 ? { warnings: runnerWarnings } : {}),
117
185
  note: "Created atomically. Open the collection in the desktop app (it refreshes when it regains focus).",
118
186
  });
119
187
  }
120
- const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection);
188
+ const resolved = target.value;
121
189
  const tree = await (0, lookups_js_1.loadTree)(ctx, ws.id, resolved.item.id);
122
190
  // Resolve every folder reference up front so a typo fails before any write.
123
191
  const folderIdByRef = new Map();
@@ -140,13 +208,18 @@ const registerRequestTools = (server, ctx) => {
140
208
  }
141
209
  const created = [];
142
210
  const failed = [];
143
- for (const request of requests) {
211
+ const warnings = [];
212
+ for (const request of inputs) {
144
213
  try {
145
- const row = await ctx.api.createRequest(ws.id, (0, requestShape_js_1.toCreateRequestPayload)(request, { collectionId: resolved.item.id, folderId: folderIdByRef.get(request.folder ?? "") ?? null }));
214
+ const built = (0, requestShape_js_1.toCreateRequestPayload)(request, { collectionId: resolved.item.id, folderId: folderIdByRef.get(request.folder ?? "") ?? null }, resolve);
215
+ const row = await ctx.api.createRequest(ws.id, built.payload);
216
+ if (request.isEnabledInRunner === false)
217
+ await ctx.api.setRequestRunState(ws.id, row.id, false);
146
218
  created.push({ id: row.id, name: row.name, method: row.method });
219
+ warnings.push(...built.warnings.map((warning) => `${request.name}: ${warning}`));
147
220
  }
148
221
  catch (error) {
149
- failed.push({ name: request.name, error: error instanceof errors_js_1.CallmanApiError ? `${error.code}: ${error.message}` : error instanceof Error ? error.message : String(error) });
222
+ failed.push({ name: request.name, error: (0, shared_js_1.failureOf)(error) });
150
223
  }
151
224
  }
152
225
  ctx.invalidate(`tree:${ws.id}:${resolved.item.id}`);
@@ -156,13 +229,15 @@ const registerRequestTools = (server, ctx) => {
156
229
  created: created.length,
157
230
  failed,
158
231
  requests: created,
232
+ ...(warnings.length > 0 ? { warnings } : {}),
159
233
  ...(duplicates.length > 0 ? { note: `${duplicates.length} request name(s) already existed in the same folder and were created again: ${duplicates.map((request) => request.name).join(", ")}` } : {}),
160
- resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved)],
234
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
235
+ resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), ...(target?.note ? [{ input: collection, note: target.note }] : [])],
161
236
  });
162
237
  }));
163
238
  server.registerTool("update_request", {
164
239
  title: "Update a request",
165
- description: "Change fields of a saved request (only the fields you pass change). To move it, use move_requests. Sending `headers`/`query` replaces the whole list.",
240
+ description: "Change fields of a saved request — only the fields you pass change, in the same shape as create_request. `headers` / `query` / `pathVariables` / `dbAssertions` replace the whole list; `url` and `query` are kept in sync (a ?query in the url becomes rows). `database` / `kafka` merge: a phase or field you leave out is kept, null turns it off. A masked secret sent back unchanged keeps the stored value. isEnabledInRunner toggles the runner switch. To move it, use move_requests; contract checks: add_contract_rules / set_data_contract.",
166
241
  inputSchema: {
167
242
  workspace: shared_js_1.workspaceArg,
168
243
  request: (0, shared_js_1.refArg)("Request", "(name, \"METHOD url\" or id)"),
@@ -170,19 +245,30 @@ const registerRequestTools = (server, ctx) => {
170
245
  },
171
246
  annotations: shared_js_1.IDEMPOTENT_WRITE,
172
247
  }, (0, shared_js_1.guarded)(async ({ workspace, request, collection, ...changes }) => {
173
- const ws = await ctx.resolveWorkspace(workspace);
174
- const { request: found, annotation } = await (0, lookups_js_1.resolveRequest)(ctx, ws.id, request, collection);
175
- const folder = changes.folder !== undefined ? await (0, lookups_js_1.resolveFolder)(ctx, ws.id, found.collectionId, changes.folder) : undefined;
176
- const payload = (0, requestShape_js_1.toUpdateRequestPayload)(changes, { ...(folder !== undefined ? { folderId: folder?.item.id ?? null } : {}) });
177
- if (Object.keys(payload).length === 0)
248
+ const { ws, value, note } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
249
+ const { request: found, annotation } = value;
250
+ const input = (0, requestShape_js_1.resolveMaskedValues)(changes, found);
251
+ const folder = input.folder !== undefined ? await (0, lookups_js_1.resolveFolder)(ctx, ws.id, found.collectionId, input.folder) : undefined;
252
+ const resolve = await (0, lookups_js_1.connectionResolverFor)(ctx, ws.id, (0, requestIntegrations_js_1.hasIntegrations)(input));
253
+ const { payload, warnings } = (0, requestShape_js_1.toUpdateRequestPayload)(input, found, { ...(folder !== undefined ? { folderId: folder?.item.id ?? null } : {}) }, resolve);
254
+ const toggle = input.isEnabledInRunner !== undefined && input.isEnabledInRunner !== (found.isEnabledInRunner !== false);
255
+ if (Object.keys(payload).length === 0 && input.isEnabledInRunner === undefined)
178
256
  throw new errors_js_1.ToolInputError("NOTHING_TO_UPDATE", "Pass at least one field to change.");
179
- const updated = await ctx.api.updateRequest(ws.id, found.id, payload);
257
+ let updated = Object.keys(payload).length > 0 ? await ctx.api.updateRequest(ws.id, found.id, payload) : found;
258
+ if (toggle)
259
+ updated = await ctx.api.setRequestRunState(ws.id, found.id, input.isEnabledInRunner);
180
260
  ctx.invalidate(`tree:${ws.id}:${found.collectionId}`);
181
- return (0, shared_js_1.ok)({ request: (0, requestShape_js_1.compactRequestView)(updated), changed: Object.keys(payload), ...(annotation ? { resolved: [{ input: request, matched: found.name, id: found.id, note: annotation }] } : {}) });
261
+ return (0, shared_js_1.ok)({
262
+ request: (0, requestShape_js_1.compactRequestView)(updated),
263
+ changed: [...Object.keys(payload), ...(toggle ? ["isEnabledInRunner"] : [])],
264
+ ...(warnings.length > 0 ? { warnings } : {}),
265
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
266
+ ...(annotation || note ? { resolved: [{ input: request, matched: found.name, id: found.id, note: [annotation, note].filter(Boolean).join("; ") }] } : {}),
267
+ });
182
268
  }));
183
269
  server.registerTool("move_requests", {
184
270
  title: "Move requests",
185
- description: "Move up to 200 requests (ids or unique names) into a collection and optional folder.",
271
+ description: "Move up to 200 requests (ids or unique names) into a collection and optional folder of the same workspace (the target collection's).",
186
272
  inputSchema: {
187
273
  workspace: shared_js_1.workspaceArg,
188
274
  requests: zod_1.z.array(zod_1.z.string().min(1).max(500)).min(1).max(MOVE_MAX),
@@ -192,8 +278,8 @@ const registerRequestTools = (server, ctx) => {
192
278
  },
193
279
  annotations: shared_js_1.IDEMPOTENT_WRITE,
194
280
  }, (0, shared_js_1.guarded)(async ({ workspace, requests, targetCollection, targetFolder, sourceCollection }) => {
195
- const ws = await ctx.resolveWorkspace(workspace);
196
- const target = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, targetCollection);
281
+ // Requests move within one workspace — the target collection's.
282
+ const { ws, value: target } = await (0, lookups_js_1.findCollection)(ctx, workspace, targetCollection);
197
283
  const folder = await (0, lookups_js_1.resolveFolder)(ctx, ws.id, target.item.id, targetFolder);
198
284
  const moved = [];
199
285
  const failed = [];
@@ -206,7 +292,7 @@ const registerRequestTools = (server, ctx) => {
206
292
  moved.push({ id: updated.id, name: updated.name });
207
293
  }
208
294
  catch (error) {
209
- failed.push({ request: ref, error: error instanceof Error ? error.message : String(error) });
295
+ failed.push({ request: ref, error: (0, shared_js_1.failureOf)(error) });
210
296
  }
211
297
  }
212
298
  for (const collectionId of touchedCollections)
@@ -216,7 +302,7 @@ const registerRequestTools = (server, ctx) => {
216
302
  }));
217
303
  server.registerTool("delete_requests", {
218
304
  title: "Delete requests",
219
- description: "Delete up to 200 requests (ids or unique names). Permanent — confirm with the user first.",
305
+ description: "Delete up to 200 requests (ids or EXACT names — partial names are not accepted here). Permanent — confirm with the user first.",
220
306
  inputSchema: {
221
307
  workspace: shared_js_1.workspaceArg,
222
308
  requests: zod_1.z.array(zod_1.z.string().min(1).max(500)).min(1).max(MOVE_MAX),
@@ -226,24 +312,26 @@ const registerRequestTools = (server, ctx) => {
226
312
  annotations: shared_js_1.DESTRUCTIVE,
227
313
  }, (0, shared_js_1.guarded)(async ({ workspace, requests, collection, confirm }) => {
228
314
  (0, shared_js_1.requireConfirm)(confirm, "requests");
229
- const ws = await ctx.resolveWorkspace(workspace);
230
315
  const deleted = [];
231
316
  const failed = [];
232
317
  const touched = new Set();
233
318
  for (const ref of requests) {
234
319
  try {
235
- const { request } = await (0, lookups_js_1.resolveRequest)(ctx, ws.id, ref, collection);
320
+ const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, workspace, ref, collection, { exact: true });
321
+ const { request } = value;
236
322
  await ctx.api.deleteRequest(ws.id, request.id);
237
- touched.add(request.collectionId);
238
- deleted.push({ id: request.id, name: request.name });
323
+ touched.add(`${ws.id}:${request.collectionId}`);
324
+ deleted.push({ id: request.id, name: request.name, workspace: (0, lookups_js_1.workspaceTag)(ws) });
239
325
  }
240
326
  catch (error) {
241
- failed.push({ request: ref, error: error instanceof Error ? error.message : String(error) });
327
+ failed.push({ request: ref, error: (0, shared_js_1.failureOf)(error) });
242
328
  }
243
329
  }
244
- for (const collectionId of touched)
245
- ctx.invalidate(`tree:${ws.id}:${collectionId}`);
246
- ctx.invalidate(`collections:${ws.id}`);
330
+ for (const key of touched) {
331
+ const [wsId, collectionId] = key.split(":");
332
+ ctx.invalidate(`tree:${wsId}:${collectionId}`);
333
+ ctx.invalidate(`collections:${wsId}`);
334
+ }
247
335
  return (0, shared_js_1.ok)({ deleted, failed });
248
336
  }));
249
337
  };