@topolo/mcp 0.7.2 → 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 +142 -0
- package/dist/http.js +142 -0
- package/dist/index.js +528 -9
- package/package.json +2 -2
package/dist/dispatch.js
CHANGED
|
@@ -260,6 +260,148 @@ var TOOLS = [
|
|
|
260
260
|
);
|
|
261
261
|
}
|
|
262
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
|
+
},
|
|
263
405
|
{
|
|
264
406
|
name: "topolo_list_application_requirements",
|
|
265
407
|
title: "List Topolo application build requirements",
|
package/dist/http.js
CHANGED
|
@@ -16068,6 +16068,148 @@ var TOOLS = [
|
|
|
16068
16068
|
);
|
|
16069
16069
|
}
|
|
16070
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
|
+
},
|
|
16071
16213
|
{
|
|
16072
16214
|
name: "topolo_list_application_requirements",
|
|
16073
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
|
|
@@ -2540,10 +2837,44 @@ var TopoloClient = class _TopoloClient {
|
|
|
2540
2837
|
const entry = await this.getAction(action);
|
|
2541
2838
|
return this.executeResolvedAction(entry, input, options);
|
|
2542
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
|
+
}
|
|
2543
2874
|
async readAction(action, input = {}, options = {}) {
|
|
2544
2875
|
const entry = await this.getAction(action);
|
|
2545
2876
|
const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
|
|
2546
|
-
if (
|
|
2877
|
+
if (!entry.readOnly || entry.destructive || entry.requiresConfirmation || agentAccess !== "auto") {
|
|
2547
2878
|
throw new TopoloPermissionError(
|
|
2548
2879
|
`Topolo action "${entry.actionId}" is not an automatic read-only action.`,
|
|
2549
2880
|
entry.requiredPermission ? [entry.requiredPermission] : []
|
|
@@ -2564,6 +2895,13 @@ var TopoloClient = class _TopoloClient {
|
|
|
2564
2895
|
`Topolo action "${entry.actionId}" requires { confirm: true } before invocation.`
|
|
2565
2896
|
);
|
|
2566
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
|
+
}
|
|
2567
2905
|
const mapped = mapActionInput(entry, input);
|
|
2568
2906
|
const service = entry.appSlug || entry.appId;
|
|
2569
2907
|
return this.request({
|
|
@@ -2578,7 +2916,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2578
2916
|
[TOPOLO_ACTION_ID_HEADER]: entry.actionId,
|
|
2579
2917
|
[TOPOLO_ACTION_NAME_HEADER]: entry.name
|
|
2580
2918
|
},
|
|
2581
|
-
confirm: options.confirm === true
|
|
2919
|
+
confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
|
|
2582
2920
|
});
|
|
2583
2921
|
}
|
|
2584
2922
|
/**
|
|
@@ -2635,7 +2973,14 @@ var TopoloClient = class _TopoloClient {
|
|
|
2635
2973
|
id: organization.id,
|
|
2636
2974
|
slug: organization.slug,
|
|
2637
2975
|
name: organization.name ?? organization.slug
|
|
2638
|
-
} : 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
|
|
2639
2984
|
};
|
|
2640
2985
|
}
|
|
2641
2986
|
};
|
|
@@ -2650,7 +2995,7 @@ function resolveConfiguredAppId(slug, appIds) {
|
|
|
2650
2995
|
return null;
|
|
2651
2996
|
}
|
|
2652
2997
|
function normalizeApiKeyResourceMetadata(input) {
|
|
2653
|
-
if (!
|
|
2998
|
+
if (!isRecord2(input)) return null;
|
|
2654
2999
|
const resourceType = readString(input.resourceType ?? input.resource_type);
|
|
2655
3000
|
const label = readString(input.label);
|
|
2656
3001
|
const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
|
|
@@ -2683,10 +3028,38 @@ function normalizeApiKeyResourceMetadataList(input) {
|
|
|
2683
3028
|
}
|
|
2684
3029
|
return resources;
|
|
2685
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
|
+
}
|
|
2686
3055
|
function readString(value) {
|
|
2687
3056
|
return typeof value === "string" ? value.trim() : "";
|
|
2688
3057
|
}
|
|
2689
|
-
function
|
|
3058
|
+
function readOptionalString(value) {
|
|
3059
|
+
const normalized = readString(value);
|
|
3060
|
+
return normalized || null;
|
|
3061
|
+
}
|
|
3062
|
+
function isRecord2(value) {
|
|
2690
3063
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2691
3064
|
}
|
|
2692
3065
|
function resolveConfiguredServiceUrl(slug, serviceUrls) {
|
|
@@ -2758,14 +3131,14 @@ function catalogSearchQuery(options) {
|
|
|
2758
3131
|
return query;
|
|
2759
3132
|
}
|
|
2760
3133
|
function normalizeCatalogPage(value) {
|
|
2761
|
-
if (!
|
|
3134
|
+
if (!isRecord2(value)) return { nextCursor: null, hasMore: false };
|
|
2762
3135
|
return {
|
|
2763
3136
|
nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
|
|
2764
3137
|
hasMore: value.hasMore === true
|
|
2765
3138
|
};
|
|
2766
3139
|
}
|
|
2767
3140
|
function normalizeServiceCatalogEntry(item) {
|
|
2768
|
-
if (!
|
|
3141
|
+
if (!isRecord2(item)) return null;
|
|
2769
3142
|
const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
|
|
2770
3143
|
if (!appId) return null;
|
|
2771
3144
|
const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
|
|
@@ -2820,6 +3193,7 @@ function normalizeActionCatalogEntry(item) {
|
|
|
2820
3193
|
authorizationMode: pickAuthorizationMode(a.authorization_mode ?? a.authorizationMode),
|
|
2821
3194
|
inputSchema: plainObject(a.input_schema ?? a.inputSchema),
|
|
2822
3195
|
outputSchema: plainObject(a.output_schema ?? a.outputSchema),
|
|
3196
|
+
agentContract: plainObject(a.agent_contract ?? a.agentContract),
|
|
2823
3197
|
readOnly: Boolean(a.read_only ?? a.readOnly),
|
|
2824
3198
|
destructive: Boolean(a.destructive),
|
|
2825
3199
|
requiresConfirmation: Boolean(a.requires_confirmation ?? a.requiresConfirmation),
|
|
@@ -2843,6 +3217,9 @@ function pickAuthorizationMode(value) {
|
|
|
2843
3217
|
function plainObject(value) {
|
|
2844
3218
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2845
3219
|
}
|
|
3220
|
+
function stringArray(value) {
|
|
3221
|
+
return Array.isArray(value) ? value.map((item) => String(item || "").trim()).filter(Boolean) : [];
|
|
3222
|
+
}
|
|
2846
3223
|
function mapActionInput(action, input) {
|
|
2847
3224
|
const remaining = { ...input || {} };
|
|
2848
3225
|
const path = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
|
|
@@ -3607,6 +3984,148 @@ var TOOLS = [
|
|
|
3607
3984
|
);
|
|
3608
3985
|
}
|
|
3609
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
|
+
},
|
|
3610
4129
|
{
|
|
3611
4130
|
name: "topolo_list_application_requirements",
|
|
3612
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",
|