@topolo/mcp 0.9.7 → 0.9.9

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
@@ -271,6 +271,60 @@ var TOOLS = [
271
271
  );
272
272
  }
273
273
  },
274
+ {
275
+ name: "topolo_upload_action",
276
+ title: "Upload content through one Topolo action",
277
+ description: "Uploads base64 content through an exact action id using its published upload contract. Mutating uploads require confirm:true.",
278
+ requiredScopes: [],
279
+ destructive: true,
280
+ inputSchema: {
281
+ type: "object",
282
+ properties: {
283
+ actionId: { type: "string", minLength: 1 },
284
+ contentBase64: { type: "string", minLength: 4, description: "Canonical base64 content without a data URL prefix." },
285
+ contentType: { type: "string", minLength: 1 },
286
+ fileName: { type: "string", minLength: 1 },
287
+ input: { type: "object", additionalProperties: true },
288
+ completionInput: { type: "object", additionalProperties: true },
289
+ resourceType: { type: "string" },
290
+ resourceId: { type: "string" },
291
+ confirm: { type: "boolean" }
292
+ },
293
+ required: ["actionId", "contentBase64", "contentType", "fileName"],
294
+ additionalProperties: false
295
+ },
296
+ handler: async (topolo, args) => {
297
+ const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
298
+ const contentBase64 = typeof args["contentBase64"] === "string" ? args["contentBase64"] : "";
299
+ const contentType = typeof args["contentType"] === "string" ? args["contentType"].trim() : "";
300
+ const fileName = typeof args["fileName"] === "string" ? args["fileName"].trim() : "";
301
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
302
+ if (!isCanonicalBase64(contentBase64)) throw new TopoloMcpPublicError("contentBase64 must be canonical base64.");
303
+ if (!contentType) throw new TopoloMcpPublicError("contentType is required.");
304
+ if (!fileName) throw new TopoloMcpPublicError("fileName is required.");
305
+ const input = args["input"];
306
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
307
+ throw new TopoloMcpPublicError("input must be an object.");
308
+ }
309
+ const completionInput = args["completionInput"];
310
+ if (completionInput !== void 0 && (!completionInput || typeof completionInput !== "object" || Array.isArray(completionInput))) {
311
+ throw new TopoloMcpPublicError("completionInput must be an object.");
312
+ }
313
+ const resource = optionalResourceContext(args);
314
+ return topolo.client.uploadActionBase64(
315
+ actionId,
316
+ contentBase64,
317
+ input ?? {},
318
+ {
319
+ fileName,
320
+ contentType,
321
+ completionInput: completionInput ?? {},
322
+ confirm: args["confirm"] === true,
323
+ ...resource ? { resource } : {}
324
+ }
325
+ );
326
+ }
327
+ },
274
328
  {
275
329
  name: "topolo_validate_action",
276
330
  title: "Validate Topolo action input",
@@ -358,7 +412,7 @@ var TOOLS = [
358
412
  {
359
413
  name: "topolo_discover_capabilities",
360
414
  title: "Discover one Topolo application capability contract",
361
- description: "Discovers capability and schema actions published by one application. Use the actionService value returned by topolo_search_applications as service.",
415
+ description: "Lists every action published by one application \u2014 its full capability contract, straight from the registry-canonical catalog. Use the actionService value returned by topolo_search_applications as service. Follow page.nextCursor via topolo_search_actions when the contract exceeds one page.",
362
416
  requiredScopes: [],
363
417
  destructive: false,
364
418
  inputSchema: {
@@ -370,7 +424,7 @@ var TOOLS = [
370
424
  handler: async (topolo, args) => {
371
425
  const service = typeof args["service"] === "string" ? args["service"].trim() : "";
372
426
  if (!service) throw new TopoloMcpPublicError("service is required.");
373
- return topolo.client.searchActions({ service, query: "capabilities", limit: 100 });
427
+ return topolo.client.searchActions({ service, limit: 100 });
374
428
  }
375
429
  },
376
430
  {
@@ -574,6 +628,9 @@ var TOOLS = [
574
628
  }
575
629
  }
576
630
  ];
631
+ function isCanonicalBase64(value) {
632
+ return value.length > 0 && value.length % 4 === 0 && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
633
+ }
577
634
  function filterToolsByScopes(tools, scopes) {
578
635
  return tools.filter((t) => hasAnyScope(scopes, t.requiredScopes));
579
636
  }
package/dist/http.js CHANGED
@@ -15824,7 +15824,7 @@ var Server = class extends Protocol {
15824
15824
  };
15825
15825
 
15826
15826
  // src/server.ts
15827
- import { TopoloAuthError, TopoloPermissionError } from "@topolo/sdk";
15827
+ import { TopoloAuthError, TopoloHttpError, TopoloPermissionError } from "@topolo/sdk";
15828
15828
 
15829
15829
  // src/tools.ts
15830
15830
  import {
@@ -16085,6 +16085,60 @@ var TOOLS = [
16085
16085
  );
16086
16086
  }
16087
16087
  },
16088
+ {
16089
+ name: "topolo_upload_action",
16090
+ title: "Upload content through one Topolo action",
16091
+ description: "Uploads base64 content through an exact action id using its published upload contract. Mutating uploads require confirm:true.",
16092
+ requiredScopes: [],
16093
+ destructive: true,
16094
+ inputSchema: {
16095
+ type: "object",
16096
+ properties: {
16097
+ actionId: { type: "string", minLength: 1 },
16098
+ contentBase64: { type: "string", minLength: 4, description: "Canonical base64 content without a data URL prefix." },
16099
+ contentType: { type: "string", minLength: 1 },
16100
+ fileName: { type: "string", minLength: 1 },
16101
+ input: { type: "object", additionalProperties: true },
16102
+ completionInput: { type: "object", additionalProperties: true },
16103
+ resourceType: { type: "string" },
16104
+ resourceId: { type: "string" },
16105
+ confirm: { type: "boolean" }
16106
+ },
16107
+ required: ["actionId", "contentBase64", "contentType", "fileName"],
16108
+ additionalProperties: false
16109
+ },
16110
+ handler: async (topolo, args) => {
16111
+ const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
16112
+ const contentBase64 = typeof args["contentBase64"] === "string" ? args["contentBase64"] : "";
16113
+ const contentType = typeof args["contentType"] === "string" ? args["contentType"].trim() : "";
16114
+ const fileName = typeof args["fileName"] === "string" ? args["fileName"].trim() : "";
16115
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
16116
+ if (!isCanonicalBase64(contentBase64)) throw new TopoloMcpPublicError("contentBase64 must be canonical base64.");
16117
+ if (!contentType) throw new TopoloMcpPublicError("contentType is required.");
16118
+ if (!fileName) throw new TopoloMcpPublicError("fileName is required.");
16119
+ const input = args["input"];
16120
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
16121
+ throw new TopoloMcpPublicError("input must be an object.");
16122
+ }
16123
+ const completionInput = args["completionInput"];
16124
+ if (completionInput !== void 0 && (!completionInput || typeof completionInput !== "object" || Array.isArray(completionInput))) {
16125
+ throw new TopoloMcpPublicError("completionInput must be an object.");
16126
+ }
16127
+ const resource = optionalResourceContext(args);
16128
+ return topolo.client.uploadActionBase64(
16129
+ actionId,
16130
+ contentBase64,
16131
+ input ?? {},
16132
+ {
16133
+ fileName,
16134
+ contentType,
16135
+ completionInput: completionInput ?? {},
16136
+ confirm: args["confirm"] === true,
16137
+ ...resource ? { resource } : {}
16138
+ }
16139
+ );
16140
+ }
16141
+ },
16088
16142
  {
16089
16143
  name: "topolo_validate_action",
16090
16144
  title: "Validate Topolo action input",
@@ -16172,7 +16226,7 @@ var TOOLS = [
16172
16226
  {
16173
16227
  name: "topolo_discover_capabilities",
16174
16228
  title: "Discover one Topolo application capability contract",
16175
- description: "Discovers capability and schema actions published by one application. Use the actionService value returned by topolo_search_applications as service.",
16229
+ description: "Lists every action published by one application \u2014 its full capability contract, straight from the registry-canonical catalog. Use the actionService value returned by topolo_search_applications as service. Follow page.nextCursor via topolo_search_actions when the contract exceeds one page.",
16176
16230
  requiredScopes: [],
16177
16231
  destructive: false,
16178
16232
  inputSchema: {
@@ -16184,7 +16238,7 @@ var TOOLS = [
16184
16238
  handler: async (topolo, args) => {
16185
16239
  const service = typeof args["service"] === "string" ? args["service"].trim() : "";
16186
16240
  if (!service) throw new TopoloMcpPublicError("service is required.");
16187
- return topolo.client.searchActions({ service, query: "capabilities", limit: 100 });
16241
+ return topolo.client.searchActions({ service, limit: 100 });
16188
16242
  }
16189
16243
  },
16190
16244
  {
@@ -16388,6 +16442,9 @@ var TOOLS = [
16388
16442
  }
16389
16443
  }
16390
16444
  ];
16445
+ function isCanonicalBase64(value) {
16446
+ return value.length > 0 && value.length % 4 === 0 && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
16447
+ }
16391
16448
  function filterToolsByScopes(tools, scopes) {
16392
16449
  return tools.filter((t) => hasAnyScope(scopes, t.requiredScopes));
16393
16450
  }
@@ -16525,16 +16582,36 @@ function buildServer(topolo, scopes, tools = TOOLS) {
16525
16582
  };
16526
16583
  } catch (err) {
16527
16584
  if (err instanceof TopoloPermissionError) {
16528
- return toolError(`Permission denied. Required: ${err.required.join(", ") || "(unknown)"}`);
16585
+ return toolStructuredError(err, `Permission denied. Required: ${err.required.join(", ") || "(unknown)"}`, {
16586
+ required: err.required
16587
+ });
16529
16588
  }
16530
16589
  if (err instanceof TopoloAuthError) {
16531
- return toolError("Authentication failed.");
16590
+ return toolStructuredError(err, "Authentication failed.");
16591
+ }
16592
+ if (err instanceof TopoloHttpError) {
16593
+ return toolStructuredError(err, err.message);
16532
16594
  }
16533
16595
  return toolError(publicMcpErrorMessage(err, "Tool execution failed."));
16534
16596
  }
16535
16597
  });
16536
16598
  return server;
16537
16599
  }
16600
+ function toolStructuredError(error2, message, extra = {}) {
16601
+ return toolError(JSON.stringify({
16602
+ error: {
16603
+ code: error2.code,
16604
+ status: error2.status,
16605
+ message,
16606
+ service: error2.service,
16607
+ path: error2.path,
16608
+ requestId: error2.requestId,
16609
+ retryAfter: error2.retryAfter,
16610
+ details: error2.body,
16611
+ ...extra
16612
+ }
16613
+ }, null, 2));
16614
+ }
16538
16615
  function toolError(message) {
16539
16616
  return {
16540
16617
  isError: true,
package/dist/index.js CHANGED
@@ -31,19 +31,54 @@ var TopoloSdkError = class extends Error {
31
31
  }
32
32
  };
33
33
  var TopoloAuthError = class extends TopoloSdkError {
34
- constructor(message, code = "auth_error") {
34
+ status;
35
+ body;
36
+ service;
37
+ path;
38
+ requestId;
39
+ retryAfter;
40
+ constructor(message, code = "auth_error", options = {}) {
35
41
  super(code, message);
36
42
  this.name = "TopoloAuthError";
43
+ this.status = options.status ?? 401;
44
+ this.body = options.body;
45
+ this.service = options.service;
46
+ this.path = options.path;
47
+ this.requestId = options.requestId;
48
+ this.retryAfter = options.retryAfter;
37
49
  }
38
50
  };
39
51
  var TopoloPermissionError = class extends TopoloSdkError {
40
52
  required;
41
- constructor(message, required) {
53
+ status;
54
+ body;
55
+ service;
56
+ path;
57
+ requestId;
58
+ retryAfter;
59
+ constructor(message, required, options = {}) {
42
60
  super("permission_denied", message);
43
61
  this.name = "TopoloPermissionError";
44
62
  this.required = required;
63
+ this.status = options.status ?? 403;
64
+ this.body = options.body;
65
+ this.service = options.service;
66
+ this.path = options.path;
67
+ this.requestId = options.requestId;
68
+ this.retryAfter = options.retryAfter;
45
69
  }
46
70
  };
71
+ function extractHttpErrorCode(body) {
72
+ if (!body || typeof body !== "object" || Array.isArray(body)) return "http_error";
73
+ const record = body;
74
+ if (typeof record.code === "string" && record.code.trim()) return record.code.trim();
75
+ const nested = record.error;
76
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
77
+ const code = nested.code;
78
+ if (typeof code === "string" && code.trim()) return code.trim();
79
+ }
80
+ return "http_error";
81
+ }
47
82
  var TopoloConfirmationRequiredError = class extends TopoloSdkError {
48
83
  constructor(message) {
49
84
  super("confirmation_required", message);
@@ -55,19 +90,24 @@ var TopoloHttpError = class extends TopoloSdkError {
55
90
  body;
56
91
  service;
57
92
  path;
58
- constructor(service, path, status, body, message) {
59
- super("http_error", message ?? `HTTP ${status} from ${service}${path}`);
93
+ requestId;
94
+ retryAfter;
95
+ constructor(service, path, status, body, message, options = {}) {
96
+ super(extractHttpErrorCode(body), message ?? `HTTP ${status} from ${service}${path}`);
60
97
  this.name = "TopoloHttpError";
61
98
  this.status = status;
62
99
  this.body = body;
63
100
  this.service = service;
64
101
  this.path = path;
102
+ this.requestId = options.requestId;
103
+ this.retryAfter = options.retryAfter;
65
104
  }
66
105
  };
67
106
  var APP_API_URLS = {
68
107
  agent: "https://agent.topolo.app",
69
108
  auth: "https://auth.topolo.app",
70
109
  backup: "https://backup.topolo.app",
110
+ blog: "https://blog.topolo.app",
71
111
  books: "https://books.topolo.app",
72
112
  brand: "https://brand.topolo.app",
73
113
  bugfix: "https://bugfix.topolo.app",
@@ -80,6 +120,7 @@ var APP_API_URLS = {
80
120
  compose: "https://compose.topolo.app",
81
121
  consent: "https://consent.topolo.app",
82
122
  crm: "https://crm.topolo.app",
123
+ design: "https://design.topolo.app",
83
124
  developers: "https://developers.topolo.app",
84
125
  director: "https://director.topolo.app",
85
126
  feed: "https://feed.topolo.app",
@@ -163,7 +204,9 @@ var APPLICATIONS = {
163
204
  "domains": [
164
205
  "agent.topolo.app"
165
206
  ],
166
- "routes": [],
207
+ "routes": [
208
+ "agent.topolo.app"
209
+ ],
167
210
  "appId": "agent"
168
211
  },
169
212
  {
@@ -224,6 +267,38 @@ var APPLICATIONS = {
224
267
  }
225
268
  ]
226
269
  },
270
+ "blog": {
271
+ "id": "blog",
272
+ "appDir": "TopoloBlog",
273
+ "name": "topolo-blog",
274
+ "packageName": "topolo-blog",
275
+ "productionUrl": "https://blog.topolo.app",
276
+ "services": [
277
+ "blog"
278
+ ],
279
+ "deployTargets": [
280
+ {
281
+ "name": "topolo-blog",
282
+ "kind": "worker",
283
+ "apiUrl": "https://blog.topolo.app",
284
+ "domains": [
285
+ "blog.topolo.app"
286
+ ],
287
+ "routes": [
288
+ "blog.topolo.app"
289
+ ],
290
+ "appId": "blog"
291
+ },
292
+ {
293
+ "name": "topolo-blog-api",
294
+ "kind": "worker",
295
+ "apiUrl": null,
296
+ "domains": [],
297
+ "routes": [],
298
+ "appId": null
299
+ }
300
+ ]
301
+ },
227
302
  "books": {
228
303
  "id": "books",
229
304
  "appDir": "TopoloBooks",
@@ -241,7 +316,9 @@ var APPLICATIONS = {
241
316
  "domains": [
242
317
  "books.topolo.app"
243
318
  ],
244
- "routes": [],
319
+ "routes": [
320
+ "books.topolo.app"
321
+ ],
245
322
  "appId": "books"
246
323
  },
247
324
  {
@@ -271,7 +348,9 @@ var APPLICATIONS = {
271
348
  "domains": [
272
349
  "brand.topolo.app"
273
350
  ],
274
- "routes": [],
351
+ "routes": [
352
+ "brand.topolo.app"
353
+ ],
275
354
  "appId": "brand"
276
355
  },
277
356
  {
@@ -389,7 +468,9 @@ var APPLICATIONS = {
389
468
  "domains": [
390
469
  "campaigns.topolo.app"
391
470
  ],
392
- "routes": [],
471
+ "routes": [
472
+ "campaigns.topolo.app"
473
+ ],
393
474
  "appId": "campaigns"
394
475
  },
395
476
  {
@@ -443,7 +524,9 @@ var APPLICATIONS = {
443
524
  "domains": [
444
525
  "chat.topolo.app"
445
526
  ],
446
- "routes": [],
527
+ "routes": [
528
+ "chat.topolo.app"
529
+ ],
447
530
  "appId": "chat"
448
531
  },
449
532
  {
@@ -529,7 +612,9 @@ var APPLICATIONS = {
529
612
  "domains": [
530
613
  "compose.topolo.app"
531
614
  ],
532
- "routes": [],
615
+ "routes": [
616
+ "compose.topolo.app"
617
+ ],
533
618
  "appId": "compose"
534
619
  },
535
620
  {
@@ -559,7 +644,9 @@ var APPLICATIONS = {
559
644
  "domains": [
560
645
  "consent.topolo.app"
561
646
  ],
562
- "routes": [],
647
+ "routes": [
648
+ "consent.topolo.app"
649
+ ],
563
650
  "appId": "consent"
564
651
  },
565
652
  {
@@ -597,11 +684,45 @@ var APPLICATIONS = {
597
684
  "domains": [
598
685
  "crm.topolo.app"
599
686
  ],
600
- "routes": [],
687
+ "routes": [
688
+ "crm.topolo.app"
689
+ ],
601
690
  "appId": "crm"
602
691
  }
603
692
  ]
604
693
  },
694
+ "design": {
695
+ "id": "design",
696
+ "appDir": "TopoloDesign",
697
+ "name": "topolo-design",
698
+ "packageName": "@topolo-io/design",
699
+ "productionUrl": "https://design.topolo.app",
700
+ "services": [
701
+ "design"
702
+ ],
703
+ "deployTargets": [
704
+ {
705
+ "name": "topolo-design",
706
+ "kind": "worker",
707
+ "apiUrl": "https://design.topolo.app",
708
+ "domains": [
709
+ "design.topolo.app"
710
+ ],
711
+ "routes": [
712
+ "design.topolo.app"
713
+ ],
714
+ "appId": "design"
715
+ },
716
+ {
717
+ "name": "topolo-design-api",
718
+ "kind": "worker",
719
+ "apiUrl": null,
720
+ "domains": [],
721
+ "routes": [],
722
+ "appId": null
723
+ }
724
+ ]
725
+ },
605
726
  "developers": {
606
727
  "id": "developers",
607
728
  "appDir": "TopoloDevelopers",
@@ -627,7 +748,9 @@ var APPLICATIONS = {
627
748
  "domains": [
628
749
  "developers.topolo.app"
629
750
  ],
630
- "routes": [],
751
+ "routes": [
752
+ "developers.topolo.app"
753
+ ],
631
754
  "appId": "developers"
632
755
  },
633
756
  {
@@ -657,7 +780,9 @@ var APPLICATIONS = {
657
780
  "domains": [
658
781
  "director.topolo.app"
659
782
  ],
660
- "routes": [],
783
+ "routes": [
784
+ "director.topolo.app"
785
+ ],
661
786
  "appId": "director"
662
787
  },
663
788
  {
@@ -765,7 +890,9 @@ var APPLICATIONS = {
765
890
  "domains": [
766
891
  "flow.topolo.app"
767
892
  ],
768
- "routes": [],
893
+ "routes": [
894
+ "flow.topolo.app"
895
+ ],
769
896
  "appId": "flow"
770
897
  },
771
898
  {
@@ -795,7 +922,9 @@ var APPLICATIONS = {
795
922
  "domains": [
796
923
  "forecast.topolo.app"
797
924
  ],
798
- "routes": [],
925
+ "routes": [
926
+ "forecast.topolo.app"
927
+ ],
799
928
  "appId": "forecast"
800
929
  },
801
930
  {
@@ -825,7 +954,9 @@ var APPLICATIONS = {
825
954
  "domains": [
826
955
  "forms.topolo.app"
827
956
  ],
828
- "routes": [],
957
+ "routes": [
958
+ "forms.topolo.app"
959
+ ],
829
960
  "appId": "forms"
830
961
  },
831
962
  {
@@ -887,7 +1018,9 @@ var APPLICATIONS = {
887
1018
  "domains": [
888
1019
  "insights.topolo.app"
889
1020
  ],
890
- "routes": [],
1021
+ "routes": [
1022
+ "insights.topolo.app"
1023
+ ],
891
1024
  "appId": "insights"
892
1025
  },
893
1026
  {
@@ -917,7 +1050,9 @@ var APPLICATIONS = {
917
1050
  "domains": [
918
1051
  "inventory.topolo.app"
919
1052
  ],
920
- "routes": [],
1053
+ "routes": [
1054
+ "inventory.topolo.app"
1055
+ ],
921
1056
  "appId": "inventory"
922
1057
  },
923
1058
  {
@@ -1003,7 +1138,9 @@ var APPLICATIONS = {
1003
1138
  "domains": [
1004
1139
  "mail.topolo.app"
1005
1140
  ],
1006
- "routes": [],
1141
+ "routes": [
1142
+ "mail.topolo.app"
1143
+ ],
1007
1144
  "appId": "mail"
1008
1145
  },
1009
1146
  {
@@ -1269,7 +1406,9 @@ var APPLICATIONS = {
1269
1406
  "domains": [
1270
1407
  "pay.topolo.app"
1271
1408
  ],
1272
- "routes": [],
1409
+ "routes": [
1410
+ "pay.topolo.app"
1411
+ ],
1273
1412
  "appId": "pay"
1274
1413
  },
1275
1414
  {
@@ -1299,7 +1438,9 @@ var APPLICATIONS = {
1299
1438
  "domains": [
1300
1439
  "people.topolo.app"
1301
1440
  ],
1302
- "routes": [],
1441
+ "routes": [
1442
+ "people.topolo.app"
1443
+ ],
1303
1444
  "appId": "people"
1304
1445
  },
1305
1446
  {
@@ -1354,7 +1495,9 @@ var APPLICATIONS = {
1354
1495
  "domains": [
1355
1496
  "quro.topolo.app"
1356
1497
  ],
1357
- "routes": [],
1498
+ "routes": [
1499
+ "quro.topolo.app"
1500
+ ],
1358
1501
  "appId": null
1359
1502
  }
1360
1503
  ]
@@ -1376,7 +1519,9 @@ var APPLICATIONS = {
1376
1519
  "domains": [
1377
1520
  "roadmapper.topolo.app"
1378
1521
  ],
1379
- "routes": [],
1522
+ "routes": [
1523
+ "roadmapper.topolo.app"
1524
+ ],
1380
1525
  "appId": "roadmapper"
1381
1526
  },
1382
1527
  {
@@ -1406,7 +1551,9 @@ var APPLICATIONS = {
1406
1551
  "domains": [
1407
1552
  "sign.topolo.app"
1408
1553
  ],
1409
- "routes": [],
1554
+ "routes": [
1555
+ "sign.topolo.app"
1556
+ ],
1410
1557
  "appId": "sign"
1411
1558
  },
1412
1559
  {
@@ -1520,7 +1667,9 @@ var APPLICATIONS = {
1520
1667
  "domains": [
1521
1668
  "spaces.topolo.app"
1522
1669
  ],
1523
- "routes": [],
1670
+ "routes": [
1671
+ "spaces.topolo.app"
1672
+ ],
1524
1673
  "appId": "spaces"
1525
1674
  },
1526
1675
  {
@@ -1638,7 +1787,9 @@ var APPLICATIONS = {
1638
1787
  "domains": [
1639
1788
  "survey.topolo.app"
1640
1789
  ],
1641
- "routes": [],
1790
+ "routes": [
1791
+ "survey.topolo.app"
1792
+ ],
1642
1793
  "appId": "survey"
1643
1794
  },
1644
1795
  {
@@ -2429,6 +2580,166 @@ function isMultipleOf(value, divisor) {
2429
2580
  const quotient = value / divisor;
2430
2581
  return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4;
2431
2582
  }
2583
+ function parseTopoloActionUploadContract(value) {
2584
+ if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
2585
+ throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
2586
+ }
2587
+ const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
2588
+ const fileNameField = requiredField(value.fileNameField, "fileNameField");
2589
+ const maxBytes = Number(value.maxBytes);
2590
+ const acceptedContentTypes = Array.isArray(value.acceptedContentTypes) ? value.acceptedContentTypes.map((item) => String(item).trim().toLowerCase()).filter(Boolean) : [];
2591
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || acceptedContentTypes.length === 0) {
2592
+ throw new TopoloSdkError("invalid_upload_contract", "The action upload contract must declare maxBytes and acceptedContentTypes.");
2593
+ }
2594
+ if (value.profile === "inline-base64") {
2595
+ return {
2596
+ profile: "inline-base64",
2597
+ contentField: requiredField(value.contentField, "contentField"),
2598
+ contentTypeField,
2599
+ fileNameField,
2600
+ maxBytes,
2601
+ acceptedContentTypes
2602
+ };
2603
+ }
2604
+ const uploadMethod = value.uploadMethod === "PUT" || value.uploadMethod === "POST" ? value.uploadMethod : null;
2605
+ const uploadUrlPath = requiredPath(value.uploadUrlPath, "uploadUrlPath");
2606
+ const complete = value.complete === void 0 ? void 0 : parseCompleteContract(value.complete);
2607
+ if (!uploadMethod) throw new TopoloSdkError("invalid_upload_contract", "The action upload contract has an invalid uploadMethod.");
2608
+ return {
2609
+ profile: "url-session",
2610
+ fileNameField,
2611
+ contentTypeField,
2612
+ sizeField: requiredField(value.sizeField, "sizeField"),
2613
+ maxBytes,
2614
+ acceptedContentTypes,
2615
+ uploadUrlPath,
2616
+ uploadMethod,
2617
+ ...complete ? { complete } : {}
2618
+ };
2619
+ }
2620
+ function prepareTopoloActionUploadInput(contractValue, content, input, options) {
2621
+ const contract = parseTopoloActionUploadContract(contractValue);
2622
+ if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
2623
+ const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
2624
+ if (bytes.byteLength === 0) {
2625
+ throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
2626
+ }
2627
+ if (bytes.byteLength > contract.maxBytes) {
2628
+ throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
2629
+ }
2630
+ return mapUploadInput(contract, bytesToBase64(bytes), input, options);
2631
+ }
2632
+ function prepareTopoloActionBase64UploadInput(contractValue, contentBase64, input, options) {
2633
+ const contract = parseTopoloActionUploadContract(contractValue);
2634
+ if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
2635
+ if (!isCanonicalBase64(contentBase64)) {
2636
+ throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
2637
+ }
2638
+ const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
2639
+ const byteLength = contentBase64.length / 4 * 3 - padding;
2640
+ if (byteLength === 0) throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
2641
+ if (byteLength > contract.maxBytes) {
2642
+ throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
2643
+ }
2644
+ return mapUploadInput(contract, contentBase64, input, options);
2645
+ }
2646
+ function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input, options) {
2647
+ const contract = parseTopoloActionUploadContract(contractValue);
2648
+ if (contract.profile !== "url-session") {
2649
+ throw new TopoloSdkError("invalid_upload_contract", "This action does not publish a URL-session upload contract.");
2650
+ }
2651
+ validateUploadMetadata(contract, byteLength, options);
2652
+ return {
2653
+ contract,
2654
+ input: {
2655
+ ...input,
2656
+ [contract.fileNameField]: options.fileName.trim(),
2657
+ [contract.contentTypeField]: options.contentType.trim().toLowerCase(),
2658
+ [contract.sizeField]: byteLength
2659
+ }
2660
+ };
2661
+ }
2662
+ function topoloUploadResponseValue(response, path) {
2663
+ return path.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
2664
+ }
2665
+ function decodeTopoloActionBase64(contentBase64) {
2666
+ if (!isCanonicalBase64(contentBase64)) {
2667
+ throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
2668
+ }
2669
+ const binary = atob(contentBase64);
2670
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
2671
+ }
2672
+ function mapUploadInput(contract, contentBase64, input, options) {
2673
+ validateUploadMetadata(contract, Math.max(1, decodedBase64Size(contentBase64)), options);
2674
+ const fileName = options.fileName.trim();
2675
+ const contentType = options.contentType.trim().toLowerCase();
2676
+ return {
2677
+ ...input,
2678
+ [contract.contentField]: contentBase64,
2679
+ [contract.contentTypeField]: contentType,
2680
+ [contract.fileNameField]: fileName
2681
+ };
2682
+ }
2683
+ function validateUploadMetadata(contract, byteLength, options) {
2684
+ const fileName = options.fileName.trim();
2685
+ const contentType = options.contentType.trim().toLowerCase();
2686
+ if (!fileName) throw new TopoloSdkError("invalid_upload_file_name", "Upload fileName must not be empty.");
2687
+ if (!Number.isSafeInteger(byteLength) || byteLength <= 0) {
2688
+ throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
2689
+ }
2690
+ if (byteLength > contract.maxBytes) {
2691
+ throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
2692
+ }
2693
+ if (!matchesContentType(contentType, contract.acceptedContentTypes)) {
2694
+ throw new TopoloSdkError("unsupported_upload_content_type", `Content type "${contentType}" is not accepted by this action.`);
2695
+ }
2696
+ }
2697
+ function decodedBase64Size(contentBase64) {
2698
+ const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
2699
+ return contentBase64.length / 4 * 3 - padding;
2700
+ }
2701
+ function parseCompleteContract(value) {
2702
+ if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
2703
+ return {
2704
+ actionId: requiredPath(value.actionId, "complete.actionId"),
2705
+ inputField: requiredField(value.inputField, "complete.inputField"),
2706
+ responsePath: requiredPath(value.responsePath, "complete.responsePath")
2707
+ };
2708
+ }
2709
+ function requiredPath(value, name) {
2710
+ const path = typeof value === "string" ? value.trim() : "";
2711
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(path)) {
2712
+ throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
2713
+ }
2714
+ return path;
2715
+ }
2716
+ function unsupportedInlineProfile() {
2717
+ return new TopoloSdkError("invalid_upload_contract", "This action does not publish an inline-base64 upload contract.");
2718
+ }
2719
+ function isCanonicalBase64(value) {
2720
+ return value.length > 0 && value.length % 4 === 0 && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
2721
+ }
2722
+ function requiredField(value, name) {
2723
+ const field = typeof value === "string" ? value.trim() : "";
2724
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(field)) {
2725
+ throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
2726
+ }
2727
+ return field;
2728
+ }
2729
+ function matchesContentType(contentType, accepted) {
2730
+ return accepted.some((candidate) => candidate === "*/*" || candidate === contentType || candidate.endsWith("/*") && contentType.startsWith(candidate.slice(0, -1)));
2731
+ }
2732
+ function bytesToBase64(bytes) {
2733
+ let binary = "";
2734
+ const chunkSize = 32768;
2735
+ for (let offset = 0; offset < bytes.length; offset += chunkSize) {
2736
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
2737
+ }
2738
+ return btoa(binary);
2739
+ }
2740
+ function isRecord2(value) {
2741
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2742
+ }
2432
2743
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
2433
2744
  var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
2434
2745
  var TOPOLO_ACTION_NAME_HEADER = "X-Topolo-Action-Name";
@@ -2505,6 +2816,9 @@ var TopoloClient = class _TopoloClient {
2505
2816
  if (v !== void 0) url.searchParams.set(k, String(v));
2506
2817
  }
2507
2818
  }
2819
+ if (opts.body !== void 0 && opts.rawBody !== void 0) {
2820
+ throw new TopoloSdkError("invalid_request_body", "body and rawBody are mutually exclusive.");
2821
+ }
2508
2822
  const headers = new Headers();
2509
2823
  headers.set("Accept", "application/json");
2510
2824
  if (opts.body !== void 0) headers.set("Content-Type", "application/json");
@@ -2539,7 +2853,7 @@ var TopoloClient = class _TopoloClient {
2539
2853
  return await this.fetchImpl(urlStr, {
2540
2854
  method,
2541
2855
  headers: new Headers(headers),
2542
- body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
2856
+ body: opts.rawBody ?? (opts.body !== void 0 ? JSON.stringify(opts.body) : null),
2543
2857
  signal
2544
2858
  });
2545
2859
  } finally {
@@ -2587,14 +2901,32 @@ var TopoloClient = class _TopoloClient {
2587
2901
  error: message,
2588
2902
  status: res.status
2589
2903
  });
2590
- if (res.status === 401) throw new TopoloAuthError(describeError(parsed, "Unauthorized"));
2904
+ const errorContext = {
2905
+ status: res.status,
2906
+ body: parsed,
2907
+ service: resolvedService.serviceKey,
2908
+ path: opts.path,
2909
+ requestId: res.headers.get("X-Topolo-Request-Id") || requestId,
2910
+ retryAfter: res.headers.get("Retry-After") || void 0
2911
+ };
2912
+ if (res.status === 401) {
2913
+ throw new TopoloAuthError(describeError(parsed, "Unauthorized"), "auth_error", errorContext);
2914
+ }
2591
2915
  if (res.status === 403) {
2592
2916
  throw new TopoloPermissionError(
2593
2917
  describeError(parsed, "Permission denied"),
2594
- extractRequired(parsed)
2918
+ extractRequired(parsed),
2919
+ errorContext
2595
2920
  );
2596
2921
  }
2597
- throw new TopoloHttpError(resolvedService.serviceKey, opts.path, res.status, parsed);
2922
+ throw new TopoloHttpError(
2923
+ resolvedService.serviceKey,
2924
+ opts.path,
2925
+ res.status,
2926
+ parsed,
2927
+ message,
2928
+ errorContext
2929
+ );
2598
2930
  }
2599
2931
  this.emit({
2600
2932
  phase: "response",
@@ -2753,7 +3085,7 @@ var TopoloClient = class _TopoloClient {
2753
3085
  }
2754
3086
  const entries = [];
2755
3087
  for (const item of raw) {
2756
- if (!isRecord2(item)) continue;
3088
+ if (!isRecord3(item)) continue;
2757
3089
  const s = item;
2758
3090
  const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
2759
3091
  if (!appId) continue;
@@ -2828,8 +3160,8 @@ var TopoloClient = class _TopoloClient {
2828
3160
  "app_topolo_auth.service_api_key_resources.list",
2829
3161
  { appId: service.appId }
2830
3162
  );
2831
- const payload = isRecord2(response) && isRecord2(response.data) ? response.data : response;
2832
- if (!isRecord2(payload) || !Array.isArray(payload.resources)) {
3163
+ const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
3164
+ if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
2833
3165
  throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
2834
3166
  }
2835
3167
  return {
@@ -2935,6 +3267,71 @@ var TopoloClient = class _TopoloClient {
2935
3267
  const entry = await this.getAction(action);
2936
3268
  return this.executeResolvedAction(entry, input, options);
2937
3269
  }
3270
+ async uploadAction(action, content, input, options) {
3271
+ const entry = await this.getAction(action);
3272
+ const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
3273
+ if (contract.profile === "url-session") {
3274
+ const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
3275
+ return this.executeUrlSessionUpload(entry, contract, bytes, input, options);
3276
+ }
3277
+ const uploadInput = prepareTopoloActionUploadInput(entry.agentContract?.upload, content, input, options);
3278
+ return this.executeResolvedAction(entry, uploadInput, options);
3279
+ }
3280
+ async uploadActionBase64(action, contentBase64, input, options) {
3281
+ const entry = await this.getAction(action);
3282
+ const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
3283
+ if (contract.profile === "url-session") {
3284
+ return this.executeUrlSessionUpload(entry, contract, decodeTopoloActionBase64(contentBase64), input, options);
3285
+ }
3286
+ const uploadInput = prepareTopoloActionBase64UploadInput(entry.agentContract?.upload, contentBase64, input, options);
3287
+ return this.executeResolvedAction(entry, uploadInput, options);
3288
+ }
3289
+ async executeUrlSessionUpload(entry, contract, bytes, input, options) {
3290
+ const prepared = prepareTopoloUrlSessionUploadInput(contract, bytes.byteLength, input, options);
3291
+ const initiation = await this.executeResolvedAction(entry, prepared.input, options);
3292
+ const uploadUrl = topoloUploadResponseValue(initiation, contract.uploadUrlPath);
3293
+ if (typeof uploadUrl !== "string" || !uploadUrl.trim()) {
3294
+ throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.uploadUrlPath}.`);
3295
+ }
3296
+ await this.putUrlSessionContent(entry, uploadUrl, contract.uploadMethod, bytes, options);
3297
+ if (!contract.complete) return initiation;
3298
+ const completionValue = topoloUploadResponseValue(initiation, contract.complete.responsePath);
3299
+ if (typeof completionValue !== "string" || !completionValue.trim()) {
3300
+ throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.complete.responsePath}.`);
3301
+ }
3302
+ const completion = await this.getAction(contract.complete.actionId);
3303
+ return this.executeResolvedAction(completion, {
3304
+ ...options.completionInput ?? {},
3305
+ [contract.complete.inputField]: completionValue
3306
+ }, options);
3307
+ }
3308
+ async putUrlSessionContent(entry, uploadUrl, method, bytes, options) {
3309
+ const service = entry.appSlug || entry.appId;
3310
+ const resolved = entry.apiBaseUrl ? { serviceKey: normalizeLookupKey(service), baseUrl: requireHttpOrigin(entry.apiBaseUrl) } : await this.resolveRequestService(service);
3311
+ const target = new URL(uploadUrl, resolved.baseUrl.endsWith("/") ? resolved.baseUrl : `${resolved.baseUrl}/`);
3312
+ const body = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
3313
+ if (target.origin === new URL(resolved.baseUrl).origin) {
3314
+ await this.request({
3315
+ service,
3316
+ baseUrl: resolved.baseUrl,
3317
+ path: target.toString(),
3318
+ method,
3319
+ rawBody: body,
3320
+ headers: { "Content-Type": options.contentType.trim().toLowerCase() },
3321
+ resource: options.resource,
3322
+ confirm: options.confirm
3323
+ });
3324
+ return;
3325
+ }
3326
+ const response = await this.fetchImpl(target, {
3327
+ method,
3328
+ headers: { "Content-Type": options.contentType.trim().toLowerCase() },
3329
+ body
3330
+ });
3331
+ if (!response.ok) {
3332
+ throw new TopoloHttpError(service, target.pathname, response.status, await response.text(), `Upload transport failed with HTTP ${response.status}.`);
3333
+ }
3334
+ }
2938
3335
  async validateActionInput(action, input = {}) {
2939
3336
  const entry = await this.getAction(action);
2940
3337
  return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input) };
@@ -3093,7 +3490,7 @@ function resolveConfiguredAppId(slug, appIds) {
3093
3490
  return null;
3094
3491
  }
3095
3492
  function normalizeApiKeyResourceMetadata(input) {
3096
- if (!isRecord2(input)) return null;
3493
+ if (!isRecord3(input)) return null;
3097
3494
  const resourceType = readString(input.resourceType ?? input.resource_type);
3098
3495
  const label = readString(input.label);
3099
3496
  const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
@@ -3127,7 +3524,7 @@ function normalizeApiKeyResourceMetadataList(input) {
3127
3524
  return resources;
3128
3525
  }
3129
3526
  function normalizeBindableResource(input) {
3130
- if (!isRecord2(input)) return null;
3527
+ if (!isRecord3(input)) return null;
3131
3528
  const resourceType = readString(input.resourceType ?? input.resource_type);
3132
3529
  const resourceId = readString(input.resourceId ?? input.resource_id);
3133
3530
  if (!resourceType || !resourceId) return null;
@@ -3138,13 +3535,13 @@ function normalizeBindableResource(input) {
3138
3535
  slug: readOptionalString(input.slug),
3139
3536
  status: readOptionalString(input.status),
3140
3537
  organizationId: readOptionalString(input.organizationId ?? input.organization_id),
3141
- metadata: isRecord2(input.metadata) ? input.metadata : {}
3538
+ metadata: isRecord3(input.metadata) ? input.metadata : {}
3142
3539
  };
3143
3540
  }
3144
3541
  function normalizeResourceBindings(input) {
3145
3542
  if (!Array.isArray(input)) return [];
3146
3543
  return input.flatMap((binding) => {
3147
- if (!isRecord2(binding)) return [];
3544
+ if (!isRecord3(binding)) return [];
3148
3545
  const resourceType = readString(binding.resourceType ?? binding.resource_type);
3149
3546
  const resourceId = readString(binding.resourceId ?? binding.resource_id);
3150
3547
  return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
@@ -3157,7 +3554,7 @@ function readOptionalString(value) {
3157
3554
  const normalized = readString(value);
3158
3555
  return normalized || null;
3159
3556
  }
3160
- function isRecord2(value) {
3557
+ function isRecord3(value) {
3161
3558
  return typeof value === "object" && value !== null && !Array.isArray(value);
3162
3559
  }
3163
3560
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
@@ -3229,14 +3626,14 @@ function catalogSearchQuery(options) {
3229
3626
  return query;
3230
3627
  }
3231
3628
  function normalizeCatalogPage(value) {
3232
- if (!isRecord2(value)) return { nextCursor: null, hasMore: false };
3629
+ if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
3233
3630
  return {
3234
3631
  nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
3235
3632
  hasMore: value.hasMore === true
3236
3633
  };
3237
3634
  }
3238
3635
  function normalizeServiceCatalogEntry(item) {
3239
- if (!isRecord2(item)) return null;
3636
+ if (!isRecord3(item)) return null;
3240
3637
  const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
3241
3638
  if (!appId) return null;
3242
3639
  const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
@@ -3482,29 +3879,57 @@ var TopoloSdkError2 = class extends Error {
3482
3879
  }
3483
3880
  };
3484
3881
  var TopoloAuthError2 = class extends TopoloSdkError2 {
3485
- constructor(message, code = "auth_error") {
3882
+ status;
3883
+ body;
3884
+ service;
3885
+ path;
3886
+ requestId;
3887
+ retryAfter;
3888
+ constructor(message, code = "auth_error", options = {}) {
3486
3889
  super(code, message);
3487
3890
  this.name = "TopoloAuthError";
3891
+ this.status = options.status ?? 401;
3892
+ this.body = options.body;
3893
+ this.service = options.service;
3894
+ this.path = options.path;
3895
+ this.requestId = options.requestId;
3896
+ this.retryAfter = options.retryAfter;
3488
3897
  }
3489
3898
  };
3899
+ function extractHttpErrorCode2(body) {
3900
+ if (!body || typeof body !== "object" || Array.isArray(body)) return "http_error";
3901
+ const record = body;
3902
+ if (typeof record.code === "string" && record.code.trim()) return record.code.trim();
3903
+ const nested = record.error;
3904
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
3905
+ const code = nested.code;
3906
+ if (typeof code === "string" && code.trim()) return code.trim();
3907
+ }
3908
+ return "http_error";
3909
+ }
3490
3910
  var TopoloHttpError2 = class extends TopoloSdkError2 {
3491
3911
  status;
3492
3912
  body;
3493
3913
  service;
3494
3914
  path;
3495
- constructor(service, path, status, body, message) {
3496
- super("http_error", message ?? `HTTP ${status} from ${service}${path}`);
3915
+ requestId;
3916
+ retryAfter;
3917
+ constructor(service, path, status, body, message, options = {}) {
3918
+ super(extractHttpErrorCode2(body), message ?? `HTTP ${status} from ${service}${path}`);
3497
3919
  this.name = "TopoloHttpError";
3498
3920
  this.status = status;
3499
3921
  this.body = body;
3500
3922
  this.service = service;
3501
3923
  this.path = path;
3924
+ this.requestId = options.requestId;
3925
+ this.retryAfter = options.retryAfter;
3502
3926
  }
3503
3927
  };
3504
3928
  var APP_API_URLS2 = {
3505
3929
  agent: "https://agent.topolo.app",
3506
3930
  auth: "https://auth.topolo.app",
3507
3931
  backup: "https://backup.topolo.app",
3932
+ blog: "https://blog.topolo.app",
3508
3933
  books: "https://books.topolo.app",
3509
3934
  brand: "https://brand.topolo.app",
3510
3935
  bugfix: "https://bugfix.topolo.app",
@@ -3517,6 +3942,7 @@ var APP_API_URLS2 = {
3517
3942
  compose: "https://compose.topolo.app",
3518
3943
  consent: "https://consent.topolo.app",
3519
3944
  crm: "https://crm.topolo.app",
3945
+ design: "https://design.topolo.app",
3520
3946
  developers: "https://developers.topolo.app",
3521
3947
  director: "https://director.topolo.app",
3522
3948
  feed: "https://feed.topolo.app",
@@ -4126,6 +4552,60 @@ var TOOLS = [
4126
4552
  );
4127
4553
  }
4128
4554
  },
4555
+ {
4556
+ name: "topolo_upload_action",
4557
+ title: "Upload content through one Topolo action",
4558
+ description: "Uploads base64 content through an exact action id using its published upload contract. Mutating uploads require confirm:true.",
4559
+ requiredScopes: [],
4560
+ destructive: true,
4561
+ inputSchema: {
4562
+ type: "object",
4563
+ properties: {
4564
+ actionId: { type: "string", minLength: 1 },
4565
+ contentBase64: { type: "string", minLength: 4, description: "Canonical base64 content without a data URL prefix." },
4566
+ contentType: { type: "string", minLength: 1 },
4567
+ fileName: { type: "string", minLength: 1 },
4568
+ input: { type: "object", additionalProperties: true },
4569
+ completionInput: { type: "object", additionalProperties: true },
4570
+ resourceType: { type: "string" },
4571
+ resourceId: { type: "string" },
4572
+ confirm: { type: "boolean" }
4573
+ },
4574
+ required: ["actionId", "contentBase64", "contentType", "fileName"],
4575
+ additionalProperties: false
4576
+ },
4577
+ handler: async (topolo, args) => {
4578
+ const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4579
+ const contentBase64 = typeof args["contentBase64"] === "string" ? args["contentBase64"] : "";
4580
+ const contentType = typeof args["contentType"] === "string" ? args["contentType"].trim() : "";
4581
+ const fileName = typeof args["fileName"] === "string" ? args["fileName"].trim() : "";
4582
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4583
+ if (!isCanonicalBase642(contentBase64)) throw new TopoloMcpPublicError("contentBase64 must be canonical base64.");
4584
+ if (!contentType) throw new TopoloMcpPublicError("contentType is required.");
4585
+ if (!fileName) throw new TopoloMcpPublicError("fileName is required.");
4586
+ const input = args["input"];
4587
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4588
+ throw new TopoloMcpPublicError("input must be an object.");
4589
+ }
4590
+ const completionInput = args["completionInput"];
4591
+ if (completionInput !== void 0 && (!completionInput || typeof completionInput !== "object" || Array.isArray(completionInput))) {
4592
+ throw new TopoloMcpPublicError("completionInput must be an object.");
4593
+ }
4594
+ const resource = optionalResourceContext(args);
4595
+ return topolo.client.uploadActionBase64(
4596
+ actionId,
4597
+ contentBase64,
4598
+ input ?? {},
4599
+ {
4600
+ fileName,
4601
+ contentType,
4602
+ completionInput: completionInput ?? {},
4603
+ confirm: args["confirm"] === true,
4604
+ ...resource ? { resource } : {}
4605
+ }
4606
+ );
4607
+ }
4608
+ },
4129
4609
  {
4130
4610
  name: "topolo_validate_action",
4131
4611
  title: "Validate Topolo action input",
@@ -4213,7 +4693,7 @@ var TOOLS = [
4213
4693
  {
4214
4694
  name: "topolo_discover_capabilities",
4215
4695
  title: "Discover one Topolo application capability contract",
4216
- description: "Discovers capability and schema actions published by one application. Use the actionService value returned by topolo_search_applications as service.",
4696
+ description: "Lists every action published by one application \u2014 its full capability contract, straight from the registry-canonical catalog. Use the actionService value returned by topolo_search_applications as service. Follow page.nextCursor via topolo_search_actions when the contract exceeds one page.",
4217
4697
  requiredScopes: [],
4218
4698
  destructive: false,
4219
4699
  inputSchema: {
@@ -4225,7 +4705,7 @@ var TOOLS = [
4225
4705
  handler: async (topolo, args) => {
4226
4706
  const service = typeof args["service"] === "string" ? args["service"].trim() : "";
4227
4707
  if (!service) throw new TopoloMcpPublicError("service is required.");
4228
- return topolo.client.searchActions({ service, query: "capabilities", limit: 100 });
4708
+ return topolo.client.searchActions({ service, limit: 100 });
4229
4709
  }
4230
4710
  },
4231
4711
  {
@@ -4429,6 +4909,9 @@ var TOOLS = [
4429
4909
  }
4430
4910
  }
4431
4911
  ];
4912
+ function isCanonicalBase642(value) {
4913
+ return value.length > 0 && value.length % 4 === 0 && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
4914
+ }
4432
4915
  function filterToolsByScopes(tools, scopes) {
4433
4916
  return tools.filter((t) => hasAnyScope(scopes, t.requiredScopes));
4434
4917
  }
@@ -4566,16 +5049,36 @@ function buildServer(topolo, scopes, tools = TOOLS) {
4566
5049
  };
4567
5050
  } catch (err) {
4568
5051
  if (err instanceof TopoloPermissionError) {
4569
- return toolError(`Permission denied. Required: ${err.required.join(", ") || "(unknown)"}`);
5052
+ return toolStructuredError(err, `Permission denied. Required: ${err.required.join(", ") || "(unknown)"}`, {
5053
+ required: err.required
5054
+ });
4570
5055
  }
4571
5056
  if (err instanceof TopoloAuthError) {
4572
- return toolError("Authentication failed.");
5057
+ return toolStructuredError(err, "Authentication failed.");
5058
+ }
5059
+ if (err instanceof TopoloHttpError) {
5060
+ return toolStructuredError(err, err.message);
4573
5061
  }
4574
5062
  return toolError(publicMcpErrorMessage(err, "Tool execution failed."));
4575
5063
  }
4576
5064
  });
4577
5065
  return server;
4578
5066
  }
5067
+ function toolStructuredError(error, message, extra = {}) {
5068
+ return toolError(JSON.stringify({
5069
+ error: {
5070
+ code: error.code,
5071
+ status: error.status,
5072
+ message,
5073
+ service: error.service,
5074
+ path: error.path,
5075
+ requestId: error.requestId,
5076
+ retryAfter: error.retryAfter,
5077
+ details: error.body,
5078
+ ...extra
5079
+ }
5080
+ }, null, 2));
5081
+ }
4579
5082
  async function advertisedToolNames(topolo, scopes, tools = TOOLS) {
4580
5083
  return (await buildTopoloTools(topolo, scopes, tools)).map((t) => t.name);
4581
5084
  }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@topolo/mcp",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
+ "topoloPublishedVersion": "0.9.9",
4
5
  "description": "Model Context Protocol server for the Topolo platform. Exposes scope-gated tools that third-party agents (Claude, Codex, etc.) can call natively.",
5
6
  "type": "module",
6
7
  "bin": {
@@ -20,7 +21,7 @@
20
21
  "dist"
21
22
  ],
22
23
  "scripts": {
23
- "build:sdk": "pnpm --dir ../topolo-sdk build",
24
+ "build:sdk": "python3 ../../tooling/with-resource-lock.py ../topolo-sdk -- pnpm --dir ../topolo-sdk build",
24
25
  "prebuild": "pnpm run build:sdk",
25
26
  "build": "tsup",
26
27
  "dev": "tsup --watch",
@@ -36,7 +37,7 @@
36
37
  "@modelcontextprotocol/sdk": "^1.0.0"
37
38
  },
38
39
  "peerDependencies": {
39
- "@topolo/sdk": "^0.9.6"
40
+ "@topolo/sdk": "^0.9.8"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@topolo/sdk": "file:../topolo-sdk",