@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 +59 -2
- package/dist/http.js +82 -5
- package/dist/index.js +551 -48
- package/package.json +4 -3
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: "
|
|
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,
|
|
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: "
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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(
|
|
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 (!
|
|
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 =
|
|
2832
|
-
if (!
|
|
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 (!
|
|
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 (!
|
|
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:
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
3496
|
-
|
|
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: "
|
|
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,
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
40
|
+
"@topolo/sdk": "^0.9.8"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@topolo/sdk": "file:../topolo-sdk",
|