forta-mcp 1.2.0 → 1.3.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/AGENTS.md +9 -6
- package/README.md +26 -6
- package/index.js +138 -13
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -37,7 +37,7 @@ from a manual database dig into one call.
|
|
|
37
37
|
|
|
38
38
|
| Path | Role |
|
|
39
39
|
|------|------|
|
|
40
|
-
| `index.js` | `--setup` flow, config guard, `api()`/`text()`/`body()` helpers, all
|
|
40
|
+
| `index.js` | `--setup` flow, config guard, `api()`/`text()`/`body()` helpers, all 38 tools, transport. |
|
|
41
41
|
| `package.json` | `bin.forta-mcp` → `index.js`. |
|
|
42
42
|
| `README.md` | User-facing setup + tool tables. |
|
|
43
43
|
| `AGENTS.md` | This file. |
|
|
@@ -113,10 +113,12 @@ be a Forta **super-admin** — the token does not bypass that.
|
|
|
113
113
|
| Group | Tools |
|
|
114
114
|
|-------|-------|
|
|
115
115
|
| Health & identity | `forta_health`, `forta_get_self` |
|
|
116
|
-
| Users | list, get, update, delete |
|
|
117
|
-
| Platforms | list, get, create, update, delete, rotate credentials. `create`/`update` carry **`backchannel_logout_uri`** — see the warning below |
|
|
118
|
-
| Roles | list, get, create, update, delete |
|
|
119
|
-
| Grants | list, create, revoke, check |
|
|
116
|
+
| Users | list (`status`, `q` filters), get, **create**, update, delete, **revoke sessions**, **activity**, **platform usage** |
|
|
117
|
+
| Platforms | list (`include_deleted`), get, create, update, delete (also revokes its grants), **restore**, rotate credentials. `create`/`update` carry **`backchannel_logout_uri`** — see the warning below — and **`requestable`** |
|
|
118
|
+
| Roles | list, get, create, update, delete (refused while anyone holds the role) |
|
|
119
|
+
| Grants | list, create (`role_id` optional → platform default), **update role**, revoke, check |
|
|
120
|
+
| Access requests | **list**, **decide** (approve/deny + `role_id`/`note`) |
|
|
121
|
+
| Bulk / audit / settings | **`forta_bulk`** (`POST /admin/bulk`, atomic by default — the op list is in the tool description and forta-api's AGENTS.md), **list activity**, **overview**, **get/update settings** (`signup_mode`) |
|
|
120
122
|
| Tokens | introspect, list/create/revoke API tokens |
|
|
121
123
|
|
|
122
124
|
### Setting `backchannel_logout_uri` — order matters
|
|
@@ -193,7 +195,8 @@ description says so; do not "fix" it by sending both.
|
|
|
193
195
|
- **`zod` is used but not declared** in `package.json`, resolving transitively through the MCP
|
|
194
196
|
SDK. Declaring it explicitly is the right fix, as a standalone change.
|
|
195
197
|
- Publishing is outward-facing and effectively irreversible (npm unpublish is restricted after
|
|
196
|
-
72 hours) —
|
|
198
|
+
72 hours) — publish **only after asking Aiden and getting an explicit yes** for that specific
|
|
199
|
+
version (see `~/CLAUDE.md` *Ask-first actions*).
|
|
197
200
|
|
|
198
201
|
## Verification — always before "done"
|
|
199
202
|
|
package/README.md
CHANGED
|
@@ -75,19 +75,24 @@ Set `FORTA_ALLOW_SECRET_VALUES=1` to turn masking off if you genuinely need a wo
|
|
|
75
75
|
### Users
|
|
76
76
|
| Tool | Description |
|
|
77
77
|
|------|-------------|
|
|
78
|
-
| `forta_list_users` | List users with status and super-admin flag |
|
|
79
|
-
| `forta_get_user` | Single user with metadata and
|
|
80
|
-
| `
|
|
78
|
+
| `forta_list_users` | List users with status and super-admin flag (`status`, `q` filters) |
|
|
79
|
+
| `forta_get_user` | Single user with metadata and sign-in methods |
|
|
80
|
+
| `forta_create_user` | Create an account (no grants) |
|
|
81
|
+
| `forta_update_user` | Update name, email, status (`pending` = awaiting approval), super-admin, password |
|
|
82
|
+
| `forta_revoke_user_sessions` | Sign a user out of every platform ⚠️ |
|
|
83
|
+
| `forta_get_user_activity` | Their trail across every platform: sign-ins, apps opened, refusals, admin changes |
|
|
84
|
+
| `forta_get_user_platform_usage` | When they last opened each platform |
|
|
81
85
|
| `forta_delete_user` | Soft-delete a user ⚠️ |
|
|
82
86
|
|
|
83
87
|
### Platforms
|
|
84
88
|
| Tool | Description |
|
|
85
89
|
|------|-------------|
|
|
86
|
-
| `forta_list_platforms` | All registered OAuth platforms |
|
|
90
|
+
| `forta_list_platforms` | All registered OAuth platforms (`include_deleted` for soft-deleted ones) |
|
|
87
91
|
| `forta_get_platform` | Full platform config |
|
|
88
92
|
| `forta_create_platform` | Register a platform (returns `client_secret` once) |
|
|
89
93
|
| `forta_update_platform` | Update platform config |
|
|
90
|
-
| `forta_delete_platform` |
|
|
94
|
+
| `forta_delete_platform` | Soft-delete a platform and revoke its grants ⚠️ |
|
|
95
|
+
| `forta_restore_platform` | Restore a deleted platform (grants stay revoked) |
|
|
91
96
|
| `forta_rotate_platform_credentials` | Rotate `client_secret` ⚠️ |
|
|
92
97
|
|
|
93
98
|
### Roles
|
|
@@ -103,10 +108,25 @@ Set `FORTA_ALLOW_SECRET_VALUES=1` to turn masking off if you genuinely need a wo
|
|
|
103
108
|
| Tool | Description |
|
|
104
109
|
|------|-------------|
|
|
105
110
|
| `forta_list_grants` | Who can access what, with which role — **start here for 403s** |
|
|
106
|
-
| `forta_create_grant` | Grant a user access to a platform |
|
|
111
|
+
| `forta_create_grant` | Grant a user access to a platform (role optional → default) |
|
|
112
|
+
| `forta_update_grant_role` | Move a grant to another role |
|
|
107
113
|
| `forta_revoke_grant` | Revoke a grant ⚠️ |
|
|
108
114
|
| `forta_check_grant` | Check a grant by `client_id`, exactly as a service sees it |
|
|
109
115
|
|
|
116
|
+
### Access requests
|
|
117
|
+
| Tool | Description |
|
|
118
|
+
|------|-------------|
|
|
119
|
+
| `forta_list_access_requests` | People asking for platforms marked requestable |
|
|
120
|
+
| `forta_decide_access_request` | Approve (creates the grant) or deny |
|
|
121
|
+
|
|
122
|
+
### Bulk, audit & settings
|
|
123
|
+
| Tool | Description |
|
|
124
|
+
|------|-------------|
|
|
125
|
+
| `forta_bulk` | Many admin changes in one request, all-or-nothing by default ⚠️ |
|
|
126
|
+
| `forta_list_activity` | Audit trail across every user and platform |
|
|
127
|
+
| `forta_get_overview` | Users by status, pending requests, sign-up mode, failed sign-ins |
|
|
128
|
+
| `forta_get_settings` / `forta_update_settings` | `signup_mode`: open, approval or closed |
|
|
129
|
+
|
|
110
130
|
### Tokens
|
|
111
131
|
| Tool | Description |
|
|
112
132
|
|------|-------------|
|
package/index.js
CHANGED
|
@@ -158,7 +158,7 @@ function body(obj) {
|
|
|
158
158
|
|
|
159
159
|
const server = new McpServer({
|
|
160
160
|
name: "forta",
|
|
161
|
-
version: "1.
|
|
161
|
+
version: "1.3.0",
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
// --- Health & identity ---
|
|
@@ -175,11 +175,51 @@ server.tool("forta_get_self", "Get the user this API token authenticates as —
|
|
|
175
175
|
|
|
176
176
|
// --- Users ---
|
|
177
177
|
|
|
178
|
-
server.tool("forta_list_users", "List
|
|
178
|
+
server.tool("forta_list_users", "List Forta users (id, uuid, email, status, is_super_admin, last_login_at). Filter with status (e.g. 'pending' for accounts awaiting approval) and q (name/email substring)", {
|
|
179
179
|
limit: z.number().optional().describe("Max users to return"),
|
|
180
180
|
offset: z.number().optional().describe("Pagination offset"),
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
status: z.enum(["active", "pending", "invited", "suspended", "disabled"]).optional().describe("Only users in this status"),
|
|
182
|
+
q: z.string().optional().describe("Substring of name, display name or email"),
|
|
183
|
+
}, async ({ limit, offset, status, q }) => {
|
|
184
|
+
const res = await api("GET", "/admin/users", { limit, offset, status, q });
|
|
185
|
+
return { content: text(res) };
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
server.tool("forta_create_user", "Create an account directly. New accounts have no grants. Without a password the user can sign in only via Google/Apple with this (verified) address", {
|
|
189
|
+
name: z.string().describe("Full name"),
|
|
190
|
+
email: z.string().describe("Email address"),
|
|
191
|
+
password: z.string().optional().describe("Initial password, 8–72 characters"),
|
|
192
|
+
status: z.enum(["active", "pending", "invited", "suspended", "disabled"]).optional().describe("Default active"),
|
|
193
|
+
email_verified: z.boolean().optional().describe("Mark the email verified"),
|
|
194
|
+
is_super_admin: z.boolean().optional().describe("Make a Forta super admin"),
|
|
195
|
+
}, async (args) => {
|
|
196
|
+
const res = await api("POST", "/admin/users", null, body(args));
|
|
197
|
+
return { content: text(res) };
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
server.tool("forta_revoke_user_sessions", "Sign a user out of every platform now (revokes refresh-token families, denylists their access tokens, queues back-channel logouts). The account and its grants are unchanged", {
|
|
201
|
+
id: z.number().describe("User ID"),
|
|
202
|
+
}, async ({ id }) => {
|
|
203
|
+
const res = await api("POST", `/admin/users/${id}/revoke-sessions`);
|
|
204
|
+
return { content: text(res) };
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
server.tool("forta_get_user_activity", "A user's activity trail across every platform: sign-ins (and failures), apps opened (access.sign_in, by platform_id), refusals, admin changes to them, and events platforms reported. Newest first; page with before_id", {
|
|
208
|
+
id: z.number().describe("User ID"),
|
|
209
|
+
category: z.enum(["auth", "access", "admin", "platform"]).optional(),
|
|
210
|
+
outcome: z.enum(["success", "failure", "denied"]).optional(),
|
|
211
|
+
platform_id: z.number().optional(),
|
|
212
|
+
limit: z.number().optional().describe("Max events (default 50, max 500)"),
|
|
213
|
+
before_id: z.number().optional().describe("next_before_id from the previous page"),
|
|
214
|
+
}, async ({ id, ...params }) => {
|
|
215
|
+
const res = await api("GET", `/admin/users/${id}/activity`, params);
|
|
216
|
+
return { content: text(res) };
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
server.tool("forta_get_user_platform_usage", "When a user last signed in to each platform, with a count — answers 'does this person still use X?' before revoking", {
|
|
220
|
+
id: z.number().describe("User ID"),
|
|
221
|
+
}, async ({ id }) => {
|
|
222
|
+
const res = await api("GET", `/admin/users/${id}/platform-usage`);
|
|
183
223
|
return { content: text(res) };
|
|
184
224
|
});
|
|
185
225
|
|
|
@@ -197,8 +237,8 @@ server.tool("forta_update_user", "Update a user. Only the fields you pass are ch
|
|
|
197
237
|
email: z.string().optional().describe("Email address (normalized to lowercase)"),
|
|
198
238
|
email_verified: z.boolean().optional().describe("Mark the email verified"),
|
|
199
239
|
profile_image_url: z.string().optional().describe("Profile image URL"),
|
|
200
|
-
status: z.enum(["active", "
|
|
201
|
-
is_super_admin: z.boolean().optional().describe("Grant or revoke Forta super-admin"),
|
|
240
|
+
status: z.enum(["active", "pending", "invited", "suspended", "disabled"]).optional().describe("Account status. 'pending' = awaiting approval. Leaving 'active' ends the user's sessions. Use forta_delete_user to delete. An administrator cannot change their own status"),
|
|
241
|
+
is_super_admin: z.boolean().optional().describe("Grant or revoke Forta super-admin (you cannot revoke your own)"),
|
|
202
242
|
password: z.string().optional().describe("Set a new password for the user's local identity"),
|
|
203
243
|
}, async ({ id, ...fields }) => {
|
|
204
244
|
const res = await api("PUT", `/admin/users/${id}`, null, body(fields));
|
|
@@ -214,8 +254,10 @@ server.tool("forta_delete_user", "Soft-delete a user (sets deleted_at). The user
|
|
|
214
254
|
|
|
215
255
|
// --- Platforms ---
|
|
216
256
|
|
|
217
|
-
server.tool("forta_list_platforms", "List
|
|
218
|
-
|
|
257
|
+
server.tool("forta_list_platforms", "List registered OAuth platforms (client_id, base_url, type, status, requestable). include_deleted adds soft-deleted ones (deleted: true), which can be restored", {
|
|
258
|
+
include_deleted: z.boolean().optional().describe("Include soft-deleted platforms"),
|
|
259
|
+
}, async ({ include_deleted }) => {
|
|
260
|
+
const res = await api("GET", "/admin/platforms", include_deleted ? { include_deleted: "true" } : null);
|
|
219
261
|
return { content: text(res) };
|
|
220
262
|
});
|
|
221
263
|
|
|
@@ -242,6 +284,7 @@ server.tool("forta_create_platform", "Register a new OAuth platform. Returns the
|
|
|
242
284
|
position: z.number().optional().describe("Sort position in the launcher"),
|
|
243
285
|
category: z.string().optional().describe("Launcher category"),
|
|
244
286
|
health_check: z.string().optional().describe("Health check URL"),
|
|
287
|
+
requestable: z.boolean().optional().describe("Let signed-in users without access see it and request access (default false)"),
|
|
245
288
|
}, async (args) => {
|
|
246
289
|
const res = await api("POST", "/admin/platforms", null, body(args));
|
|
247
290
|
return { content: text(res) };
|
|
@@ -264,19 +307,27 @@ server.tool("forta_update_platform", "Update a platform's configuration. Only th
|
|
|
264
307
|
position: z.number().optional(),
|
|
265
308
|
category: z.string().optional(),
|
|
266
309
|
health_check: z.string().optional(),
|
|
310
|
+
requestable: z.boolean().optional().describe("Let signed-in users without access request it"),
|
|
267
311
|
status: z.enum(["active", "disabled"]).optional().describe("Disabling blocks all new logins to this platform"),
|
|
268
312
|
}, async ({ id, ...fields }) => {
|
|
269
313
|
const res = await api("PUT", `/admin/platforms/${id}`, null, body(fields));
|
|
270
314
|
return { content: text(res) };
|
|
271
315
|
});
|
|
272
316
|
|
|
273
|
-
server.tool("forta_delete_platform", "
|
|
317
|
+
server.tool("forta_delete_platform", "Soft-delete a platform and revoke every grant on it, in one transaction. All its users lose access. Restorable with forta_restore_platform (grants stay revoked). Destructive — confirm with forta_get_platform first", {
|
|
274
318
|
id: z.number().describe("Platform ID"),
|
|
275
319
|
}, async ({ id }) => {
|
|
276
320
|
const res = await api("DELETE", `/admin/platforms/${id}`);
|
|
277
321
|
return { content: text(res) };
|
|
278
322
|
});
|
|
279
323
|
|
|
324
|
+
server.tool("forta_restore_platform", "Restore a soft-deleted platform (status back to active). Its grants stay revoked — re-grant deliberately", {
|
|
325
|
+
id: z.number().describe("Platform ID (from forta_list_platforms with include_deleted)"),
|
|
326
|
+
}, async ({ id }) => {
|
|
327
|
+
const res = await api("POST", `/admin/platforms/${id}/restore`);
|
|
328
|
+
return { content: text(res) };
|
|
329
|
+
});
|
|
330
|
+
|
|
280
331
|
server.tool("forta_rotate_platform_credentials", "Rotate a platform's client_secret. The old secret stops working immediately — the platform is down until its deployed config is updated. Returns the new secret once", {
|
|
281
332
|
id: z.number().describe("Platform ID"),
|
|
282
333
|
}, async ({ id }) => {
|
|
@@ -320,7 +371,7 @@ server.tool("forta_update_role", "Update a role's name or permissions. Changes a
|
|
|
320
371
|
return { content: text(res) };
|
|
321
372
|
});
|
|
322
373
|
|
|
323
|
-
server.tool("forta_delete_role", "Delete a role.
|
|
374
|
+
server.tool("forta_delete_role", "Delete a role. Refused (409) while anyone actively holds it — move them first with forta_update_grant_role. Revoked grants pointing at it are moved to another of the platform's roles", {
|
|
324
375
|
platform_id: z.number().describe("Platform ID"),
|
|
325
376
|
role_id: z.number().describe("Role ID"),
|
|
326
377
|
}, async ({ platform_id, role_id }) => {
|
|
@@ -338,12 +389,20 @@ server.tool("forta_list_grants", "List access grants — which users can access
|
|
|
338
389
|
return { content: text(res) };
|
|
339
390
|
});
|
|
340
391
|
|
|
341
|
-
server.tool("forta_create_grant", "Grant a user access to a platform
|
|
392
|
+
server.tool("forta_create_grant", "Grant a user access to a platform. role_id omitted or 0 = the platform's default (lowest-id) role. Reactivates a previously revoked grant for the same pair", {
|
|
342
393
|
user_id: z.number().describe("User ID"),
|
|
343
394
|
platform_id: z.number().describe("Platform ID"),
|
|
344
|
-
role_id: z.number().describe("Role ID to assign"),
|
|
395
|
+
role_id: z.number().optional().describe("Role ID to assign (default: the platform's default role)"),
|
|
345
396
|
}, async ({ user_id, platform_id, role_id }) => {
|
|
346
|
-
const res = await api("POST", "/admin/grants", null, { user_id, platform_id, role_id });
|
|
397
|
+
const res = await api("POST", "/admin/grants", null, { user_id, platform_id, role_id: role_id ?? 0 });
|
|
398
|
+
return { content: text(res) };
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
server.tool("forta_update_grant_role", "Move an active grant to another role on the same platform. Access tokens already issued keep the old role until they expire (10 min)", {
|
|
402
|
+
id: z.number().describe("Grant ID (from forta_list_grants)"),
|
|
403
|
+
role_id: z.number().describe("New role ID (from forta_list_roles)"),
|
|
404
|
+
}, async ({ id, role_id }) => {
|
|
405
|
+
const res = await api("PUT", `/admin/grants/${id}`, null, { role_id });
|
|
347
406
|
return { content: text(res) };
|
|
348
407
|
});
|
|
349
408
|
|
|
@@ -361,6 +420,72 @@ server.tool("forta_check_grant", "Check whether the token's user holds an active
|
|
|
361
420
|
return { content: text(res) };
|
|
362
421
|
});
|
|
363
422
|
|
|
423
|
+
// --- Access requests ---
|
|
424
|
+
|
|
425
|
+
server.tool("forta_list_access_requests", "List access requests — users asking for a grant on a platform marked requestable. Pending first", {
|
|
426
|
+
status: z.enum(["pending", "approved", "denied", "cancelled"]).optional(),
|
|
427
|
+
user_id: z.number().optional(),
|
|
428
|
+
platform_id: z.number().optional(),
|
|
429
|
+
}, async (params) => {
|
|
430
|
+
const res = await api("GET", "/admin/access-requests", params);
|
|
431
|
+
return { content: text(res) };
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
server.tool("forta_decide_access_request", "Approve (creates the grant) or deny a pending access request. The requester sees the decision and note on their dashboard", {
|
|
435
|
+
id: z.number().describe("Access request ID"),
|
|
436
|
+
decision: z.enum(["approve", "deny"]),
|
|
437
|
+
role_id: z.number().optional().describe("Approve only: role to grant (default: the requested role, else the platform default)"),
|
|
438
|
+
note: z.string().optional().describe("Shown to the requester"),
|
|
439
|
+
}, async ({ id, decision, role_id, note }) => {
|
|
440
|
+
const res = await api("POST", `/admin/access-requests/${id}/${decision}`, null, body({ role_id, note }));
|
|
441
|
+
return { content: text(res) };
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// --- Bulk, activity, settings ---
|
|
445
|
+
|
|
446
|
+
server.tool("forta_bulk", "Run many admin changes in one request — the dashboard's action cart. atomic (default true) runs them in one transaction: the first failure rolls back everything. Each op goes through the same logic as its single endpoint (session revocation, back-channel logout, audit). Ops: grant.create {user_id, platform_id, role_id?} · grant.revoke {grant_id} · grant.update_role {grant_id, role_id} · user.update {user_id, ...fields} · user.delete {user_id} · user.revoke_sessions {user_id} · platform.update {platform_id, ...fields} · platform.delete {platform_id} · platform.restore {platform_id} · access_request.approve {request_id, role_id?, note?} · access_request.deny {request_id, note?}. Max 250 operations", {
|
|
447
|
+
operations: z.array(z.object({
|
|
448
|
+
op: z.enum(["grant.create", "grant.revoke", "grant.update_role", "user.update", "user.delete", "user.revoke_sessions", "platform.update", "platform.delete", "platform.restore", "access_request.approve", "access_request.deny"]),
|
|
449
|
+
args: z.record(z.any()),
|
|
450
|
+
})).min(1).max(250),
|
|
451
|
+
atomic: z.boolean().optional().describe("All or nothing (default true)"),
|
|
452
|
+
}, async ({ operations, atomic }) => {
|
|
453
|
+
const res = await api("POST", "/admin/bulk", null, { operations, atomic: atomic ?? true });
|
|
454
|
+
return { content: text(res) };
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
server.tool("forta_list_activity", "The audit trail across every user and platform: sign-ins and failures, apps opened, refusals, admin changes, platform-reported events. Persisted in Forta (survives Monitor's 30-day retention). Newest first; page with before_id", {
|
|
458
|
+
user_id: z.number().optional().describe("Subject or actor"),
|
|
459
|
+
platform_id: z.number().optional(),
|
|
460
|
+
category: z.enum(["auth", "access", "admin", "platform"]).optional(),
|
|
461
|
+
action: z.string().optional().describe("Action prefix, e.g. 'grant.' or 'auth.login'"),
|
|
462
|
+
outcome: z.enum(["success", "failure", "denied"]).optional(),
|
|
463
|
+
from: z.string().optional().describe("RFC 3339"),
|
|
464
|
+
to: z.string().optional().describe("RFC 3339"),
|
|
465
|
+
limit: z.number().optional().describe("Default 50, max 500"),
|
|
466
|
+
before_id: z.number().optional(),
|
|
467
|
+
}, async (params) => {
|
|
468
|
+
const res = await api("GET", "/admin/activity", params);
|
|
469
|
+
return { content: text(res) };
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
server.tool("forta_get_overview", "Admin headline counts: users by status, pending access requests, sign-up mode, failed sign-ins in the last 24h", {}, async () => {
|
|
473
|
+
const res = await api("GET", "/admin/overview");
|
|
474
|
+
return { content: text(res) };
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
server.tool("forta_get_settings", "Service-wide settings (signup_mode: open | approval | closed)", {}, async () => {
|
|
478
|
+
const res = await api("GET", "/admin/settings");
|
|
479
|
+
return { content: text(res) };
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
server.tool("forta_update_settings", "Change service-wide settings. signup_mode: open (new accounts active, no grants), approval (new accounts pending until approved), closed (no new accounts; first-time Google/Apple sign-ins refused)", {
|
|
483
|
+
signup_mode: z.enum(["open", "approval", "closed"]).optional(),
|
|
484
|
+
}, async (args) => {
|
|
485
|
+
const res = await api("PUT", "/admin/settings", null, body(args));
|
|
486
|
+
return { content: text(res) };
|
|
487
|
+
});
|
|
488
|
+
|
|
364
489
|
// --- Token introspection ---
|
|
365
490
|
|
|
366
491
|
server.tool("forta_introspect_token", "Introspect an access JWT (RFC 7662) — returns active, sub, exp, iat, token_type. Answers 'is the token this service rejected expired, malformed, or lacking a grant?'. Requires the calling platform's client credentials: introspection is scoped to that platform, and a token valid elsewhere returns active=false here. Only works for access JWTs — opaque frt_ API tokens always report active=false, so use forta_list_api_tokens to inspect those instead", {
|