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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sameName = exports.connectionResolverFor = exports.loadConnections = exports.resolveDiagram = exports.loadDiagrams = exports.resolveScenarioFolder = exports.resolveScenario = exports.loadScenarioCatalog = exports.resolveEnvironment = exports.loadEnvironments = exports.resolveRequest = exports.searchRequests = exports.requestIdsInFolder = exports.resolveFolder = exports.loadTree = exports.resolveCollection = exports.loadCollections = void 0;
|
|
3
|
+
exports.sameName = exports.connectionResolverFor = exports.loadConnections = exports.resolveDiagram = exports.loadDiagrams = exports.resolveScenarioFolder = exports.resolveScenario = exports.loadScenarioCatalog = exports.resolveEnvironment = exports.loadEnvironments = exports.resolveRequest = exports.searchRequests = exports.requestIdsInFolder = exports.resolveFolder = exports.loadTree = exports.resolveCollection = exports.findEnvironment = exports.findRequest = exports.findCollection = exports.findInWorkspaces = exports.workspaceTag = exports.workspacesForListing = exports.isAllWorkspaces = exports.loadCollections = void 0;
|
|
4
|
+
const errors_js_1 = require("../client/errors.js");
|
|
4
5
|
const requestIntegrations_js_1 = require("../domain/requestIntegrations.js");
|
|
5
6
|
const resolve_js_1 = require("../domain/resolve.js");
|
|
6
7
|
const ids_js_1 = require("../lib/ids.js");
|
|
@@ -10,6 +11,105 @@ const truncate_js_1 = require("../lib/truncate.js");
|
|
|
10
11
|
// prefixes it touches so the next read is fresh.
|
|
11
12
|
const loadCollections = (ctx, ws) => ctx.cache.getOrSet(`collections:${ws}`, () => ctx.api.listAllCollections(ws));
|
|
12
13
|
exports.loadCollections = loadCollections;
|
|
14
|
+
// ── across workspaces ──────────────────────────────────────────────────
|
|
15
|
+
// A personal token acts in EVERY workspace the user belongs to. Reads span
|
|
16
|
+
// all of them; the session default only orders them and wins ties.
|
|
17
|
+
// `workspace: "all"` (or "*") asks a listing for every workspace explicitly.
|
|
18
|
+
const isAllWorkspaces = (ref) => Boolean(ref && ["all", "*", "any"].includes(ref.trim().toLowerCase()));
|
|
19
|
+
exports.isAllWorkspaces = isAllWorkspaces;
|
|
20
|
+
const workspacesForListing = async (ctx, explicit) => {
|
|
21
|
+
if (explicit && explicit.trim() && !(0, exports.isAllWorkspaces)(explicit))
|
|
22
|
+
return [await ctx.resolveWorkspace(explicit)];
|
|
23
|
+
const workspaces = await ctx.listWorkspaces();
|
|
24
|
+
const preferred = ctx.getDefaultWorkspaceId();
|
|
25
|
+
return [...workspaces].sort((a, b) => Number(b.id === preferred) - Number(a.id === preferred));
|
|
26
|
+
};
|
|
27
|
+
exports.workspacesForListing = workspacesForListing;
|
|
28
|
+
const workspaceTag = (ws) => ({ id: ws.id, name: ws.name });
|
|
29
|
+
exports.workspaceTag = workspaceTag;
|
|
30
|
+
// Find something the model named (a collection, request, environment…).
|
|
31
|
+
// `workspace` given → only there. Otherwise the default workspace first — an
|
|
32
|
+
// exact or id match there wins at once; then every other workspace: a unique
|
|
33
|
+
// match anywhere is used, the same name in several workspaces comes back as
|
|
34
|
+
// candidates tagged with their workspace. A workspace where the thing is
|
|
35
|
+
// missing (404 / REF_NOT_FOUND) is simply skipped.
|
|
36
|
+
const findInWorkspaces = async (ctx, explicit, kind, ref, find, options = {}) => {
|
|
37
|
+
if (explicit && explicit.trim() && !(0, exports.isAllWorkspaces)(explicit)) {
|
|
38
|
+
const ws = await ctx.resolveWorkspace(explicit);
|
|
39
|
+
return { ws, value: await find(ws) };
|
|
40
|
+
}
|
|
41
|
+
const order = await (0, exports.workspacesForListing)(ctx, null);
|
|
42
|
+
if (order.length === 0)
|
|
43
|
+
throw new errors_js_1.ToolInputError("WORKSPACE_NONE", "You are not a member of any workspace.");
|
|
44
|
+
const preferred = ctx.getDefaultWorkspaceId();
|
|
45
|
+
const hits = [];
|
|
46
|
+
const ambiguous = [];
|
|
47
|
+
for (const ws of order) {
|
|
48
|
+
let value;
|
|
49
|
+
try {
|
|
50
|
+
value = await find(ws);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error instanceof errors_js_1.ToolInputError && error.code === "REF_NOT_FOUND")
|
|
54
|
+
continue;
|
|
55
|
+
if (error instanceof errors_js_1.ToolInputError && error.code === "REF_AMBIGUOUS") {
|
|
56
|
+
ambiguous.push({ ws, error });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (error instanceof errors_js_1.CallmanApiError && (error.status === 404 || error.status === 403))
|
|
60
|
+
continue;
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
const matched = options.matchOf?.(value);
|
|
64
|
+
if (matched === "id" || (ws.id === preferred && matched !== "partial")) {
|
|
65
|
+
return { ws, value, ...(ws.id !== preferred && order.length > 1 ? { note: `found in workspace "${ws.name}"` } : {}) };
|
|
66
|
+
}
|
|
67
|
+
hits.push({ ws, value, matched });
|
|
68
|
+
}
|
|
69
|
+
const exact = hits.filter((hit) => hit.matched !== "partial");
|
|
70
|
+
const pool = exact.length > 0 ? exact : hits;
|
|
71
|
+
// Ambiguous inside ONE workspace and nothing elsewhere: that workspace's
|
|
72
|
+
// own candidate list, tagged with where it is.
|
|
73
|
+
if (pool.length === 0 && ambiguous.length === 1) {
|
|
74
|
+
const [{ ws, error }] = ambiguous;
|
|
75
|
+
throw new errors_js_1.ToolInputError(error.code, order.length > 1 ? `${error.message} (in workspace "${ws.name}")` : error.message, { ...(error.data ?? {}), workspace: (0, exports.workspaceTag)(ws) });
|
|
76
|
+
}
|
|
77
|
+
if (pool.length === 1 && ambiguous.length === 0) {
|
|
78
|
+
const [hit] = pool;
|
|
79
|
+
return { ws: hit.ws, value: hit.value, ...(hit.ws.id !== preferred && order.length > 1 ? { note: `found in workspace "${hit.ws.name}"` } : {}) };
|
|
80
|
+
}
|
|
81
|
+
if (pool.length > 1 || ambiguous.length > 0) {
|
|
82
|
+
throw new errors_js_1.ToolInputError("REF_AMBIGUOUS", `"${ref}" matches ${kind}s in several places — pass \`workspace\` (name or id), or use the id.`, {
|
|
83
|
+
kind,
|
|
84
|
+
ref,
|
|
85
|
+
candidates: [
|
|
86
|
+
...pool.map((hit) => ({ workspace: (0, exports.workspaceTag)(hit.ws), ...(options.describe?.(hit.value) ?? {}) })),
|
|
87
|
+
...ambiguous.map((entry) => ({ workspace: (0, exports.workspaceTag)(entry.ws), candidates: entry.error.data?.candidates })),
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
throw new errors_js_1.ToolInputError("REF_NOT_FOUND", `No ${kind} matches "${ref}" in ${order.length === 1 ? "your workspace" : `any of your ${order.length} workspaces`}.`, {
|
|
92
|
+
kind,
|
|
93
|
+
ref,
|
|
94
|
+
searchedWorkspaces: order.map(exports.workspaceTag),
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
exports.findInWorkspaces = findInWorkspaces;
|
|
98
|
+
const findCollection = (ctx, workspace, ref, options = {}) => (0, exports.findInWorkspaces)(ctx, workspace, "collection", ref, (ws) => (0, exports.resolveCollection)(ctx, ws.id, ref, options), {
|
|
99
|
+
matchOf: (resolved) => resolved.matched,
|
|
100
|
+
describe: (resolved) => ({ id: resolved.item.id, name: resolved.item.name }),
|
|
101
|
+
});
|
|
102
|
+
exports.findCollection = findCollection;
|
|
103
|
+
const findRequest = (ctx, workspace, ref, collectionRef, options = {}) => (0, exports.findInWorkspaces)(ctx, workspace, "request", ref, (ws) => (0, exports.resolveRequest)(ctx, ws.id, ref, collectionRef, options), {
|
|
104
|
+
matchOf: (resolved) => resolved.matched,
|
|
105
|
+
describe: (resolved) => ({ id: resolved.request.id, name: resolved.request.name, method: resolved.request.method, url: resolved.request.url }),
|
|
106
|
+
});
|
|
107
|
+
exports.findRequest = findRequest;
|
|
108
|
+
const findEnvironment = (ctx, workspace, ref, options = {}) => (0, exports.findInWorkspaces)(ctx, workspace, "environment", ref, (ws) => (0, exports.resolveEnvironment)(ctx, ws.id, ref, options), {
|
|
109
|
+
matchOf: (resolved) => resolved.matched,
|
|
110
|
+
describe: (resolved) => ({ id: resolved.item.id, name: resolved.item.name }),
|
|
111
|
+
});
|
|
112
|
+
exports.findEnvironment = findEnvironment;
|
|
13
113
|
const resolveCollection = async (ctx, ws, ref, options = {}) => {
|
|
14
114
|
const collections = await (0, exports.loadCollections)(ctx, ws);
|
|
15
115
|
if ((0, ids_js_1.isObjectId)(ref) && !collections.some((collection) => collection.id === ref)) {
|
|
@@ -89,7 +189,7 @@ exports.searchRequests = searchRequests;
|
|
|
89
189
|
// given collection, or across the workspace when no collection is given.
|
|
90
190
|
const resolveRequest = async (ctx, ws, ref, collectionRef, options = {}) => {
|
|
91
191
|
if ((0, ids_js_1.isObjectId)(ref)) {
|
|
92
|
-
return { request: await ctx.api.getRequest(ws, ref) };
|
|
192
|
+
return { request: await ctx.api.getRequest(ws, ref), matched: "id" };
|
|
93
193
|
}
|
|
94
194
|
const collectionId = collectionRef ? (await (0, exports.resolveCollection)(ctx, ws, collectionRef)).item.id : null;
|
|
95
195
|
const collections = await (0, exports.loadCollections)(ctx, ws);
|
|
@@ -105,7 +205,7 @@ const resolveRequest = async (ctx, ws, ref, collectionRef, options = {}) => {
|
|
|
105
205
|
aliases: (request) => [`${request.method} ${request.url}`, request.url],
|
|
106
206
|
exact: options.exact,
|
|
107
207
|
});
|
|
108
|
-
return { request: await ctx.api.getRequest(ws, resolved.item.id), annotation: resolved.annotation };
|
|
208
|
+
return { request: await ctx.api.getRequest(ws, resolved.item.id), annotation: resolved.annotation, matched: resolved.matched };
|
|
109
209
|
};
|
|
110
210
|
exports.resolveRequest = resolveRequest;
|
|
111
211
|
const loadEnvironments = (ctx, ws) => ctx.cache.getOrSet(`environments:${ws}`, () => ctx.api.listAllEnvironments(ws));
|
package/dist-cjs/tools/meta.js
CHANGED
|
@@ -46,8 +46,9 @@ const registerMetaTools = (server, ctx) => {
|
|
|
46
46
|
const me = meResult.value;
|
|
47
47
|
const workspaces = workspacesResult.status === "fulfilled" ? workspacesResult.value : [];
|
|
48
48
|
const features = featuresResult.status === "fulfilled" ? featuresResult.value.features : {};
|
|
49
|
-
const
|
|
50
|
-
|
|
49
|
+
const preferred = ctx.getDefaultWorkspaceId();
|
|
50
|
+
const defaultWorkspace = preferred
|
|
51
|
+
? (workspaces.find((workspace) => workspace.id === preferred) ?? null)
|
|
51
52
|
: workspaces.length === 1
|
|
52
53
|
? workspaces[0]
|
|
53
54
|
: null;
|
|
@@ -69,8 +70,12 @@ const registerMetaTools = (server, ctx) => {
|
|
|
69
70
|
isDefault: workspace.id === defaultWorkspace?.id,
|
|
70
71
|
})),
|
|
71
72
|
defaultWorkspaceId: defaultWorkspace?.id ?? null,
|
|
72
|
-
...(workspaces.length > 1
|
|
73
|
-
? {
|
|
73
|
+
...(workspaces.length > 1
|
|
74
|
+
? {
|
|
75
|
+
workspaceScope: `This token works in all ${workspaces.length} of your workspaces. Lists and searches cover every workspace unless you pass \`workspace\`; a name is looked up in the default workspace first, then in all others. ${defaultWorkspace
|
|
76
|
+
? `The default ("${defaultWorkspace.name}") only decides where NEW things go and wins a name that exists in several workspaces`
|
|
77
|
+
: "There is no default, so creating something needs `workspace`"} — change it for this session with use_workspace.`,
|
|
78
|
+
}
|
|
74
79
|
: {}),
|
|
75
80
|
features,
|
|
76
81
|
localRun: ctx.config.allowLocalRun,
|
|
@@ -15,7 +15,7 @@ const MOVE_MAX = 200;
|
|
|
15
15
|
const registerRequestTools = (server, ctx) => {
|
|
16
16
|
server.registerTool("search_requests", {
|
|
17
17
|
title: "Search requests",
|
|
18
|
-
description: "Find saved requests by words in their name, method or URL (all words must match)
|
|
18
|
+
description: "Find saved requests by words in their name, method or URL (all words must match) — across ALL your workspaces, one `workspace`, or inside one collection. Returns up to 50 matches (each with its collection and workspace) with ids you can pass to get_request / run_request.",
|
|
19
19
|
inputSchema: {
|
|
20
20
|
workspace: shared_js_1.workspaceArg,
|
|
21
21
|
query: zod_1.z.string().min(1).max(200).describe("e.g. \"POST login\", \"users\", \"GET /orders\""),
|
|
@@ -24,14 +24,23 @@ const registerRequestTools = (server, ctx) => {
|
|
|
24
24
|
},
|
|
25
25
|
annotations: shared_js_1.READ,
|
|
26
26
|
}, (0, shared_js_1.guarded)(async ({ workspace, query, collection, limit }) => {
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
27
|
+
const max = limit ?? truncate_js_1.LIMITS.SEARCH_RESULTS;
|
|
28
|
+
const scoped = collection ? await (0, lookups_js_1.findCollection)(ctx, workspace, collection) : null;
|
|
29
|
+
const workspaces = scoped ? [scoped.ws] : await (0, lookups_js_1.workspacesForListing)(ctx, workspace);
|
|
30
|
+
const matches = [];
|
|
31
|
+
for (const ws of workspaces) {
|
|
32
|
+
if (matches.length >= max)
|
|
33
|
+
break;
|
|
34
|
+
for (const match of await (0, lookups_js_1.searchRequests)(ctx, ws.id, query, scoped?.value.item.id ?? null, max - matches.length))
|
|
35
|
+
matches.push({ ...match, ws });
|
|
36
|
+
}
|
|
37
|
+
const scope = scoped?.value ?? null;
|
|
30
38
|
return (0, shared_js_1.ok)({
|
|
31
39
|
query,
|
|
32
40
|
...(scope ? { collection: { id: scope.item.id, name: scope.item.name } } : {}),
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
...(workspaces.length > 1 ? { searchedWorkspaces: workspaces.length } : { workspace: (0, lookups_js_1.workspaceTag)(workspaces[0]) }),
|
|
42
|
+
matches: matches.map((request) => ({ ...(0, requestShape_js_1.requestSummaryView)(request), collection: request.collectionName, ...(workspaces.length > 1 ? { workspace: (0, lookups_js_1.workspaceTag)(request.ws) } : {}) })),
|
|
43
|
+
...(matches.length >= max ? { truncated: true } : {}),
|
|
35
44
|
});
|
|
36
45
|
}));
|
|
37
46
|
server.registerTool("get_request", {
|
|
@@ -46,9 +55,13 @@ const registerRequestTools = (server, ctx) => {
|
|
|
46
55
|
},
|
|
47
56
|
annotations: shared_js_1.READ,
|
|
48
57
|
}, (0, shared_js_1.guarded)(async ({ workspace, request, collection, full, reveal }) => {
|
|
49
|
-
const ws = await
|
|
50
|
-
const { request: found, annotation } =
|
|
51
|
-
return (0, shared_js_1.ok)({
|
|
58
|
+
const { ws, value, note } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
|
|
59
|
+
const { request: found, annotation } = value;
|
|
60
|
+
return (0, shared_js_1.ok)({
|
|
61
|
+
request: (0, requestShape_js_1.compactRequestView)(found, { full, reveal }),
|
|
62
|
+
workspace: (0, lookups_js_1.workspaceTag)(ws),
|
|
63
|
+
...(annotation || note ? { resolved: [{ input: request, matched: found.name, id: found.id, note: [annotation, note].filter(Boolean).join("; ") }] } : {}),
|
|
64
|
+
});
|
|
52
65
|
}));
|
|
53
66
|
server.registerTool("create_request", {
|
|
54
67
|
title: "Create a request",
|
|
@@ -56,8 +69,7 @@ const registerRequestTools = (server, ctx) => {
|
|
|
56
69
|
inputSchema: { workspace: shared_js_1.workspaceArg, collection: (0, shared_js_1.refArg)("Collection"), ...requestShape_js_1.requestInputSchema.omit({ folderKey: true }).shape },
|
|
57
70
|
annotations: shared_js_1.WRITE,
|
|
58
71
|
}, (0, shared_js_1.guarded)(async ({ workspace, collection, ...item }) => {
|
|
59
|
-
const ws = await
|
|
60
|
-
const resolved = await (0, lookups_js_1.resolveCollection)(ctx, ws.id, collection);
|
|
72
|
+
const { ws, value: resolved, note } = await (0, lookups_js_1.findCollection)(ctx, workspace, collection);
|
|
61
73
|
const folder = await (0, lookups_js_1.resolveFolder)(ctx, ws.id, resolved.item.id, item.folder);
|
|
62
74
|
const input = (0, requestShape_js_1.resolveMaskedValues)(item);
|
|
63
75
|
const resolve = await (0, lookups_js_1.connectionResolverFor)(ctx, ws.id, (0, requestIntegrations_js_1.hasIntegrations)(input));
|
|
@@ -70,9 +82,10 @@ const registerRequestTools = (server, ctx) => {
|
|
|
70
82
|
return (0, shared_js_1.ok)({
|
|
71
83
|
request: { ...(0, requestShape_js_1.requestSummaryView)(created), isEnabledInRunner: created.isEnabledInRunner !== false },
|
|
72
84
|
collection: { id: resolved.item.id, name: resolved.item.name },
|
|
85
|
+
workspace: (0, lookups_js_1.workspaceTag)(ws),
|
|
73
86
|
...(folder ? { folder: { id: folder.item.id, path: folder.item.path } } : {}),
|
|
74
87
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
75
|
-
resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), ...(folder && item.folder ? [(0, resolve_js_1.resolvedSummary)(item.folder, folder)] : [])],
|
|
88
|
+
resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), ...(note ? [{ input: collection, note }] : []), ...(folder && item.folder ? [(0, resolve_js_1.resolvedSummary)(item.folder, folder)] : [])],
|
|
76
89
|
});
|
|
77
90
|
}));
|
|
78
91
|
server.registerTool("create_requests_bulk", {
|
|
@@ -99,7 +112,9 @@ const registerRequestTools = (server, ctx) => {
|
|
|
99
112
|
if ((collectionName ? 1 : 0) + (collection ? 1 : 0) !== 1) {
|
|
100
113
|
throw new errors_js_1.ToolInputError("TARGET_REQUIRED", "Pass exactly one of collectionName (new collection) or collection (existing).");
|
|
101
114
|
}
|
|
102
|
-
|
|
115
|
+
// An existing collection decides the workspace; a new one needs a target.
|
|
116
|
+
const target = collection ? await (0, lookups_js_1.findCollection)(ctx, workspace, collection) : null;
|
|
117
|
+
const ws = target ? target.ws : await ctx.resolveWorkspace(workspace);
|
|
103
118
|
const folderKeys = new Set((folders ?? []).map((folder) => folder.key));
|
|
104
119
|
for (const folder of folders ?? []) {
|
|
105
120
|
if (folder.parentKey && !folderKeys.has(folder.parentKey)) {
|
|
@@ -163,13 +178,14 @@ const registerRequestTools = (server, ctx) => {
|
|
|
163
178
|
}
|
|
164
179
|
return (0, shared_js_1.ok)({
|
|
165
180
|
collection: { id: result.collection.id, name: result.collection.name },
|
|
181
|
+
workspace: (0, lookups_js_1.workspaceTag)(ws),
|
|
166
182
|
created: result.importedCounts,
|
|
167
183
|
...(runnerOff.length > 0 ? { disabledInRunner: runnerOff } : {}),
|
|
168
184
|
...(runnerWarnings.length > 0 ? { warnings: runnerWarnings } : {}),
|
|
169
185
|
note: "Created atomically. Open the collection in the desktop app (it refreshes when it regains focus).",
|
|
170
186
|
});
|
|
171
187
|
}
|
|
172
|
-
const resolved =
|
|
188
|
+
const resolved = target.value;
|
|
173
189
|
const tree = await (0, lookups_js_1.loadTree)(ctx, ws.id, resolved.item.id);
|
|
174
190
|
// Resolve every folder reference up front so a typo fails before any write.
|
|
175
191
|
const folderIdByRef = new Map();
|
|
@@ -215,7 +231,8 @@ const registerRequestTools = (server, ctx) => {
|
|
|
215
231
|
requests: created,
|
|
216
232
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
217
233
|
...(duplicates.length > 0 ? { note: `${duplicates.length} request name(s) already existed in the same folder and were created again: ${duplicates.map((request) => request.name).join(", ")}` } : {}),
|
|
218
|
-
|
|
234
|
+
workspace: (0, lookups_js_1.workspaceTag)(ws),
|
|
235
|
+
resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved), ...(target?.note ? [{ input: collection, note: target.note }] : [])],
|
|
219
236
|
});
|
|
220
237
|
}));
|
|
221
238
|
server.registerTool("update_request", {
|
|
@@ -228,8 +245,8 @@ const registerRequestTools = (server, ctx) => {
|
|
|
228
245
|
},
|
|
229
246
|
annotations: shared_js_1.IDEMPOTENT_WRITE,
|
|
230
247
|
}, (0, shared_js_1.guarded)(async ({ workspace, request, collection, ...changes }) => {
|
|
231
|
-
const ws = await
|
|
232
|
-
const { request: found, annotation } =
|
|
248
|
+
const { ws, value, note } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
|
|
249
|
+
const { request: found, annotation } = value;
|
|
233
250
|
const input = (0, requestShape_js_1.resolveMaskedValues)(changes, found);
|
|
234
251
|
const folder = input.folder !== undefined ? await (0, lookups_js_1.resolveFolder)(ctx, ws.id, found.collectionId, input.folder) : undefined;
|
|
235
252
|
const resolve = await (0, lookups_js_1.connectionResolverFor)(ctx, ws.id, (0, requestIntegrations_js_1.hasIntegrations)(input));
|
|
@@ -245,12 +262,13 @@ const registerRequestTools = (server, ctx) => {
|
|
|
245
262
|
request: (0, requestShape_js_1.compactRequestView)(updated),
|
|
246
263
|
changed: [...Object.keys(payload), ...(toggle ? ["isEnabledInRunner"] : [])],
|
|
247
264
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
248
|
-
|
|
265
|
+
workspace: (0, lookups_js_1.workspaceTag)(ws),
|
|
266
|
+
...(annotation || note ? { resolved: [{ input: request, matched: found.name, id: found.id, note: [annotation, note].filter(Boolean).join("; ") }] } : {}),
|
|
249
267
|
});
|
|
250
268
|
}));
|
|
251
269
|
server.registerTool("move_requests", {
|
|
252
270
|
title: "Move requests",
|
|
253
|
-
description: "Move up to 200 requests (ids or unique names) into a collection and optional folder.",
|
|
271
|
+
description: "Move up to 200 requests (ids or unique names) into a collection and optional folder of the same workspace (the target collection's).",
|
|
254
272
|
inputSchema: {
|
|
255
273
|
workspace: shared_js_1.workspaceArg,
|
|
256
274
|
requests: zod_1.z.array(zod_1.z.string().min(1).max(500)).min(1).max(MOVE_MAX),
|
|
@@ -260,8 +278,8 @@ const registerRequestTools = (server, ctx) => {
|
|
|
260
278
|
},
|
|
261
279
|
annotations: shared_js_1.IDEMPOTENT_WRITE,
|
|
262
280
|
}, (0, shared_js_1.guarded)(async ({ workspace, requests, targetCollection, targetFolder, sourceCollection }) => {
|
|
263
|
-
|
|
264
|
-
const target = await (0, lookups_js_1.
|
|
281
|
+
// Requests move within one workspace — the target collection's.
|
|
282
|
+
const { ws, value: target } = await (0, lookups_js_1.findCollection)(ctx, workspace, targetCollection);
|
|
265
283
|
const folder = await (0, lookups_js_1.resolveFolder)(ctx, ws.id, target.item.id, targetFolder);
|
|
266
284
|
const moved = [];
|
|
267
285
|
const failed = [];
|
|
@@ -294,24 +312,26 @@ const registerRequestTools = (server, ctx) => {
|
|
|
294
312
|
annotations: shared_js_1.DESTRUCTIVE,
|
|
295
313
|
}, (0, shared_js_1.guarded)(async ({ workspace, requests, collection, confirm }) => {
|
|
296
314
|
(0, shared_js_1.requireConfirm)(confirm, "requests");
|
|
297
|
-
const ws = await ctx.resolveWorkspace(workspace);
|
|
298
315
|
const deleted = [];
|
|
299
316
|
const failed = [];
|
|
300
317
|
const touched = new Set();
|
|
301
318
|
for (const ref of requests) {
|
|
302
319
|
try {
|
|
303
|
-
const {
|
|
320
|
+
const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, workspace, ref, collection, { exact: true });
|
|
321
|
+
const { request } = value;
|
|
304
322
|
await ctx.api.deleteRequest(ws.id, request.id);
|
|
305
|
-
touched.add(request.collectionId);
|
|
306
|
-
deleted.push({ id: request.id, name: request.name });
|
|
323
|
+
touched.add(`${ws.id}:${request.collectionId}`);
|
|
324
|
+
deleted.push({ id: request.id, name: request.name, workspace: (0, lookups_js_1.workspaceTag)(ws) });
|
|
307
325
|
}
|
|
308
326
|
catch (error) {
|
|
309
327
|
failed.push({ request: ref, error: (0, shared_js_1.failureOf)(error) });
|
|
310
328
|
}
|
|
311
329
|
}
|
|
312
|
-
for (const
|
|
313
|
-
|
|
314
|
-
|
|
330
|
+
for (const key of touched) {
|
|
331
|
+
const [wsId, collectionId] = key.split(":");
|
|
332
|
+
ctx.invalidate(`tree:${wsId}:${collectionId}`);
|
|
333
|
+
ctx.invalidate(`collections:${wsId}`);
|
|
334
|
+
}
|
|
315
335
|
return (0, shared_js_1.ok)({ deleted, failed });
|
|
316
336
|
}));
|
|
317
337
|
};
|
package/dist-cjs/tools/run.js
CHANGED
|
@@ -131,14 +131,16 @@ const registerRunTools = (server, ctx) => {
|
|
|
131
131
|
workspace: shared_js_1.workspaceArg,
|
|
132
132
|
request: (0, shared_js_1.refArg)("Request", "(name, \"METHOD url\" or id)"),
|
|
133
133
|
collection: zod_1.z.string().max(200).optional().describe("Narrow a name lookup to one collection"),
|
|
134
|
-
environment: zod_1.z.string().max(200).optional().describe("Environment name or id; omit to run without one"),
|
|
134
|
+
environment: zod_1.z.string().max(200).optional().describe("Environment name or id from the request's own workspace; omit to run without one"),
|
|
135
135
|
dryRun: zod_1.z.boolean().optional(),
|
|
136
136
|
maxBodyBytes: maxBodyArg,
|
|
137
137
|
},
|
|
138
138
|
annotations: shared_js_1.RUN,
|
|
139
139
|
}, (0, shared_js_1.guarded)(async ({ workspace, request, collection, environment, dryRun, maxBodyBytes }) => {
|
|
140
|
-
|
|
141
|
-
const {
|
|
140
|
+
// The request decides the workspace; the environment must be one of its.
|
|
141
|
+
const { ws, value, note } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
|
|
142
|
+
const { request: found } = value;
|
|
143
|
+
const annotation = [value.annotation, note].filter(Boolean).join("; ") || undefined;
|
|
142
144
|
const prepared = await prepareRun(ctx, ws.id, found.collectionId, environment);
|
|
143
145
|
const runnable = prepared.collection.requests.find((entry) => entry.id === found.id);
|
|
144
146
|
if (!runnable)
|
|
@@ -179,6 +181,7 @@ const registerRunTools = (server, ctx) => {
|
|
|
179
181
|
}));
|
|
180
182
|
const summary = (0, summarize_js_1.summarizeRun)(outcome, maxBodyBytes ?? ctx.config.maxBodyBytes, {
|
|
181
183
|
collection: { id: found.collectionId },
|
|
184
|
+
workspace: (0, lookups_js_1.workspaceTag)(ws),
|
|
182
185
|
environment: prepared.environment,
|
|
183
186
|
warnings: [...prepared.warnings, ...skippedWarnings(prepared, [found.id])],
|
|
184
187
|
...(annotation ? { resolved: [{ input: request, matched: found.name, id: found.id, note: annotation }] } : {}),
|
|
@@ -199,8 +202,7 @@ const registerRunTools = (server, ctx) => {
|
|
|
199
202
|
},
|
|
200
203
|
annotations: shared_js_1.RUN,
|
|
201
204
|
}, (0, shared_js_1.guarded)(async ({ workspace, collection, folder, only, environment, bail, maxBodyBytes }) => {
|
|
202
|
-
const ws = await
|
|
203
|
-
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);
|
|
204
206
|
const prepared = await prepareRun(ctx, ws.id, resolved.item.id, environment);
|
|
205
207
|
let selection = null;
|
|
206
208
|
if (folder) {
|
|
@@ -229,6 +231,7 @@ const registerRunTools = (server, ctx) => {
|
|
|
229
231
|
}));
|
|
230
232
|
return (0, shared_js_1.ok)((0, summarize_js_1.summarizeRun)(outcome, maxBodyBytes ?? ctx.config.maxBodyBytes, {
|
|
231
233
|
collection: { id: resolved.item.id, name: resolved.item.name },
|
|
234
|
+
workspace: (0, lookups_js_1.workspaceTag)(ws),
|
|
232
235
|
environment: prepared.environment,
|
|
233
236
|
warnings: [...prepared.warnings, ...skippedWarnings(prepared, outcome.results.map((result) => result.requestId))],
|
|
234
237
|
resolved: [(0, resolve_js_1.resolvedSummary)(collection, resolved)],
|
|
@@ -47,15 +47,17 @@ const latestScenario = async (ctx, ws, id) => ctx.api.getScenario(ws, id);
|
|
|
47
47
|
const registerScenarioTools = (server, ctx) => {
|
|
48
48
|
server.registerTool("list_scenarios", {
|
|
49
49
|
title: "List scenarios",
|
|
50
|
-
description: "List API test scenarios (multi-step flows) with their folder, review status and version, plus the scenario folders and (on-prem) the groups you can create scenarios in. `query` filters by name.
|
|
50
|
+
description: "List API test scenarios (multi-step flows) you can access — regardless of workspace (on-prem: your groups) — with their folder, workspace, review status and version, plus the scenario folders and (on-prem) the groups you can create scenarios in. `query` filters by name.",
|
|
51
51
|
inputSchema: { workspace: shared_js_1.workspaceArg, query: zod_1.z.string().max(200).optional(), folder: zod_1.z.string().max(200).optional(), ...shared_js_1.cursorArgs },
|
|
52
52
|
annotations: shared_js_1.READ,
|
|
53
53
|
}, (0, shared_js_1.guarded)(async ({ workspace, query, folder, cursor, limit }) => {
|
|
54
54
|
const ws = await ctx.resolveWorkspaceOrNull(workspace);
|
|
55
|
-
const [{ scenarios, folders }, groups] = await Promise.all([
|
|
55
|
+
const [{ scenarios, folders }, groups, workspaces] = await Promise.all([
|
|
56
56
|
(0, lookups_js_1.loadScenarioCatalog)(ctx, ws?.id ?? null),
|
|
57
57
|
ctx.api.listMyScenarioGroups(ws?.id ?? null).catch(() => ({ items: [] })),
|
|
58
|
+
ctx.listWorkspaces(),
|
|
58
59
|
]);
|
|
60
|
+
const workspaceName = new Map(workspaces.map((entry) => [entry.id, entry.name]));
|
|
59
61
|
const folderName = new Map(folders.map((entry) => [entry.id, entry.name]));
|
|
60
62
|
const scope = folder ? await (0, lookups_js_1.resolveScenarioFolder)(ctx, ws?.id ?? null, folder) : null;
|
|
61
63
|
const needle = query?.trim().toLowerCase();
|
|
@@ -67,7 +69,10 @@ const registerScenarioTools = (server, ctx) => {
|
|
|
67
69
|
...(ws ? { workspace: { id: ws.id, name: ws.name } } : {}),
|
|
68
70
|
folders: folders.map((entry) => ({ id: entry.id, name: entry.name })),
|
|
69
71
|
...(groups.items.length > 0 ? { groups: groups.items.map((group) => ({ id: group.id, name: group.name, role: group.role })) } : {}),
|
|
70
|
-
scenarios: page.items.map((scenario) => (
|
|
72
|
+
scenarios: page.items.map((scenario) => ({
|
|
73
|
+
...(0, summarize_js_1.scenarioSummaryView)(scenario, scenario.folderId ? (folderName.get(scenario.folderId) ?? null) : null),
|
|
74
|
+
...(workspaces.length > 1 && scenario.workspaceId ? { workspace: { id: scenario.workspaceId, name: workspaceName.get(scenario.workspaceId) ?? null } } : {}),
|
|
75
|
+
})),
|
|
71
76
|
total: page.total,
|
|
72
77
|
nextCursor: page.nextCursor,
|
|
73
78
|
});
|
package/dist-cjs/tools/shared.js
CHANGED
|
@@ -71,7 +71,7 @@ exports.workspaceArg = zod_1.z
|
|
|
71
71
|
.string()
|
|
72
72
|
.max(200)
|
|
73
73
|
.optional()
|
|
74
|
-
.describe(
|
|
74
|
+
.describe('Workspace id or name to narrow to. Omit it: lists/searches cover ALL your workspaces and names are found in whichever workspace has them; creating uses the session default (use_workspace). "all" = every workspace.');
|
|
75
75
|
const refArg = (what, extra = "") => zod_1.z.string().min(1).max(500).describe(`${what} id or name${extra ? ` ${extra}` : ""}`);
|
|
76
76
|
exports.refArg = refArg;
|
|
77
77
|
exports.cursorArgs = {
|
|
@@ -9,7 +9,7 @@ const shared_js_1 = require("./shared.js");
|
|
|
9
9
|
const registerWorkspaceTools = (server, ctx) => {
|
|
10
10
|
server.registerTool("list_workspaces", {
|
|
11
11
|
title: "List workspaces",
|
|
12
|
-
description: "List the workspaces you belong to, with your role in each (owner / editor / viewer).
|
|
12
|
+
description: "List the workspaces you belong to, with your role in each (owner / editor / viewer) and which one is the session default. The token works in all of them; a token keeps your role: writes are refused where you are a viewer.",
|
|
13
13
|
inputSchema: {},
|
|
14
14
|
annotations: shared_js_1.READ,
|
|
15
15
|
}, (0, shared_js_1.guarded)(async () => {
|
|
@@ -20,10 +20,26 @@ const registerWorkspaceTools = (server, ctx) => {
|
|
|
20
20
|
name: workspace.name,
|
|
21
21
|
role: workspace.role,
|
|
22
22
|
memberCount: workspace.memberCount ?? null,
|
|
23
|
-
isDefault: workspace.id === ctx.
|
|
23
|
+
isDefault: workspace.id === ctx.getDefaultWorkspaceId(),
|
|
24
24
|
})),
|
|
25
25
|
});
|
|
26
26
|
}));
|
|
27
|
+
server.registerTool("use_workspace", {
|
|
28
|
+
title: "Set the default workspace for this session",
|
|
29
|
+
description: "Pick the workspace new things go to for the rest of this session (collections, environments, scenarios, imports — anything created without `workspace`), and the one that wins when a name exists in several workspaces. It never hides the others: lists and searches still cover every workspace. `workspace: null` clears the default. Lasts until the MCP server restarts; CALLMAN_WORKSPACE_ID sets it at start.",
|
|
30
|
+
inputSchema: { workspace: zod_1.z.string().max(200).nullable().describe("Workspace name or id; null = no default") },
|
|
31
|
+
annotations: shared_js_1.IDEMPOTENT_WRITE,
|
|
32
|
+
}, (0, shared_js_1.guarded)(async ({ workspace }) => {
|
|
33
|
+
const previous = ctx.getDefaultWorkspaceId();
|
|
34
|
+
if (workspace === null || !workspace.trim()) {
|
|
35
|
+
ctx.setDefaultWorkspaceId(null);
|
|
36
|
+
return (0, shared_js_1.ok)({ defaultWorkspace: null, previousDefaultId: previous, note: "No default workspace: creating something now needs `workspace`; reads still cover all workspaces." });
|
|
37
|
+
}
|
|
38
|
+
const target = await ctx.resolveWorkspace(workspace);
|
|
39
|
+
ctx.setDefaultWorkspaceId(target.id);
|
|
40
|
+
ctx.invalidate();
|
|
41
|
+
return (0, shared_js_1.ok)({ defaultWorkspace: { id: target.id, name: target.name, role: target.role }, previousDefaultId: previous });
|
|
42
|
+
}));
|
|
27
43
|
server.registerTool("list_workspace_members", {
|
|
28
44
|
title: "List workspace members",
|
|
29
45
|
description: "List the members of a workspace with their role and status (active / invited).",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "callman-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for Callman: lets Claude Code, Claude Desktop, Cursor, Codex and Gemini CLI manage collections, requests, environments, test scenarios and system-design diagrams in your Callman workspace — cloud or on-prem.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"callman",
|