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.
- package/CHANGELOG.md +20 -0
- package/README.md +18 -4
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +7 -2
- package/dist/context.js.map +1 -1
- package/dist/resources/docs/overview.d.ts +1 -1
- package/dist/resources/docs/overview.d.ts.map +1 -1
- package/dist/resources/docs/overview.js +15 -2
- package/dist/resources/docs/overview.js.map +1 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +10 -13
- package/dist/resources/index.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/catalog.d.ts.map +1 -1
- package/dist/tools/catalog.js +1 -0
- package/dist/tools/catalog.js.map +1 -1
- package/dist/tools/collections.d.ts.map +1 -1
- package/dist/tools/collections.js +27 -30
- package/dist/tools/collections.js.map +1 -1
- package/dist/tools/contracts.d.ts.map +1 -1
- package/dist/tools/contracts.js +35 -26
- package/dist/tools/contracts.js.map +1 -1
- package/dist/tools/environments.d.ts.map +1 -1
- package/dist/tools/environments.js +32 -31
- package/dist/tools/environments.js.map +1 -1
- package/dist/tools/import.d.ts.map +1 -1
- package/dist/tools/import.js +9 -5
- package/dist/tools/import.js.map +1 -1
- package/dist/tools/lookups.d.ts +26 -1
- package/dist/tools/lookups.d.ts.map +1 -1
- package/dist/tools/lookups.js +95 -2
- package/dist/tools/lookups.js.map +1 -1
- package/dist/tools/meta.d.ts.map +1 -1
- package/dist/tools/meta.js +9 -4
- package/dist/tools/meta.js.map +1 -1
- package/dist/tools/requests.d.ts.map +1 -1
- package/dist/tools/requests.js +49 -29
- package/dist/tools/requests.js.map +1 -1
- package/dist/tools/run.d.ts.map +1 -1
- package/dist/tools/run.js +9 -6
- package/dist/tools/run.js.map +1 -1
- package/dist/tools/scenarios.d.ts.map +1 -1
- package/dist/tools/scenarios.js +8 -3
- package/dist/tools/scenarios.js.map +1 -1
- package/dist/tools/shared.d.ts.map +1 -1
- package/dist/tools/shared.js +1 -1
- package/dist/tools/shared.js.map +1 -1
- package/dist/tools/workspaces.d.ts.map +1 -1
- package/dist/tools/workspaces.js +18 -2
- package/dist/tools/workspaces.js.map +1 -1
- package/dist-cjs/context.js +7 -2
- package/dist-cjs/resources/docs/overview.js +15 -2
- package/dist-cjs/resources/index.js +9 -12
- package/dist-cjs/server.js +1 -1
- package/dist-cjs/tools/catalog.js +1 -0
- package/dist-cjs/tools/collections.js +26 -29
- package/dist-cjs/tools/contracts.js +34 -25
- package/dist-cjs/tools/environments.js +31 -30
- package/dist-cjs/tools/import.js +8 -4
- package/dist-cjs/tools/lookups.js +103 -3
- package/dist-cjs/tools/meta.js +9 -4
- package/dist-cjs/tools/requests.js +48 -28
- package/dist-cjs/tools/run.js +8 -5
- package/dist-cjs/tools/scenarios.js +8 -3
- package/dist-cjs/tools/shared.js +1 -1
- package/dist-cjs/tools/workspaces.js +18 -2
- 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
|
|
90
|
-
const ws = await ctx.
|
|
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
|
|
96
|
-
const ws = await ctx.
|
|
97
|
-
|
|
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
|
|
101
|
-
const ws = await ctx.
|
|
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();
|
package/dist-cjs/server.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
31
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
28
|
-
const { request: found, annotation } =
|
|
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
|
|
49
|
-
const { request: found } =
|
|
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),
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
98
|
-
const { request: found } =
|
|
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),
|
|
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
|
|
128
|
-
const { request: found } =
|
|
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),
|
|
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
|
|
147
|
-
const { request: found } =
|
|
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
|
|
165
|
-
const { request: found } =
|
|
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),
|
|
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
|
|
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
|
|
179
|
-
const
|
|
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
|
-
|
|
182
|
-
connections:
|
|
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
|
|
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
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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;
|
package/dist-cjs/tools/import.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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) } } : {}),
|