@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 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.callAction(
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.3/node_modules/fast-uri/lib/utils.js
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.3/node_modules/fast-uri/lib/utils.js"(exports, module) {
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.3/node_modules/fast-uri/lib/schemes.js
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.3/node_modules/fast-uri/lib/schemes.js"(exports, module) {
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.3/node_modules/fast-uri/index.js
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.3/node_modules/fast-uri/index.js"(exports, module) {
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 = new URL("http://" + parsed.host).hostname;
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.callAction(
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
- return baseUrl ? { serviceKey, baseUrl } : null;
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 (!isRecord(item)) continue;
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 (!isRecord(input)) return null;
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 isRecord(value) {
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 (!isRecord(value)) return { nextCursor: null, hasMore: false };
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 (!isRecord(item)) return null;
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.callAction(
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.7.1",
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.8.1"
39
+ "@topolo/sdk": "^0.9.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@topolo/sdk": "file:../topolo-sdk",