forta-mcp 1.1.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 +32 -6
- package/README.md +26 -6
- package/index.js +140 -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,12 +113,29 @@ 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 |
|
|
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
|
|
|
124
|
+
### Setting `backchannel_logout_uri` — order matters
|
|
125
|
+
|
|
126
|
+
Forta POSTs a signed `logout_token` to this URL the instant a grant is revoked, so the consumer
|
|
127
|
+
ends the session immediately instead of waiting up to five minutes for its introspection poll.
|
|
128
|
+
|
|
129
|
+
⚠️ **Only set it once the platform actually serves the endpoint.** Until then every notification
|
|
130
|
+
404s, is retried six times on a backoff, and is marked `exhausted` — which looks in the logs like
|
|
131
|
+
a broken receiver rather than one that was never built. Deploy first, register second.
|
|
132
|
+
|
|
133
|
+
Pass `null` to clear it. Absent is the normal state for a platform that has not implemented the
|
|
134
|
+
receiver, and it is never an error — such a platform is simply skipped by the sender.
|
|
135
|
+
|
|
136
|
+
`https` only; the emitter refuses anything else at send time and logs
|
|
137
|
+
`FORTA_BACKCHANNEL_BAD_ENDPOINT`.
|
|
138
|
+
|
|
122
139
|
### Two behaviours that are easy to get wrong
|
|
123
140
|
|
|
124
141
|
**1. `/oauth/introspect` is a resource-server endpoint, not an admin one.** It requires the
|
|
@@ -148,6 +165,12 @@ description says so; do not "fix" it by sending both.
|
|
|
148
165
|
|
|
149
166
|
- **Published to npm** as `forta-mcp` (public). `npx -y` resolves the latest, so **publishing
|
|
150
167
|
is deployment**.
|
|
168
|
+
- **Bump the version in TWO places before publishing** — `package.json` *and* the `McpServer({
|
|
169
|
+
version })` literal in `index.js`. npm refuses to republish an existing version, so forgetting
|
|
170
|
+
`package.json` fails loudly (`cannot publish over the previously published versions`), but
|
|
171
|
+
forgetting the one in `index.js` fails SILENTLY: the server then reports a version it is not,
|
|
172
|
+
and a client debugging "which build am I talking to" is misled by the only field that answers
|
|
173
|
+
that question.
|
|
151
174
|
- **Publishing requires 2FA via passkey** and must run from an interactive terminal — npm's web
|
|
152
175
|
auth flow needs a browser, and from a non-TTY subprocess it degrades to demanding an OTP a
|
|
153
176
|
passkey-only account cannot produce.
|
|
@@ -172,7 +195,8 @@ description says so; do not "fix" it by sending both.
|
|
|
172
195
|
- **`zod` is used but not declared** in `package.json`, resolving transitively through the MCP
|
|
173
196
|
SDK. Declaring it explicitly is the right fix, as a standalone change.
|
|
174
197
|
- Publishing is outward-facing and effectively irreversible (npm unpublish is restricted after
|
|
175
|
-
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*).
|
|
176
200
|
|
|
177
201
|
## Verification — always before "done"
|
|
178
202
|
|
|
@@ -198,5 +222,7 @@ Update this AGENTS.md in the same change when you:
|
|
|
198
222
|
- **Change `api()` or its `opts`** → update *How code is written here*.
|
|
199
223
|
- **Discover a `forta-api` behaviour that contradicts a tool description** → fix both, and
|
|
200
224
|
record it under *Two behaviours that are easy to get wrong* if it is non-obvious.
|
|
225
|
+
- **Add/change a tool parameter** → bump the minor version in BOTH places (see *Operations*) in
|
|
226
|
+
the same change, so the commit that adds the capability is the commit that can be published.
|
|
201
227
|
- **Publish** → note the version and what changed.
|
|
202
228
|
- Also keep `README.md`'s tool tables in sync.
|
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
|
|
|
@@ -233,6 +275,7 @@ server.tool("forta_create_platform", "Register a new OAuth platform. Returns the
|
|
|
233
275
|
description: z.string().optional().describe("Description"),
|
|
234
276
|
redirect_uris: z.array(z.string()).optional().describe("Allowed OAuth redirect URIs"),
|
|
235
277
|
logout_redirect_uris: z.array(z.string()).optional().describe("Allowed post-logout redirect URIs"),
|
|
278
|
+
backchannel_logout_uri: z.string().nullable().optional().describe("OIDC Back-Channel Logout 1.0 endpoint on the platform (https only). Forta POSTs a signed logout_token here the moment a grant is revoked, instead of the consumer waiting up to 5 minutes for its introspection poll. Pass null to clear. ⚠️ Only set this once the platform actually serves the endpoint — until then every notification 404s, retries 6 times and is marked exhausted"),
|
|
236
279
|
allowed_origins: z.array(z.string()).optional().describe("Allowed CORS origins"),
|
|
237
280
|
is_first_party: z.boolean().optional().describe("First-party app (skips the consent screen)"),
|
|
238
281
|
internal_resource: z.boolean().optional().describe("Internal resource, hidden from the app launcher"),
|
|
@@ -241,6 +284,7 @@ server.tool("forta_create_platform", "Register a new OAuth platform. Returns the
|
|
|
241
284
|
position: z.number().optional().describe("Sort position in the launcher"),
|
|
242
285
|
category: z.string().optional().describe("Launcher category"),
|
|
243
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)"),
|
|
244
288
|
}, async (args) => {
|
|
245
289
|
const res = await api("POST", "/admin/platforms", null, body(args));
|
|
246
290
|
return { content: text(res) };
|
|
@@ -254,6 +298,7 @@ server.tool("forta_update_platform", "Update a platform's configuration. Only th
|
|
|
254
298
|
platform_type: z.enum(["WEB", "SPA", "SERVICE", "MOBILE"]).optional(),
|
|
255
299
|
redirect_uris: z.array(z.string()).optional(),
|
|
256
300
|
logout_redirect_uris: z.array(z.string()).optional(),
|
|
301
|
+
backchannel_logout_uri: z.string().nullable().optional().describe("OIDC Back-Channel Logout 1.0 endpoint on the platform (https only). Forta POSTs a signed logout_token here the moment a grant is revoked, instead of the consumer waiting up to 5 minutes for its introspection poll. Pass null to clear. ⚠️ Only set this once the platform actually serves the endpoint — until then every notification 404s, retries 6 times and is marked exhausted"),
|
|
257
302
|
allowed_origins: z.array(z.string()).optional(),
|
|
258
303
|
is_first_party: z.boolean().optional(),
|
|
259
304
|
internal_resource: z.boolean().optional(),
|
|
@@ -262,19 +307,27 @@ server.tool("forta_update_platform", "Update a platform's configuration. Only th
|
|
|
262
307
|
position: z.number().optional(),
|
|
263
308
|
category: z.string().optional(),
|
|
264
309
|
health_check: z.string().optional(),
|
|
310
|
+
requestable: z.boolean().optional().describe("Let signed-in users without access request it"),
|
|
265
311
|
status: z.enum(["active", "disabled"]).optional().describe("Disabling blocks all new logins to this platform"),
|
|
266
312
|
}, async ({ id, ...fields }) => {
|
|
267
313
|
const res = await api("PUT", `/admin/platforms/${id}`, null, body(fields));
|
|
268
314
|
return { content: text(res) };
|
|
269
315
|
});
|
|
270
316
|
|
|
271
|
-
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", {
|
|
272
318
|
id: z.number().describe("Platform ID"),
|
|
273
319
|
}, async ({ id }) => {
|
|
274
320
|
const res = await api("DELETE", `/admin/platforms/${id}`);
|
|
275
321
|
return { content: text(res) };
|
|
276
322
|
});
|
|
277
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
|
+
|
|
278
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", {
|
|
279
332
|
id: z.number().describe("Platform ID"),
|
|
280
333
|
}, async ({ id }) => {
|
|
@@ -318,7 +371,7 @@ server.tool("forta_update_role", "Update a role's name or permissions. Changes a
|
|
|
318
371
|
return { content: text(res) };
|
|
319
372
|
});
|
|
320
373
|
|
|
321
|
-
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", {
|
|
322
375
|
platform_id: z.number().describe("Platform ID"),
|
|
323
376
|
role_id: z.number().describe("Role ID"),
|
|
324
377
|
}, async ({ platform_id, role_id }) => {
|
|
@@ -336,12 +389,20 @@ server.tool("forta_list_grants", "List access grants — which users can access
|
|
|
336
389
|
return { content: text(res) };
|
|
337
390
|
});
|
|
338
391
|
|
|
339
|
-
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", {
|
|
340
393
|
user_id: z.number().describe("User ID"),
|
|
341
394
|
platform_id: z.number().describe("Platform ID"),
|
|
342
|
-
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)"),
|
|
343
396
|
}, async ({ user_id, platform_id, role_id }) => {
|
|
344
|
-
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 });
|
|
345
406
|
return { content: text(res) };
|
|
346
407
|
});
|
|
347
408
|
|
|
@@ -359,6 +420,72 @@ server.tool("forta_check_grant", "Check whether the token's user holds an active
|
|
|
359
420
|
return { content: text(res) };
|
|
360
421
|
});
|
|
361
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
|
+
|
|
362
489
|
// --- Token introspection ---
|
|
363
490
|
|
|
364
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", {
|