@topolo/mcp 0.11.10 → 0.11.12

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/index.js CHANGED
@@ -80,6 +80,7 @@ var ERROR_CODE_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
80
80
  var TOPOLO_REQUEST_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
81
81
  var CLOUDFLARE_RAY_ID_PATTERN = /^[0-9a-f]{16}-[A-Z]{3}$/;
82
82
  var PERMISSION_PATTERN = /^[a-z][a-z0-9_-]{0,63}(?:\.[a-z][a-z0-9_-]{0,63})*:(?:[a-z][a-z0-9_-]{0,63}|\*)$/;
83
+ var PUBLIC_FIELD_PATH_PATTERN = /^[$.a-z0-9_[\]-]{1,160}$/i;
83
84
  var RETRY_AFTER_DATE_PATTERN = /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT$/;
84
85
  var PUBLIC_DETAIL_SOURCES = /* @__PURE__ */ new Set([
85
86
  "api_key_introspection",
@@ -192,7 +193,11 @@ function extractTopoloServiceErrorDetails(body) {
192
193
  if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
193
194
  }
194
195
  if (candidate === void 0) candidate = record;
195
- if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return void 0;
196
+ if (Array.isArray(candidate)) {
197
+ const paths = canonicalTopoloValidationPaths(candidate);
198
+ return paths.length ? { paths } : void 0;
199
+ }
200
+ if (!candidate || typeof candidate !== "object") return void 0;
196
201
  const source = candidate;
197
202
  const safe = {};
198
203
  for (const [key, value] of Object.entries(source)) {
@@ -229,12 +234,42 @@ function extractTopoloServiceErrorDetails(body) {
229
234
  continue;
230
235
  }
231
236
  if (["missingFields", "fields", "paths"].includes(key) && Array.isArray(value)) {
232
- const paths = value.filter((item) => typeof item === "string" && /^[$.a-z0-9_[\]-]{1,160}$/i.test(item));
237
+ const paths = value.filter((item) => typeof item === "string" && PUBLIC_FIELD_PATH_PATTERN.test(item));
233
238
  if (paths.length) safe[key] = paths;
234
239
  }
235
240
  }
236
241
  return Object.keys(safe).length ? safe : void 0;
237
242
  }
243
+ function canonicalTopoloValidationPaths(issues) {
244
+ const paths = [];
245
+ for (const issue of issues) {
246
+ if (!issue || typeof issue !== "object" || Array.isArray(issue)) continue;
247
+ const record = issue;
248
+ const path = canonicalTopoloValidationPath(record.field) ?? canonicalTopoloValidationPath(record.path);
249
+ if (path && !paths.includes(path)) paths.push(path);
250
+ if (paths.length === 32) break;
251
+ }
252
+ return paths;
253
+ }
254
+ function canonicalTopoloValidationPath(value) {
255
+ if (typeof value === "string") {
256
+ const path2 = value.trim();
257
+ return PUBLIC_FIELD_PATH_PATTERN.test(path2) ? path2 : void 0;
258
+ }
259
+ if (!Array.isArray(value) || value.length === 0 || value.length > 32) return void 0;
260
+ const segments = value.map((segment) => {
261
+ if (typeof segment === "number" && Number.isSafeInteger(segment) && segment >= 0) {
262
+ return String(segment);
263
+ }
264
+ if (typeof segment === "string" && /^[a-z0-9_$-]{1,80}$/i.test(segment)) {
265
+ return segment;
266
+ }
267
+ return null;
268
+ });
269
+ if (segments.includes(null)) return void 0;
270
+ const path = segments.join(".");
271
+ return PUBLIC_FIELD_PATH_PATTERN.test(path) ? path : void 0;
272
+ }
238
273
  function publicTopoloErrorMessage(error) {
239
274
  const code = canonicalTopoloErrorCode(
240
275
  typeof error === "string" ? error : error.code ?? extractTopoloServiceErrorCode(error.body)
@@ -1011,16 +1046,29 @@ var TopoloClient = class _TopoloClient {
1011
1046
  clearTimeout(timeoutId);
1012
1047
  }
1013
1048
  };
1049
+ const fetchWithReadRetry = async () => {
1050
+ const maxAttempts = method === "GET" ? 2 : 1;
1051
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
1052
+ try {
1053
+ return await fetchOnce();
1054
+ } catch (error) {
1055
+ const transportFailure = !(error instanceof TopoloSdkError) && !opts.signal?.aborted;
1056
+ if (!transportFailure || attempt === maxAttempts) throw error;
1057
+ await new Promise((resolve2) => setTimeout(resolve2, 250));
1058
+ }
1059
+ }
1060
+ throw new TopoloSdkError("network_error", "Topolo read retry loop exhausted.");
1061
+ };
1014
1062
  let res;
1015
1063
  try {
1016
1064
  const attemptedCredential = this.credential;
1017
- res = await fetchOnce();
1065
+ res = await fetchWithReadRetry();
1018
1066
  if (res.status === 401 && attemptedCredential.kind === "access_token" && this.refreshCredential) {
1019
1067
  await res.body?.cancel().catch(() => void 0);
1020
1068
  const refreshed = await this.refreshRejectedAccessToken(attemptedCredential.accessToken);
1021
1069
  if (refreshed) {
1022
1070
  applyAuthHeaders(headers, refreshed);
1023
- res = await fetchOnce();
1071
+ res = await fetchWithReadRetry();
1024
1072
  }
1025
1073
  }
1026
1074
  } catch (err) {
@@ -1540,7 +1588,9 @@ var TopoloClient = class _TopoloClient {
1540
1588
  };
1541
1589
  }
1542
1590
  planResolvedAction(entry, input, options) {
1543
- const validation = validateTopoloActionInput(entry.inputSchema, input);
1591
+ const inputValidation = validateTopoloActionInput(entry.inputSchema, input);
1592
+ const resourceIssue = validateActionResource(entry, options.resource);
1593
+ const validation = resourceIssue ? { valid: false, errors: [...inputValidation.errors, resourceIssue] } : inputValidation;
1544
1594
  const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
1545
1595
  const confirmationRequired = agentAccess === "confirm" || entry.requiresConfirmation || entry.destructive;
1546
1596
  let request = null;
@@ -1599,6 +1649,13 @@ var TopoloClient = class _TopoloClient {
1599
1649
  `Topolo action "${entry.actionId}" input is invalid: ${validation.errors.map((issue) => `${issue.path} ${issue.message}`).join("; ")}`
1600
1650
  );
1601
1651
  }
1652
+ const resourceIssue = validateActionResource(entry, options.resource);
1653
+ if (resourceIssue) {
1654
+ throw new TopoloSdkError(
1655
+ "invalid_action_resource",
1656
+ `Topolo action "${entry.actionId}" resource context is invalid: ${resourceIssue.message}`
1657
+ );
1658
+ }
1602
1659
  const mapped = mapActionInput(entry, input);
1603
1660
  const service = entry.appSlug || entry.appId;
1604
1661
  const result = await this.request({
@@ -1661,6 +1718,35 @@ var TopoloClient = class _TopoloClient {
1661
1718
  };
1662
1719
  }
1663
1720
  };
1721
+ function validateActionResource(entry, resource) {
1722
+ const permissionName = entry.permissionName || entry.requiredPermission.split(".").at(-1) || "";
1723
+ const declaredContext = entry.agentContract?.resourceContext;
1724
+ const declaredKind = typeof declaredContext?.kind === "string" ? declaredContext.kind.trim() : "";
1725
+ const expectedType = declaredKind || (permissionName.startsWith("workspace:") ? "workspace" : "");
1726
+ if (!expectedType || declaredContext?.required === false && !permissionName.startsWith("workspace:")) return null;
1727
+ if (!resource) {
1728
+ return {
1729
+ path: "$.resource",
1730
+ code: "required",
1731
+ message: `Select resourceType "${expectedType}" with the application resource catalog before planning or calling this action.`
1732
+ };
1733
+ }
1734
+ if (resource.resourceType !== expectedType) {
1735
+ return {
1736
+ path: "$.resource.resourceType",
1737
+ code: "const",
1738
+ message: `This action requires resourceType "${expectedType}".`
1739
+ };
1740
+ }
1741
+ if (!resource.resourceId.trim()) {
1742
+ return {
1743
+ path: "$.resource.resourceId",
1744
+ code: "required",
1745
+ message: `This action requires a ${expectedType} resourceId.`
1746
+ };
1747
+ }
1748
+ return null;
1749
+ }
1664
1750
  function resolveConfiguredAppId(slug, appIds) {
1665
1751
  for (const serviceKey of serviceKeyCandidates2(slug)) {
1666
1752
  const configured = appIds?.[serviceKey];
@@ -2028,7 +2114,8 @@ function compactActionCatalogEntry(action) {
2028
2114
  agentAccess: action.agentAccess ?? (action.requiresConfirmation ? "confirm" : action.readOnly ? "auto" : "confirm"),
2029
2115
  requiredPermission: action.requiredPermission,
2030
2116
  requiredInput: required,
2031
- resourceType: action.agentContract?.resource?.kind ?? null,
2117
+ resourceType: action.agentContract?.resourceContext?.kind ?? (action.permissionName.startsWith("workspace:") ? "workspace" : null),
2118
+ resultResourceType: action.agentContract?.resource?.kind ?? null,
2032
2119
  exampleInput: example.value,
2033
2120
  exampleInputAvailable: example.available,
2034
2121
  docsUrl: action.agentContract?.docsUrl ?? null
@@ -2376,6 +2463,7 @@ var ERROR_CODE_PATTERN2 = /^[a-z][a-z0-9_]{0,63}$/;
2376
2463
  var TOPOLO_REQUEST_ID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
2377
2464
  var CLOUDFLARE_RAY_ID_PATTERN2 = /^[0-9a-f]{16}-[A-Z]{3}$/;
2378
2465
  var PERMISSION_PATTERN2 = /^[a-z][a-z0-9_-]{0,63}(?:\.[a-z][a-z0-9_-]{0,63})*:(?:[a-z][a-z0-9_-]{0,63}|\*)$/;
2466
+ var PUBLIC_FIELD_PATH_PATTERN2 = /^[$.a-z0-9_[\]-]{1,160}$/i;
2379
2467
  var RETRY_AFTER_DATE_PATTERN2 = /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT$/;
2380
2468
  var PUBLIC_DETAIL_SOURCES2 = /* @__PURE__ */ new Set([
2381
2469
  "api_key_introspection",
@@ -2461,7 +2549,11 @@ function extractTopoloServiceErrorDetails2(body) {
2461
2549
  if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
2462
2550
  }
2463
2551
  if (candidate === void 0) candidate = record;
2464
- if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return void 0;
2552
+ if (Array.isArray(candidate)) {
2553
+ const paths = canonicalTopoloValidationPaths2(candidate);
2554
+ return paths.length ? { paths } : void 0;
2555
+ }
2556
+ if (!candidate || typeof candidate !== "object") return void 0;
2465
2557
  const source = candidate;
2466
2558
  const safe = {};
2467
2559
  for (const [key, value] of Object.entries(source)) {
@@ -2498,12 +2590,42 @@ function extractTopoloServiceErrorDetails2(body) {
2498
2590
  continue;
2499
2591
  }
2500
2592
  if (["missingFields", "fields", "paths"].includes(key) && Array.isArray(value)) {
2501
- const paths = value.filter((item) => typeof item === "string" && /^[$.a-z0-9_[\]-]{1,160}$/i.test(item));
2593
+ const paths = value.filter((item) => typeof item === "string" && PUBLIC_FIELD_PATH_PATTERN2.test(item));
2502
2594
  if (paths.length) safe[key] = paths;
2503
2595
  }
2504
2596
  }
2505
2597
  return Object.keys(safe).length ? safe : void 0;
2506
2598
  }
2599
+ function canonicalTopoloValidationPaths2(issues) {
2600
+ const paths = [];
2601
+ for (const issue of issues) {
2602
+ if (!issue || typeof issue !== "object" || Array.isArray(issue)) continue;
2603
+ const record = issue;
2604
+ const path = canonicalTopoloValidationPath2(record.field) ?? canonicalTopoloValidationPath2(record.path);
2605
+ if (path && !paths.includes(path)) paths.push(path);
2606
+ if (paths.length === 32) break;
2607
+ }
2608
+ return paths;
2609
+ }
2610
+ function canonicalTopoloValidationPath2(value) {
2611
+ if (typeof value === "string") {
2612
+ const path2 = value.trim();
2613
+ return PUBLIC_FIELD_PATH_PATTERN2.test(path2) ? path2 : void 0;
2614
+ }
2615
+ if (!Array.isArray(value) || value.length === 0 || value.length > 32) return void 0;
2616
+ const segments = value.map((segment) => {
2617
+ if (typeof segment === "number" && Number.isSafeInteger(segment) && segment >= 0) {
2618
+ return String(segment);
2619
+ }
2620
+ if (typeof segment === "string" && /^[a-z0-9_$-]{1,80}$/i.test(segment)) {
2621
+ return segment;
2622
+ }
2623
+ return null;
2624
+ });
2625
+ if (segments.includes(null)) return void 0;
2626
+ const path = segments.join(".");
2627
+ return PUBLIC_FIELD_PATH_PATTERN2.test(path) ? path : void 0;
2628
+ }
2507
2629
  var TopoloHttpError2 = class extends TopoloSdkError2 {
2508
2630
  status;
2509
2631
  body;
@@ -3012,7 +3134,7 @@ function summarizeActionCapabilities(service, actions) {
3012
3134
  mutating: actions.filter((action) => !action.readOnly).length,
3013
3135
  destructive: actions.filter((action) => action.destructive).length,
3014
3136
  confirmationRequired: actions.filter((action) => action.requiresConfirmation).length,
3015
- resourceTypes: [...new Set(actions.map((action) => action.agentContract?.resource?.kind).filter(Boolean))].sort()
3137
+ resourceTypes: [...new Set(actions.map((action) => action.agentContract?.resourceContext?.kind ?? (action.permissionName.startsWith("workspace:") ? "workspace" : null)).filter(Boolean))].sort()
3016
3138
  };
3017
3139
  }
3018
3140
  function coerceObjectParam(value, name) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@topolo/mcp",
3
- "version": "0.11.10",
4
- "topoloPublishedVersion": "0.11.10",
3
+ "version": "0.11.12",
4
+ "topoloPublishedVersion": "0.11.12",
5
5
  "description": "Model Context Protocol server for the Topolo platform. Exposes scope-gated tools that third-party agents (Claude, Codex, etc.) can call natively.",
6
6
  "license": "UNLICENSED",
7
7
  "author": "Topolo",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@modelcontextprotocol/sdk": "^1.0.0",
38
- "@topolo/sdk": "0.11.10"
38
+ "@topolo/sdk": "0.14.2"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^20.12.0",