@topolo/mcp 0.7.1 → 0.8.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/dist/dispatch.js +143 -6
- package/dist/http.js +150 -13
- package/dist/index.js +548 -14
- package/package.json +2 -2
package/dist/dispatch.js
CHANGED
|
@@ -216,13 +216,8 @@ var TOOLS = [
|
|
|
216
216
|
if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
|
|
217
217
|
throw new Error("input must be an object.");
|
|
218
218
|
}
|
|
219
|
-
const action = await topolo.client.getAction(actionId);
|
|
220
|
-
const agentAccess = action.agentAccess ?? (action.destructive ? "off" : action.readOnly && !action.requiresConfirmation ? "auto" : "confirm");
|
|
221
|
-
if (action.method !== "GET" || !action.readOnly || action.destructive || action.requiresConfirmation || agentAccess !== "auto") {
|
|
222
|
-
throw new Error(`Action "${actionId}" is not an automatic read-only action. Use topolo_call_action with confirmation.`);
|
|
223
|
-
}
|
|
224
219
|
const resource = optionalResourceContext(args);
|
|
225
|
-
return topolo.client.
|
|
220
|
+
return topolo.client.readAction(
|
|
226
221
|
actionId,
|
|
227
222
|
input ?? {},
|
|
228
223
|
resource ? { resource } : {}
|
|
@@ -265,6 +260,148 @@ var TOOLS = [
|
|
|
265
260
|
);
|
|
266
261
|
}
|
|
267
262
|
},
|
|
263
|
+
{
|
|
264
|
+
name: "topolo_validate_action",
|
|
265
|
+
title: "Validate Topolo action input",
|
|
266
|
+
description: "Validates input locally against the exact JSON Schema published for one credential-scoped action. Does not invoke the action.",
|
|
267
|
+
requiredScopes: [],
|
|
268
|
+
destructive: false,
|
|
269
|
+
inputSchema: {
|
|
270
|
+
type: "object",
|
|
271
|
+
properties: {
|
|
272
|
+
actionId: { type: "string", minLength: 1 },
|
|
273
|
+
input: { type: "object", additionalProperties: true }
|
|
274
|
+
},
|
|
275
|
+
required: ["actionId"],
|
|
276
|
+
additionalProperties: false
|
|
277
|
+
},
|
|
278
|
+
handler: async (topolo, args) => {
|
|
279
|
+
const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
|
|
280
|
+
if (!actionId) throw new Error("actionId is required.");
|
|
281
|
+
const input = args["input"];
|
|
282
|
+
if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
|
|
283
|
+
throw new Error("input must be an object.");
|
|
284
|
+
}
|
|
285
|
+
return topolo.client.validateActionInput(actionId, input ?? {});
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "topolo_plan_action",
|
|
290
|
+
title: "Plan one Topolo action",
|
|
291
|
+
description: "Validates one credential-scoped action and returns its exact request, confirmation state, verification steps, rollback action, and next actions without executing it.",
|
|
292
|
+
requiredScopes: [],
|
|
293
|
+
destructive: false,
|
|
294
|
+
inputSchema: {
|
|
295
|
+
type: "object",
|
|
296
|
+
properties: {
|
|
297
|
+
actionId: { type: "string", minLength: 1 },
|
|
298
|
+
input: { type: "object", additionalProperties: true },
|
|
299
|
+
resourceType: { type: "string", description: "Optional app-local resource alias, such as workspace." },
|
|
300
|
+
resourceId: { type: "string", description: "Resource id. Required when resourceType is provided." },
|
|
301
|
+
confirm: { type: "boolean", description: "Include confirmation in the plan without executing the action." }
|
|
302
|
+
},
|
|
303
|
+
required: ["actionId"],
|
|
304
|
+
additionalProperties: false
|
|
305
|
+
},
|
|
306
|
+
handler: async (topolo, args) => {
|
|
307
|
+
const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
|
|
308
|
+
if (!actionId) throw new Error("actionId is required.");
|
|
309
|
+
const input = args["input"];
|
|
310
|
+
if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
|
|
311
|
+
throw new Error("input must be an object.");
|
|
312
|
+
}
|
|
313
|
+
const resource = optionalResourceContext(args);
|
|
314
|
+
return topolo.client.planAction(
|
|
315
|
+
actionId,
|
|
316
|
+
input ?? {},
|
|
317
|
+
{
|
|
318
|
+
confirm: args["confirm"] === true,
|
|
319
|
+
...resource ? { resource } : {}
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: "topolo_get_action_examples",
|
|
326
|
+
title: "Get Topolo action examples and documentation",
|
|
327
|
+
description: "Returns the public documentation URL and inline examples published for one action.",
|
|
328
|
+
requiredScopes: [],
|
|
329
|
+
destructive: false,
|
|
330
|
+
inputSchema: {
|
|
331
|
+
type: "object",
|
|
332
|
+
properties: { actionId: { type: "string", minLength: 1 } },
|
|
333
|
+
required: ["actionId"],
|
|
334
|
+
additionalProperties: false
|
|
335
|
+
},
|
|
336
|
+
handler: async (topolo, args) => {
|
|
337
|
+
const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
|
|
338
|
+
if (!actionId) throw new Error("actionId is required.");
|
|
339
|
+
const action = await topolo.client.getAction(actionId);
|
|
340
|
+
return {
|
|
341
|
+
actionId: action.actionId,
|
|
342
|
+
docsUrl: action.agentContract?.docsUrl ?? null,
|
|
343
|
+
examples: action.agentContract?.examples ?? []
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: "topolo_discover_capabilities",
|
|
349
|
+
title: "Discover one Topolo application capability contract",
|
|
350
|
+
description: "Discovers capability and schema actions published by one application. Use the actionService value returned by topolo_search_applications as service.",
|
|
351
|
+
requiredScopes: [],
|
|
352
|
+
destructive: false,
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: "object",
|
|
355
|
+
properties: { service: { type: "string", minLength: 1 } },
|
|
356
|
+
required: ["service"],
|
|
357
|
+
additionalProperties: false
|
|
358
|
+
},
|
|
359
|
+
handler: async (topolo, args) => {
|
|
360
|
+
const service = typeof args["service"] === "string" ? args["service"].trim() : "";
|
|
361
|
+
if (!service) throw new Error("service is required.");
|
|
362
|
+
return topolo.client.searchActions({ service, query: "capabilities", limit: 100 });
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: "topolo_get_resource_types",
|
|
367
|
+
title: "Get application resource-scoping types",
|
|
368
|
+
description: "Returns the canonical resource-scoping metadata published for one application, including aliases accepted by resource-scoped API keys and action calls.",
|
|
369
|
+
requiredScopes: [],
|
|
370
|
+
destructive: false,
|
|
371
|
+
inputSchema: {
|
|
372
|
+
type: "object",
|
|
373
|
+
properties: { application: { type: "string", minLength: 1 } },
|
|
374
|
+
required: ["application"],
|
|
375
|
+
additionalProperties: false
|
|
376
|
+
},
|
|
377
|
+
handler: async (topolo, args) => {
|
|
378
|
+
const application = typeof args["application"] === "string" ? args["application"].trim() : "";
|
|
379
|
+
if (!application) throw new Error("application is required.");
|
|
380
|
+
const entry = await topolo.client.getService(application);
|
|
381
|
+
return {
|
|
382
|
+
application: { appId: entry.appId, slug: entry.slug, name: entry.name },
|
|
383
|
+
resourceTypes: entry.apiKeyResources
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: "topolo_list_resources",
|
|
389
|
+
title: "List bindable Topolo resource ids",
|
|
390
|
+
description: "Returns credential-scoped resource ids for one application. OAuth credentials use the published Auth resource catalog; API keys return only their own bound resources.",
|
|
391
|
+
requiredScopes: [],
|
|
392
|
+
destructive: false,
|
|
393
|
+
inputSchema: {
|
|
394
|
+
type: "object",
|
|
395
|
+
properties: { application: { type: "string", minLength: 1 } },
|
|
396
|
+
required: ["application"],
|
|
397
|
+
additionalProperties: false
|
|
398
|
+
},
|
|
399
|
+
handler: async (topolo, args) => {
|
|
400
|
+
const application = typeof args["application"] === "string" ? args["application"].trim() : "";
|
|
401
|
+
if (!application) throw new Error("application is required.");
|
|
402
|
+
return topolo.client.listBindableResources(application);
|
|
403
|
+
}
|
|
404
|
+
},
|
|
268
405
|
{
|
|
269
406
|
name: "topolo_list_application_requirements",
|
|
270
407
|
title: "List Topolo application build requirements",
|
package/dist/http.js
CHANGED
|
@@ -3098,9 +3098,9 @@ var require_data = __commonJS({
|
|
|
3098
3098
|
}
|
|
3099
3099
|
});
|
|
3100
3100
|
|
|
3101
|
-
// ../../node_modules/.pnpm/fast-uri@3.1.
|
|
3101
|
+
// ../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/utils.js
|
|
3102
3102
|
var require_utils = __commonJS({
|
|
3103
|
-
"../../node_modules/.pnpm/fast-uri@3.1.
|
|
3103
|
+
"../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/utils.js"(exports, module) {
|
|
3104
3104
|
"use strict";
|
|
3105
3105
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3106
3106
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
@@ -3411,9 +3411,9 @@ var require_utils = __commonJS({
|
|
|
3411
3411
|
}
|
|
3412
3412
|
});
|
|
3413
3413
|
|
|
3414
|
-
// ../../node_modules/.pnpm/fast-uri@3.1.
|
|
3414
|
+
// ../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/schemes.js
|
|
3415
3415
|
var require_schemes = __commonJS({
|
|
3416
|
-
"../../node_modules/.pnpm/fast-uri@3.1.
|
|
3416
|
+
"../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/schemes.js"(exports, module) {
|
|
3417
3417
|
"use strict";
|
|
3418
3418
|
var { isUUID } = require_utils();
|
|
3419
3419
|
var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
|
|
@@ -3621,9 +3621,9 @@ var require_schemes = __commonJS({
|
|
|
3621
3621
|
}
|
|
3622
3622
|
});
|
|
3623
3623
|
|
|
3624
|
-
// ../../node_modules/.pnpm/fast-uri@3.1.
|
|
3624
|
+
// ../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/index.js
|
|
3625
3625
|
var require_fast_uri = __commonJS({
|
|
3626
|
-
"../../node_modules/.pnpm/fast-uri@3.1.
|
|
3626
|
+
"../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/index.js"(exports, module) {
|
|
3627
3627
|
"use strict";
|
|
3628
3628
|
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3629
3629
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
@@ -3836,7 +3836,7 @@ var require_fast_uri = __commonJS({
|
|
|
3836
3836
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
|
|
3837
3837
|
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
3838
3838
|
try {
|
|
3839
|
-
parsed.host =
|
|
3839
|
+
parsed.host = URL.domainToASCII(parsed.host.toLowerCase());
|
|
3840
3840
|
} catch (e) {
|
|
3841
3841
|
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
3842
3842
|
}
|
|
@@ -16024,13 +16024,8 @@ var TOOLS = [
|
|
|
16024
16024
|
if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
|
|
16025
16025
|
throw new Error("input must be an object.");
|
|
16026
16026
|
}
|
|
16027
|
-
const action = await topolo.client.getAction(actionId);
|
|
16028
|
-
const agentAccess = action.agentAccess ?? (action.destructive ? "off" : action.readOnly && !action.requiresConfirmation ? "auto" : "confirm");
|
|
16029
|
-
if (action.method !== "GET" || !action.readOnly || action.destructive || action.requiresConfirmation || agentAccess !== "auto") {
|
|
16030
|
-
throw new Error(`Action "${actionId}" is not an automatic read-only action. Use topolo_call_action with confirmation.`);
|
|
16031
|
-
}
|
|
16032
16027
|
const resource = optionalResourceContext(args);
|
|
16033
|
-
return topolo.client.
|
|
16028
|
+
return topolo.client.readAction(
|
|
16034
16029
|
actionId,
|
|
16035
16030
|
input ?? {},
|
|
16036
16031
|
resource ? { resource } : {}
|
|
@@ -16073,6 +16068,148 @@ var TOOLS = [
|
|
|
16073
16068
|
);
|
|
16074
16069
|
}
|
|
16075
16070
|
},
|
|
16071
|
+
{
|
|
16072
|
+
name: "topolo_validate_action",
|
|
16073
|
+
title: "Validate Topolo action input",
|
|
16074
|
+
description: "Validates input locally against the exact JSON Schema published for one credential-scoped action. Does not invoke the action.",
|
|
16075
|
+
requiredScopes: [],
|
|
16076
|
+
destructive: false,
|
|
16077
|
+
inputSchema: {
|
|
16078
|
+
type: "object",
|
|
16079
|
+
properties: {
|
|
16080
|
+
actionId: { type: "string", minLength: 1 },
|
|
16081
|
+
input: { type: "object", additionalProperties: true }
|
|
16082
|
+
},
|
|
16083
|
+
required: ["actionId"],
|
|
16084
|
+
additionalProperties: false
|
|
16085
|
+
},
|
|
16086
|
+
handler: async (topolo, args) => {
|
|
16087
|
+
const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
|
|
16088
|
+
if (!actionId) throw new Error("actionId is required.");
|
|
16089
|
+
const input = args["input"];
|
|
16090
|
+
if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
|
|
16091
|
+
throw new Error("input must be an object.");
|
|
16092
|
+
}
|
|
16093
|
+
return topolo.client.validateActionInput(actionId, input ?? {});
|
|
16094
|
+
}
|
|
16095
|
+
},
|
|
16096
|
+
{
|
|
16097
|
+
name: "topolo_plan_action",
|
|
16098
|
+
title: "Plan one Topolo action",
|
|
16099
|
+
description: "Validates one credential-scoped action and returns its exact request, confirmation state, verification steps, rollback action, and next actions without executing it.",
|
|
16100
|
+
requiredScopes: [],
|
|
16101
|
+
destructive: false,
|
|
16102
|
+
inputSchema: {
|
|
16103
|
+
type: "object",
|
|
16104
|
+
properties: {
|
|
16105
|
+
actionId: { type: "string", minLength: 1 },
|
|
16106
|
+
input: { type: "object", additionalProperties: true },
|
|
16107
|
+
resourceType: { type: "string", description: "Optional app-local resource alias, such as workspace." },
|
|
16108
|
+
resourceId: { type: "string", description: "Resource id. Required when resourceType is provided." },
|
|
16109
|
+
confirm: { type: "boolean", description: "Include confirmation in the plan without executing the action." }
|
|
16110
|
+
},
|
|
16111
|
+
required: ["actionId"],
|
|
16112
|
+
additionalProperties: false
|
|
16113
|
+
},
|
|
16114
|
+
handler: async (topolo, args) => {
|
|
16115
|
+
const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
|
|
16116
|
+
if (!actionId) throw new Error("actionId is required.");
|
|
16117
|
+
const input = args["input"];
|
|
16118
|
+
if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
|
|
16119
|
+
throw new Error("input must be an object.");
|
|
16120
|
+
}
|
|
16121
|
+
const resource = optionalResourceContext(args);
|
|
16122
|
+
return topolo.client.planAction(
|
|
16123
|
+
actionId,
|
|
16124
|
+
input ?? {},
|
|
16125
|
+
{
|
|
16126
|
+
confirm: args["confirm"] === true,
|
|
16127
|
+
...resource ? { resource } : {}
|
|
16128
|
+
}
|
|
16129
|
+
);
|
|
16130
|
+
}
|
|
16131
|
+
},
|
|
16132
|
+
{
|
|
16133
|
+
name: "topolo_get_action_examples",
|
|
16134
|
+
title: "Get Topolo action examples and documentation",
|
|
16135
|
+
description: "Returns the public documentation URL and inline examples published for one action.",
|
|
16136
|
+
requiredScopes: [],
|
|
16137
|
+
destructive: false,
|
|
16138
|
+
inputSchema: {
|
|
16139
|
+
type: "object",
|
|
16140
|
+
properties: { actionId: { type: "string", minLength: 1 } },
|
|
16141
|
+
required: ["actionId"],
|
|
16142
|
+
additionalProperties: false
|
|
16143
|
+
},
|
|
16144
|
+
handler: async (topolo, args) => {
|
|
16145
|
+
const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
|
|
16146
|
+
if (!actionId) throw new Error("actionId is required.");
|
|
16147
|
+
const action = await topolo.client.getAction(actionId);
|
|
16148
|
+
return {
|
|
16149
|
+
actionId: action.actionId,
|
|
16150
|
+
docsUrl: action.agentContract?.docsUrl ?? null,
|
|
16151
|
+
examples: action.agentContract?.examples ?? []
|
|
16152
|
+
};
|
|
16153
|
+
}
|
|
16154
|
+
},
|
|
16155
|
+
{
|
|
16156
|
+
name: "topolo_discover_capabilities",
|
|
16157
|
+
title: "Discover one Topolo application capability contract",
|
|
16158
|
+
description: "Discovers capability and schema actions published by one application. Use the actionService value returned by topolo_search_applications as service.",
|
|
16159
|
+
requiredScopes: [],
|
|
16160
|
+
destructive: false,
|
|
16161
|
+
inputSchema: {
|
|
16162
|
+
type: "object",
|
|
16163
|
+
properties: { service: { type: "string", minLength: 1 } },
|
|
16164
|
+
required: ["service"],
|
|
16165
|
+
additionalProperties: false
|
|
16166
|
+
},
|
|
16167
|
+
handler: async (topolo, args) => {
|
|
16168
|
+
const service = typeof args["service"] === "string" ? args["service"].trim() : "";
|
|
16169
|
+
if (!service) throw new Error("service is required.");
|
|
16170
|
+
return topolo.client.searchActions({ service, query: "capabilities", limit: 100 });
|
|
16171
|
+
}
|
|
16172
|
+
},
|
|
16173
|
+
{
|
|
16174
|
+
name: "topolo_get_resource_types",
|
|
16175
|
+
title: "Get application resource-scoping types",
|
|
16176
|
+
description: "Returns the canonical resource-scoping metadata published for one application, including aliases accepted by resource-scoped API keys and action calls.",
|
|
16177
|
+
requiredScopes: [],
|
|
16178
|
+
destructive: false,
|
|
16179
|
+
inputSchema: {
|
|
16180
|
+
type: "object",
|
|
16181
|
+
properties: { application: { type: "string", minLength: 1 } },
|
|
16182
|
+
required: ["application"],
|
|
16183
|
+
additionalProperties: false
|
|
16184
|
+
},
|
|
16185
|
+
handler: async (topolo, args) => {
|
|
16186
|
+
const application = typeof args["application"] === "string" ? args["application"].trim() : "";
|
|
16187
|
+
if (!application) throw new Error("application is required.");
|
|
16188
|
+
const entry = await topolo.client.getService(application);
|
|
16189
|
+
return {
|
|
16190
|
+
application: { appId: entry.appId, slug: entry.slug, name: entry.name },
|
|
16191
|
+
resourceTypes: entry.apiKeyResources
|
|
16192
|
+
};
|
|
16193
|
+
}
|
|
16194
|
+
},
|
|
16195
|
+
{
|
|
16196
|
+
name: "topolo_list_resources",
|
|
16197
|
+
title: "List bindable Topolo resource ids",
|
|
16198
|
+
description: "Returns credential-scoped resource ids for one application. OAuth credentials use the published Auth resource catalog; API keys return only their own bound resources.",
|
|
16199
|
+
requiredScopes: [],
|
|
16200
|
+
destructive: false,
|
|
16201
|
+
inputSchema: {
|
|
16202
|
+
type: "object",
|
|
16203
|
+
properties: { application: { type: "string", minLength: 1 } },
|
|
16204
|
+
required: ["application"],
|
|
16205
|
+
additionalProperties: false
|
|
16206
|
+
},
|
|
16207
|
+
handler: async (topolo, args) => {
|
|
16208
|
+
const application = typeof args["application"] === "string" ? args["application"].trim() : "";
|
|
16209
|
+
if (!application) throw new Error("application is required.");
|
|
16210
|
+
return topolo.client.listBindableResources(application);
|
|
16211
|
+
}
|
|
16212
|
+
},
|
|
16076
16213
|
{
|
|
16077
16214
|
name: "topolo_list_application_requirements",
|
|
16078
16215
|
title: "List Topolo application build requirements",
|
package/dist/index.js
CHANGED
|
@@ -2092,6 +2092,262 @@ function firstString(...values) {
|
|
|
2092
2092
|
}
|
|
2093
2093
|
return null;
|
|
2094
2094
|
}
|
|
2095
|
+
var SUPPORTED_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
|
|
2096
|
+
"$schema",
|
|
2097
|
+
"$id",
|
|
2098
|
+
"$ref",
|
|
2099
|
+
"$defs",
|
|
2100
|
+
"definitions",
|
|
2101
|
+
"title",
|
|
2102
|
+
"description",
|
|
2103
|
+
"default",
|
|
2104
|
+
"examples",
|
|
2105
|
+
"deprecated",
|
|
2106
|
+
"readOnly",
|
|
2107
|
+
"writeOnly",
|
|
2108
|
+
"type",
|
|
2109
|
+
"nullable",
|
|
2110
|
+
"const",
|
|
2111
|
+
"enum",
|
|
2112
|
+
"allOf",
|
|
2113
|
+
"anyOf",
|
|
2114
|
+
"oneOf",
|
|
2115
|
+
"properties",
|
|
2116
|
+
"required",
|
|
2117
|
+
"additionalProperties",
|
|
2118
|
+
"propertyNames",
|
|
2119
|
+
"minProperties",
|
|
2120
|
+
"maxProperties",
|
|
2121
|
+
"items",
|
|
2122
|
+
"minItems",
|
|
2123
|
+
"maxItems",
|
|
2124
|
+
"uniqueItems",
|
|
2125
|
+
"minLength",
|
|
2126
|
+
"maxLength",
|
|
2127
|
+
"pattern",
|
|
2128
|
+
"format",
|
|
2129
|
+
"minimum",
|
|
2130
|
+
"maximum",
|
|
2131
|
+
"exclusiveMinimum",
|
|
2132
|
+
"exclusiveMaximum",
|
|
2133
|
+
"multipleOf"
|
|
2134
|
+
]);
|
|
2135
|
+
function validateTopoloActionInput(schema, input) {
|
|
2136
|
+
const schemaIssue = inspectSchema(schema, "$", schema, 0);
|
|
2137
|
+
if (schemaIssue) return { valid: false, errors: [schemaIssue] };
|
|
2138
|
+
const errors = [];
|
|
2139
|
+
validateNode(schema, input, "$", schema, errors, 0);
|
|
2140
|
+
return { valid: errors.length === 0, errors };
|
|
2141
|
+
}
|
|
2142
|
+
function inspectSchema(schema, path, root, depth) {
|
|
2143
|
+
if (depth > 128) return { path, code: "schema_depth", message: "Schema recursion exceeded 128 levels." };
|
|
2144
|
+
const unsupportedKeyword = Object.keys(schema).find((keyword) => !SUPPORTED_SCHEMA_KEYWORDS.has(keyword));
|
|
2145
|
+
if (unsupportedKeyword) {
|
|
2146
|
+
return { path, code: "unsupported_schema_keyword", message: `Action schema uses unsupported keyword ${unsupportedKeyword}.` };
|
|
2147
|
+
}
|
|
2148
|
+
if (typeof schema.format === "string" && !["uri", "email", "date-time"].includes(schema.format)) {
|
|
2149
|
+
return { path, code: "unsupported_schema_format", message: `Action schema uses unsupported format ${schema.format}.` };
|
|
2150
|
+
}
|
|
2151
|
+
if (typeof schema.$ref === "string" && !resolveLocalReference(root, schema.$ref)) {
|
|
2152
|
+
return { path, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
|
|
2153
|
+
}
|
|
2154
|
+
const children = [];
|
|
2155
|
+
if (isRecord(schema.properties)) children.push(...Object.entries(schema.properties));
|
|
2156
|
+
if (isRecord(schema.$defs)) children.push(...Object.entries(schema.$defs));
|
|
2157
|
+
if (isRecord(schema.definitions)) children.push(...Object.entries(schema.definitions));
|
|
2158
|
+
children.push(
|
|
2159
|
+
["additionalProperties", schema.additionalProperties],
|
|
2160
|
+
["propertyNames", schema.propertyNames],
|
|
2161
|
+
["items", schema.items],
|
|
2162
|
+
...arraySchemaEntries("allOf", schema.allOf),
|
|
2163
|
+
...arraySchemaEntries("anyOf", schema.anyOf),
|
|
2164
|
+
...arraySchemaEntries("oneOf", schema.oneOf)
|
|
2165
|
+
);
|
|
2166
|
+
for (const [key, child] of children) {
|
|
2167
|
+
if (!isRecord(child)) continue;
|
|
2168
|
+
const issue = inspectSchema(child, `${path}.${key}`, root, depth + 1);
|
|
2169
|
+
if (issue) return issue;
|
|
2170
|
+
}
|
|
2171
|
+
return null;
|
|
2172
|
+
}
|
|
2173
|
+
function arraySchemaEntries(keyword, value) {
|
|
2174
|
+
return Array.isArray(value) ? value.map((candidate, index) => [`${keyword}[${index}]`, candidate]) : [];
|
|
2175
|
+
}
|
|
2176
|
+
function validateNode(schema, value, path, root, errors, depth) {
|
|
2177
|
+
if (depth > 128) {
|
|
2178
|
+
errors.push({ path, code: "schema_depth", message: "Schema recursion exceeded 128 levels." });
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
if (typeof schema.$ref === "string") {
|
|
2182
|
+
const resolved = resolveLocalReference(root, schema.$ref);
|
|
2183
|
+
if (!resolved) {
|
|
2184
|
+
errors.push({ path, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` });
|
|
2185
|
+
return;
|
|
2186
|
+
}
|
|
2187
|
+
validateNode(resolved, value, path, root, errors, depth + 1);
|
|
2188
|
+
}
|
|
2189
|
+
if (Array.isArray(schema.allOf)) {
|
|
2190
|
+
for (const candidate of schema.allOf) {
|
|
2191
|
+
if (isRecord(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
|
|
2195
|
+
errors.push({ path, code: "any_of", message: "Value does not match any allowed schema." });
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
if (Array.isArray(schema.oneOf)) {
|
|
2199
|
+
const matches = schema.oneOf.filter((candidate) => candidateMatches(candidate, value, root, depth)).length;
|
|
2200
|
+
if (matches !== 1) {
|
|
2201
|
+
errors.push({ path, code: "one_of", message: "Value must match exactly one allowed schema." });
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
if (value === null && schema.nullable === true) return;
|
|
2206
|
+
if (Object.prototype.hasOwnProperty.call(schema, "const") && !sameJsonValue(value, schema.const)) {
|
|
2207
|
+
errors.push({ path, code: "const", message: `Value must equal ${JSON.stringify(schema.const)}.` });
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => sameJsonValue(value, candidate))) {
|
|
2211
|
+
errors.push({ path, code: "enum", message: `Value must be one of ${schema.enum.map((item) => JSON.stringify(item)).join(", ")}.` });
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
if (!matchesType(schema.type, value)) {
|
|
2215
|
+
errors.push({ path, code: "type", message: `Expected ${String(schema.type)}.` });
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
if (typeof value === "string") validateString(schema, value, path, errors);
|
|
2219
|
+
if (typeof value === "number") validateNumber(schema, value, path, errors);
|
|
2220
|
+
if (Array.isArray(value)) validateArray(schema, value, path, root, errors, depth);
|
|
2221
|
+
if (isRecord(value)) validateObject(schema, value, path, root, errors, depth);
|
|
2222
|
+
}
|
|
2223
|
+
function validateObject(schema, value, path, root, errors, depth) {
|
|
2224
|
+
if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
|
|
2225
|
+
errors.push({ path, code: "min_properties", message: `Expected at least ${schema.minProperties} properties.` });
|
|
2226
|
+
}
|
|
2227
|
+
if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
|
|
2228
|
+
errors.push({ path, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
|
|
2229
|
+
}
|
|
2230
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
2231
|
+
const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
|
|
2232
|
+
for (const key of required) {
|
|
2233
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) {
|
|
2234
|
+
errors.push({ path: `${path}.${key}`, code: "required", message: "Required property is missing." });
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
if (isRecord(schema.propertyNames)) {
|
|
2238
|
+
for (const key of Object.keys(value)) {
|
|
2239
|
+
validateNode(schema.propertyNames, key, `${path}.${key}`, root, errors, depth + 1);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2243
|
+
const childSchema = properties[key];
|
|
2244
|
+
if (isRecord(childSchema)) {
|
|
2245
|
+
validateNode(childSchema, child, `${path}.${key}`, root, errors, depth + 1);
|
|
2246
|
+
} else if (schema.additionalProperties === false) {
|
|
2247
|
+
errors.push({ path: `${path}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
|
|
2248
|
+
} else if (isRecord(schema.additionalProperties)) {
|
|
2249
|
+
validateNode(schema.additionalProperties, child, `${path}.${key}`, root, errors, depth + 1);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
function validateArray(schema, value, path, root, errors, depth) {
|
|
2254
|
+
if (typeof schema.minItems === "number" && value.length < schema.minItems) {
|
|
2255
|
+
errors.push({ path, code: "min_items", message: `Expected at least ${schema.minItems} items.` });
|
|
2256
|
+
}
|
|
2257
|
+
if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
|
|
2258
|
+
errors.push({ path, code: "max_items", message: `Expected at most ${schema.maxItems} items.` });
|
|
2259
|
+
}
|
|
2260
|
+
if (schema.uniqueItems === true) {
|
|
2261
|
+
const unique = new Set(value.map((item) => JSON.stringify(item)));
|
|
2262
|
+
if (unique.size !== value.length) errors.push({ path, code: "unique_items", message: "Expected every item to be unique." });
|
|
2263
|
+
}
|
|
2264
|
+
if (isRecord(schema.items)) {
|
|
2265
|
+
value.forEach((item, index) => validateNode(schema.items, item, `${path}[${index}]`, root, errors, depth + 1));
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
function validateString(schema, value, path, errors) {
|
|
2269
|
+
if (typeof schema.minLength === "number" && value.length < schema.minLength) {
|
|
2270
|
+
errors.push({ path, code: "min_length", message: `Expected at least ${schema.minLength} characters.` });
|
|
2271
|
+
}
|
|
2272
|
+
if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
|
|
2273
|
+
errors.push({ path, code: "max_length", message: `Expected at most ${schema.maxLength} characters.` });
|
|
2274
|
+
}
|
|
2275
|
+
if (typeof schema.pattern === "string") {
|
|
2276
|
+
try {
|
|
2277
|
+
if (!new RegExp(schema.pattern).test(value)) errors.push({ path, code: "pattern", message: "Value does not match the required pattern." });
|
|
2278
|
+
} catch {
|
|
2279
|
+
errors.push({ path, code: "invalid_schema_pattern", message: "Action schema contains an invalid pattern." });
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
if (schema.format === "uri" && !isUrl(value)) errors.push({ path, code: "format_uri", message: "Expected an absolute URL." });
|
|
2283
|
+
if (schema.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errors.push({ path, code: "format_email", message: "Expected an email address." });
|
|
2284
|
+
if (schema.format === "date-time" && !isDateTime(value)) errors.push({ path, code: "format_date_time", message: "Expected an ISO-8601 date-time." });
|
|
2285
|
+
}
|
|
2286
|
+
function validateNumber(schema, value, path, errors) {
|
|
2287
|
+
if (typeof schema.minimum === "number" && value < schema.minimum) errors.push({ path, code: "minimum", message: `Expected a value of at least ${schema.minimum}.` });
|
|
2288
|
+
if (typeof schema.maximum === "number" && value > schema.maximum) errors.push({ path, code: "maximum", message: `Expected a value of at most ${schema.maximum}.` });
|
|
2289
|
+
const exclusiveMinimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.exclusiveMinimum === true && typeof schema.minimum === "number" ? schema.minimum : null;
|
|
2290
|
+
const exclusiveMaximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.exclusiveMaximum === true && typeof schema.maximum === "number" ? schema.maximum : null;
|
|
2291
|
+
if (exclusiveMinimum !== null && value <= exclusiveMinimum) {
|
|
2292
|
+
errors.push({ path, code: "exclusive_minimum", message: `Expected a value greater than ${exclusiveMinimum}.` });
|
|
2293
|
+
}
|
|
2294
|
+
if (exclusiveMaximum !== null && value >= exclusiveMaximum) {
|
|
2295
|
+
errors.push({ path, code: "exclusive_maximum", message: `Expected a value less than ${exclusiveMaximum}.` });
|
|
2296
|
+
}
|
|
2297
|
+
if (typeof schema.multipleOf === "number" && !isMultipleOf(value, schema.multipleOf)) {
|
|
2298
|
+
errors.push({ path, code: "multiple_of", message: `Expected a multiple of ${schema.multipleOf}.` });
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
function candidateMatches(candidate, value, root, depth) {
|
|
2302
|
+
if (!isRecord(candidate)) return false;
|
|
2303
|
+
const issues = [];
|
|
2304
|
+
validateNode(candidate, value, "$", root, issues, depth + 1);
|
|
2305
|
+
return issues.length === 0;
|
|
2306
|
+
}
|
|
2307
|
+
function resolveLocalReference(root, reference) {
|
|
2308
|
+
if (!reference.startsWith("#/")) return null;
|
|
2309
|
+
let current = root;
|
|
2310
|
+
for (const token of reference.slice(2).split("/").map((part) => part.replace(/~1/g, "/").replace(/~0/g, "~"))) {
|
|
2311
|
+
if (Array.isArray(current)) {
|
|
2312
|
+
if (!/^\d+$/.test(token) || Number(token) >= current.length) return null;
|
|
2313
|
+
current = current[Number(token)];
|
|
2314
|
+
continue;
|
|
2315
|
+
}
|
|
2316
|
+
if (!isRecord(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
|
|
2317
|
+
current = current[token];
|
|
2318
|
+
}
|
|
2319
|
+
return isRecord(current) ? current : null;
|
|
2320
|
+
}
|
|
2321
|
+
function matchesType(type, value) {
|
|
2322
|
+
if (type === void 0) return true;
|
|
2323
|
+
if (Array.isArray(type)) return type.some((candidate) => matchesType(candidate, value));
|
|
2324
|
+
if (type === "null") return value === null;
|
|
2325
|
+
if (type === "array") return Array.isArray(value);
|
|
2326
|
+
if (type === "object") return isRecord(value);
|
|
2327
|
+
if (type === "integer") return typeof value === "number" && Number.isInteger(value);
|
|
2328
|
+
return typeof value === type;
|
|
2329
|
+
}
|
|
2330
|
+
function sameJsonValue(left, right) {
|
|
2331
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
2332
|
+
}
|
|
2333
|
+
function isRecord(value) {
|
|
2334
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2335
|
+
}
|
|
2336
|
+
function isUrl(value) {
|
|
2337
|
+
try {
|
|
2338
|
+
return Boolean(new URL(value).protocol);
|
|
2339
|
+
} catch {
|
|
2340
|
+
return false;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
function isDateTime(value) {
|
|
2344
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) && !Number.isNaN(Date.parse(value));
|
|
2345
|
+
}
|
|
2346
|
+
function isMultipleOf(value, divisor) {
|
|
2347
|
+
if (!(divisor > 0)) return false;
|
|
2348
|
+
const quotient = value / divisor;
|
|
2349
|
+
return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4;
|
|
2350
|
+
}
|
|
2095
2351
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
2096
2352
|
var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
|
|
2097
2353
|
var TOPOLO_ACTION_NAME_HEADER = "X-Topolo-Action-Name";
|
|
@@ -2311,7 +2567,10 @@ var TopoloClient = class _TopoloClient {
|
|
|
2311
2567
|
serviceKey,
|
|
2312
2568
|
overrides: this.serviceUrls
|
|
2313
2569
|
});
|
|
2314
|
-
|
|
2570
|
+
if (baseUrl) return { serviceKey, baseUrl };
|
|
2571
|
+
const actionPage = await this.searchActions({ service: match.appId, limit: 1 }).catch(() => null);
|
|
2572
|
+
const actionBaseUrl = actionPage?.actions.find((action) => action.apiBaseUrl)?.apiBaseUrl ?? null;
|
|
2573
|
+
return actionBaseUrl ? { serviceKey, baseUrl: requireHttpOrigin(actionBaseUrl) } : null;
|
|
2315
2574
|
}
|
|
2316
2575
|
/**
|
|
2317
2576
|
* Resolve a service-slug → opaque app-id via caller config or TopoloAuth.
|
|
@@ -2396,7 +2655,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2396
2655
|
}
|
|
2397
2656
|
const entries = [];
|
|
2398
2657
|
for (const item of raw) {
|
|
2399
|
-
if (!
|
|
2658
|
+
if (!isRecord2(item)) continue;
|
|
2400
2659
|
const s = item;
|
|
2401
2660
|
const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
|
|
2402
2661
|
if (!appId) continue;
|
|
@@ -2446,6 +2705,44 @@ var TopoloClient = class _TopoloClient {
|
|
|
2446
2705
|
}
|
|
2447
2706
|
return entry;
|
|
2448
2707
|
}
|
|
2708
|
+
async listBindableResources(application) {
|
|
2709
|
+
const service = await this.getService(application);
|
|
2710
|
+
const identity = await this.introspect();
|
|
2711
|
+
if (identity.apiKey) {
|
|
2712
|
+
return {
|
|
2713
|
+
application: { appId: service.appId, slug: service.slug, name: service.name },
|
|
2714
|
+
organizationId: identity.organization?.id ?? null,
|
|
2715
|
+
resources: identity.apiKey.resourceBindings.map((binding) => ({
|
|
2716
|
+
resourceType: binding.resourceType,
|
|
2717
|
+
resourceId: binding.resourceId,
|
|
2718
|
+
label: null,
|
|
2719
|
+
slug: null,
|
|
2720
|
+
status: "active",
|
|
2721
|
+
organizationId: identity.organization?.id ?? null,
|
|
2722
|
+
metadata: {}
|
|
2723
|
+
})),
|
|
2724
|
+
resourceTypes: service.apiKeyResources,
|
|
2725
|
+
syncError: null,
|
|
2726
|
+
source: "api_key_binding"
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
const response = await this.readAction(
|
|
2730
|
+
"app_topolo_auth.service_api_key_resources.list",
|
|
2731
|
+
{ appId: service.appId }
|
|
2732
|
+
);
|
|
2733
|
+
const payload = isRecord2(response) && isRecord2(response.data) ? response.data : response;
|
|
2734
|
+
if (!isRecord2(payload) || !Array.isArray(payload.resources)) {
|
|
2735
|
+
throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
|
|
2736
|
+
}
|
|
2737
|
+
return {
|
|
2738
|
+
application: { appId: service.appId, slug: service.slug, name: service.name },
|
|
2739
|
+
organizationId: readOptionalString(payload.organizationId ?? payload.organization_id),
|
|
2740
|
+
resources: payload.resources.map(normalizeBindableResource).filter((resource) => Boolean(resource)),
|
|
2741
|
+
resourceTypes: normalizeApiKeyResourceMetadataList(payload.resourceTypes ?? payload.resource_types),
|
|
2742
|
+
syncError: readOptionalString(payload.syncError ?? payload.sync_error),
|
|
2743
|
+
source: "auth_catalog"
|
|
2744
|
+
};
|
|
2745
|
+
}
|
|
2449
2746
|
/**
|
|
2450
2747
|
* List callable actions from the live registry-canonical catalog (definitions
|
|
2451
2748
|
* from the Catalog registry, scoping from Auth). The response is scoped to the
|
|
@@ -2538,6 +2835,54 @@ var TopoloClient = class _TopoloClient {
|
|
|
2538
2835
|
}
|
|
2539
2836
|
async callAction(action, input = {}, options = {}) {
|
|
2540
2837
|
const entry = await this.getAction(action);
|
|
2838
|
+
return this.executeResolvedAction(entry, input, options);
|
|
2839
|
+
}
|
|
2840
|
+
async validateActionInput(action, input = {}) {
|
|
2841
|
+
const entry = await this.getAction(action);
|
|
2842
|
+
return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input) };
|
|
2843
|
+
}
|
|
2844
|
+
async planAction(action, input = {}, options = {}) {
|
|
2845
|
+
const entry = await this.getAction(action);
|
|
2846
|
+
const validation = validateTopoloActionInput(entry.inputSchema, input);
|
|
2847
|
+
const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
|
|
2848
|
+
const confirmationRequired = agentAccess === "confirm" || entry.requiresConfirmation || entry.destructive;
|
|
2849
|
+
let request = null;
|
|
2850
|
+
if (validation.valid) {
|
|
2851
|
+
const mapped = mapActionInput(entry, input);
|
|
2852
|
+
request = {
|
|
2853
|
+
method: entry.method,
|
|
2854
|
+
service: entry.appSlug || entry.appId,
|
|
2855
|
+
apiBaseUrl: entry.apiBaseUrl,
|
|
2856
|
+
path: mapped.path,
|
|
2857
|
+
...mapped.query ? { query: mapped.query } : {},
|
|
2858
|
+
...mapped.body !== void 0 ? { body: mapped.body } : {},
|
|
2859
|
+
...options.resource ? { resource: options.resource } : {}
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
return {
|
|
2863
|
+
action: entry,
|
|
2864
|
+
validation,
|
|
2865
|
+
confirmationRequired,
|
|
2866
|
+
confirmationProvided: options.confirm === true,
|
|
2867
|
+
executable: validation.valid && agentAccess !== "off" && (!confirmationRequired || options.confirm === true),
|
|
2868
|
+
request,
|
|
2869
|
+
verification: stringArray(entry.agentContract?.verify),
|
|
2870
|
+
rollbackActionId: pickActionString(entry.agentContract?.rollbackActionId),
|
|
2871
|
+
nextActionIds: stringArray(entry.agentContract?.nextActionIds)
|
|
2872
|
+
};
|
|
2873
|
+
}
|
|
2874
|
+
async readAction(action, input = {}, options = {}) {
|
|
2875
|
+
const entry = await this.getAction(action);
|
|
2876
|
+
const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
|
|
2877
|
+
if (!entry.readOnly || entry.destructive || entry.requiresConfirmation || agentAccess !== "auto") {
|
|
2878
|
+
throw new TopoloPermissionError(
|
|
2879
|
+
`Topolo action "${entry.actionId}" is not an automatic read-only action.`,
|
|
2880
|
+
entry.requiredPermission ? [entry.requiredPermission] : []
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
return this.executeResolvedAction(entry, input, options);
|
|
2884
|
+
}
|
|
2885
|
+
async executeResolvedAction(entry, input, options) {
|
|
2541
2886
|
const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
|
|
2542
2887
|
if (agentAccess === "off") {
|
|
2543
2888
|
throw new TopoloPermissionError(
|
|
@@ -2550,6 +2895,13 @@ var TopoloClient = class _TopoloClient {
|
|
|
2550
2895
|
`Topolo action "${entry.actionId}" requires { confirm: true } before invocation.`
|
|
2551
2896
|
);
|
|
2552
2897
|
}
|
|
2898
|
+
const validation = validateTopoloActionInput(entry.inputSchema, input);
|
|
2899
|
+
if (!validation.valid) {
|
|
2900
|
+
throw new TopoloSdkError(
|
|
2901
|
+
"invalid_action_input",
|
|
2902
|
+
`Topolo action "${entry.actionId}" input is invalid: ${validation.errors.map((issue) => `${issue.path} ${issue.message}`).join("; ")}`
|
|
2903
|
+
);
|
|
2904
|
+
}
|
|
2553
2905
|
const mapped = mapActionInput(entry, input);
|
|
2554
2906
|
const service = entry.appSlug || entry.appId;
|
|
2555
2907
|
return this.request({
|
|
@@ -2564,7 +2916,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2564
2916
|
[TOPOLO_ACTION_ID_HEADER]: entry.actionId,
|
|
2565
2917
|
[TOPOLO_ACTION_NAME_HEADER]: entry.name
|
|
2566
2918
|
},
|
|
2567
|
-
confirm: options.confirm === true
|
|
2919
|
+
confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
|
|
2568
2920
|
});
|
|
2569
2921
|
}
|
|
2570
2922
|
/**
|
|
@@ -2609,6 +2961,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2609
2961
|
const kind = payload.credentialType === "api_key" ? "api_key" : "access_token";
|
|
2610
2962
|
return {
|
|
2611
2963
|
kind,
|
|
2964
|
+
authorizationMode: normalizeCredentialAuthorizationMode(payload.authorizationMode, kind),
|
|
2612
2965
|
user: {
|
|
2613
2966
|
id: user.id,
|
|
2614
2967
|
email: user.email,
|
|
@@ -2620,7 +2973,14 @@ var TopoloClient = class _TopoloClient {
|
|
|
2620
2973
|
id: organization.id,
|
|
2621
2974
|
slug: organization.slug,
|
|
2622
2975
|
name: organization.name ?? organization.slug
|
|
2623
|
-
} : user.orgId && user.orgSlug ? { id: user.orgId, slug: user.orgSlug, name: user.orgSlug } : null
|
|
2976
|
+
} : user.orgId && user.orgSlug ? { id: user.orgId, slug: user.orgSlug, name: user.orgSlug } : null,
|
|
2977
|
+
apiKey: kind === "api_key" ? {
|
|
2978
|
+
id: readOptionalString(payload.apiKey?.id),
|
|
2979
|
+
name: readOptionalString(payload.apiKey?.name),
|
|
2980
|
+
appId: readOptionalString(payload.apiKey?.appId),
|
|
2981
|
+
scopes: stringArray(payload.apiKey?.scopes),
|
|
2982
|
+
resourceBindings: normalizeResourceBindings(payload.apiKey?.resourceBindings)
|
|
2983
|
+
} : null
|
|
2624
2984
|
};
|
|
2625
2985
|
}
|
|
2626
2986
|
};
|
|
@@ -2635,7 +2995,7 @@ function resolveConfiguredAppId(slug, appIds) {
|
|
|
2635
2995
|
return null;
|
|
2636
2996
|
}
|
|
2637
2997
|
function normalizeApiKeyResourceMetadata(input) {
|
|
2638
|
-
if (!
|
|
2998
|
+
if (!isRecord2(input)) return null;
|
|
2639
2999
|
const resourceType = readString(input.resourceType ?? input.resource_type);
|
|
2640
3000
|
const label = readString(input.label);
|
|
2641
3001
|
const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
|
|
@@ -2668,10 +3028,38 @@ function normalizeApiKeyResourceMetadataList(input) {
|
|
|
2668
3028
|
}
|
|
2669
3029
|
return resources;
|
|
2670
3030
|
}
|
|
3031
|
+
function normalizeBindableResource(input) {
|
|
3032
|
+
if (!isRecord2(input)) return null;
|
|
3033
|
+
const resourceType = readString(input.resourceType ?? input.resource_type);
|
|
3034
|
+
const resourceId = readString(input.resourceId ?? input.resource_id);
|
|
3035
|
+
if (!resourceType || !resourceId) return null;
|
|
3036
|
+
return {
|
|
3037
|
+
resourceType,
|
|
3038
|
+
resourceId,
|
|
3039
|
+
label: readOptionalString(input.label),
|
|
3040
|
+
slug: readOptionalString(input.slug),
|
|
3041
|
+
status: readOptionalString(input.status),
|
|
3042
|
+
organizationId: readOptionalString(input.organizationId ?? input.organization_id),
|
|
3043
|
+
metadata: isRecord2(input.metadata) ? input.metadata : {}
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3046
|
+
function normalizeResourceBindings(input) {
|
|
3047
|
+
if (!Array.isArray(input)) return [];
|
|
3048
|
+
return input.flatMap((binding) => {
|
|
3049
|
+
if (!isRecord2(binding)) return [];
|
|
3050
|
+
const resourceType = readString(binding.resourceType ?? binding.resource_type);
|
|
3051
|
+
const resourceId = readString(binding.resourceId ?? binding.resource_id);
|
|
3052
|
+
return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
|
|
3053
|
+
});
|
|
3054
|
+
}
|
|
2671
3055
|
function readString(value) {
|
|
2672
3056
|
return typeof value === "string" ? value.trim() : "";
|
|
2673
3057
|
}
|
|
2674
|
-
function
|
|
3058
|
+
function readOptionalString(value) {
|
|
3059
|
+
const normalized = readString(value);
|
|
3060
|
+
return normalized || null;
|
|
3061
|
+
}
|
|
3062
|
+
function isRecord2(value) {
|
|
2675
3063
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2676
3064
|
}
|
|
2677
3065
|
function resolveConfiguredServiceUrl(slug, serviceUrls) {
|
|
@@ -2743,14 +3131,14 @@ function catalogSearchQuery(options) {
|
|
|
2743
3131
|
return query;
|
|
2744
3132
|
}
|
|
2745
3133
|
function normalizeCatalogPage(value) {
|
|
2746
|
-
if (!
|
|
3134
|
+
if (!isRecord2(value)) return { nextCursor: null, hasMore: false };
|
|
2747
3135
|
return {
|
|
2748
3136
|
nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
|
|
2749
3137
|
hasMore: value.hasMore === true
|
|
2750
3138
|
};
|
|
2751
3139
|
}
|
|
2752
3140
|
function normalizeServiceCatalogEntry(item) {
|
|
2753
|
-
if (!
|
|
3141
|
+
if (!isRecord2(item)) return null;
|
|
2754
3142
|
const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
|
|
2755
3143
|
if (!appId) return null;
|
|
2756
3144
|
const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
|
|
@@ -2805,6 +3193,7 @@ function normalizeActionCatalogEntry(item) {
|
|
|
2805
3193
|
authorizationMode: pickAuthorizationMode(a.authorization_mode ?? a.authorizationMode),
|
|
2806
3194
|
inputSchema: plainObject(a.input_schema ?? a.inputSchema),
|
|
2807
3195
|
outputSchema: plainObject(a.output_schema ?? a.outputSchema),
|
|
3196
|
+
agentContract: plainObject(a.agent_contract ?? a.agentContract),
|
|
2808
3197
|
readOnly: Boolean(a.read_only ?? a.readOnly),
|
|
2809
3198
|
destructive: Boolean(a.destructive),
|
|
2810
3199
|
requiresConfirmation: Boolean(a.requires_confirmation ?? a.requiresConfirmation),
|
|
@@ -2828,6 +3217,9 @@ function pickAuthorizationMode(value) {
|
|
|
2828
3217
|
function plainObject(value) {
|
|
2829
3218
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2830
3219
|
}
|
|
3220
|
+
function stringArray(value) {
|
|
3221
|
+
return Array.isArray(value) ? value.map((item) => String(item || "").trim()).filter(Boolean) : [];
|
|
3222
|
+
}
|
|
2831
3223
|
function mapActionInput(action, input) {
|
|
2832
3224
|
const remaining = { ...input || {} };
|
|
2833
3225
|
const path = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
|
|
@@ -2871,6 +3263,11 @@ function objectToQuery(value) {
|
|
|
2871
3263
|
}
|
|
2872
3264
|
return Object.keys(query).length > 0 ? query : void 0;
|
|
2873
3265
|
}
|
|
3266
|
+
function normalizeCredentialAuthorizationMode(value, kind) {
|
|
3267
|
+
if (kind === "api_key") return "api_key";
|
|
3268
|
+
if (value === "context_rbac" || value === "oauth_scopes" || value === "session_rbac") return value;
|
|
3269
|
+
return "unknown";
|
|
3270
|
+
}
|
|
2874
3271
|
function describeError(body, fallback) {
|
|
2875
3272
|
if (body && typeof body === "object") {
|
|
2876
3273
|
const maybe = body;
|
|
@@ -3543,13 +3940,8 @@ var TOOLS = [
|
|
|
3543
3940
|
if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
|
|
3544
3941
|
throw new Error("input must be an object.");
|
|
3545
3942
|
}
|
|
3546
|
-
const action = await topolo.client.getAction(actionId);
|
|
3547
|
-
const agentAccess = action.agentAccess ?? (action.destructive ? "off" : action.readOnly && !action.requiresConfirmation ? "auto" : "confirm");
|
|
3548
|
-
if (action.method !== "GET" || !action.readOnly || action.destructive || action.requiresConfirmation || agentAccess !== "auto") {
|
|
3549
|
-
throw new Error(`Action "${actionId}" is not an automatic read-only action. Use topolo_call_action with confirmation.`);
|
|
3550
|
-
}
|
|
3551
3943
|
const resource = optionalResourceContext(args);
|
|
3552
|
-
return topolo.client.
|
|
3944
|
+
return topolo.client.readAction(
|
|
3553
3945
|
actionId,
|
|
3554
3946
|
input ?? {},
|
|
3555
3947
|
resource ? { resource } : {}
|
|
@@ -3592,6 +3984,148 @@ var TOOLS = [
|
|
|
3592
3984
|
);
|
|
3593
3985
|
}
|
|
3594
3986
|
},
|
|
3987
|
+
{
|
|
3988
|
+
name: "topolo_validate_action",
|
|
3989
|
+
title: "Validate Topolo action input",
|
|
3990
|
+
description: "Validates input locally against the exact JSON Schema published for one credential-scoped action. Does not invoke the action.",
|
|
3991
|
+
requiredScopes: [],
|
|
3992
|
+
destructive: false,
|
|
3993
|
+
inputSchema: {
|
|
3994
|
+
type: "object",
|
|
3995
|
+
properties: {
|
|
3996
|
+
actionId: { type: "string", minLength: 1 },
|
|
3997
|
+
input: { type: "object", additionalProperties: true }
|
|
3998
|
+
},
|
|
3999
|
+
required: ["actionId"],
|
|
4000
|
+
additionalProperties: false
|
|
4001
|
+
},
|
|
4002
|
+
handler: async (topolo, args) => {
|
|
4003
|
+
const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
|
|
4004
|
+
if (!actionId) throw new Error("actionId is required.");
|
|
4005
|
+
const input = args["input"];
|
|
4006
|
+
if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
|
|
4007
|
+
throw new Error("input must be an object.");
|
|
4008
|
+
}
|
|
4009
|
+
return topolo.client.validateActionInput(actionId, input ?? {});
|
|
4010
|
+
}
|
|
4011
|
+
},
|
|
4012
|
+
{
|
|
4013
|
+
name: "topolo_plan_action",
|
|
4014
|
+
title: "Plan one Topolo action",
|
|
4015
|
+
description: "Validates one credential-scoped action and returns its exact request, confirmation state, verification steps, rollback action, and next actions without executing it.",
|
|
4016
|
+
requiredScopes: [],
|
|
4017
|
+
destructive: false,
|
|
4018
|
+
inputSchema: {
|
|
4019
|
+
type: "object",
|
|
4020
|
+
properties: {
|
|
4021
|
+
actionId: { type: "string", minLength: 1 },
|
|
4022
|
+
input: { type: "object", additionalProperties: true },
|
|
4023
|
+
resourceType: { type: "string", description: "Optional app-local resource alias, such as workspace." },
|
|
4024
|
+
resourceId: { type: "string", description: "Resource id. Required when resourceType is provided." },
|
|
4025
|
+
confirm: { type: "boolean", description: "Include confirmation in the plan without executing the action." }
|
|
4026
|
+
},
|
|
4027
|
+
required: ["actionId"],
|
|
4028
|
+
additionalProperties: false
|
|
4029
|
+
},
|
|
4030
|
+
handler: async (topolo, args) => {
|
|
4031
|
+
const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
|
|
4032
|
+
if (!actionId) throw new Error("actionId is required.");
|
|
4033
|
+
const input = args["input"];
|
|
4034
|
+
if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
|
|
4035
|
+
throw new Error("input must be an object.");
|
|
4036
|
+
}
|
|
4037
|
+
const resource = optionalResourceContext(args);
|
|
4038
|
+
return topolo.client.planAction(
|
|
4039
|
+
actionId,
|
|
4040
|
+
input ?? {},
|
|
4041
|
+
{
|
|
4042
|
+
confirm: args["confirm"] === true,
|
|
4043
|
+
...resource ? { resource } : {}
|
|
4044
|
+
}
|
|
4045
|
+
);
|
|
4046
|
+
}
|
|
4047
|
+
},
|
|
4048
|
+
{
|
|
4049
|
+
name: "topolo_get_action_examples",
|
|
4050
|
+
title: "Get Topolo action examples and documentation",
|
|
4051
|
+
description: "Returns the public documentation URL and inline examples published for one action.",
|
|
4052
|
+
requiredScopes: [],
|
|
4053
|
+
destructive: false,
|
|
4054
|
+
inputSchema: {
|
|
4055
|
+
type: "object",
|
|
4056
|
+
properties: { actionId: { type: "string", minLength: 1 } },
|
|
4057
|
+
required: ["actionId"],
|
|
4058
|
+
additionalProperties: false
|
|
4059
|
+
},
|
|
4060
|
+
handler: async (topolo, args) => {
|
|
4061
|
+
const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
|
|
4062
|
+
if (!actionId) throw new Error("actionId is required.");
|
|
4063
|
+
const action = await topolo.client.getAction(actionId);
|
|
4064
|
+
return {
|
|
4065
|
+
actionId: action.actionId,
|
|
4066
|
+
docsUrl: action.agentContract?.docsUrl ?? null,
|
|
4067
|
+
examples: action.agentContract?.examples ?? []
|
|
4068
|
+
};
|
|
4069
|
+
}
|
|
4070
|
+
},
|
|
4071
|
+
{
|
|
4072
|
+
name: "topolo_discover_capabilities",
|
|
4073
|
+
title: "Discover one Topolo application capability contract",
|
|
4074
|
+
description: "Discovers capability and schema actions published by one application. Use the actionService value returned by topolo_search_applications as service.",
|
|
4075
|
+
requiredScopes: [],
|
|
4076
|
+
destructive: false,
|
|
4077
|
+
inputSchema: {
|
|
4078
|
+
type: "object",
|
|
4079
|
+
properties: { service: { type: "string", minLength: 1 } },
|
|
4080
|
+
required: ["service"],
|
|
4081
|
+
additionalProperties: false
|
|
4082
|
+
},
|
|
4083
|
+
handler: async (topolo, args) => {
|
|
4084
|
+
const service = typeof args["service"] === "string" ? args["service"].trim() : "";
|
|
4085
|
+
if (!service) throw new Error("service is required.");
|
|
4086
|
+
return topolo.client.searchActions({ service, query: "capabilities", limit: 100 });
|
|
4087
|
+
}
|
|
4088
|
+
},
|
|
4089
|
+
{
|
|
4090
|
+
name: "topolo_get_resource_types",
|
|
4091
|
+
title: "Get application resource-scoping types",
|
|
4092
|
+
description: "Returns the canonical resource-scoping metadata published for one application, including aliases accepted by resource-scoped API keys and action calls.",
|
|
4093
|
+
requiredScopes: [],
|
|
4094
|
+
destructive: false,
|
|
4095
|
+
inputSchema: {
|
|
4096
|
+
type: "object",
|
|
4097
|
+
properties: { application: { type: "string", minLength: 1 } },
|
|
4098
|
+
required: ["application"],
|
|
4099
|
+
additionalProperties: false
|
|
4100
|
+
},
|
|
4101
|
+
handler: async (topolo, args) => {
|
|
4102
|
+
const application = typeof args["application"] === "string" ? args["application"].trim() : "";
|
|
4103
|
+
if (!application) throw new Error("application is required.");
|
|
4104
|
+
const entry = await topolo.client.getService(application);
|
|
4105
|
+
return {
|
|
4106
|
+
application: { appId: entry.appId, slug: entry.slug, name: entry.name },
|
|
4107
|
+
resourceTypes: entry.apiKeyResources
|
|
4108
|
+
};
|
|
4109
|
+
}
|
|
4110
|
+
},
|
|
4111
|
+
{
|
|
4112
|
+
name: "topolo_list_resources",
|
|
4113
|
+
title: "List bindable Topolo resource ids",
|
|
4114
|
+
description: "Returns credential-scoped resource ids for one application. OAuth credentials use the published Auth resource catalog; API keys return only their own bound resources.",
|
|
4115
|
+
requiredScopes: [],
|
|
4116
|
+
destructive: false,
|
|
4117
|
+
inputSchema: {
|
|
4118
|
+
type: "object",
|
|
4119
|
+
properties: { application: { type: "string", minLength: 1 } },
|
|
4120
|
+
required: ["application"],
|
|
4121
|
+
additionalProperties: false
|
|
4122
|
+
},
|
|
4123
|
+
handler: async (topolo, args) => {
|
|
4124
|
+
const application = typeof args["application"] === "string" ? args["application"].trim() : "";
|
|
4125
|
+
if (!application) throw new Error("application is required.");
|
|
4126
|
+
return topolo.client.listBindableResources(application);
|
|
4127
|
+
}
|
|
4128
|
+
},
|
|
3595
4129
|
{
|
|
3596
4130
|
name: "topolo_list_application_requirements",
|
|
3597
4131
|
title: "List Topolo application build requirements",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Model Context Protocol server for the Topolo platform. Exposes scope-gated tools that third-party agents (Claude, Codex, etc.) can call natively.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@topolo/sdk": "^0.
|
|
39
|
+
"@topolo/sdk": "^0.9.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@topolo/sdk": "file:../topolo-sdk",
|