@lightdash-tools/mcp 0.2.4 → 0.2.6

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 CHANGED
@@ -98,9 +98,9 @@ The server registers the following tools (names prefixed with `lightdash_tools__
98
98
 
99
99
  The MCP server implements a hierarchical safety model. You can control which tools are available to AI agents using the `LIGHTDASH_TOOL_SAFETY_MODE` environment variable or the `--safety-mode` CLI option.
100
100
 
101
- - `read-only`: Only allows non-modifying tools (e.g., `list_*`, `get_*`).
101
+ - `read-only` (default): Only allows non-modifying tools (e.g., `list_*`, `get_*`).
102
102
  - `write-idempotent`: Allows read tools and non-destructive writes (e.g., `upsert_chart_as_code`).
103
- - `write-destructive` (default): Allows all tools, including destructive ones (e.g., `delete_member`).
103
+ - `write-destructive`: Allows all tools, including destructive ones (e.g., `delete_member`).
104
104
 
105
105
  ### Enforcement Layers
106
106
 
package/dist/bin.js CHANGED
@@ -44,7 +44,7 @@ const program = new commander_1.Command();
44
44
  program
45
45
  .name('lightdash-mcp')
46
46
  .description('MCP server for Lightdash AI')
47
- .version('0.2.3')
47
+ .version('0.2.6')
48
48
  .option('--http', 'Run as HTTP server instead of Stdio')
49
49
  .option('--safety-mode <mode>', 'Filter registered tools by safety mode (read-only, write-idempotent, write-destructive)')
50
50
  .action((options) => {
@@ -38,4 +38,84 @@ function registerGroupTools(server, client) {
38
38
  const group = yield c.v1.groups.getGroup(groupUuid);
39
39
  return { content: [{ type: 'text', text: JSON.stringify(group, null, 2) }] };
40
40
  })));
41
+ (0, shared_js_1.registerToolSafe)(server, 'create_group', {
42
+ title: 'Create group',
43
+ description: 'Create a new group in the organization',
44
+ inputSchema: {
45
+ name: zod_1.z.string().describe('Group name'),
46
+ },
47
+ annotations: shared_js_1.WRITE_IDEMPOTENT,
48
+ }, (0, shared_js_1.wrapTool)(client, (c) => (_a) => __awaiter(this, [_a], void 0, function* ({ name }) {
49
+ const group = yield c.v1.groups.createGroup({ name });
50
+ return { content: [{ type: 'text', text: JSON.stringify(group, null, 2) }] };
51
+ })));
52
+ (0, shared_js_1.registerToolSafe)(server, 'update_group', {
53
+ title: 'Update group',
54
+ description: 'Update a group name',
55
+ inputSchema: {
56
+ groupUuid: zod_1.z.string().describe('Group UUID'),
57
+ name: zod_1.z.string().describe('New group name'),
58
+ },
59
+ annotations: shared_js_1.WRITE_IDEMPOTENT,
60
+ }, (0, shared_js_1.wrapTool)(client, (c) => (_a) => __awaiter(this, [_a], void 0, function* ({ groupUuid, name }) {
61
+ const group = yield c.v1.groups.updateGroup(groupUuid, { name });
62
+ return { content: [{ type: 'text', text: JSON.stringify(group, null, 2) }] };
63
+ })));
64
+ (0, shared_js_1.registerToolSafe)(server, 'delete_group', {
65
+ title: 'Delete group',
66
+ description: 'Delete a group by UUID',
67
+ inputSchema: {
68
+ groupUuid: zod_1.z.string().describe('Group UUID'),
69
+ },
70
+ annotations: shared_js_1.WRITE_DESTRUCTIVE,
71
+ }, (0, shared_js_1.wrapTool)(client, (c) => (_a) => __awaiter(this, [_a], void 0, function* ({ groupUuid }) {
72
+ yield c.v1.groups.deleteGroup(groupUuid);
73
+ return { content: [{ type: 'text', text: `Group ${groupUuid} deleted successfully` }] };
74
+ })));
75
+ (0, shared_js_1.registerToolSafe)(server, 'list_group_members', {
76
+ title: 'List group members',
77
+ description: 'List members of a group',
78
+ inputSchema: {
79
+ groupUuid: zod_1.z.string().describe('Group UUID'),
80
+ },
81
+ annotations: shared_js_1.READ_ONLY_DEFAULT,
82
+ }, (0, shared_js_1.wrapTool)(client, (c) => (_a) => __awaiter(this, [_a], void 0, function* ({ groupUuid }) {
83
+ const members = yield c.v1.groups.getGroupMembers(groupUuid);
84
+ return { content: [{ type: 'text', text: JSON.stringify(members, null, 2) }] };
85
+ })));
86
+ (0, shared_js_1.registerToolSafe)(server, 'add_user_to_group', {
87
+ title: 'Add user to group',
88
+ description: 'Add a user to a group',
89
+ inputSchema: {
90
+ groupUuid: zod_1.z.string().describe('Group UUID'),
91
+ userUuid: zod_1.z.string().describe('User UUID'),
92
+ },
93
+ annotations: shared_js_1.WRITE_IDEMPOTENT,
94
+ }, (0, shared_js_1.wrapTool)(client, (c) => (_a) => __awaiter(this, [_a], void 0, function* ({ groupUuid, userUuid }) {
95
+ yield c.v1.groups.addUserToGroup(groupUuid, userUuid);
96
+ return {
97
+ content: [
98
+ { type: 'text', text: `User ${userUuid} added to group ${groupUuid} successfully` },
99
+ ],
100
+ };
101
+ })));
102
+ (0, shared_js_1.registerToolSafe)(server, 'remove_user_from_group', {
103
+ title: 'Remove user from group',
104
+ description: 'Remove a user from a group',
105
+ inputSchema: {
106
+ groupUuid: zod_1.z.string().describe('Group UUID'),
107
+ userUuid: zod_1.z.string().describe('User UUID'),
108
+ },
109
+ annotations: shared_js_1.WRITE_DESTRUCTIVE,
110
+ }, (0, shared_js_1.wrapTool)(client, (c) => (_a) => __awaiter(this, [_a], void 0, function* ({ groupUuid, userUuid }) {
111
+ yield c.v1.groups.removeUserFromGroup(groupUuid, userUuid);
112
+ return {
113
+ content: [
114
+ {
115
+ type: 'text',
116
+ text: `User ${userUuid} removed from group ${groupUuid} successfully`,
117
+ },
118
+ ],
119
+ };
120
+ })));
41
121
  }
@@ -37,4 +37,92 @@ function registerSpaceTools(server, client) {
37
37
  const space = yield c.v1.spaces.getSpace(projectUuid, spaceUuid);
38
38
  return { content: [{ type: 'text', text: JSON.stringify(space, null, 2) }] };
39
39
  })));
40
+ (0, shared_js_1.registerToolSafe)(server, 'grant_user_space_access', {
41
+ title: 'Grant user access to space',
42
+ description: 'Grant a user access to a space',
43
+ inputSchema: {
44
+ projectUuid: zod_1.z.string().describe('Project UUID'),
45
+ spaceUuid: zod_1.z.string().describe('Space UUID'),
46
+ userUuid: zod_1.z.string().describe('User UUID'),
47
+ spaceRole: zod_1.z.enum(['viewer', 'editor', 'admin']).describe('Space role'),
48
+ },
49
+ annotations: shared_js_1.WRITE_IDEMPOTENT,
50
+ }, (0, shared_js_1.wrapTool)(client, (c) => (_a) => __awaiter(this, [_a], void 0, function* ({ projectUuid, spaceUuid, userUuid, spaceRole, }) {
51
+ yield c.v1.spaces.grantUserAccessToSpace(projectUuid, spaceUuid, {
52
+ userUuid,
53
+ spaceRole: spaceRole,
54
+ });
55
+ return {
56
+ content: [
57
+ {
58
+ type: 'text',
59
+ text: `Successfully granted ${spaceRole} access to user ${userUuid} in space ${spaceUuid}`,
60
+ },
61
+ ],
62
+ };
63
+ })));
64
+ (0, shared_js_1.registerToolSafe)(server, 'revoke_user_space_access', {
65
+ title: 'Revoke user access to space',
66
+ description: "Revoke a user's access to a space",
67
+ inputSchema: {
68
+ projectUuid: zod_1.z.string().describe('Project UUID'),
69
+ spaceUuid: zod_1.z.string().describe('Space UUID'),
70
+ userUuid: zod_1.z.string().describe('User UUID'),
71
+ },
72
+ annotations: shared_js_1.WRITE_DESTRUCTIVE,
73
+ }, (0, shared_js_1.wrapTool)(client, (c) => (_a) => __awaiter(this, [_a], void 0, function* ({ projectUuid, spaceUuid, userUuid, }) {
74
+ yield c.v1.spaces.revokeUserAccessToSpace(projectUuid, spaceUuid, userUuid);
75
+ return {
76
+ content: [
77
+ {
78
+ type: 'text',
79
+ text: `Successfully revoked access for user ${userUuid} in space ${spaceUuid}`,
80
+ },
81
+ ],
82
+ };
83
+ })));
84
+ (0, shared_js_1.registerToolSafe)(server, 'grant_group_space_access', {
85
+ title: 'Grant group access to space',
86
+ description: 'Grant a group access to a space',
87
+ inputSchema: {
88
+ projectUuid: zod_1.z.string().describe('Project UUID'),
89
+ spaceUuid: zod_1.z.string().describe('Space UUID'),
90
+ groupUuid: zod_1.z.string().describe('Group UUID'),
91
+ spaceRole: zod_1.z.enum(['viewer', 'editor', 'admin']).describe('Space role'),
92
+ },
93
+ annotations: shared_js_1.WRITE_IDEMPOTENT,
94
+ }, (0, shared_js_1.wrapTool)(client, (c) => (_a) => __awaiter(this, [_a], void 0, function* ({ projectUuid, spaceUuid, groupUuid, spaceRole, }) {
95
+ yield c.v1.spaces.grantGroupAccessToSpace(projectUuid, spaceUuid, {
96
+ groupUuid,
97
+ spaceRole: spaceRole,
98
+ });
99
+ return {
100
+ content: [
101
+ {
102
+ type: 'text',
103
+ text: `Successfully granted ${spaceRole} access to group ${groupUuid} in space ${spaceUuid}`,
104
+ },
105
+ ],
106
+ };
107
+ })));
108
+ (0, shared_js_1.registerToolSafe)(server, 'revoke_group_space_access', {
109
+ title: 'Revoke group access to space',
110
+ description: "Revoke a group's access to a space",
111
+ inputSchema: {
112
+ projectUuid: zod_1.z.string().describe('Project UUID'),
113
+ spaceUuid: zod_1.z.string().describe('Space UUID'),
114
+ groupUuid: zod_1.z.string().describe('Group UUID'),
115
+ },
116
+ annotations: shared_js_1.WRITE_DESTRUCTIVE,
117
+ }, (0, shared_js_1.wrapTool)(client, (c) => (_a) => __awaiter(this, [_a], void 0, function* ({ projectUuid, spaceUuid, groupUuid, }) {
118
+ yield c.v1.spaces.revokeGroupAccessToSpace(projectUuid, spaceUuid, groupUuid);
119
+ return {
120
+ content: [
121
+ {
122
+ type: 'text',
123
+ text: `Successfully revoked access for group ${groupUuid} in space ${spaceUuid}`,
124
+ },
125
+ ],
126
+ };
127
+ })));
40
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash-tools/mcp",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "MCP server and utilities for Lightdash AI.",
5
5
  "keywords": [],
6
6
  "license": "Apache-2.0",
@@ -14,8 +14,8 @@
14
14
  "@modelcontextprotocol/sdk": "^1.26.0",
15
15
  "commander": "^14.0.3",
16
16
  "zod": "^4.3.6",
17
- "@lightdash-tools/client": "0.2.4",
18
- "@lightdash-tools/common": "0.2.4"
17
+ "@lightdash-tools/common": "0.2.6",
18
+ "@lightdash-tools/client": "0.2.6"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^25.2.3"
package/src/bin.ts CHANGED
@@ -12,7 +12,7 @@ const program = new Command();
12
12
  program
13
13
  .name('lightdash-mcp')
14
14
  .description('MCP server for Lightdash AI')
15
- .version('0.2.3')
15
+ .version('0.2.6')
16
16
  .option('--http', 'Run as HTTP server instead of Stdio')
17
17
  .option(
18
18
  '--safety-mode <mode>',
@@ -5,7 +5,13 @@
5
5
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
6
  import type { LightdashClient } from '@lightdash-tools/client';
7
7
  import { z } from 'zod';
8
- import { wrapTool, registerToolSafe, READ_ONLY_DEFAULT } from './shared.js';
8
+ import {
9
+ wrapTool,
10
+ registerToolSafe,
11
+ READ_ONLY_DEFAULT,
12
+ WRITE_IDEMPOTENT,
13
+ WRITE_DESTRUCTIVE,
14
+ } from './shared.js';
9
15
 
10
16
  type ListGroupsParams = {
11
17
  page?: number;
@@ -32,6 +38,7 @@ export function registerGroupTools(server: McpServer, client: LightdashClient):
32
38
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
33
39
  }),
34
40
  );
41
+
35
42
  registerToolSafe(
36
43
  server,
37
44
  'get_group',
@@ -46,4 +53,128 @@ export function registerGroupTools(server: McpServer, client: LightdashClient):
46
53
  return { content: [{ type: 'text', text: JSON.stringify(group, null, 2) }] };
47
54
  }),
48
55
  );
56
+
57
+ registerToolSafe(
58
+ server,
59
+ 'create_group',
60
+ {
61
+ title: 'Create group',
62
+ description: 'Create a new group in the organization',
63
+ inputSchema: {
64
+ name: z.string().describe('Group name'),
65
+ },
66
+ annotations: WRITE_IDEMPOTENT,
67
+ },
68
+ wrapTool(client, (c) => async ({ name }: { name: string }) => {
69
+ const group = await c.v1.groups.createGroup({ name });
70
+ return { content: [{ type: 'text', text: JSON.stringify(group, null, 2) }] };
71
+ }),
72
+ );
73
+
74
+ registerToolSafe(
75
+ server,
76
+ 'update_group',
77
+ {
78
+ title: 'Update group',
79
+ description: 'Update a group name',
80
+ inputSchema: {
81
+ groupUuid: z.string().describe('Group UUID'),
82
+ name: z.string().describe('New group name'),
83
+ },
84
+ annotations: WRITE_IDEMPOTENT,
85
+ },
86
+ wrapTool(client, (c) => async ({ groupUuid, name }: { groupUuid: string; name: string }) => {
87
+ const group = await c.v1.groups.updateGroup(groupUuid, { name });
88
+ return { content: [{ type: 'text', text: JSON.stringify(group, null, 2) }] };
89
+ }),
90
+ );
91
+
92
+ registerToolSafe(
93
+ server,
94
+ 'delete_group',
95
+ {
96
+ title: 'Delete group',
97
+ description: 'Delete a group by UUID',
98
+ inputSchema: {
99
+ groupUuid: z.string().describe('Group UUID'),
100
+ },
101
+ annotations: WRITE_DESTRUCTIVE,
102
+ },
103
+ wrapTool(client, (c) => async ({ groupUuid }: { groupUuid: string }) => {
104
+ await c.v1.groups.deleteGroup(groupUuid);
105
+ return { content: [{ type: 'text', text: `Group ${groupUuid} deleted successfully` }] };
106
+ }),
107
+ );
108
+
109
+ registerToolSafe(
110
+ server,
111
+ 'list_group_members',
112
+ {
113
+ title: 'List group members',
114
+ description: 'List members of a group',
115
+ inputSchema: {
116
+ groupUuid: z.string().describe('Group UUID'),
117
+ },
118
+ annotations: READ_ONLY_DEFAULT,
119
+ },
120
+ wrapTool(client, (c) => async ({ groupUuid }: { groupUuid: string }) => {
121
+ const members = await c.v1.groups.getGroupMembers(groupUuid);
122
+ return { content: [{ type: 'text', text: JSON.stringify(members, null, 2) }] };
123
+ }),
124
+ );
125
+
126
+ registerToolSafe(
127
+ server,
128
+ 'add_user_to_group',
129
+ {
130
+ title: 'Add user to group',
131
+ description: 'Add a user to a group',
132
+ inputSchema: {
133
+ groupUuid: z.string().describe('Group UUID'),
134
+ userUuid: z.string().describe('User UUID'),
135
+ },
136
+ annotations: WRITE_IDEMPOTENT,
137
+ },
138
+ wrapTool(
139
+ client,
140
+ (c) =>
141
+ async ({ groupUuid, userUuid }: { groupUuid: string; userUuid: string }) => {
142
+ await c.v1.groups.addUserToGroup(groupUuid, userUuid);
143
+ return {
144
+ content: [
145
+ { type: 'text', text: `User ${userUuid} added to group ${groupUuid} successfully` },
146
+ ],
147
+ };
148
+ },
149
+ ),
150
+ );
151
+
152
+ registerToolSafe(
153
+ server,
154
+ 'remove_user_from_group',
155
+ {
156
+ title: 'Remove user from group',
157
+ description: 'Remove a user from a group',
158
+ inputSchema: {
159
+ groupUuid: z.string().describe('Group UUID'),
160
+ userUuid: z.string().describe('User UUID'),
161
+ },
162
+ annotations: WRITE_DESTRUCTIVE,
163
+ },
164
+ wrapTool(
165
+ client,
166
+ (c) =>
167
+ async ({ groupUuid, userUuid }: { groupUuid: string; userUuid: string }) => {
168
+ await c.v1.groups.removeUserFromGroup(groupUuid, userUuid);
169
+ return {
170
+ content: [
171
+ {
172
+ type: 'text',
173
+ text: `User ${userUuid} removed from group ${groupUuid} successfully`,
174
+ },
175
+ ],
176
+ };
177
+ },
178
+ ),
179
+ );
49
180
  }
@@ -5,7 +5,14 @@
5
5
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
6
  import type { LightdashClient } from '@lightdash-tools/client';
7
7
  import { z } from 'zod';
8
- import { wrapTool, registerToolSafe, READ_ONLY_DEFAULT } from './shared.js';
8
+ import {
9
+ wrapTool,
10
+ registerToolSafe,
11
+ READ_ONLY_DEFAULT,
12
+ WRITE_IDEMPOTENT,
13
+ WRITE_DESTRUCTIVE,
14
+ } from './shared.js';
15
+ import { type SpaceMemberRole } from '@lightdash-tools/common';
9
16
 
10
17
  export function registerSpaceTools(server: McpServer, client: LightdashClient): void {
11
18
  registerToolSafe(
@@ -43,4 +50,168 @@ export function registerSpaceTools(server: McpServer, client: LightdashClient):
43
50
  },
44
51
  ),
45
52
  );
53
+
54
+ registerToolSafe(
55
+ server,
56
+ 'grant_user_space_access',
57
+ {
58
+ title: 'Grant user access to space',
59
+ description: 'Grant a user access to a space',
60
+ inputSchema: {
61
+ projectUuid: z.string().describe('Project UUID'),
62
+ spaceUuid: z.string().describe('Space UUID'),
63
+ userUuid: z.string().describe('User UUID'),
64
+ spaceRole: z.enum(['viewer', 'editor', 'admin']).describe('Space role'),
65
+ },
66
+ annotations: WRITE_IDEMPOTENT,
67
+ },
68
+ wrapTool(
69
+ client,
70
+ (c) =>
71
+ async ({
72
+ projectUuid,
73
+ spaceUuid,
74
+ userUuid,
75
+ spaceRole,
76
+ }: {
77
+ projectUuid: string;
78
+ spaceUuid: string;
79
+ userUuid: string;
80
+ spaceRole: string;
81
+ }) => {
82
+ await c.v1.spaces.grantUserAccessToSpace(projectUuid, spaceUuid, {
83
+ userUuid,
84
+ spaceRole: spaceRole as SpaceMemberRole,
85
+ });
86
+ return {
87
+ content: [
88
+ {
89
+ type: 'text',
90
+ text: `Successfully granted ${spaceRole} access to user ${userUuid} in space ${spaceUuid}`,
91
+ },
92
+ ],
93
+ };
94
+ },
95
+ ),
96
+ );
97
+
98
+ registerToolSafe(
99
+ server,
100
+ 'revoke_user_space_access',
101
+ {
102
+ title: 'Revoke user access to space',
103
+ description: "Revoke a user's access to a space",
104
+ inputSchema: {
105
+ projectUuid: z.string().describe('Project UUID'),
106
+ spaceUuid: z.string().describe('Space UUID'),
107
+ userUuid: z.string().describe('User UUID'),
108
+ },
109
+ annotations: WRITE_DESTRUCTIVE,
110
+ },
111
+ wrapTool(
112
+ client,
113
+ (c) =>
114
+ async ({
115
+ projectUuid,
116
+ spaceUuid,
117
+ userUuid,
118
+ }: {
119
+ projectUuid: string;
120
+ spaceUuid: string;
121
+ userUuid: string;
122
+ }) => {
123
+ await c.v1.spaces.revokeUserAccessToSpace(projectUuid, spaceUuid, userUuid);
124
+ return {
125
+ content: [
126
+ {
127
+ type: 'text',
128
+ text: `Successfully revoked access for user ${userUuid} in space ${spaceUuid}`,
129
+ },
130
+ ],
131
+ };
132
+ },
133
+ ),
134
+ );
135
+
136
+ registerToolSafe(
137
+ server,
138
+ 'grant_group_space_access',
139
+ {
140
+ title: 'Grant group access to space',
141
+ description: 'Grant a group access to a space',
142
+ inputSchema: {
143
+ projectUuid: z.string().describe('Project UUID'),
144
+ spaceUuid: z.string().describe('Space UUID'),
145
+ groupUuid: z.string().describe('Group UUID'),
146
+ spaceRole: z.enum(['viewer', 'editor', 'admin']).describe('Space role'),
147
+ },
148
+ annotations: WRITE_IDEMPOTENT,
149
+ },
150
+ wrapTool(
151
+ client,
152
+ (c) =>
153
+ async ({
154
+ projectUuid,
155
+ spaceUuid,
156
+ groupUuid,
157
+ spaceRole,
158
+ }: {
159
+ projectUuid: string;
160
+ spaceUuid: string;
161
+ groupUuid: string;
162
+ spaceRole: string;
163
+ }) => {
164
+ await c.v1.spaces.grantGroupAccessToSpace(projectUuid, spaceUuid, {
165
+ groupUuid,
166
+ spaceRole: spaceRole as SpaceMemberRole,
167
+ });
168
+ return {
169
+ content: [
170
+ {
171
+ type: 'text',
172
+ text: `Successfully granted ${spaceRole} access to group ${groupUuid} in space ${spaceUuid}`,
173
+ },
174
+ ],
175
+ };
176
+ },
177
+ ),
178
+ );
179
+
180
+ registerToolSafe(
181
+ server,
182
+ 'revoke_group_space_access',
183
+ {
184
+ title: 'Revoke group access to space',
185
+ description: "Revoke a group's access to a space",
186
+ inputSchema: {
187
+ projectUuid: z.string().describe('Project UUID'),
188
+ spaceUuid: z.string().describe('Space UUID'),
189
+ groupUuid: z.string().describe('Group UUID'),
190
+ },
191
+ annotations: WRITE_DESTRUCTIVE,
192
+ },
193
+ wrapTool(
194
+ client,
195
+ (c) =>
196
+ async ({
197
+ projectUuid,
198
+ spaceUuid,
199
+ groupUuid,
200
+ }: {
201
+ projectUuid: string;
202
+ spaceUuid: string;
203
+ groupUuid: string;
204
+ }) => {
205
+ await c.v1.spaces.revokeGroupAccessToSpace(projectUuid, spaceUuid, groupUuid);
206
+ return {
207
+ content: [
208
+ {
209
+ type: 'text',
210
+ text: `Successfully revoked access for group ${groupUuid} in space ${spaceUuid}`,
211
+ },
212
+ ],
213
+ };
214
+ },
215
+ ),
216
+ );
46
217
  }