@clavex/mcp-server 1.0.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 +107 -0
- package/dist/client.d.ts +38 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +121 -0
- package/dist/client.js.map +1 -0
- package/dist/helpers.d.ts +14 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +44 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/access_reviews.d.ts +3 -0
- package/dist/tools/access_reviews.d.ts.map +1 -0
- package/dist/tools/access_reviews.js +131 -0
- package/dist/tools/access_reviews.js.map +1 -0
- package/dist/tools/ai.d.ts +3 -0
- package/dist/tools/ai.d.ts.map +1 -0
- package/dist/tools/ai.js +443 -0
- package/dist/tools/ai.js.map +1 -0
- package/dist/tools/ciba.d.ts +3 -0
- package/dist/tools/ciba.d.ts.map +1 -0
- package/dist/tools/ciba.js +85 -0
- package/dist/tools/ciba.js.map +1 -0
- package/dist/tools/clients.d.ts +3 -0
- package/dist/tools/clients.d.ts.map +1 -0
- package/dist/tools/clients.js +124 -0
- package/dist/tools/clients.js.map +1 -0
- package/dist/tools/developer.d.ts +3 -0
- package/dist/tools/developer.d.ts.map +1 -0
- package/dist/tools/developer.js +580 -0
- package/dist/tools/developer.js.map +1 -0
- package/dist/tools/fga.d.ts +3 -0
- package/dist/tools/fga.d.ts.map +1 -0
- package/dist/tools/fga.js +126 -0
- package/dist/tools/fga.js.map +1 -0
- package/dist/tools/groups.d.ts +3 -0
- package/dist/tools/groups.d.ts.map +1 -0
- package/dist/tools/groups.js +135 -0
- package/dist/tools/groups.js.map +1 -0
- package/dist/tools/idps.d.ts +3 -0
- package/dist/tools/idps.d.ts.map +1 -0
- package/dist/tools/idps.js +98 -0
- package/dist/tools/idps.js.map +1 -0
- package/dist/tools/orgs.d.ts +3 -0
- package/dist/tools/orgs.d.ts.map +1 -0
- package/dist/tools/orgs.js +90 -0
- package/dist/tools/orgs.js.map +1 -0
- package/dist/tools/pam.d.ts +3 -0
- package/dist/tools/pam.d.ts.map +1 -0
- package/dist/tools/pam.js +238 -0
- package/dist/tools/pam.js.map +1 -0
- package/dist/tools/policies.d.ts +3 -0
- package/dist/tools/policies.d.ts.map +1 -0
- package/dist/tools/policies.js +173 -0
- package/dist/tools/policies.js.map +1 -0
- package/dist/tools/ssf.d.ts +3 -0
- package/dist/tools/ssf.d.ts.map +1 -0
- package/dist/tools/ssf.js +65 -0
- package/dist/tools/ssf.js.map +1 -0
- package/dist/tools/users.d.ts +3 -0
- package/dist/tools/users.d.ts.map +1 -0
- package/dist/tools/users.js +144 -0
- package/dist/tools/users.js.map +1 -0
- package/package.json +48 -0
- package/src/client.ts +148 -0
- package/src/helpers.ts +45 -0
- package/src/index.ts +63 -0
- package/src/tools/access_reviews.ts +163 -0
- package/src/tools/ai.ts +581 -0
- package/src/tools/ciba.ts +109 -0
- package/src/tools/clients.ts +168 -0
- package/src/tools/developer.ts +661 -0
- package/src/tools/fga.ts +148 -0
- package/src/tools/groups.ts +200 -0
- package/src/tools/idps.ts +137 -0
- package/src/tools/orgs.ts +119 -0
- package/src/tools/pam.ts +285 -0
- package/src/tools/policies.ts +233 -0
- package/src/tools/ssf.ts +82 -0
- package/src/tools/users.ts +202 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getClient } from "../client.js";
|
|
4
|
+
import { handleError, mdTable } from "../helpers.js";
|
|
5
|
+
|
|
6
|
+
export function registerCIBATools(server: McpServer): void {
|
|
7
|
+
// ── List pending CIBA requests ─────────────────────────────────────────────
|
|
8
|
+
server.registerTool(
|
|
9
|
+
"clavex_ciba_list_pending",
|
|
10
|
+
{
|
|
11
|
+
title: "List Pending CIBA Requests",
|
|
12
|
+
description: `List all pending Client-Initiated Backchannel Authentication (CIBA) requests for an organization.
|
|
13
|
+
|
|
14
|
+
CIBA decouples the authentication device from the consuming device: a client (e.g. call-centre app)
|
|
15
|
+
initiates a backchannel auth; the end-user must approve on a separate channel (e.g. mobile push).
|
|
16
|
+
|
|
17
|
+
Returns: auth_req_id, login_hint (user email), binding_message, client_id, expires_at.
|
|
18
|
+
|
|
19
|
+
Use when:
|
|
20
|
+
"show pending CIBA requests for org <id>"
|
|
21
|
+
"which users have pending backchannel auth requests?"
|
|
22
|
+
"approva la richiesta CIBA per Mario Rossi" ← first call this to get the auth_req_id`,
|
|
23
|
+
inputSchema: {
|
|
24
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
25
|
+
},
|
|
26
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
27
|
+
},
|
|
28
|
+
async ({ org_id }) =>
|
|
29
|
+
handleError(async () => {
|
|
30
|
+
const reqs = await getClient().get<Array<Record<string, unknown>>>(
|
|
31
|
+
getClient().orgPath(org_id, "/ciba/pending"),
|
|
32
|
+
);
|
|
33
|
+
if (!Array.isArray(reqs) || reqs.length === 0) return "_No pending CIBA requests._";
|
|
34
|
+
return mdTable(reqs, ["auth_req_id", "login_hint", "binding_message", "client_id", "expires_at", "created_at"]);
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// ── Approve CIBA request ───────────────────────────────────────────────────
|
|
39
|
+
server.registerTool(
|
|
40
|
+
"clavex_ciba_approve",
|
|
41
|
+
{
|
|
42
|
+
title: "Approve CIBA Request",
|
|
43
|
+
description: `Approve a pending CIBA (backchannel authentication) request on behalf of an admin.
|
|
44
|
+
|
|
45
|
+
After approval the client polling the token endpoint will receive an access + ID token.
|
|
46
|
+
The user_id MUST be the UUID of the authenticated end-user whose identity is being asserted.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
- org_id: Organization UUID
|
|
50
|
+
- auth_req_id: The auth_req_id returned by the backchannel authorize endpoint (use clavex_ciba_list_pending)
|
|
51
|
+
- user_id: UUID of the user whose identity is confirmed
|
|
52
|
+
|
|
53
|
+
Returns: confirmation JSON { status: "approved" }.
|
|
54
|
+
|
|
55
|
+
Use when:
|
|
56
|
+
"approva la richiesta CIBA per Mario Rossi" (after finding auth_req_id via list_pending)
|
|
57
|
+
"approve CIBA request <auth_req_id> for user <user_id>"
|
|
58
|
+
"confirm backchannel authentication"`,
|
|
59
|
+
inputSchema: {
|
|
60
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
61
|
+
auth_req_id: z.string().describe("CIBA auth_req_id to approve"),
|
|
62
|
+
user_id: z.string().uuid().describe("UUID of the user whose identity is confirmed"),
|
|
63
|
+
},
|
|
64
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
65
|
+
},
|
|
66
|
+
async ({ org_id, auth_req_id, user_id }) =>
|
|
67
|
+
handleError(async () => {
|
|
68
|
+
const result = await getClient().post<Record<string, unknown>>(
|
|
69
|
+
getClient().orgPath(org_id, `/ciba/${auth_req_id}/approve`),
|
|
70
|
+
{ user_id },
|
|
71
|
+
);
|
|
72
|
+
return `CIBA request ${auth_req_id} approved for user ${user_id}.\n\n${JSON.stringify(result, null, 2)}`;
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// ── Deny CIBA request ──────────────────────────────────────────────────────
|
|
77
|
+
server.registerTool(
|
|
78
|
+
"clavex_ciba_deny",
|
|
79
|
+
{
|
|
80
|
+
title: "Deny CIBA Request",
|
|
81
|
+
description: `Deny a pending CIBA (backchannel authentication) request.
|
|
82
|
+
|
|
83
|
+
After denial the polling client receives an access_denied error on the next token request.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
- org_id: Organization UUID
|
|
87
|
+
- auth_req_id: The auth_req_id to deny (use clavex_ciba_list_pending to find it)
|
|
88
|
+
|
|
89
|
+
Returns: confirmation JSON { status: "denied" }.
|
|
90
|
+
|
|
91
|
+
Use when:
|
|
92
|
+
"nega la richiesta CIBA <auth_req_id>"
|
|
93
|
+
"deny CIBA request — the user did not initiate this"
|
|
94
|
+
"reject backchannel auth for suspicious request"`,
|
|
95
|
+
inputSchema: {
|
|
96
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
97
|
+
auth_req_id: z.string().describe("CIBA auth_req_id to deny"),
|
|
98
|
+
},
|
|
99
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
100
|
+
},
|
|
101
|
+
async ({ org_id, auth_req_id }) =>
|
|
102
|
+
handleError(async () => {
|
|
103
|
+
const result = await getClient().post<Record<string, unknown>>(
|
|
104
|
+
getClient().orgPath(org_id, `/ciba/${auth_req_id}/deny`),
|
|
105
|
+
);
|
|
106
|
+
return `CIBA request ${auth_req_id} denied.\n\n${JSON.stringify(result, null, 2)}`;
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getClient } from "../client.js";
|
|
4
|
+
import { handleError, mdTable } from "../helpers.js";
|
|
5
|
+
|
|
6
|
+
export function registerClientTools(server: McpServer): void {
|
|
7
|
+
// ── List clients ───────────────────────────────────────────────────────────
|
|
8
|
+
server.registerTool(
|
|
9
|
+
"clavex_list_clients",
|
|
10
|
+
{
|
|
11
|
+
title: "List OIDC Clients",
|
|
12
|
+
description: `List all OIDC/OAuth2 clients registered in an organization.
|
|
13
|
+
|
|
14
|
+
Returns: client_id, name, grant_types, redirect_uris, is_active.
|
|
15
|
+
|
|
16
|
+
Use when: "show me all apps registered in org <id>", "list OIDC clients for acme".`,
|
|
17
|
+
inputSchema: {
|
|
18
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
19
|
+
},
|
|
20
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
21
|
+
},
|
|
22
|
+
async ({ org_id }) =>
|
|
23
|
+
handleError(async () => {
|
|
24
|
+
const clients = await getClient().get<Array<Record<string, unknown>>>(
|
|
25
|
+
getClient().orgPath(org_id, "/clients"),
|
|
26
|
+
);
|
|
27
|
+
return mdTable(clients, ["client_id", "name", "grant_types", "is_active"]);
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// ── Get client ─────────────────────────────────────────────────────────────
|
|
32
|
+
server.registerTool(
|
|
33
|
+
"clavex_get_client",
|
|
34
|
+
{
|
|
35
|
+
title: "Get OIDC Client",
|
|
36
|
+
description: `Get full details of an OIDC/OAuth2 client registration.
|
|
37
|
+
|
|
38
|
+
Returns full client JSON including redirect URIs, scopes, grant types, and settings.
|
|
39
|
+
|
|
40
|
+
Note: client_secret is never returned after creation — use clavex_rotate_client_secret to reset it.`,
|
|
41
|
+
inputSchema: {
|
|
42
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
43
|
+
client_id: z.string().describe("OIDC client_id"),
|
|
44
|
+
},
|
|
45
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
46
|
+
},
|
|
47
|
+
async ({ org_id, client_id }) =>
|
|
48
|
+
handleError(async () => {
|
|
49
|
+
const client = await getClient().get<Record<string, unknown>>(
|
|
50
|
+
getClient().orgPath(org_id, `/clients/${client_id}`),
|
|
51
|
+
);
|
|
52
|
+
return JSON.stringify(client, null, 2);
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// ── Create client ──────────────────────────────────────────────────────────
|
|
57
|
+
server.registerTool(
|
|
58
|
+
"clavex_create_client",
|
|
59
|
+
{
|
|
60
|
+
title: "Create OIDC Client",
|
|
61
|
+
description: `Register a new OIDC/OAuth2 client in an organization.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
- org_id: Organization UUID
|
|
65
|
+
- name: Human-readable client name (e.g. "My Web App")
|
|
66
|
+
- redirect_uris: Array of allowed redirect URIs (must be https:// in production)
|
|
67
|
+
- grant_types: Array of allowed grant types. Common values:
|
|
68
|
+
["authorization_code"] — standard web app
|
|
69
|
+
["authorization_code", "refresh_token"] — web app with refresh
|
|
70
|
+
["client_credentials"] — machine-to-machine
|
|
71
|
+
- response_types (optional): defaults to ["code"]
|
|
72
|
+
- scopes (optional): allowed scopes, defaults to ["openid", "profile", "email"]
|
|
73
|
+
- is_public (optional): true for SPAs/mobile apps (no client_secret)
|
|
74
|
+
- post_logout_redirect_uris (optional): allowed logout redirect URIs
|
|
75
|
+
|
|
76
|
+
Returns: Created client JSON including the one-time plaintext client_secret.
|
|
77
|
+
IMPORTANT: Save the client_secret immediately — it will never be shown again.
|
|
78
|
+
|
|
79
|
+
Use when: "register Grafana as an OIDC client", "add a new web app to acme org".`,
|
|
80
|
+
inputSchema: {
|
|
81
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
82
|
+
name: z.string().describe("Client display name"),
|
|
83
|
+
redirect_uris: z.array(z.string().url()).min(1).describe("Allowed redirect URIs"),
|
|
84
|
+
grant_types: z.array(z.string()).describe('e.g. ["authorization_code","refresh_token"]'),
|
|
85
|
+
response_types: z.array(z.string()).optional().describe('e.g. ["code"]'),
|
|
86
|
+
scopes: z.array(z.string()).optional().describe('e.g. ["openid","profile","email","groups"]'),
|
|
87
|
+
is_public: z.boolean().optional().describe("True for SPA/mobile (no client_secret)"),
|
|
88
|
+
post_logout_redirect_uris: z.array(z.string().url()).optional().describe("Allowed post-logout redirect URIs"),
|
|
89
|
+
},
|
|
90
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
91
|
+
},
|
|
92
|
+
async ({ org_id, ...params }) =>
|
|
93
|
+
handleError(async () => {
|
|
94
|
+
const result = await getClient().post<Record<string, unknown>>(
|
|
95
|
+
getClient().orgPath(org_id, "/clients"),
|
|
96
|
+
params,
|
|
97
|
+
);
|
|
98
|
+
const secret = result.client_secret
|
|
99
|
+
? `\n\n⚠️ SAVE THIS SECRET — it will not be shown again:\nclient_secret: ${result.client_secret}`
|
|
100
|
+
: "";
|
|
101
|
+
return `Client registered successfully.${secret}\n\n${JSON.stringify(result, null, 2)}`;
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// ── Update client ──────────────────────────────────────────────────────────
|
|
106
|
+
server.registerTool(
|
|
107
|
+
"clavex_update_client",
|
|
108
|
+
{
|
|
109
|
+
title: "Update OIDC Client",
|
|
110
|
+
description: `Update an existing OIDC client registration (PATCH semantics).
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
- org_id, client_id: identifiers
|
|
114
|
+
- name, redirect_uris, grant_types, scopes, is_active (all optional): new values
|
|
115
|
+
|
|
116
|
+
Returns: Updated client JSON.
|
|
117
|
+
|
|
118
|
+
Use when: "add a redirect URI to the Grafana client", "disable client <id>".`,
|
|
119
|
+
inputSchema: {
|
|
120
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
121
|
+
client_id: z.string().describe("OIDC client_id"),
|
|
122
|
+
name: z.string().optional().describe("New display name"),
|
|
123
|
+
redirect_uris: z.array(z.string().url()).optional().describe("New redirect URIs (replaces existing)"),
|
|
124
|
+
post_logout_redirect_uris: z.array(z.string().url()).optional().describe("New logout redirect URIs"),
|
|
125
|
+
scopes: z.array(z.string()).optional().describe("New allowed scopes"),
|
|
126
|
+
is_active: z.boolean().optional().describe("Enable or disable the client"),
|
|
127
|
+
},
|
|
128
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
|
|
129
|
+
},
|
|
130
|
+
async ({ org_id, client_id, ...updates }) =>
|
|
131
|
+
handleError(async () => {
|
|
132
|
+
const body = Object.fromEntries(
|
|
133
|
+
Object.entries(updates).filter(([, v]) => v !== undefined),
|
|
134
|
+
);
|
|
135
|
+
const result = await getClient().patch<Record<string, unknown>>(
|
|
136
|
+
getClient().orgPath(org_id, `/clients/${client_id}`),
|
|
137
|
+
body,
|
|
138
|
+
);
|
|
139
|
+
return `Client updated:\n\n${JSON.stringify(result, null, 2)}`;
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// ── Rotate secret ──────────────────────────────────────────────────────────
|
|
144
|
+
server.registerTool(
|
|
145
|
+
"clavex_rotate_client_secret",
|
|
146
|
+
{
|
|
147
|
+
title: "Rotate Client Secret",
|
|
148
|
+
description: `Generate a new client_secret for an OIDC client. The old secret is immediately invalidated.
|
|
149
|
+
|
|
150
|
+
Returns: New plaintext client_secret.
|
|
151
|
+
IMPORTANT: Update the secret in your application immediately after rotation.
|
|
152
|
+
|
|
153
|
+
Use when: "rotate secret for client <id>", "regenerate client credentials".`,
|
|
154
|
+
inputSchema: {
|
|
155
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
156
|
+
client_id: z.string().describe("OIDC client_id"),
|
|
157
|
+
},
|
|
158
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
159
|
+
},
|
|
160
|
+
async ({ org_id, client_id }) =>
|
|
161
|
+
handleError(async () => {
|
|
162
|
+
const result = await getClient().post<{ client_secret: string }>(
|
|
163
|
+
getClient().orgPath(org_id, `/clients/${client_id}/rotate-secret`),
|
|
164
|
+
);
|
|
165
|
+
return `⚠️ New secret (save immediately):\nclient_secret: ${result.client_secret}`;
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
}
|