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.
- package/CHANGELOG.md +82 -0
- package/README.md +45 -16
- package/dist/client/api.d.ts +64 -3
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +67 -2
- package/dist/client/api.js.map +1 -1
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +33 -7
- package/dist/client/errors.js.map +1 -1
- package/dist/client/types.d.ts +91 -2
- package/dist/client/types.d.ts.map +1 -1
- 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/domain/contracts.d.ts +893 -0
- package/dist/domain/contracts.d.ts.map +1 -0
- package/dist/domain/contracts.js +90 -0
- package/dist/domain/contracts.js.map +1 -0
- package/dist/domain/diagram/build.d.ts +520 -18
- package/dist/domain/diagram/build.d.ts.map +1 -1
- package/dist/domain/diagram/build.js +65 -19
- package/dist/domain/diagram/build.js.map +1 -1
- package/dist/domain/diagram/patch.d.ts +624 -51
- package/dist/domain/diagram/patch.d.ts.map +1 -1
- package/dist/domain/diagram/patch.js +180 -40
- package/dist/domain/diagram/patch.js.map +1 -1
- package/dist/domain/diagram/style.d.ts +315 -0
- package/dist/domain/diagram/style.d.ts.map +1 -0
- package/dist/domain/diagram/style.js +124 -0
- package/dist/domain/diagram/style.js.map +1 -0
- package/dist/domain/diagram/summarize.d.ts +8 -0
- package/dist/domain/diagram/summarize.d.ts.map +1 -1
- package/dist/domain/diagram/summarize.js +28 -13
- package/dist/domain/diagram/summarize.js.map +1 -1
- package/dist/domain/requestIntegrations.d.ts +719 -0
- package/dist/domain/requestIntegrations.d.ts.map +1 -0
- package/dist/domain/requestIntegrations.js +222 -0
- package/dist/domain/requestIntegrations.js.map +1 -0
- package/dist/domain/requestShape.d.ts +1350 -152
- package/dist/domain/requestShape.d.ts.map +1 -1
- package/dist/domain/requestShape.js +336 -121
- package/dist/domain/requestShape.js.map +1 -1
- package/dist/domain/resolve.d.ts +2 -1
- package/dist/domain/resolve.d.ts.map +1 -1
- package/dist/domain/resolve.js +5 -1
- package/dist/domain/resolve.js.map +1 -1
- package/dist/domain/scenario/build.d.ts +82 -26
- package/dist/domain/scenario/build.d.ts.map +1 -1
- package/dist/domain/scenario/build.js +196 -65
- package/dist/domain/scenario/build.js.map +1 -1
- package/dist/domain/scenario/nodeConfig.d.ts +11 -0
- package/dist/domain/scenario/nodeConfig.d.ts.map +1 -0
- package/dist/domain/scenario/nodeConfig.js +251 -0
- package/dist/domain/scenario/nodeConfig.js.map +1 -0
- package/dist/domain/scenario/summarize.d.ts +145 -25
- package/dist/domain/scenario/summarize.d.ts.map +1 -1
- package/dist/domain/scenario/summarize.js +84 -29
- package/dist/domain/scenario/summarize.js.map +1 -1
- package/dist/lib/mask.d.ts +0 -18
- package/dist/lib/mask.d.ts.map +1 -1
- package/dist/lib/mask.js +2 -44
- package/dist/lib/mask.js.map +1 -1
- package/dist/lib/pagination.d.ts +3 -2
- package/dist/lib/pagination.d.ts.map +1 -1
- package/dist/lib/pagination.js +13 -8
- package/dist/lib/pagination.js.map +1 -1
- package/dist/lib/redact.d.ts +12 -0
- package/dist/lib/redact.d.ts.map +1 -0
- package/dist/lib/redact.js +50 -0
- package/dist/lib/redact.js.map +1 -0
- package/dist/lib/strict.d.ts +4 -0
- package/dist/lib/strict.d.ts.map +1 -0
- package/dist/lib/strict.js +14 -0
- package/dist/lib/strict.js.map +1 -0
- package/dist/prompts/index.js +7 -7
- package/dist/prompts/index.js.map +1 -1
- package/dist/resources/docs/contracts.d.ts +2 -0
- package/dist/resources/docs/contracts.d.ts.map +1 -0
- package/dist/resources/docs/contracts.js +74 -0
- package/dist/resources/docs/contracts.js.map +1 -0
- package/dist/resources/docs/diagramSchema.d.ts +1 -1
- package/dist/resources/docs/diagramSchema.d.ts.map +1 -1
- package/dist/resources/docs/diagramSchema.js +95 -28
- package/dist/resources/docs/diagramSchema.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 +29 -7
- package/dist/resources/docs/overview.js.map +1 -1
- package/dist/resources/docs/requestSchema.d.ts +1 -1
- package/dist/resources/docs/requestSchema.d.ts.map +1 -1
- package/dist/resources/docs/requestSchema.js +85 -16
- package/dist/resources/docs/requestSchema.js.map +1 -1
- package/dist/resources/generated/aiPacks.d.ts +1 -1
- package/dist/resources/generated/aiPacks.d.ts.map +1 -1
- package/dist/resources/generated/aiPacks.js +3 -2
- package/dist/resources/generated/aiPacks.js.map +1 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +19 -19
- package/dist/resources/index.js.map +1 -1
- package/dist/run/executeCollection.d.ts +3 -1
- package/dist/run/executeCollection.d.ts.map +1 -1
- package/dist/run/executeCollection.js +16 -4
- package/dist/run/executeCollection.js.map +1 -1
- package/dist/run/summarize.d.ts +17 -6
- package/dist/run/summarize.d.ts.map +1 -1
- package/dist/run/summarize.js +48 -41
- package/dist/run/summarize.js.map +1 -1
- package/dist/run/types.d.ts +1 -0
- package/dist/run/types.d.ts.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 +1 -1
- package/dist/tools/catalog.d.ts.map +1 -1
- package/dist/tools/catalog.js +18 -0
- package/dist/tools/catalog.js.map +1 -1
- package/dist/tools/collections.d.ts.map +1 -1
- package/dist/tools/collections.js +53 -41
- package/dist/tools/collections.js.map +1 -1
- package/dist/tools/contracts.d.ts +4 -0
- package/dist/tools/contracts.d.ts.map +1 -0
- package/dist/tools/contracts.js +201 -0
- package/dist/tools/contracts.js.map +1 -0
- package/dist/tools/diagrams.d.ts.map +1 -1
- package/dist/tools/diagrams.js +19 -10
- package/dist/tools/diagrams.js.map +1 -1
- package/dist/tools/environments.d.ts.map +1 -1
- package/dist/tools/environments.js +184 -64
- package/dist/tools/environments.js.map +1 -1
- package/dist/tools/import.d.ts.map +1 -1
- package/dist/tools/import.js +111 -20
- package/dist/tools/import.js.map +1 -1
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +19 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/lookups.d.ts +38 -7
- package/dist/tools/lookups.d.ts.map +1 -1
- package/dist/tools/lookups.js +113 -10
- 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 +138 -50
- package/dist/tools/requests.js.map +1 -1
- package/dist/tools/run.d.ts.map +1 -1
- package/dist/tools/run.js +84 -28
- package/dist/tools/run.js.map +1 -1
- package/dist/tools/scenarios.d.ts.map +1 -1
- package/dist/tools/scenarios.js +238 -75
- package/dist/tools/scenarios.js.map +1 -1
- package/dist/tools/shared.d.ts +1 -0
- package/dist/tools/shared.d.ts.map +1 -1
- package/dist/tools/shared.js +9 -1
- package/dist/tools/shared.js.map +1 -1
- package/dist/tools/workspaces.d.ts.map +1 -1
- package/dist/tools/workspaces.js +44 -7
- package/dist/tools/workspaces.js.map +1 -1
- package/dist-cjs/client/api.js +67 -2
- package/dist-cjs/client/errors.js +33 -7
- package/dist-cjs/context.js +7 -2
- package/dist-cjs/domain/contracts.js +93 -0
- package/dist-cjs/domain/diagram/build.js +71 -20
- package/dist-cjs/domain/diagram/patch.js +179 -39
- package/dist-cjs/domain/diagram/style.js +130 -0
- package/dist-cjs/domain/diagram/summarize.js +28 -13
- package/dist-cjs/domain/requestIntegrations.js +228 -0
- package/dist-cjs/domain/requestShape.js +342 -121
- package/dist-cjs/domain/resolve.js +5 -1
- package/dist-cjs/domain/scenario/build.js +197 -65
- package/dist-cjs/domain/scenario/nodeConfig.js +260 -0
- package/dist-cjs/domain/scenario/summarize.js +86 -29
- package/dist-cjs/lib/mask.js +3 -48
- package/dist-cjs/lib/pagination.js +13 -8
- package/dist-cjs/lib/redact.js +53 -0
- package/dist-cjs/lib/strict.js +18 -0
- package/dist-cjs/prompts/index.js +7 -7
- package/dist-cjs/resources/docs/contracts.js +76 -0
- package/dist-cjs/resources/docs/diagramSchema.js +95 -28
- package/dist-cjs/resources/docs/overview.js +29 -7
- package/dist-cjs/resources/docs/requestSchema.js +85 -16
- package/dist-cjs/resources/generated/aiPacks.js +3 -2
- package/dist-cjs/resources/index.js +17 -17
- package/dist-cjs/run/executeCollection.js +16 -4
- package/dist-cjs/run/summarize.js +48 -41
- package/dist-cjs/server.js +1 -1
- package/dist-cjs/tools/catalog.js +18 -0
- package/dist-cjs/tools/collections.js +52 -40
- package/dist-cjs/tools/contracts.js +204 -0
- package/dist-cjs/tools/diagrams.js +19 -10
- package/dist-cjs/tools/environments.js +182 -62
- package/dist-cjs/tools/import.js +107 -16
- package/dist-cjs/tools/index.js +21 -2
- package/dist-cjs/tools/lookups.js +123 -11
- package/dist-cjs/tools/meta.js +9 -4
- package/dist-cjs/tools/requests.js +134 -46
- package/dist-cjs/tools/run.js +82 -26
- package/dist-cjs/tools/scenarios.js +235 -72
- package/dist-cjs/tools/shared.js +11 -2
- package/dist-cjs/tools/workspaces.js +43 -6
- package/package.json +2 -2
|
@@ -8,6 +8,7 @@ const requestShape_js_1 = require("../domain/requestShape.js");
|
|
|
8
8
|
const summarize_js_2 = require("../domain/scenario/summarize.js");
|
|
9
9
|
const truncate_js_1 = require("../lib/truncate.js");
|
|
10
10
|
const lookups_js_1 = require("../tools/lookups.js");
|
|
11
|
+
const contracts_js_1 = require("./docs/contracts.js");
|
|
11
12
|
const diagramSchema_js_1 = require("./docs/diagramSchema.js");
|
|
12
13
|
const overview_js_1 = require("./docs/overview.js");
|
|
13
14
|
const requestSchema_js_1 = require("./docs/requestSchema.js");
|
|
@@ -35,13 +36,14 @@ const registerResources = (server, ctx) => {
|
|
|
35
36
|
// ── static docs ──────────────────────────────────────────────────────
|
|
36
37
|
const staticDocs = [
|
|
37
38
|
{ name: "docs-overview", uri: "callman://docs/overview", title: "How to work with Callman", description: "Conventions: whoami first, names as references, dryRun, confirm, secrets, scenarios, diagrams.", body: () => overview_js_1.OVERVIEW_DOC },
|
|
38
|
-
{ name: "docs-request-schema", uri: "callman://docs/request-schema", title: "Request
|
|
39
|
-
{ name: "docs-
|
|
39
|
+
{ name: "docs-request-schema", uri: "callman://docs/request-schema", title: "Request schema", description: "Every request field as create/update/get use it: url ⇄ query, body modes, auth (incl. oauth2), masked secrets, database / Kafka / Redis steps, DB assertions, scripts, folder skeleton.", body: () => requestSchema_js_1.REQUEST_SCHEMA_DOC },
|
|
40
|
+
{ name: "docs-contracts", uri: "callman://docs/contracts", title: "Contract checks", description: "Field rules (path grammar, every rule and format) and data-driven contracts on a request, and how runs evaluate them.", body: () => contracts_js_1.CONTRACTS_DOC },
|
|
41
|
+
{ name: "docs-scenario-nodes", uri: "callman://docs/scenario-nodes", title: "Scenario node reference", description: "Every scenario node type: config fields, defaults, what is required, outputs, connections, labels, graph rules, rules/operands, templates and examples (from callman-core, checked against the server's schema).", body: () => (0, callman_core_1.renderScenarioNodeReference)() },
|
|
40
42
|
{ name: "docs-scripting-pm-api", uri: "callman://docs/scripting/pm-api", title: "Scripting: pm.* API", description: "The pm.* API available in pre-request / post-response / test scripts, per stage.", body: () => (0, callman_core_1.renderPmApiReference)() },
|
|
41
43
|
{ name: "docs-scripting-db-queries", uri: "callman://docs/scripting/db-queries", title: "Scripting: database queries", description: "How pm.db.* queries work per database provider.", body: () => (0, callman_core_1.renderDbQueryReference)() },
|
|
42
44
|
{ name: "docs-scripting-templating", uri: "callman://docs/scripting/templating", title: "Templating: {{variables}} and generators", description: "Variable resolution order and dynamic generators ({{$uuid}}, {{$randomInt}} …).", body: () => aiPacks_js_1.AI_PACKS.templating ?? "" },
|
|
43
45
|
{ name: "docs-scripting-sandbox", uri: "callman://docs/scripting/sandbox", title: "Scripting: sandbox and feature policy", description: "Globals available in the script sandbox and the feature policy for DB/Kafka capabilities.", body: () => `${aiPacks_js_1.AI_PACKS["sandbox-globals"] ?? ""}\n\n${aiPacks_js_1.AI_PACKS["feature-policy"] ?? ""}` },
|
|
44
|
-
{ name: "docs-diagram-schema", uri: "callman://docs/diagram-schema", title: "Diagram schema", description: "
|
|
46
|
+
{ name: "docs-diagram-schema", uri: "callman://docs/diagram-schema", title: "Diagram schema", description: "Every node and edge field, styling (colors, borders, fonts), containers, edge semantics and colors, patch operations and merge rules, and the stored DiagramDocument.", body: () => diagramSchema_js_1.DIAGRAM_SCHEMA_DOC },
|
|
45
47
|
];
|
|
46
48
|
for (const doc of staticDocs) {
|
|
47
49
|
server.registerResource(doc.name, doc.uri, { title: doc.title, description: doc.description, mimeType: "text/markdown" }, async (uri) => text(uri, doc.body()));
|
|
@@ -56,11 +58,12 @@ const registerResources = (server, ctx) => {
|
|
|
56
58
|
type: component.type,
|
|
57
59
|
label: component.label,
|
|
58
60
|
...(component.aliases?.length ? { aliases: component.aliases } : {}),
|
|
59
|
-
...(component.container ? { container: true } : {}),
|
|
61
|
+
...(component.container || component.swimlane ? { container: true } : {}),
|
|
62
|
+
...(component.variant === "line" ? { connector: true } : {}),
|
|
60
63
|
})),
|
|
61
64
|
}));
|
|
62
65
|
};
|
|
63
|
-
server.registerResource("diagram-components", "callman://diagram/components", { title: "Diagram component catalog", description: "Every component type usable in create_diagram / patch_diagram, grouped by category. Use the `type` values verbatim.", mimeType: "application/json" }, async (uri) => json(uri, { categories: catalog(), note: "Use `type` verbatim as a node's type.
|
|
66
|
+
server.registerResource("diagram-components", "callman://diagram/components", { title: "Diagram component catalog", description: "Every component type usable in create_diagram / patch_diagram, grouped by category. Use the `type` values verbatim.", mimeType: "application/json" }, async (uri) => json(uri, { categories: catalog(), note: "Use `type` verbatim as a node's type. `container` types (frame, pools) take children via `parent`. `connector` entries are palette lines, not nodes — draw them as edges. Styling and every node/edge field: callman://docs/diagram-schema." }));
|
|
64
67
|
server.registerResource("diagram-components-category", new mcp_js_1.ResourceTemplate("callman://diagram/components/{category}", {
|
|
65
68
|
list: undefined,
|
|
66
69
|
complete: { category: () => [...callman_core_1.DIAGRAM_CATEGORY_ORDER] },
|
|
@@ -83,22 +86,19 @@ const registerResources = (server, ctx) => {
|
|
|
83
86
|
const { items } = await ctx.api.listEnvironments(ws.id);
|
|
84
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) })) });
|
|
85
88
|
});
|
|
86
|
-
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
|
|
87
|
-
const ws = await ctx.
|
|
88
|
-
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));
|
|
89
91
|
const tree = await (0, lookups_js_1.loadTree)(ctx, ws.id, resolved.item.id);
|
|
90
|
-
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) });
|
|
91
93
|
});
|
|
92
|
-
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
|
|
93
|
-
const ws = await ctx.
|
|
94
|
-
|
|
95
|
-
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) });
|
|
96
97
|
});
|
|
97
|
-
server.registerResource("environment", new mcp_js_1.ResourceTemplate("callman://environment/{environment}", { list: undefined }), { title: "An environment", description: "One environment (id or name) in
|
|
98
|
-
const ws = await ctx.
|
|
99
|
-
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));
|
|
100
100
|
const fresh = await ctx.api.getEnvironment(ws.id, resolved.item.id);
|
|
101
|
-
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) });
|
|
102
102
|
});
|
|
103
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) => {
|
|
104
104
|
const ws = await ctx.resolveWorkspaceOrNull();
|
|
@@ -21,8 +21,17 @@ const diffEnv = (before, after, set, unset) => {
|
|
|
21
21
|
if (!(key in after))
|
|
22
22
|
unset.add(key);
|
|
23
23
|
};
|
|
24
|
-
const executeCollection = async ({ collection, env, connections = {}, bail = false, only = null, deadlineMs, onProgress, }) => {
|
|
24
|
+
const executeCollection = async ({ collection, env, globals = {}, connections = {}, bail = false, only = null, includeDisabled = false, deadlineMs, onProgress, }) => {
|
|
25
25
|
const envStore = new callman_core_1.EnvStore(env);
|
|
26
|
+
const globalStore = new callman_core_1.EnvStore(globals);
|
|
27
|
+
const seen = new Map();
|
|
28
|
+
const remember = (values) => {
|
|
29
|
+
for (const [key, value] of Object.entries(values))
|
|
30
|
+
if (typeof value === "string" && value)
|
|
31
|
+
seen.set(`${key}\u0000${value}`, key);
|
|
32
|
+
};
|
|
33
|
+
remember(globals);
|
|
34
|
+
remember(env);
|
|
26
35
|
const results = [];
|
|
27
36
|
const setKeys = new Set();
|
|
28
37
|
const unsetKeys = new Set();
|
|
@@ -34,7 +43,7 @@ const executeCollection = async ({ collection, env, connections = {}, bail = fal
|
|
|
34
43
|
kafka: false,
|
|
35
44
|
};
|
|
36
45
|
const wanted = only ? new Set(only) : null;
|
|
37
|
-
const runnable = collection.requests.filter((request) => request.isEnabledInRunner !== false && (!wanted || wanted.has(request.id)));
|
|
46
|
+
const runnable = collection.requests.filter((request) => (includeDisabled || request.isEnabledInRunner !== false) && (!wanted || wanted.has(request.id)));
|
|
38
47
|
for (const [index, request] of runnable.entries()) {
|
|
39
48
|
if (deadlineMs && Date.now() - startedAt > deadlineMs) {
|
|
40
49
|
bailed = true;
|
|
@@ -43,7 +52,7 @@ const executeCollection = async ({ collection, env, connections = {}, bail = fal
|
|
|
43
52
|
const before = envStore.all();
|
|
44
53
|
const rows = request.dataContract?.rows ?? [];
|
|
45
54
|
if (rows.length > 0) {
|
|
46
|
-
const { iterations } = await (0, callman_core_1.runRequestWithDataset)({ request, env: envStore, previousResponse, connections, featurePolicy }, { requestId: request.id, columns: request.dataContract?.columns ?? [], rows });
|
|
55
|
+
const { iterations } = await (0, callman_core_1.runRequestWithDataset)({ request, env: envStore, globals: globalStore, previousResponse, connections, featurePolicy }, { requestId: request.id, columns: request.dataContract?.columns ?? [], rows });
|
|
47
56
|
let rowBailed = false;
|
|
48
57
|
for (const iteration of iterations) {
|
|
49
58
|
const result = {
|
|
@@ -65,6 +74,7 @@ const executeCollection = async ({ collection, env, connections = {}, bail = fal
|
|
|
65
74
|
}
|
|
66
75
|
}
|
|
67
76
|
diffEnv(before, envStore.all(), setKeys, unsetKeys);
|
|
77
|
+
remember(envStore.all());
|
|
68
78
|
previousResponse = null;
|
|
69
79
|
if (rowBailed) {
|
|
70
80
|
bailed = true;
|
|
@@ -72,8 +82,9 @@ const executeCollection = async ({ collection, env, connections = {}, bail = fal
|
|
|
72
82
|
}
|
|
73
83
|
continue;
|
|
74
84
|
}
|
|
75
|
-
const outcome = await (0, callman_core_1.runRequest)({ request, env: envStore, previousResponse, connections, featurePolicy });
|
|
85
|
+
const outcome = await (0, callman_core_1.runRequest)({ request, env: envStore, globals: globalStore, previousResponse, connections, featurePolicy });
|
|
76
86
|
diffEnv(before, envStore.all(), setKeys, unsetKeys);
|
|
87
|
+
remember(envStore.all());
|
|
77
88
|
previousResponse = outcome.response;
|
|
78
89
|
results.push(outcome.result);
|
|
79
90
|
onProgress?.({ index, total: runnable.length, request, result: outcome.result });
|
|
@@ -88,6 +99,7 @@ const executeCollection = async ({ collection, env, connections = {}, bail = fal
|
|
|
88
99
|
summary: { total: results.length, passed, failed: results.length - passed },
|
|
89
100
|
envChanges: { set: [...setKeys].sort(), unset: [...unsetKeys].sort() },
|
|
90
101
|
bailed,
|
|
102
|
+
knownValues: [...seen].map(([entry, key]) => [key, entry.slice(entry.indexOf("\u0000") + 1)]),
|
|
91
103
|
};
|
|
92
104
|
};
|
|
93
105
|
exports.executeCollection = executeCollection;
|
|
@@ -1,44 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.summarizeRun = exports.summarizeResult = void 0;
|
|
4
|
-
const
|
|
4
|
+
const redact_js_1 = require("../lib/redact.js");
|
|
5
5
|
const truncate_js_1 = require("../lib/truncate.js");
|
|
6
6
|
const executeCollection_js_1 = require("./executeCollection.js");
|
|
7
7
|
// What a local run reports back to the model: enough to judge and debug
|
|
8
|
-
// (status, timing, tests
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"location",
|
|
15
|
-
"cache-control",
|
|
16
|
-
"x-request-id",
|
|
17
|
-
"x-correlation-id",
|
|
18
|
-
"retry-after",
|
|
19
|
-
"www-authenticate",
|
|
20
|
-
]);
|
|
21
|
-
const responseHeadersOf = (response) => {
|
|
8
|
+
// (status, timing, tests and contracts, the request as SENT, response headers
|
|
9
|
+
// and a bounded body) and no variable VALUES — every value the run held is
|
|
10
|
+
// shown as its {{key}} (lib/redact), credentials that are not variables are
|
|
11
|
+
// masked by name.
|
|
12
|
+
const RESPONSE_HEADERS_MAX = 40;
|
|
13
|
+
const responseHeadersOf = (response, redactor) => {
|
|
22
14
|
const headers = response?.headers;
|
|
23
15
|
if (!headers || typeof headers !== "object")
|
|
24
|
-
return {};
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
return out;
|
|
16
|
+
return { headers: {} };
|
|
17
|
+
const entries = Object.entries(headers);
|
|
18
|
+
return {
|
|
19
|
+
headers: redactor.headers(Object.fromEntries(entries.slice(0, RESPONSE_HEADERS_MAX))),
|
|
20
|
+
...(entries.length > RESPONSE_HEADERS_MAX ? { headersTruncated: true, headerCount: entries.length } : {}),
|
|
21
|
+
};
|
|
31
22
|
};
|
|
32
|
-
|
|
23
|
+
// XML (and other parsed) bodies keep their original text in `rawText` —
|
|
24
|
+
// report what the server actually sent.
|
|
25
|
+
const bodyTextOf = (response) => {
|
|
33
26
|
if (!response || typeof response !== "object")
|
|
34
|
-
return
|
|
27
|
+
return "";
|
|
35
28
|
const record = response;
|
|
36
|
-
|
|
29
|
+
if (typeof record.rawText === "string" && record.rawText)
|
|
30
|
+
return record.rawText;
|
|
31
|
+
const body = record.data !== undefined ? record.data : record.body;
|
|
32
|
+
if (body === null || body === undefined)
|
|
33
|
+
return "";
|
|
34
|
+
return typeof body === "string" ? body : (0, truncate_js_1.safeStringify)(body);
|
|
37
35
|
};
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
const clipped = (0, truncate_js_1.clipBytes)(bodyText, maxBodyBytes);
|
|
36
|
+
const testsView = (tests) => tests.map((test) => ({ name: test.name, status: test.status, ...(test.message ? { message: (0, truncate_js_1.clip)(test.message, 300) } : {}) }));
|
|
37
|
+
const summarizeResult = (result, maxBodyBytes, redactor) => {
|
|
38
|
+
const clipped = (0, truncate_js_1.clipBytes)(redactor.text(bodyTextOf(result.response)), maxBodyBytes);
|
|
42
39
|
const sent = result.request;
|
|
43
40
|
return {
|
|
44
41
|
requestId: result.requestId,
|
|
@@ -49,25 +46,35 @@ const summarizeResult = (result, maxBodyBytes) => {
|
|
|
49
46
|
request: sent
|
|
50
47
|
? {
|
|
51
48
|
method: sent.method,
|
|
52
|
-
url: (0, truncate_js_1.clip)(sent.url, 2048),
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
url: (0, truncate_js_1.clip)(redactor.text(sent.url), 2048),
|
|
50
|
+
...(sent.query && Object.keys(sent.query).length > 0 ? { query: redactor.query(sent.query) } : {}),
|
|
51
|
+
headers: redactor.headers(sent.headers),
|
|
52
|
+
...(sent.body !== undefined && sent.body !== null
|
|
53
|
+
? { body: (0, truncate_js_1.clip)(redactor.text(typeof sent.body === "string" ? sent.body : (0, truncate_js_1.safeStringify)(sent.body)), truncate_js_1.LIMITS.BODY_PREVIEW) }
|
|
54
|
+
: {}),
|
|
55
55
|
}
|
|
56
56
|
: null,
|
|
57
57
|
response: {
|
|
58
|
-
|
|
58
|
+
...responseHeadersOf(result.response, redactor),
|
|
59
59
|
body: clipped.text || null,
|
|
60
60
|
...(clipped.truncated ? { bodyTruncated: true, bodyBytes: clipped.totalBytes } : {}),
|
|
61
61
|
},
|
|
62
|
-
tests: result.scriptResults
|
|
63
|
-
contracts: result.contractResults
|
|
62
|
+
tests: testsView(result.scriptResults),
|
|
63
|
+
contracts: testsView(result.contractResults),
|
|
64
|
+
...(result.unresolvedVariables && result.unresolvedVariables.length > 0 ? { unresolvedVariables: result.unresolvedVariables } : {}),
|
|
65
|
+
...(result.logs && result.logs.length > 0
|
|
66
|
+
? { logs: result.logs.slice(0, 50).map((entry) => (0, truncate_js_1.clip)(redactor.text(`[${entry.stage}${entry.level === "log" ? "" : ` ${entry.level}`}] ${entry.message}`), 500)) }
|
|
67
|
+
: {}),
|
|
64
68
|
};
|
|
65
69
|
};
|
|
66
70
|
exports.summarizeResult = summarizeResult;
|
|
67
|
-
const summarizeRun = (outcome, maxBodyBytes, extra = {}) =>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
const summarizeRun = (outcome, maxBodyBytes, extra = {}, redaction = {}) => {
|
|
72
|
+
const redactor = (0, redact_js_1.createRedactor)(outcome.knownValues, redaction);
|
|
73
|
+
return {
|
|
74
|
+
...extra,
|
|
75
|
+
summary: { ...outcome.summary, bailed: outcome.bailed },
|
|
76
|
+
results: outcome.results.map((result) => (0, exports.summarizeResult)(result, maxBodyBytes, redactor)),
|
|
77
|
+
env: outcome.envChanges,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
73
80
|
exports.summarizeRun = summarizeRun;
|
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. Read callman://docs/overview for the conventions, callman://docs/request-schema before creating requests, callman://docs/scenario-nodes before scenarios, and 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,8 +8,10 @@ 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"] },
|
|
14
|
+
{ name: "update_workspace_member", group: "workspaces", scopes: ["members:manage"] },
|
|
13
15
|
{ name: "remove_workspace_member", group: "workspaces", scopes: ["members:manage"], destructive: true },
|
|
14
16
|
{ name: "list_collections", group: "collections", scopes: ["collections:read"] },
|
|
15
17
|
{ name: "get_collection_tree", group: "collections", scopes: ["collections:read", "requests:read"] },
|
|
@@ -31,6 +33,17 @@ exports.TOOL_CATALOG = [
|
|
|
31
33
|
{ name: "delete_requests", group: "requests", scopes: ["requests:delete"], destructive: true },
|
|
32
34
|
{ name: "run_request", group: "run", scopes: ["requests:run"], localRun: true },
|
|
33
35
|
{ name: "run_collection", group: "run", scopes: ["requests:run"], localRun: true },
|
|
36
|
+
// Contract checks belong to their request (the backend maps them onto the
|
|
37
|
+
// requests:* scopes). add_contract_rules with replace: true also deletes.
|
|
38
|
+
{ name: "get_contract_checks", group: "contracts", scopes: ["requests:read"] },
|
|
39
|
+
{ name: "add_contract_rules", group: "contracts", scopes: ["requests:write"] },
|
|
40
|
+
{ name: "update_contract_rule", group: "contracts", scopes: ["requests:write"] },
|
|
41
|
+
{ name: "delete_contract_rules", group: "contracts", scopes: ["requests:delete"], destructive: true },
|
|
42
|
+
{ name: "test_contract_rules", group: "contracts", scopes: ["requests:read"] },
|
|
43
|
+
{ name: "set_data_contract", group: "contracts", scopes: ["requests:write"] },
|
|
44
|
+
{ name: "delete_data_contract", group: "contracts", scopes: ["requests:delete"], destructive: true },
|
|
45
|
+
// The safe connection record (no credentials) — readable with either family.
|
|
46
|
+
{ name: "list_connections", group: "connections", scopes: ["requests:read", "scenarios:read"], anyOf: true },
|
|
34
47
|
// Documents and new collections → import:write; cURL/.http into an existing
|
|
35
48
|
// collection → requests:write.
|
|
36
49
|
{ name: "import_content", group: "import", scopes: ["import:write", "requests:write"], anyOf: true },
|
|
@@ -38,13 +51,18 @@ exports.TOOL_CATALOG = [
|
|
|
38
51
|
{ name: "list_environments", group: "environments", scopes: ["environments:read"] },
|
|
39
52
|
{ name: "get_environment", group: "environments", scopes: ["environments:read"] },
|
|
40
53
|
{ name: "create_environment", group: "environments", scopes: ["environments:write"] },
|
|
54
|
+
{ name: "update_environment", group: "environments", scopes: ["environments:write"] },
|
|
55
|
+
// `unset` additionally needs environments:delete (DELETE …/variables/:key).
|
|
41
56
|
{ name: "set_environment_variables", group: "environments", scopes: ["environments:write"] },
|
|
57
|
+
{ name: "set_global_variables", group: "environments", scopes: ["environments:write"] },
|
|
42
58
|
{ name: "delete_environment", group: "environments", scopes: ["environments:delete"], destructive: true },
|
|
43
59
|
{ name: "list_scenarios", group: "scenarios", scopes: ["scenarios:read"] },
|
|
44
60
|
{ name: "get_scenario", group: "scenarios", scopes: ["scenarios:read"] },
|
|
45
61
|
{ name: "get_scenario_report", group: "scenarios", scopes: ["scenarios:read"] },
|
|
46
62
|
{ name: "create_scenario", group: "scenarios", scopes: ["scenarios:write"] },
|
|
47
63
|
{ name: "update_scenario", group: "scenarios", scopes: ["scenarios:write"] },
|
|
64
|
+
{ name: "patch_scenario", group: "scenarios", scopes: ["scenarios:write"] },
|
|
65
|
+
{ name: "submit_scenario", group: "scenarios", scopes: ["scenarios:write"] },
|
|
48
66
|
{ name: "delete_scenario", group: "scenarios", scopes: ["scenarios:delete"], destructive: true },
|
|
49
67
|
{ name: "run_scenario", group: "scenarios", scopes: ["scenarios:run"] },
|
|
50
68
|
{ name: "list_diagrams", group: "diagrams", scopes: ["diagrams:read"] },
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerCollectionTools = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const errors_js_1 = require("../client/errors.js");
|
|
6
|
+
const strict_js_1 = require("../lib/strict.js");
|
|
6
7
|
const requestShape_js_1 = require("../domain/requestShape.js");
|
|
7
8
|
const resolve_js_1 = require("../domain/resolve.js");
|
|
8
9
|
const pagination_js_1 = require("../lib/pagination.js");
|
|
@@ -10,23 +11,31 @@ const truncate_js_1 = require("../lib/truncate.js");
|
|
|
10
11
|
const lookups_js_1 = require("./lookups.js");
|
|
11
12
|
const shared_js_1 = require("./shared.js");
|
|
12
13
|
const EXPORT_MAX_BYTES = 1024 * 1024;
|
|
14
|
+
const runnerSettingsArg = (0, strict_js_1.strictObject)({
|
|
15
|
+
database: zod_1.z.boolean().optional().describe("Run each request's database pre/post steps during collection runs"),
|
|
16
|
+
kafka: zod_1.z.boolean().optional().describe("Run each request's Kafka publish/consume steps during collection runs"),
|
|
17
|
+
}, "runner setting")
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Collection runner switches (both default to on)");
|
|
13
20
|
const registerCollectionTools = (server, ctx) => {
|
|
14
21
|
server.registerTool("list_collections", {
|
|
15
22
|
title: "List collections",
|
|
16
|
-
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.",
|
|
17
24
|
inputSchema: { workspace: shared_js_1.workspaceArg, query: zod_1.z.string().max(200).optional(), ...shared_js_1.cursorArgs },
|
|
18
25
|
annotations: shared_js_1.READ,
|
|
19
26
|
}, (0, shared_js_1.guarded)(async ({ workspace, query, cursor, limit }) => {
|
|
20
|
-
const
|
|
21
|
-
const all = await (0, lookups_js_1.loadCollections)(ctx, ws.id);
|
|
27
|
+
const workspaces = await (0, lookups_js_1.workspacesForListing)(ctx, workspace);
|
|
22
28
|
const needle = query?.trim().toLowerCase();
|
|
23
|
-
const
|
|
24
|
-
|
|
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 ?? ""}`);
|
|
25
33
|
return (0, shared_js_1.ok)({
|
|
26
|
-
|
|
27
|
-
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 }) => ({
|
|
28
36
|
id: collection.id,
|
|
29
37
|
name: collection.name,
|
|
38
|
+
...(workspaces.length > 1 ? { workspace: (0, lookups_js_1.workspaceTag)(ws) } : {}),
|
|
30
39
|
requestCount: collection.requestCount ?? null,
|
|
31
40
|
folderCount: collection.folderCount ?? null,
|
|
32
41
|
updatedAt: collection.updatedAt ?? null,
|
|
@@ -37,17 +46,17 @@ const registerCollectionTools = (server, ctx) => {
|
|
|
37
46
|
}));
|
|
38
47
|
server.registerTool("get_collection_tree", {
|
|
39
48
|
title: "Get a collection's folders and requests",
|
|
40
|
-
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.",
|
|
41
50
|
inputSchema: { workspace: shared_js_1.workspaceArg, collection: (0, shared_js_1.refArg)("Collection") },
|
|
42
51
|
annotations: shared_js_1.READ,
|
|
43
52
|
}, (0, shared_js_1.guarded)(async ({ workspace, collection }) => {
|
|
44
|
-
const ws = await
|
|
45
|
-
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);
|
|
46
54
|
const tree = await (0, lookups_js_1.loadTree)(ctx, ws.id, resolved.item.id);
|
|
47
55
|
const truncated = tree.requests.length > truncate_js_1.LIMITS.TREE_ITEMS;
|
|
48
56
|
return (0, shared_js_1.ok)({
|
|
49
57
|
collection: { id: resolved.item.id, name: resolved.item.name },
|
|
50
|
-
|
|
58
|
+
workspace: (0, lookups_js_1.workspaceTag)(ws),
|
|
59
|
+
resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), ...(note ? [{ input: collection, note }] : [])],
|
|
51
60
|
folders: tree.folders.map((folder) => ({ id: folder.id, name: folder.name, parentId: folder.parentId, path: folder.path })),
|
|
52
61
|
requests: tree.requests.slice(0, truncate_js_1.LIMITS.TREE_ITEMS).map(requestShape_js_1.requestSummaryView),
|
|
53
62
|
...(truncated ? { truncated: true, totalRequests: tree.requests.length } : {}),
|
|
@@ -59,23 +68,22 @@ const registerCollectionTools = (server, ctx) => {
|
|
|
59
68
|
inputSchema: { workspace: shared_js_1.workspaceArg, collection: (0, shared_js_1.refArg)("Collection"), format: zod_1.z.enum(["internal", "postman"]).default("internal") },
|
|
60
69
|
annotations: shared_js_1.READ,
|
|
61
70
|
}, (0, shared_js_1.guarded)(async ({ workspace, collection, format }) => {
|
|
62
|
-
const ws = await
|
|
63
|
-
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);
|
|
64
72
|
const document = await ctx.api.exportCollection(ws.id, resolved.item.id, format);
|
|
65
73
|
const bytes = (0, truncate_js_1.byteLength)(document);
|
|
66
74
|
if (bytes > EXPORT_MAX_BYTES) {
|
|
67
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 });
|
|
68
76
|
}
|
|
69
|
-
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 });
|
|
70
78
|
}));
|
|
71
79
|
server.registerTool("create_collection", {
|
|
72
80
|
title: "Create a collection",
|
|
73
|
-
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.",
|
|
74
82
|
inputSchema: {
|
|
75
83
|
workspace: shared_js_1.workspaceArg,
|
|
76
84
|
name: zod_1.z.string().min(1).max(truncate_js_1.LIMITS.NAME),
|
|
77
85
|
allowDuplicateName: zod_1.z.boolean().optional(),
|
|
78
|
-
runnerSettings:
|
|
86
|
+
runnerSettings: runnerSettingsArg,
|
|
79
87
|
},
|
|
80
88
|
annotations: shared_js_1.WRITE,
|
|
81
89
|
}, (0, shared_js_1.guarded)(async ({ workspace, name, allowDuplicateName, runnerSettings }) => {
|
|
@@ -88,29 +96,28 @@ const registerCollectionTools = (server, ctx) => {
|
|
|
88
96
|
}
|
|
89
97
|
const created = await ctx.api.createCollection(ws.id, { name: name.trim(), ...(runnerSettings ? { runnerSettings: compactSettings(runnerSettings) } : {}) });
|
|
90
98
|
ctx.invalidate(`collections:${ws.id}`);
|
|
91
|
-
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." });
|
|
92
100
|
}));
|
|
93
101
|
server.registerTool("update_collection", {
|
|
94
102
|
title: "Rename a collection / change runner settings",
|
|
95
|
-
description: "Rename a collection or toggle its runner settings
|
|
103
|
+
description: "Rename a collection or toggle its runner settings: whether collection runs execute the requests' database steps and Kafka steps.",
|
|
96
104
|
inputSchema: {
|
|
97
105
|
workspace: shared_js_1.workspaceArg,
|
|
98
106
|
collection: (0, shared_js_1.refArg)("Collection"),
|
|
99
107
|
name: zod_1.z.string().min(1).max(truncate_js_1.LIMITS.NAME).optional(),
|
|
100
|
-
runnerSettings:
|
|
108
|
+
runnerSettings: runnerSettingsArg,
|
|
101
109
|
},
|
|
102
110
|
annotations: shared_js_1.IDEMPOTENT_WRITE,
|
|
103
111
|
}, (0, shared_js_1.guarded)(async ({ workspace, collection, name, runnerSettings }) => {
|
|
104
112
|
if (name === undefined && runnerSettings === undefined)
|
|
105
113
|
throw new errors_js_1.ToolInputError("NOTHING_TO_UPDATE", "Pass name and/or runnerSettings.");
|
|
106
|
-
const ws = await
|
|
107
|
-
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);
|
|
108
115
|
const updated = await ctx.api.updateCollection(ws.id, resolved.item.id, {
|
|
109
116
|
...(name !== undefined ? { name: name.trim() } : {}),
|
|
110
117
|
...(runnerSettings ? { runnerSettings: compactSettings(runnerSettings) } : {}),
|
|
111
118
|
});
|
|
112
119
|
ctx.invalidate(`collections:${ws.id}`);
|
|
113
|
-
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 }] : [])] });
|
|
114
121
|
}));
|
|
115
122
|
server.registerTool("create_folder", {
|
|
116
123
|
title: "Create a folder",
|
|
@@ -123,8 +130,7 @@ const registerCollectionTools = (server, ctx) => {
|
|
|
123
130
|
},
|
|
124
131
|
annotations: shared_js_1.WRITE,
|
|
125
132
|
}, (0, shared_js_1.guarded)(async ({ workspace, collection, name, parent }) => {
|
|
126
|
-
const ws = await
|
|
127
|
-
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);
|
|
128
134
|
const parentFolder = await (0, lookups_js_1.resolveFolder)(ctx, ws.id, resolved.item.id, parent);
|
|
129
135
|
const tree = await (0, lookups_js_1.loadTree)(ctx, ws.id, resolved.item.id);
|
|
130
136
|
const sibling = tree.folders.find((folder) => folder.parentId === (parentFolder?.item.id ?? null) && (0, lookups_js_1.sameName)(folder.name, name));
|
|
@@ -136,36 +142,44 @@ const registerCollectionTools = (server, ctx) => {
|
|
|
136
142
|
ctx.invalidate(`collections:${ws.id}`);
|
|
137
143
|
return (0, shared_js_1.ok)({
|
|
138
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),
|
|
139
146
|
resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), ...(parentFolder ? [(0, resolve_js_1.resolvedSummary)(parent, parentFolder)] : [])],
|
|
140
147
|
});
|
|
141
148
|
}));
|
|
142
149
|
server.registerTool("update_folder", {
|
|
143
150
|
title: "Rename or move a folder",
|
|
144
|
-
description: "Rename a folder and/or move it under another parent (
|
|
151
|
+
description: "Rename a folder and/or move it — under another parent in the same collection (parent: null = top level) or into another collection (targetCollection, with its subfolders and requests).",
|
|
145
152
|
inputSchema: {
|
|
146
153
|
workspace: shared_js_1.workspaceArg,
|
|
147
154
|
collection: (0, shared_js_1.refArg)("Collection"),
|
|
148
155
|
folder: (0, shared_js_1.refArg)("Folder", "(or path)"),
|
|
149
156
|
name: zod_1.z.string().min(1).max(truncate_js_1.LIMITS.NAME).optional(),
|
|
150
|
-
parent: zod_1.z.string().max(300).nullable().optional().describe("New parent folder (id, name or path); null = top level"),
|
|
157
|
+
parent: zod_1.z.string().max(300).nullable().optional().describe("New parent folder (id, name or path) in the target collection; null = top level"),
|
|
158
|
+
targetCollection: zod_1.z.string().max(200).optional().describe("Move the folder into this collection (id or name)"),
|
|
151
159
|
},
|
|
152
160
|
annotations: shared_js_1.IDEMPOTENT_WRITE,
|
|
153
|
-
}, (0, shared_js_1.guarded)(async ({ workspace, collection, folder, name, parent }) => {
|
|
154
|
-
if (name === undefined && parent === undefined)
|
|
155
|
-
throw new errors_js_1.ToolInputError("NOTHING_TO_UPDATE", "Pass name and/or
|
|
156
|
-
const ws = await
|
|
157
|
-
const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection);
|
|
161
|
+
}, (0, shared_js_1.guarded)(async ({ workspace, collection, folder, name, parent, targetCollection }) => {
|
|
162
|
+
if (name === undefined && parent === undefined && targetCollection === undefined)
|
|
163
|
+
throw new errors_js_1.ToolInputError("NOTHING_TO_UPDATE", "Pass name, parent and/or targetCollection.");
|
|
164
|
+
const { ws, value: resolved } = await (0, lookups_js_1.findCollection)(ctx, workspace, collection);
|
|
158
165
|
const target = await (0, lookups_js_1.resolveFolder)(ctx, ws.id, resolved.item.id, folder);
|
|
159
166
|
if (!target)
|
|
160
167
|
throw new errors_js_1.ToolInputError("REF_REQUIRED", "folder is required.");
|
|
161
|
-
const
|
|
168
|
+
const destination = targetCollection ? await (0, lookups_js_1.resolveCollection)(ctx, ws.id, targetCollection) : resolved;
|
|
169
|
+
const moving = destination.item.id !== resolved.item.id;
|
|
170
|
+
const parentFolder = parent ? await (0, lookups_js_1.resolveFolder)(ctx, ws.id, destination.item.id, parent) : null;
|
|
162
171
|
if (parentFolder && parentFolder.item.id === target.item.id)
|
|
163
172
|
throw new errors_js_1.ToolInputError("FOLDER_CYCLE", "A folder cannot be its own parent.");
|
|
164
173
|
const updated = await ctx.api.updateFolder(ws.id, target.item.id, {
|
|
165
174
|
...(name !== undefined ? { name: name.trim() } : {}),
|
|
166
|
-
...(parent !== undefined ? { parentId: parentFolder?.item.id ?? null } : {}),
|
|
175
|
+
...(parent !== undefined || moving ? { parentId: parentFolder?.item.id ?? null } : {}),
|
|
176
|
+
...(moving ? { collectionId: destination.item.id } : {}),
|
|
167
177
|
});
|
|
168
178
|
ctx.invalidate(`tree:${ws.id}:${resolved.item.id}`);
|
|
179
|
+
if (moving) {
|
|
180
|
+
ctx.invalidate(`tree:${ws.id}:${destination.item.id}`);
|
|
181
|
+
ctx.invalidate(`collections:${ws.id}`);
|
|
182
|
+
}
|
|
169
183
|
return (0, shared_js_1.ok)({ folder: { id: updated.id, name: updated.name, parentId: updated.parentId }, resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), (0, resolve_js_1.resolvedSummary)(folder, target)] });
|
|
170
184
|
}));
|
|
171
185
|
server.registerTool("delete_collection", {
|
|
@@ -175,12 +189,11 @@ const registerCollectionTools = (server, ctx) => {
|
|
|
175
189
|
annotations: shared_js_1.DESTRUCTIVE,
|
|
176
190
|
}, (0, shared_js_1.guarded)(async ({ workspace, collection, confirm }) => {
|
|
177
191
|
(0, shared_js_1.requireConfirm)(confirm, "a collection");
|
|
178
|
-
const ws = await
|
|
179
|
-
const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection);
|
|
192
|
+
const { ws, value: resolved } = await (0, lookups_js_1.findCollection)(ctx, workspace, collection, { exact: true });
|
|
180
193
|
await ctx.api.deleteCollection(ws.id, resolved.item.id);
|
|
181
194
|
ctx.invalidate(`collections:${ws.id}`);
|
|
182
195
|
ctx.invalidate(`tree:${ws.id}:${resolved.item.id}`);
|
|
183
|
-
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)] });
|
|
184
197
|
}));
|
|
185
198
|
server.registerTool("delete_folder", {
|
|
186
199
|
title: "Delete a folder",
|
|
@@ -189,15 +202,14 @@ const registerCollectionTools = (server, ctx) => {
|
|
|
189
202
|
annotations: shared_js_1.DESTRUCTIVE,
|
|
190
203
|
}, (0, shared_js_1.guarded)(async ({ workspace, collection, folder, confirm }) => {
|
|
191
204
|
(0, shared_js_1.requireConfirm)(confirm, "a folder");
|
|
192
|
-
const ws = await
|
|
193
|
-
const
|
|
194
|
-
const target = await (0, lookups_js_1.resolveFolder)(ctx, ws.id, resolved.item.id, folder);
|
|
205
|
+
const { ws, value: resolved } = await (0, lookups_js_1.findCollection)(ctx, workspace, collection);
|
|
206
|
+
const target = await (0, lookups_js_1.resolveFolder)(ctx, ws.id, resolved.item.id, folder, { exact: true });
|
|
195
207
|
if (!target)
|
|
196
208
|
throw new errors_js_1.ToolInputError("REF_REQUIRED", "folder is required.");
|
|
197
209
|
await ctx.api.deleteFolder(ws.id, target.item.id);
|
|
198
210
|
ctx.invalidate(`tree:${ws.id}:${resolved.item.id}`);
|
|
199
211
|
ctx.invalidate(`collections:${ws.id}`);
|
|
200
|
-
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)] });
|
|
201
213
|
}));
|
|
202
214
|
};
|
|
203
215
|
exports.registerCollectionTools = registerCollectionTools;
|