@topolo/mcp 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -22
- package/dist/dispatch.js +143 -106
- package/dist/http.js +144 -107
- package/dist/index.js +382 -167
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -26,9 +26,10 @@ Either:
|
|
|
26
26
|
|
|
27
27
|
- **Long-lived**: mint an API key at the Topolo Developers console
|
|
28
28
|
(`TOPOLO_API_KEY=topo_live_...`). Preferred for persistent agent installs.
|
|
29
|
-
- **Short-lived**: `topolo auth login
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
- **Short-lived**: run `topolo auth login`. The stdio MCP reads and refreshes
|
|
30
|
+
the same environment-specific credential store, so no token copying is
|
|
31
|
+
needed. Explicit `TOPOLO_API_KEY` or `TOPOLO_ACCESS_TOKEN` values still take
|
|
32
|
+
precedence for headless installations.
|
|
32
33
|
|
|
33
34
|
## Register with an MCP client
|
|
34
35
|
|
|
@@ -89,9 +90,9 @@ wins.
|
|
|
89
90
|
1. Resolve the credential from env. **Refuse to start** if none is set.
|
|
90
91
|
2. Call `GET /api/me` to load the user's granted scopes + role.
|
|
91
92
|
3. Connect the stdio transport and begin serving MCP requests.
|
|
92
|
-
4.
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
4. Advertise a fixed, bounded discovery/invocation surface. Agents search one
|
|
94
|
+
application at a time and call exact action IDs; the server never expands
|
|
95
|
+
one MCP tool per action or materializes the global catalog.
|
|
95
96
|
|
|
96
97
|
Startup is synchronous and fast (< 500 ms typical). If the credential is
|
|
97
98
|
rejected, the process logs the error to stderr and exits non-zero — host
|
|
@@ -102,44 +103,64 @@ clients surface that as a failed server launch.
|
|
|
102
103
|
| Tool | Required scopes | Destructive hint |
|
|
103
104
|
| ---------------------------- | --------------------- | ---------------- |
|
|
104
105
|
| `topolo_whoami` | (none) | no |
|
|
105
|
-
| `
|
|
106
|
-
| `topolo_list_applications` | (none) | no |
|
|
106
|
+
| `topolo_search_applications` | (none) | no |
|
|
107
107
|
| `topolo_get_application` | (none) | no |
|
|
108
108
|
| `topolo_list_application_requirements` | (none) | no |
|
|
109
109
|
| `topolo_audit_applications` | (none) | no |
|
|
110
|
-
| `
|
|
111
|
-
|
|
|
110
|
+
| `topolo_search_actions` | (credential-scoped) | no |
|
|
111
|
+
| `topolo_get_action` | (credential-scoped) | no |
|
|
112
|
+
| `topolo_read_action` | catalog or target policy | no |
|
|
113
|
+
| `topolo_call_action` | catalog or target policy | yes |
|
|
112
114
|
| `topolo_api_call` | (service-enforced) | yes |
|
|
113
115
|
|
|
114
116
|
Applications publish machine-action definitions through Topolo Developers.
|
|
115
117
|
TopoloAuth projects those into a credential-scoped action catalog using the
|
|
116
118
|
same service permissions already used for API keys and application access. MCP
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
keeps six stable application/action tools regardless of whether the platform
|
|
120
|
+
contains ten actions or one billion: search applications, search actions within
|
|
121
|
+
one app, inspect exact applications/actions, safely invoke automatic reads, and
|
|
122
|
+
invoke confirmation-gated actions.
|
|
123
|
+
|
|
124
|
+
Most actions use `authorizationMode: static` and are filtered by one declared
|
|
125
|
+
application permission before discovery. Operations whose authority depends on
|
|
126
|
+
request input (for example, organization admin OR `grants:write` in the target
|
|
127
|
+
application) use `authorizationMode: runtime`: the catalog exposes the typed
|
|
128
|
+
capability without inventing a global permission, and the target service checks
|
|
129
|
+
the current database-backed policy for the supplied organization/application on
|
|
130
|
+
every call.
|
|
120
131
|
|
|
121
132
|
`topolo_api_call` is the escape hatch for endpoints without typed MCP tools
|
|
122
133
|
yet. GET requests work without confirmation; mutating HTTP methods must pass
|
|
123
134
|
`confirm: true` or the SDK rejects the call. Backing services still enforce
|
|
124
135
|
permissions for every request. The tool schema intentionally does not embed a
|
|
125
|
-
large service enum; agents should call `
|
|
136
|
+
large service enum; agents should call `topolo_search_applications` first, then pass an
|
|
126
137
|
app ID/slug from that credential-scoped result. New services become callable
|
|
127
138
|
without an MCP release once Auth grants the organization and credential the
|
|
128
|
-
relevant permissions, and new actions become
|
|
129
|
-
once the application publishes them.
|
|
139
|
+
relevant permissions, and new actions become discoverable/callable without an
|
|
140
|
+
MCP release once the application publishes them.
|
|
141
|
+
|
|
142
|
+
`topolo_read_action` and `topolo_call_action` accept an optional paired
|
|
143
|
+
`resourceType` / `resourceId` context for app-local workspace boundaries. The
|
|
144
|
+
SDK sends it as resource headers without changing the credential's personal or
|
|
145
|
+
organization context. This lets agents address many workspaces independently;
|
|
146
|
+
providing only one of the pair is rejected before the request is sent.
|
|
130
147
|
|
|
131
148
|
## Safety rails
|
|
132
149
|
|
|
133
150
|
- **No `orgId` parameter** on any tool. The organization is derived entirely
|
|
134
151
|
from the credential by each backend app. There is no path for Org A's agent
|
|
135
152
|
to see Org B's data.
|
|
136
|
-
- **
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
153
|
+
- **Credential-bound context.** OAuth tokens and refresh rotation retain their
|
|
154
|
+
own personal/org context. CLI/MCP context switches do not move the user's web
|
|
155
|
+
session, and browser switches cannot redirect a running agent credential.
|
|
156
|
+
- **Scope-gated tool advertisement.** Static actions are removed unless their
|
|
157
|
+
permission is granted. Input-dependent runtime actions remain discoverable
|
|
158
|
+
and are authorized by the target service for the concrete request.
|
|
159
|
+
- **Credential-scoped catalog discovery.** `topolo_search_applications` uses
|
|
160
|
+
TopoloAuth's live organization/permission
|
|
140
161
|
catalog. They do not expose the global generated platform catalog.
|
|
141
|
-
- **Credential-scoped action discovery.**
|
|
142
|
-
|
|
162
|
+
- **Credential-scoped action discovery.** `topolo_search_actions` reads only one
|
|
163
|
+
app partition and returns at most 100 permission-filtered actions per page.
|
|
143
164
|
- **Audit headers.** Every request sends `X-Topolo-Client: topolo-mcp/<ver>`,
|
|
144
165
|
`X-Topolo-Agent: <label>`, `X-Topolo-Request-Id: <uuid>`.
|
|
145
166
|
- **Write-action confirmation.** The SDK refuses mutating HTTP methods unless
|
package/dist/dispatch.js
CHANGED
|
@@ -53,6 +53,15 @@ function splitService(servicePart) {
|
|
|
53
53
|
// src/tools.ts
|
|
54
54
|
var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
|
|
55
55
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
56
|
+
function optionalResourceContext(args) {
|
|
57
|
+
const resourceType = typeof args["resourceType"] === "string" ? args["resourceType"].trim() : "";
|
|
58
|
+
const resourceId = typeof args["resourceId"] === "string" ? args["resourceId"].trim() : "";
|
|
59
|
+
if (!resourceType && !resourceId) return void 0;
|
|
60
|
+
if (!resourceType || !resourceId) {
|
|
61
|
+
throw new Error("resourceType and resourceId must be provided together.");
|
|
62
|
+
}
|
|
63
|
+
return { resourceType, resourceId };
|
|
64
|
+
}
|
|
56
65
|
var TOOLS = [
|
|
57
66
|
{
|
|
58
67
|
name: "topolo_whoami",
|
|
@@ -68,18 +77,27 @@ var TOOLS = [
|
|
|
68
77
|
handler: async (topolo) => topolo.identity.whoami()
|
|
69
78
|
},
|
|
70
79
|
{
|
|
71
|
-
name: "
|
|
72
|
-
title: "
|
|
73
|
-
description: "
|
|
80
|
+
name: "topolo_search_applications",
|
|
81
|
+
title: "Search Topolo applications the agent can reach",
|
|
82
|
+
description: "Searches the credential-scoped application registry with a bounded page and continuation cursor. Returns granted permissions, API URL, and launch URL without loading the global platform catalog.",
|
|
74
83
|
requiredScopes: [],
|
|
75
84
|
destructive: false,
|
|
76
85
|
inputSchema: {
|
|
77
86
|
type: "object",
|
|
78
|
-
properties: {
|
|
87
|
+
properties: {
|
|
88
|
+
query: { type: "string" },
|
|
89
|
+
cursor: { type: "string" },
|
|
90
|
+
limit: { type: "integer", minimum: 1, maximum: 100, default: 25 }
|
|
91
|
+
},
|
|
79
92
|
additionalProperties: false
|
|
80
93
|
},
|
|
81
|
-
handler: async (topolo) => {
|
|
82
|
-
const
|
|
94
|
+
handler: async (topolo, args) => {
|
|
95
|
+
const result = await topolo.client.searchServices({
|
|
96
|
+
query: typeof args["query"] === "string" ? args["query"] : void 0,
|
|
97
|
+
cursor: typeof args["cursor"] === "string" ? args["cursor"] : void 0,
|
|
98
|
+
limit: typeof args["limit"] === "number" ? args["limit"] : void 0
|
|
99
|
+
});
|
|
100
|
+
const applications = result.services.map((entry) => {
|
|
83
101
|
const id = displayAppId(entry);
|
|
84
102
|
return {
|
|
85
103
|
id,
|
|
@@ -96,13 +114,13 @@ var TOOLS = [
|
|
|
96
114
|
permissions: [...entry.permissions]
|
|
97
115
|
};
|
|
98
116
|
}).sort((left, right) => left.id.localeCompare(right.id));
|
|
99
|
-
return {
|
|
117
|
+
return { applications, page: result.page };
|
|
100
118
|
}
|
|
101
119
|
},
|
|
102
120
|
{
|
|
103
|
-
name: "
|
|
104
|
-
title: "
|
|
105
|
-
description: "
|
|
121
|
+
name: "topolo_search_actions",
|
|
122
|
+
title: "Search Topolo actions the agent can call",
|
|
123
|
+
description: "Searches one application's credential-scoped actions with a bounded page and continuation cursor.",
|
|
106
124
|
requiredScopes: [],
|
|
107
125
|
destructive: false,
|
|
108
126
|
inputSchema: {
|
|
@@ -110,31 +128,25 @@ var TOOLS = [
|
|
|
110
128
|
properties: {
|
|
111
129
|
service: {
|
|
112
130
|
type: "string",
|
|
113
|
-
description: "
|
|
114
|
-
}
|
|
131
|
+
description: "Required app id, slug, or name."
|
|
132
|
+
},
|
|
133
|
+
query: { type: "string" },
|
|
134
|
+
cursor: { type: "string" },
|
|
135
|
+
limit: { type: "integer", minimum: 1, maximum: 100, default: 25 }
|
|
115
136
|
},
|
|
137
|
+
required: ["service"],
|
|
116
138
|
additionalProperties: false
|
|
117
139
|
},
|
|
118
|
-
handler: async (topolo, args) =>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
requiredScopes: [],
|
|
129
|
-
destructive: false,
|
|
130
|
-
inputSchema: {
|
|
131
|
-
type: "object",
|
|
132
|
-
properties: {},
|
|
133
|
-
additionalProperties: false
|
|
134
|
-
},
|
|
135
|
-
handler: async (topolo) => ({
|
|
136
|
-
applications: await topolo.client.listServices()
|
|
137
|
-
})
|
|
140
|
+
handler: async (topolo, args) => {
|
|
141
|
+
const service = typeof args["service"] === "string" ? args["service"].trim() : "";
|
|
142
|
+
if (!service) throw new Error("service is required for bounded action discovery.");
|
|
143
|
+
return topolo.client.searchActions({
|
|
144
|
+
service,
|
|
145
|
+
query: typeof args["query"] === "string" ? args["query"] : void 0,
|
|
146
|
+
cursor: typeof args["cursor"] === "string" ? args["cursor"] : void 0,
|
|
147
|
+
limit: typeof args["limit"] === "number" ? args["limit"] : void 0
|
|
148
|
+
});
|
|
149
|
+
}
|
|
138
150
|
},
|
|
139
151
|
{
|
|
140
152
|
name: "topolo_get_application",
|
|
@@ -147,7 +159,7 @@ var TOOLS = [
|
|
|
147
159
|
properties: {
|
|
148
160
|
application: {
|
|
149
161
|
type: "string",
|
|
150
|
-
description: "Application identifier: app id, slug, or name from
|
|
162
|
+
description: "Application identifier: app id, slug, or name from topolo_search_applications."
|
|
151
163
|
}
|
|
152
164
|
},
|
|
153
165
|
required: ["application"],
|
|
@@ -158,18 +170,98 @@ var TOOLS = [
|
|
|
158
170
|
if (typeof application !== "string" || !application.trim()) {
|
|
159
171
|
throw new Error("`application` must be a non-empty string.");
|
|
160
172
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
173
|
+
return { application: await topolo.client.getService(application) };
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "topolo_get_action",
|
|
178
|
+
title: "Describe one Topolo action",
|
|
179
|
+
description: "Returns one credential-scoped action by exact action id.",
|
|
180
|
+
requiredScopes: [],
|
|
181
|
+
destructive: false,
|
|
182
|
+
inputSchema: {
|
|
183
|
+
type: "object",
|
|
184
|
+
properties: { actionId: { type: "string", minLength: 1 } },
|
|
185
|
+
required: ["actionId"],
|
|
186
|
+
additionalProperties: false
|
|
187
|
+
},
|
|
188
|
+
handler: async (topolo, args) => {
|
|
189
|
+
const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
|
|
190
|
+
if (!actionId) throw new Error("actionId is required.");
|
|
191
|
+
return { action: await topolo.client.getAction(actionId) };
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "topolo_read_action",
|
|
196
|
+
title: "Call one read-only Topolo action",
|
|
197
|
+
description: "Calls one exact action id only when its published contract is read-only and needs no confirmation.",
|
|
198
|
+
requiredScopes: [],
|
|
199
|
+
destructive: false,
|
|
200
|
+
inputSchema: {
|
|
201
|
+
type: "object",
|
|
202
|
+
properties: {
|
|
203
|
+
actionId: { type: "string", minLength: 1 },
|
|
204
|
+
input: { type: "object", additionalProperties: true },
|
|
205
|
+
resourceType: { type: "string", description: "Optional app-local resource alias, such as workspace." },
|
|
206
|
+
resourceId: { type: "string", description: "Resource id. Required when resourceType is provided." }
|
|
207
|
+
},
|
|
208
|
+
required: ["actionId"],
|
|
209
|
+
additionalProperties: false
|
|
210
|
+
},
|
|
211
|
+
handler: async (topolo, args) => {
|
|
212
|
+
const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
|
|
213
|
+
if (!actionId) throw new Error("actionId is required.");
|
|
214
|
+
const input = args["input"];
|
|
215
|
+
if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
|
|
216
|
+
throw new Error("input must be an object.");
|
|
217
|
+
}
|
|
218
|
+
const action = await topolo.client.getAction(actionId);
|
|
219
|
+
const agentAccess = action.agentAccess ?? (action.destructive ? "off" : action.readOnly && !action.requiresConfirmation ? "auto" : "confirm");
|
|
220
|
+
if (action.method !== "GET" || !action.readOnly || action.destructive || action.requiresConfirmation || agentAccess !== "auto") {
|
|
221
|
+
throw new Error(`Action "${actionId}" is not an automatic read-only action. Use topolo_call_action with confirmation.`);
|
|
222
|
+
}
|
|
223
|
+
const resource = optionalResourceContext(args);
|
|
224
|
+
return topolo.client.callAction(
|
|
225
|
+
actionId,
|
|
226
|
+
input ?? {},
|
|
227
|
+
resource ? { resource } : {}
|
|
165
228
|
);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: "topolo_call_action",
|
|
233
|
+
title: "Call one Topolo action",
|
|
234
|
+
description: "Calls one exact action id. Mutating actions require confirm:true.",
|
|
235
|
+
requiredScopes: [],
|
|
236
|
+
destructive: true,
|
|
237
|
+
inputSchema: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: {
|
|
240
|
+
actionId: { type: "string", minLength: 1 },
|
|
241
|
+
input: { type: "object", additionalProperties: true },
|
|
242
|
+
resourceType: { type: "string", description: "Optional app-local resource alias, such as workspace." },
|
|
243
|
+
resourceId: { type: "string", description: "Resource id. Required when resourceType is provided." },
|
|
244
|
+
confirm: { type: "boolean" }
|
|
245
|
+
},
|
|
246
|
+
required: ["actionId"],
|
|
247
|
+
additionalProperties: false
|
|
248
|
+
},
|
|
249
|
+
handler: async (topolo, args) => {
|
|
250
|
+
const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
|
|
251
|
+
if (!actionId) throw new Error("actionId is required.");
|
|
252
|
+
const input = args["input"];
|
|
253
|
+
if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
|
|
254
|
+
throw new Error("input must be an object.");
|
|
171
255
|
}
|
|
172
|
-
|
|
256
|
+
const resource = optionalResourceContext(args);
|
|
257
|
+
return topolo.client.callAction(
|
|
258
|
+
actionId,
|
|
259
|
+
input ?? {},
|
|
260
|
+
{
|
|
261
|
+
confirm: args["confirm"] === true,
|
|
262
|
+
...resource ? { resource } : {}
|
|
263
|
+
}
|
|
264
|
+
);
|
|
173
265
|
}
|
|
174
266
|
},
|
|
175
267
|
{
|
|
@@ -183,7 +275,7 @@ var TOOLS = [
|
|
|
183
275
|
properties: {
|
|
184
276
|
application: {
|
|
185
277
|
type: "string",
|
|
186
|
-
description: "Optional application ID from
|
|
278
|
+
description: "Optional application ID from topolo_search_applications."
|
|
187
279
|
}
|
|
188
280
|
},
|
|
189
281
|
additionalProperties: false
|
|
@@ -223,7 +315,7 @@ var TOOLS = [
|
|
|
223
315
|
properties: {
|
|
224
316
|
application: {
|
|
225
317
|
type: "string",
|
|
226
|
-
description: "Optional application ID from
|
|
318
|
+
description: "Optional application ID from topolo_search_applications."
|
|
227
319
|
},
|
|
228
320
|
failOn: {
|
|
229
321
|
type: "string",
|
|
@@ -257,7 +349,7 @@ var TOOLS = [
|
|
|
257
349
|
{
|
|
258
350
|
name: "topolo_api_call",
|
|
259
351
|
title: "Call any Topolo platform service",
|
|
260
|
-
description: "Low-level passthrough to any Topolo service. Use when no domain-specific tool exists for the operation you need. The request is signed with the current credential and scoped to the caller's organization \u2014 the target service will return 403 if your scopes do not permit the operation, even though this tool is advertised. Mutating calls (non-GET methods) require confirm:true. Call
|
|
352
|
+
description: "Low-level passthrough to any Topolo service. Use when no domain-specific tool exists for the operation you need. The request is signed with the current credential and scoped to the caller's organization \u2014 the target service will return 403 if your scopes do not permit the operation, even though this tool is advertised. Mutating calls (non-GET methods) require confirm:true. Call topolo_search_applications first to discover callable app IDs; the SDK can also resolve live Auth-catalog app ids/slugs for newly registered API surfaces.",
|
|
261
353
|
requiredScopes: [],
|
|
262
354
|
destructive: true,
|
|
263
355
|
inputSchema: {
|
|
@@ -288,7 +380,7 @@ var TOOLS = [
|
|
|
288
380
|
},
|
|
289
381
|
resourceType: {
|
|
290
382
|
type: "string",
|
|
291
|
-
description: "Optional workspace resource alias from
|
|
383
|
+
description: "Optional workspace resource alias from topolo_search_applications apiKeyResources. Aliases are app-defined, e.g. brand, mailbox, venue, merchant, or any other cataloged alias."
|
|
292
384
|
},
|
|
293
385
|
resourceId: {
|
|
294
386
|
type: "string",
|
|
@@ -306,7 +398,7 @@ var TOOLS = [
|
|
|
306
398
|
const service = args["service"];
|
|
307
399
|
const path = args["path"];
|
|
308
400
|
if (typeof service !== "string" || !service.trim()) {
|
|
309
|
-
throw new Error("service is required. Call
|
|
401
|
+
throw new Error("service is required. Call topolo_search_applications first to discover callable services.");
|
|
310
402
|
}
|
|
311
403
|
if (typeof path !== "string" || !path) {
|
|
312
404
|
throw new Error("path is required");
|
|
@@ -320,12 +412,7 @@ var TOOLS = [
|
|
|
320
412
|
const normalisedQuery = query && typeof query === "object" && !Array.isArray(query) ? Object.fromEntries(
|
|
321
413
|
Object.entries(query).map(([k, v]) => [k, String(v)])
|
|
322
414
|
) : void 0;
|
|
323
|
-
const
|
|
324
|
-
const resourceId = typeof args["resourceId"] === "string" ? args["resourceId"].trim() : "";
|
|
325
|
-
if (resourceType && !resourceId || !resourceType && resourceId) {
|
|
326
|
-
throw new Error("resourceType and resourceId must be provided together.");
|
|
327
|
-
}
|
|
328
|
-
const resource = resourceType && resourceId ? { resourceType, resourceId } : void 0;
|
|
415
|
+
const resource = optionalResourceContext(args);
|
|
329
416
|
return topolo.client.request({
|
|
330
417
|
service: service.trim(),
|
|
331
418
|
method,
|
|
@@ -342,58 +429,8 @@ function filterToolsByScopes(tools, scopes) {
|
|
|
342
429
|
return tools.filter((t) => hasAnyScope(scopes, t.requiredScopes));
|
|
343
430
|
}
|
|
344
431
|
async function buildTopoloTools(topolo, scopes, tools = TOOLS) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const actionTools = actionsToToolDefs(actions);
|
|
348
|
-
return filterToolsByScopes([...baseTools, ...actionTools], scopes);
|
|
349
|
-
}
|
|
350
|
-
function effectiveAgentAccess(a) {
|
|
351
|
-
if (a.agentAccess) return a.agentAccess;
|
|
352
|
-
if (a.destructive) return "off";
|
|
353
|
-
if (a.readOnly && !a.requiresConfirmation) return "auto";
|
|
354
|
-
return "confirm";
|
|
355
|
-
}
|
|
356
|
-
function actionsToToolDefs(actions) {
|
|
357
|
-
return actions.map((action) => ({ action, access: effectiveAgentAccess(action) })).filter(({ access }) => access !== "off").map(({ action, access }) => ({
|
|
358
|
-
name: action.toolName || toolNameForAction(action),
|
|
359
|
-
title: action.title,
|
|
360
|
-
description: action.description,
|
|
361
|
-
requiredScopes: action.requiredPermission ? [action.requiredPermission] : [],
|
|
362
|
-
destructive: access === "confirm",
|
|
363
|
-
inputSchema: actionInputSchema(action, access),
|
|
364
|
-
handler: async (topolo, args) => {
|
|
365
|
-
const confirm = Boolean(args["confirm"]);
|
|
366
|
-
const input = { ...args };
|
|
367
|
-
delete input["confirm"];
|
|
368
|
-
return topolo.client.callAction(action.actionId, input, { confirm });
|
|
369
|
-
}
|
|
370
|
-
}));
|
|
371
|
-
}
|
|
372
|
-
function toolNameForAction(action) {
|
|
373
|
-
const service = (action.appSlug || action.appId || "service").replace(/^topolo-/, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
374
|
-
const name = action.name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
375
|
-
return ["topolo", service, name].filter(Boolean).join("_").slice(0, 96);
|
|
376
|
-
}
|
|
377
|
-
function actionInputSchema(action, access = "auto") {
|
|
378
|
-
const schema = {
|
|
379
|
-
type: "object",
|
|
380
|
-
properties: {},
|
|
381
|
-
additionalProperties: false,
|
|
382
|
-
...action.inputSchema
|
|
383
|
-
};
|
|
384
|
-
const properties = schema.properties && typeof schema.properties === "object" && !Array.isArray(schema.properties) ? { ...schema.properties } : {};
|
|
385
|
-
if (access === "confirm") {
|
|
386
|
-
properties.confirm = {
|
|
387
|
-
type: "boolean",
|
|
388
|
-
description: "Required for mutating actions. Explicit human-in-the-loop acknowledgement."
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
return {
|
|
392
|
-
...schema,
|
|
393
|
-
type: "object",
|
|
394
|
-
properties,
|
|
395
|
-
additionalProperties: schema.additionalProperties ?? false
|
|
396
|
-
};
|
|
432
|
+
void topolo;
|
|
433
|
+
return filterToolsByScopes(tools, scopes);
|
|
397
434
|
}
|
|
398
435
|
function evaluateApplicationAuditGate(report, threshold) {
|
|
399
436
|
const counts = {
|