@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,144 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getClient } from "../client.js";
|
|
3
|
+
import { handleError, mdTable } from "../helpers.js";
|
|
4
|
+
export function registerUserTools(server) {
|
|
5
|
+
// ── List users ─────────────────────────────────────────────────────────────
|
|
6
|
+
server.registerTool("clavex_list_users", {
|
|
7
|
+
title: "List Users",
|
|
8
|
+
description: `List all users in an organization.
|
|
9
|
+
|
|
10
|
+
Returns a table with: id, email, first_name, last_name, is_active, created_at.
|
|
11
|
+
|
|
12
|
+
Use when: "show users in org <id>", "list all accounts for acme", "who are the members?".`,
|
|
13
|
+
inputSchema: {
|
|
14
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
15
|
+
},
|
|
16
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
17
|
+
}, async ({ org_id }) => handleError(async () => {
|
|
18
|
+
const users = await getClient().get(getClient().orgPath(org_id, "/users"));
|
|
19
|
+
return mdTable(users, ["id", "email", "first_name", "last_name", "is_active", "created_at"]);
|
|
20
|
+
}));
|
|
21
|
+
// ── Get user ───────────────────────────────────────────────────────────────
|
|
22
|
+
server.registerTool("clavex_get_user", {
|
|
23
|
+
title: "Get User",
|
|
24
|
+
description: `Get detailed information about a specific user.
|
|
25
|
+
|
|
26
|
+
Returns full user JSON including roles, groups, and metadata.
|
|
27
|
+
|
|
28
|
+
Use when: "get user <id>", "show me alice's account details".`,
|
|
29
|
+
inputSchema: {
|
|
30
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
31
|
+
user_id: z.string().uuid().describe("User UUID"),
|
|
32
|
+
},
|
|
33
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
34
|
+
}, async ({ org_id, user_id }) => handleError(async () => {
|
|
35
|
+
const user = await getClient().get(getClient().orgPath(org_id, `/users/${user_id}`));
|
|
36
|
+
return JSON.stringify(user, null, 2);
|
|
37
|
+
}));
|
|
38
|
+
// ── Create user ────────────────────────────────────────────────────────────
|
|
39
|
+
server.registerTool("clavex_create_user", {
|
|
40
|
+
title: "Create User",
|
|
41
|
+
description: `Create a new user in an organization.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
- org_id: Organization UUID
|
|
45
|
+
- email: User's email address (must be unique within the org)
|
|
46
|
+
- first_name: Given name
|
|
47
|
+
- last_name: Family name
|
|
48
|
+
- password (optional): Initial password. If omitted, a welcome/reset email is sent.
|
|
49
|
+
- send_welcome_email (optional): Send a welcome email (default true)
|
|
50
|
+
|
|
51
|
+
Returns: Created user JSON with the new user_id.
|
|
52
|
+
|
|
53
|
+
Use when: "add user alice@example.com to org <id>", "create account for John Doe".`,
|
|
54
|
+
inputSchema: {
|
|
55
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
56
|
+
email: z.string().email().describe("User email address"),
|
|
57
|
+
first_name: z.string().describe("Given name"),
|
|
58
|
+
last_name: z.string().describe("Family name"),
|
|
59
|
+
password: z.string().optional().describe("Initial password (omit to trigger welcome email)"),
|
|
60
|
+
send_welcome_email: z.boolean().default(true).describe("Send welcome/activation email"),
|
|
61
|
+
},
|
|
62
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
63
|
+
}, async ({ org_id, email, first_name, last_name, password, send_welcome_email }) => handleError(async () => {
|
|
64
|
+
const user = await getClient().post(getClient().orgPath(org_id, "/users"), { email, first_name, last_name, password, send_welcome_email });
|
|
65
|
+
return `User created:\n\n${JSON.stringify(user, null, 2)}`;
|
|
66
|
+
}));
|
|
67
|
+
// ── Update user ────────────────────────────────────────────────────────────
|
|
68
|
+
server.registerTool("clavex_update_user", {
|
|
69
|
+
title: "Update User",
|
|
70
|
+
description: `Update a user's profile or status (PATCH semantics — only provided fields change).
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
- org_id, user_id: identifiers
|
|
74
|
+
- first_name, last_name, email (optional): new values
|
|
75
|
+
- is_active (optional): set to false to suspend the account
|
|
76
|
+
- password (optional): force-set a new password
|
|
77
|
+
|
|
78
|
+
Returns: Updated user JSON.
|
|
79
|
+
|
|
80
|
+
Use when: "disable Alice's account", "change Bob's email", "rename user".`,
|
|
81
|
+
inputSchema: {
|
|
82
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
83
|
+
user_id: z.string().uuid().describe("User UUID"),
|
|
84
|
+
email: z.string().email().optional().describe("New email address"),
|
|
85
|
+
first_name: z.string().optional().describe("New given name"),
|
|
86
|
+
last_name: z.string().optional().describe("New family name"),
|
|
87
|
+
is_active: z.boolean().optional().describe("Set to false to suspend the account"),
|
|
88
|
+
password: z.string().optional().describe("Force-set a new password"),
|
|
89
|
+
},
|
|
90
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
|
|
91
|
+
}, async ({ org_id, user_id, ...updates }) => handleError(async () => {
|
|
92
|
+
const body = Object.fromEntries(Object.entries(updates).filter(([, v]) => v !== undefined));
|
|
93
|
+
const user = await getClient().patch(getClient().orgPath(org_id, `/users/${user_id}`), body);
|
|
94
|
+
return `User updated:\n\n${JSON.stringify(user, null, 2)}`;
|
|
95
|
+
}));
|
|
96
|
+
// ── Delete user ────────────────────────────────────────────────────────────
|
|
97
|
+
server.registerTool("clavex_delete_user", {
|
|
98
|
+
title: "Delete User",
|
|
99
|
+
description: `Permanently delete a user from an organization. This action is irreversible.
|
|
100
|
+
|
|
101
|
+
Consider using clavex_update_user with is_active=false to suspend without deleting.
|
|
102
|
+
|
|
103
|
+
Use when: "delete user <id>", "remove Alice's account permanently".`,
|
|
104
|
+
inputSchema: {
|
|
105
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
106
|
+
user_id: z.string().uuid().describe("User UUID to delete"),
|
|
107
|
+
},
|
|
108
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },
|
|
109
|
+
}, async ({ org_id, user_id }) => handleError(async () => {
|
|
110
|
+
await getClient().del(getClient().orgPath(org_id, `/users/${user_id}`));
|
|
111
|
+
return `User ${user_id} deleted from org ${org_id}.`;
|
|
112
|
+
}));
|
|
113
|
+
// ── Password reset ─────────────────────────────────────────────────────────
|
|
114
|
+
server.registerTool("clavex_send_password_reset", {
|
|
115
|
+
title: "Send Password Reset Email",
|
|
116
|
+
description: `Send a password reset email to a user.
|
|
117
|
+
|
|
118
|
+
Use when: "send password reset to alice", "user locked out — send reset email".`,
|
|
119
|
+
inputSchema: {
|
|
120
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
121
|
+
user_id: z.string().uuid().describe("User UUID"),
|
|
122
|
+
},
|
|
123
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
124
|
+
}, async ({ org_id, user_id }) => handleError(async () => {
|
|
125
|
+
await getClient().post(getClient().orgPath(org_id, `/users/${user_id}/send-password-reset`));
|
|
126
|
+
return `Password reset email sent to user ${user_id}.`;
|
|
127
|
+
}));
|
|
128
|
+
// ── List user roles ────────────────────────────────────────────────────────
|
|
129
|
+
server.registerTool("clavex_list_user_roles", {
|
|
130
|
+
title: "List User Roles",
|
|
131
|
+
description: `List all roles assigned to a specific user.
|
|
132
|
+
|
|
133
|
+
Use when: "what roles does Alice have?", "list permissions for user <id>".`,
|
|
134
|
+
inputSchema: {
|
|
135
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
136
|
+
user_id: z.string().uuid().describe("User UUID"),
|
|
137
|
+
},
|
|
138
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
139
|
+
}, async ({ org_id, user_id }) => handleError(async () => {
|
|
140
|
+
const roles = await getClient().get(getClient().orgPath(org_id, `/users/${user_id}/roles`));
|
|
141
|
+
return mdTable(roles, ["id", "name", "description"]);
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=users.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/tools/users.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clavex/mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for the Clavex Identity Platform — manage orgs, users, IdPs and OIDC clients via natural language",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"clavex-mcp-server": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "node --loader ts-node/esm src/index.ts",
|
|
14
|
+
"lint": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"clavex",
|
|
18
|
+
"mcp",
|
|
19
|
+
"identity",
|
|
20
|
+
"iam",
|
|
21
|
+
"oidc",
|
|
22
|
+
"claude"
|
|
23
|
+
],
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
27
|
+
"zod": "^3.23.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"typescript": "^5.4.5",
|
|
31
|
+
"@types/node": "^20.12.7"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/clavex-eu/clavex-sdk",
|
|
39
|
+
"directory": "packages/mcp-server"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/clavex-eu/clavex-sdk#readme",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/clavex-eu/clavex-sdk/issues"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clavex API client used by all MCP tools.
|
|
3
|
+
* Reads credentials from environment variables:
|
|
4
|
+
*
|
|
5
|
+
* CLAVEX_BASE_URL — required, e.g. https://auth.example.com
|
|
6
|
+
* CLAVEX_TOKEN — pre-acquired JWT bearer token (preferred)
|
|
7
|
+
*
|
|
8
|
+
* — or —
|
|
9
|
+
*
|
|
10
|
+
* CLAVEX_EMAIL — admin email (auto-login on first call)
|
|
11
|
+
* CLAVEX_PASSWORD — admin password
|
|
12
|
+
* CLAVEX_ORG_SLUG — org slug (omit for superadmin)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export class ClavexAPIError extends Error {
|
|
16
|
+
constructor(
|
|
17
|
+
public readonly status: number,
|
|
18
|
+
public readonly body: unknown,
|
|
19
|
+
message: string,
|
|
20
|
+
) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "ClavexAPIError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface LoginResponse {
|
|
27
|
+
token: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ClavexClient {
|
|
31
|
+
private readonly baseURL: string;
|
|
32
|
+
private token: string | undefined;
|
|
33
|
+
private loginPromise: Promise<void> | null = null;
|
|
34
|
+
|
|
35
|
+
// Credential fields for auto-login
|
|
36
|
+
private readonly email: string | undefined;
|
|
37
|
+
private readonly password: string | undefined;
|
|
38
|
+
private readonly orgSlug: string | undefined;
|
|
39
|
+
|
|
40
|
+
constructor() {
|
|
41
|
+
const base = process.env.CLAVEX_BASE_URL;
|
|
42
|
+
if (!base) throw new Error("CLAVEX_BASE_URL environment variable is required");
|
|
43
|
+
this.baseURL = base.replace(/\/+$/, "");
|
|
44
|
+
this.token = process.env.CLAVEX_TOKEN;
|
|
45
|
+
this.email = process.env.CLAVEX_EMAIL;
|
|
46
|
+
this.password = process.env.CLAVEX_PASSWORD;
|
|
47
|
+
this.orgSlug = process.env.CLAVEX_ORG_SLUG;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private async ensureToken(): Promise<void> {
|
|
51
|
+
if (this.token) return;
|
|
52
|
+
if (!this.email || !this.password) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
"Authentication required: set CLAVEX_TOKEN or CLAVEX_EMAIL + CLAVEX_PASSWORD",
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (!this.loginPromise) {
|
|
58
|
+
this.loginPromise = this.doLogin().then(() => {
|
|
59
|
+
this.loginPromise = null;
|
|
60
|
+
}).catch((e) => {
|
|
61
|
+
this.loginPromise = null;
|
|
62
|
+
throw e;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
await this.loginPromise;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async doLogin(): Promise<void> {
|
|
69
|
+
const resp = await this.request<LoginResponse>(
|
|
70
|
+
"POST",
|
|
71
|
+
"/api/v1/auth/login",
|
|
72
|
+
{ org_slug: this.orgSlug, email: this.email, password: this.password },
|
|
73
|
+
false,
|
|
74
|
+
);
|
|
75
|
+
this.token = resp.token;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async request<T>(
|
|
79
|
+
method: string,
|
|
80
|
+
path: string,
|
|
81
|
+
body?: unknown,
|
|
82
|
+
auth = true,
|
|
83
|
+
): Promise<T> {
|
|
84
|
+
if (auth) await this.ensureToken();
|
|
85
|
+
|
|
86
|
+
const headers: Record<string, string> = {
|
|
87
|
+
"Content-Type": "application/json",
|
|
88
|
+
Accept: "application/json",
|
|
89
|
+
};
|
|
90
|
+
if (auth && this.token) {
|
|
91
|
+
headers["Authorization"] = `Bearer ${this.token}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const res = await fetch(this.baseURL + path, {
|
|
95
|
+
method,
|
|
96
|
+
headers,
|
|
97
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (res.status === 204) return undefined as unknown as T;
|
|
101
|
+
|
|
102
|
+
let resBody: unknown;
|
|
103
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
104
|
+
if (ct.includes("application/json")) {
|
|
105
|
+
resBody = await res.json();
|
|
106
|
+
} else {
|
|
107
|
+
resBody = await res.text();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
const msg =
|
|
112
|
+
typeof resBody === "object" && resBody !== null && "error" in resBody
|
|
113
|
+
? String((resBody as Record<string, unknown>).error)
|
|
114
|
+
: String(resBody);
|
|
115
|
+
throw new ClavexAPIError(res.status, resBody, `${method} ${path} → ${res.status}: ${msg}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return resBody as T;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
get<T>(path: string): Promise<T> {
|
|
122
|
+
return this.request<T>("GET", path);
|
|
123
|
+
}
|
|
124
|
+
post<T>(path: string, body?: unknown): Promise<T> {
|
|
125
|
+
return this.request<T>("POST", path, body);
|
|
126
|
+
}
|
|
127
|
+
patch<T>(path: string, body: unknown): Promise<T> {
|
|
128
|
+
return this.request<T>("PATCH", path, body);
|
|
129
|
+
}
|
|
130
|
+
put<T>(path: string, body?: unknown): Promise<T> {
|
|
131
|
+
return this.request<T>("PUT", path, body);
|
|
132
|
+
}
|
|
133
|
+
del<T>(path: string): Promise<T> {
|
|
134
|
+
return this.request<T>("DELETE", path);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
orgPath(orgID: string, suffix: string): string {
|
|
138
|
+
return `/api/v1/organizations/${orgID}${suffix}`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Singleton — created lazily on first tool call
|
|
143
|
+
let _client: ClavexClient | null = null;
|
|
144
|
+
|
|
145
|
+
export function getClient(): ClavexClient {
|
|
146
|
+
if (!_client) _client = new ClavexClient();
|
|
147
|
+
return _client;
|
|
148
|
+
}
|
package/src/helpers.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ClavexAPIError } from "./client.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a tool handler so API errors become user-facing error messages
|
|
5
|
+
* instead of crashing the MCP server.
|
|
6
|
+
*/
|
|
7
|
+
export async function handleError<T>(
|
|
8
|
+
fn: () => Promise<T>,
|
|
9
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
10
|
+
try {
|
|
11
|
+
const result = await fn();
|
|
12
|
+
return {
|
|
13
|
+
content: [{ type: "text", text: typeof result === "string" ? result : JSON.stringify(result, null, 2) }],
|
|
14
|
+
};
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (err instanceof ClavexAPIError) {
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: "text", text: `Error ${err.status}: ${err.message}` }],
|
|
19
|
+
isError: true,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (err instanceof Error) {
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
25
|
+
isError: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: `Unknown error: ${String(err)}` }],
|
|
30
|
+
isError: true,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Format an array of objects as a readable Markdown table. */
|
|
36
|
+
export function mdTable(rows: Record<string, unknown>[], cols?: string[]): string {
|
|
37
|
+
if (!rows.length) return "_No results._";
|
|
38
|
+
const keys = cols ?? Object.keys(rows[0]);
|
|
39
|
+
const header = `| ${keys.join(" | ")} |`;
|
|
40
|
+
const sep = `| ${keys.map(() => "---").join(" | ")} |`;
|
|
41
|
+
const body = rows
|
|
42
|
+
.map((r) => `| ${keys.map((k) => String(r[k] ?? "—")).join(" | ")} |`)
|
|
43
|
+
.join("\n");
|
|
44
|
+
return [header, sep, body].join("\n");
|
|
45
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Clavex MCP Server — entry point
|
|
4
|
+
*
|
|
5
|
+
* Exposes ~30 tools for managing Clavex via natural language through Claude.
|
|
6
|
+
* Uses stdio transport (compatible with Claude Desktop, Claude API, MCP Inspector).
|
|
7
|
+
*
|
|
8
|
+
* Configuration via environment variables:
|
|
9
|
+
*
|
|
10
|
+
* CLAVEX_BASE_URL — required, e.g. https://auth.example.com
|
|
11
|
+
* CLAVEX_TOKEN — pre-acquired JWT bearer token (preferred)
|
|
12
|
+
*
|
|
13
|
+
* — or —
|
|
14
|
+
*
|
|
15
|
+
* CLAVEX_EMAIL — admin email (auto-login on first call)
|
|
16
|
+
* CLAVEX_PASSWORD — admin password
|
|
17
|
+
* CLAVEX_ORG_SLUG — org slug (omit for superadmin login)
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* node dist/index.js
|
|
21
|
+
* npx @clavex/mcp-server
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
25
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
26
|
+
|
|
27
|
+
import { registerOrgTools } from "./tools/orgs.js";
|
|
28
|
+
import { registerUserTools } from "./tools/users.js";
|
|
29
|
+
import { registerGroupTools } from "./tools/groups.js";
|
|
30
|
+
import { registerClientTools } from "./tools/clients.js";
|
|
31
|
+
import { registerIDPTools } from "./tools/idps.js";
|
|
32
|
+
import { registerPolicyTools } from "./tools/policies.js";
|
|
33
|
+
import { registerCIBATools } from "./tools/ciba.js";
|
|
34
|
+
import { registerFGATools } from "./tools/fga.js";
|
|
35
|
+
import { registerAccessReviewTools } from "./tools/access_reviews.js";
|
|
36
|
+
import { registerSSFTools } from "./tools/ssf.js";
|
|
37
|
+
import { registerAITools } from "./tools/ai.js";
|
|
38
|
+
import { registerPAMTools } from "./tools/pam.js";
|
|
39
|
+
import { registerDeveloperTools } from "./tools/developer.js";
|
|
40
|
+
|
|
41
|
+
const server = new McpServer({
|
|
42
|
+
name: "clavex-mcp-server",
|
|
43
|
+
version: "1.2.0",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Register all tool domains
|
|
47
|
+
registerOrgTools(server);
|
|
48
|
+
registerUserTools(server);
|
|
49
|
+
registerGroupTools(server);
|
|
50
|
+
registerClientTools(server);
|
|
51
|
+
registerIDPTools(server);
|
|
52
|
+
registerPolicyTools(server);
|
|
53
|
+
registerCIBATools(server);
|
|
54
|
+
registerFGATools(server);
|
|
55
|
+
registerAccessReviewTools(server);
|
|
56
|
+
registerSSFTools(server);
|
|
57
|
+
registerAITools(server);
|
|
58
|
+
registerPAMTools(server);
|
|
59
|
+
registerDeveloperTools(server);
|
|
60
|
+
|
|
61
|
+
// Connect via stdio (Claude Desktop / Claude API compatible)
|
|
62
|
+
const transport = new StdioServerTransport();
|
|
63
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,163 @@
|
|
|
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 registerAccessReviewTools(server: McpServer): void {
|
|
7
|
+
// ── List access review campaigns ───────────────────────────────────────────
|
|
8
|
+
server.registerTool(
|
|
9
|
+
"clavex_list_access_reviews",
|
|
10
|
+
{
|
|
11
|
+
title: "List Access Review Campaigns",
|
|
12
|
+
description: `List access review (access certification) campaigns for an organization.
|
|
13
|
+
|
|
14
|
+
Access reviews are periodic or triggered campaigns where managers certify whether
|
|
15
|
+
users should retain their roles and permissions (JML — Joiner/Mover/Leaver compliance).
|
|
16
|
+
|
|
17
|
+
Returns: id, name, status (pending/active/completed/cancelled), starts_at, ends_at, item counts.
|
|
18
|
+
|
|
19
|
+
Status values:
|
|
20
|
+
"pending" — scheduled, not yet started
|
|
21
|
+
"active" — in progress, items are awaiting reviewer decisions
|
|
22
|
+
"completed" — all items decided
|
|
23
|
+
"cancelled" — cancelled before completion
|
|
24
|
+
|
|
25
|
+
Use when:
|
|
26
|
+
"show access review campaigns for org <id>"
|
|
27
|
+
"what reviews are currently active?"
|
|
28
|
+
"mostrami le campagne di access review"`,
|
|
29
|
+
inputSchema: {
|
|
30
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
31
|
+
},
|
|
32
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
33
|
+
},
|
|
34
|
+
async ({ org_id }) =>
|
|
35
|
+
handleError(async () => {
|
|
36
|
+
const campaigns = await getClient().get<Array<Record<string, unknown>>>(
|
|
37
|
+
getClient().orgPath(org_id, "/access-reviews"),
|
|
38
|
+
);
|
|
39
|
+
if (!Array.isArray(campaigns) || campaigns.length === 0) {
|
|
40
|
+
return "_No access review campaigns found._";
|
|
41
|
+
}
|
|
42
|
+
return mdTable(campaigns, ["id", "name", "status", "starts_at", "ends_at"]);
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// ── Create access review campaign ──────────────────────────────────────────
|
|
47
|
+
server.registerTool(
|
|
48
|
+
"clavex_create_access_review",
|
|
49
|
+
{
|
|
50
|
+
title: "Create Access Review Campaign",
|
|
51
|
+
description: `Create a new access review (certification) campaign for an organization.
|
|
52
|
+
|
|
53
|
+
A campaign generates one review item per (user, role) assignment in the org.
|
|
54
|
+
Items are sent to reviewers who decide keep/revoke for each assignment.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
- org_id: Organization UUID
|
|
58
|
+
- name: Campaign name — e.g. "Q2 2026 Quarterly Access Review" or "Annual HR Certification"
|
|
59
|
+
- starts_at: ISO 8601 datetime when the campaign begins (e.g. "2026-06-01T00:00:00Z")
|
|
60
|
+
- ends_at: ISO 8601 datetime when the campaign expires — unreviewed items auto-revoke
|
|
61
|
+
- description (optional): Human-readable purpose of this campaign
|
|
62
|
+
|
|
63
|
+
Returns: Created campaign JSON including the campaign_id.
|
|
64
|
+
|
|
65
|
+
Use when:
|
|
66
|
+
"create a quarterly access review for org <id> starting June 1"
|
|
67
|
+
"lancia la campagna di access review trimestrale"
|
|
68
|
+
"schedule annual certification"`,
|
|
69
|
+
inputSchema: {
|
|
70
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
71
|
+
name: z.string().describe("Campaign name, e.g. 'Q2 2026 Quarterly Review'"),
|
|
72
|
+
starts_at: z.string().describe("ISO 8601 start datetime, e.g. '2026-06-01T00:00:00Z'"),
|
|
73
|
+
ends_at: z.string().describe("ISO 8601 end/deadline datetime"),
|
|
74
|
+
description: z.string().optional().describe("Optional description of this review's scope"),
|
|
75
|
+
},
|
|
76
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
77
|
+
},
|
|
78
|
+
async ({ org_id, name, starts_at, ends_at, description }) =>
|
|
79
|
+
handleError(async () => {
|
|
80
|
+
const campaign = await getClient().post<Record<string, unknown>>(
|
|
81
|
+
getClient().orgPath(org_id, "/access-reviews"),
|
|
82
|
+
{ name, starts_at, ends_at, description },
|
|
83
|
+
);
|
|
84
|
+
return `Access review campaign created:\n\n${JSON.stringify(campaign, null, 2)}`;
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// ── Launch access review campaign ─────────────────────────────────────────
|
|
89
|
+
server.registerTool(
|
|
90
|
+
"clavex_launch_review",
|
|
91
|
+
{
|
|
92
|
+
title: "Launch Access Review Campaign",
|
|
93
|
+
description: `Immediately launch (activate) an access review campaign, generating review items and sending notifications to reviewers.
|
|
94
|
+
|
|
95
|
+
This moves the campaign from "pending" to "active" without waiting for the scheduled starts_at.
|
|
96
|
+
Use this to trigger an unscheduled or emergency review.
|
|
97
|
+
|
|
98
|
+
After launching:
|
|
99
|
+
- One review item is created per (user, role) assignment in the org
|
|
100
|
+
- Reviewer notification emails are sent
|
|
101
|
+
- The worker processes items on a 15-minute tick
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
- org_id: Organization UUID
|
|
105
|
+
- campaign_id: UUID of the campaign to launch (use clavex_list_access_reviews to find it)
|
|
106
|
+
|
|
107
|
+
Returns: Launched campaign JSON.
|
|
108
|
+
|
|
109
|
+
Use when:
|
|
110
|
+
"lancia la campagna di access review trimestrale"
|
|
111
|
+
"launch access review <campaign_id> now"
|
|
112
|
+
"activate the pending access certification"`,
|
|
113
|
+
inputSchema: {
|
|
114
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
115
|
+
campaign_id: z.string().uuid().describe("Campaign UUID to launch"),
|
|
116
|
+
},
|
|
117
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
118
|
+
},
|
|
119
|
+
async ({ org_id, campaign_id }) =>
|
|
120
|
+
handleError(async () => {
|
|
121
|
+
const campaign = await getClient().post<Record<string, unknown>>(
|
|
122
|
+
getClient().orgPath(org_id, `/access-reviews/${campaign_id}/launch`),
|
|
123
|
+
);
|
|
124
|
+
return `Campaign launched successfully:\n\n${JSON.stringify(campaign, null, 2)}`;
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// ── Get campaign items ────────────────────────────────────────────────────
|
|
129
|
+
server.registerTool(
|
|
130
|
+
"clavex_get_review_items",
|
|
131
|
+
{
|
|
132
|
+
title: "Get Access Review Items",
|
|
133
|
+
description: `Get all items (user-role pairs) in an access review campaign along with their status.
|
|
134
|
+
|
|
135
|
+
Each item represents one (user, role) assignment awaiting a keep/revoke decision.
|
|
136
|
+
|
|
137
|
+
Returns: user_id, user_email, role_id, role_name, reviewer_email, decision, decided_at.
|
|
138
|
+
|
|
139
|
+
Decision values:
|
|
140
|
+
null — not yet reviewed
|
|
141
|
+
"keep" — reviewer confirmed access should be retained
|
|
142
|
+
"revoke" — reviewer decided access should be removed (processed by the worker)
|
|
143
|
+
|
|
144
|
+
Use when:
|
|
145
|
+
"show items for campaign <id>"
|
|
146
|
+
"how many reviews are still pending?"
|
|
147
|
+
"quali utenti sono in attesa di revisione?"`,
|
|
148
|
+
inputSchema: {
|
|
149
|
+
org_id: z.string().uuid().describe("Organization UUID"),
|
|
150
|
+
campaign_id: z.string().uuid().describe("Campaign UUID"),
|
|
151
|
+
},
|
|
152
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
153
|
+
},
|
|
154
|
+
async ({ org_id, campaign_id }) =>
|
|
155
|
+
handleError(async () => {
|
|
156
|
+
const items = await getClient().get<Array<Record<string, unknown>>>(
|
|
157
|
+
getClient().orgPath(org_id, `/access-reviews/${campaign_id}/items`),
|
|
158
|
+
);
|
|
159
|
+
if (!Array.isArray(items) || items.length === 0) return "_No items found for this campaign._";
|
|
160
|
+
return mdTable(items, ["id", "user_email", "role_name", "reviewer_email", "decision", "decided_at"]);
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
}
|