forta-mcp 1.0.0 → 1.0.1

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.
Files changed (2) hide show
  1. package/index.js +19 -12
  2. package/package.json +5 -2
package/index.js CHANGED
@@ -64,7 +64,7 @@ if (!API_URL || !API_TOKEN) {
64
64
 
65
65
  // --- HTTP helper ---
66
66
 
67
- async function api(method, path, params, body) {
67
+ async function api(method, path, params, body, opts_ = {}) {
68
68
  const url = new URL(path, API_URL);
69
69
  if (params) {
70
70
  for (const [k, v] of Object.entries(params)) {
@@ -73,12 +73,16 @@ async function api(method, path, params, body) {
73
73
  }
74
74
  const opts = {
75
75
  method,
76
- headers: {
77
- Authorization: `Bearer ${API_TOKEN}`,
78
- },
76
+ headers: {},
79
77
  signal: AbortSignal.timeout(30000),
80
78
  };
81
- if (body) {
79
+ // The OAuth endpoints authenticate with client credentials, not the API
80
+ // token, and reject a stray Authorization header.
81
+ if (!opts_.noAuth) opts.headers.Authorization = `Bearer ${API_TOKEN}`;
82
+ if (body && opts_.form) {
83
+ opts.headers["Content-Type"] = "application/x-www-form-urlencoded";
84
+ opts.body = new URLSearchParams(body).toString();
85
+ } else if (body) {
82
86
  opts.headers["Content-Type"] = "application/json";
83
87
  opts.body = JSON.stringify(body);
84
88
  }
@@ -279,9 +283,9 @@ server.tool("forta_delete_role", "Delete a role. Users holding it lose its permi
279
283
 
280
284
  // --- Grants ---
281
285
 
282
- server.tool("forta_list_grants", "List access grants — which users can access which platforms, and with what role. Filter by platform_id or user_id; omit both to list all. This is the first place to look when a user gets a 403 from an appleby.cloud service", {
283
- platform_id: z.number().optional().describe("Filter to one platform"),
284
- user_id: z.number().optional().describe("Filter to one user"),
286
+ server.tool("forta_list_grants", "List access grants — which users can access which platforms, and with what role. The first place to look when a user gets a 403 from an appleby.cloud service. Filters are mutually exclusive, not combined: pass platform_id OR user_id, or neither to list all. Passing both silently ignores user_id", {
287
+ platform_id: z.number().optional().describe("Filter to one platform (takes precedence over user_id)"),
288
+ user_id: z.number().optional().describe("Filter to one user (ignored if platform_id is also given)"),
285
289
  }, async ({ platform_id, user_id }) => {
286
290
  const res = await api("GET", "/admin/grants", { platform_id, user_id });
287
291
  return { content: text(res) };
@@ -312,10 +316,13 @@ server.tool("forta_check_grant", "Check whether the token's user holds an active
312
316
 
313
317
  // --- Token introspection ---
314
318
 
315
- server.tool("forta_introspect_token", "Introspect an access token (RFC 7662) — returns active, sub, exp, iat, client_id, token_type. Use this to diagnose whether a token a service rejected is expired, malformed, or bound to a different platform", {
316
- token: z.string().describe("The access token to introspect"),
317
- }, async ({ token }) => {
318
- const res = await api("POST", "/oauth/introspect", null, { token });
319
+ 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", {
320
+ token: z.string().describe("The access JWT to introspect"),
321
+ client_id: z.string().describe("Calling platform's client_id (from forta_list_platforms)"),
322
+ client_secret: z.string().describe("Calling platform's client_secret — shown only when the platform is created or its credentials are rotated"),
323
+ }, async ({ token, client_id, client_secret }) => {
324
+ const res = await api("POST", "/oauth/introspect", null,
325
+ { token, client_id, client_secret }, { form: true, noAuth: true });
319
326
  return { content: text(res) };
320
327
  });
321
328
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forta-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "MCP server for the Forta OAuth2 identity provider",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -13,7 +13,10 @@
13
13
  "keywords": [
14
14
  "mcp",
15
15
  "forta",
16
- "auth", "oauth", "identity", "sso",
16
+ "auth",
17
+ "oauth",
18
+ "identity",
19
+ "sso",
17
20
  "model-context-protocol"
18
21
  ],
19
22
  "author": "Aiden Appleby",