metabase-ai-assistant 3.4.3 → 4.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/package.json +2 -2
- package/src/mcp/handlers/actions.js +213 -0
- package/src/mcp/handlers/analytics.js +1647 -0
- package/src/mcp/handlers/cards.js +1544 -0
- package/src/mcp/handlers/collections.js +244 -0
- package/src/mcp/handlers/dashboard_direct.js +292 -0
- package/src/mcp/handlers/docs.js +399 -0
- package/src/mcp/handlers/metadata.js +190 -0
- package/src/mcp/handlers/schema.js +1699 -0
- package/src/mcp/handlers/sql.js +559 -0
- package/src/mcp/handlers/users.js +251 -0
- package/src/mcp/server.js +261 -9698
- package/src/mcp/tool-registry.js +3244 -0
- package/src/mcp/tool-router.js +149 -0
- package/src/metabase/client.js +32 -8
- package/src/utils/cache.js +11 -1
- package/src/utils/config.js +3 -0
- package/src/utils/logger.js +3 -19
- package/src/utils/sql-sanitizer.js +97 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metabase-ai-assistant",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"mcpName": "io.github.enessari/metabase-ai-assistant",
|
|
5
5
|
"description": "The most powerful MCP Server for Metabase - 111+ tools for AI-powered SQL generation, dashboard automation, user management & enterprise BI. Works with Claude, Cursor, and any MCP-compatible AI.",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -96,4 +96,4 @@
|
|
|
96
96
|
"eslint": "^8.56.0",
|
|
97
97
|
"jest": "^29.7.0"
|
|
98
98
|
}
|
|
99
|
-
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { logger } from '../../utils/logger.js';
|
|
3
|
+
|
|
4
|
+
export class ActionsHandler {
|
|
5
|
+
constructor(metabaseClient) {
|
|
6
|
+
this.metabaseClient = metabaseClient;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
routes() {
|
|
10
|
+
return {
|
|
11
|
+
'mb_action_create': (args) => this.handleActionCreate(args),
|
|
12
|
+
'mb_action_list': (args) => this.handleActionList(args),
|
|
13
|
+
'mb_action_execute': (args) => this.handleActionExecute(args),
|
|
14
|
+
'mb_alert_create': (args) => this.handleAlertCreate(args),
|
|
15
|
+
'mb_alert_list': (args) => this.handleAlertList(args),
|
|
16
|
+
'mb_pulse_create': (args) => this.handlePulseCreate(args),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async handleActionCreate(args) {
|
|
21
|
+
try {
|
|
22
|
+
await this.ensureInitialized();
|
|
23
|
+
|
|
24
|
+
const actionData = {
|
|
25
|
+
name: args.name,
|
|
26
|
+
description: args.description || '',
|
|
27
|
+
model_id: args.model_id,
|
|
28
|
+
type: args.type || 'query',
|
|
29
|
+
database_id: args.database_id,
|
|
30
|
+
dataset_query: args.dataset_query,
|
|
31
|
+
parameters: args.parameters || [],
|
|
32
|
+
visualization_settings: args.visualization_settings || {}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const action = await this.metabaseClient.request('POST', '/api/action', actionData);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
content: [{
|
|
39
|
+
type: 'text',
|
|
40
|
+
text: `✅ **Action Created!**\\n\\n` +
|
|
41
|
+
`🆔 Action ID: ${action.id}\\n` +
|
|
42
|
+
`📋 Name: ${action.name}\\n` +
|
|
43
|
+
`⚙️ Type: ${action.type}\\n` +
|
|
44
|
+
`📊 Model ID: ${args.model_id}\\n` +
|
|
45
|
+
`🔧 Parameters: ${(args.parameters || []).length}`
|
|
46
|
+
}]
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: 'text', text: `❌ Action creation failed: ${error.message}` }]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async handleActionList(args) {
|
|
57
|
+
try {
|
|
58
|
+
await this.ensureInitialized();
|
|
59
|
+
|
|
60
|
+
const actions = await this.metabaseClient.request('GET', `/api/action?model-id=${args.model_id}`);
|
|
61
|
+
|
|
62
|
+
let output = `📋 **Actions for Model ${args.model_id}**\\n\\n`;
|
|
63
|
+
|
|
64
|
+
if (actions.length === 0) {
|
|
65
|
+
output += 'No actions found for this model.';
|
|
66
|
+
} else {
|
|
67
|
+
actions.forEach((action, i) => {
|
|
68
|
+
output += `${i + 1}. **${action.name}** (ID: ${action.id})\\n`;
|
|
69
|
+
output += ` Type: ${action.type}\\n`;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: 'text', text: output }]
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: 'text', text: `❌ Action list failed: ${error.message}` }]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async handleActionExecute(args) {
|
|
85
|
+
try {
|
|
86
|
+
await this.ensureInitialized();
|
|
87
|
+
|
|
88
|
+
const result = await this.metabaseClient.request('POST', `/api/action/${args.action_id}/execute`, {
|
|
89
|
+
parameters: args.parameters
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
content: [{
|
|
94
|
+
type: 'text',
|
|
95
|
+
text: `✅ **Action Executed!**\\n\\n` +
|
|
96
|
+
`🆔 Action ID: ${args.action_id}\\n` +
|
|
97
|
+
`📋 Parameters: ${JSON.stringify(args.parameters)}\\n` +
|
|
98
|
+
`📊 Result: ${JSON.stringify(result)}`
|
|
99
|
+
}]
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return {
|
|
104
|
+
content: [{ type: 'text', text: `❌ Action execution failed: ${error.message}` }]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// === ALERTS & NOTIFICATIONS HANDLERS ===
|
|
110
|
+
|
|
111
|
+
async handleAlertCreate(args) {
|
|
112
|
+
try {
|
|
113
|
+
await this.ensureInitialized();
|
|
114
|
+
|
|
115
|
+
const alertData = {
|
|
116
|
+
card: { id: args.card_id },
|
|
117
|
+
alert_condition: args.alert_condition || 'rows',
|
|
118
|
+
alert_first_only: args.alert_first_only || false,
|
|
119
|
+
alert_above_goal: args.alert_above_goal,
|
|
120
|
+
channels: args.channels || [{
|
|
121
|
+
channel_type: 'email',
|
|
122
|
+
enabled: true,
|
|
123
|
+
recipients: [],
|
|
124
|
+
schedule_type: 'hourly'
|
|
125
|
+
}]
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const alert = await this.metabaseClient.request('POST', '/api/alert', alertData);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
content: [{
|
|
132
|
+
type: 'text',
|
|
133
|
+
text: `✅ **Alert Created!**\\n\\n` +
|
|
134
|
+
`🆔 Alert ID: ${alert.id}\\n` +
|
|
135
|
+
`🔔 Card ID: ${args.card_id}\\n` +
|
|
136
|
+
`⚙️ Condition: ${args.alert_condition || 'rows'}\\n` +
|
|
137
|
+
`📧 Channels: ${(args.channels || []).length}`
|
|
138
|
+
}]
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return {
|
|
143
|
+
content: [{ type: 'text', text: `❌ Alert creation failed: ${error.message}` }]
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async handleAlertList(args) {
|
|
149
|
+
try {
|
|
150
|
+
await this.ensureInitialized();
|
|
151
|
+
|
|
152
|
+
let endpoint = '/api/alert';
|
|
153
|
+
if (args.card_id) {
|
|
154
|
+
endpoint = `/api/alert/question/${args.card_id}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const alerts = await this.metabaseClient.request('GET', endpoint);
|
|
158
|
+
|
|
159
|
+
let output = `🔔 **Alerts**\\n\\n`;
|
|
160
|
+
|
|
161
|
+
if (alerts.length === 0) {
|
|
162
|
+
output += 'No alerts found.';
|
|
163
|
+
} else {
|
|
164
|
+
alerts.forEach((alert, i) => {
|
|
165
|
+
output += `${i + 1}. Alert ID: ${alert.id}\\n`;
|
|
166
|
+
output += ` Card: ${alert.card?.name || alert.card?.id}\\n`;
|
|
167
|
+
output += ` Condition: ${alert.alert_condition}\\n\\n`;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
content: [{ type: 'text', text: output }]
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
} catch (error) {
|
|
176
|
+
return {
|
|
177
|
+
content: [{ type: 'text', text: `❌ Alert list failed: ${error.message}` }]
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async handlePulseCreate(args) {
|
|
183
|
+
try {
|
|
184
|
+
await this.ensureInitialized();
|
|
185
|
+
|
|
186
|
+
const pulseData = {
|
|
187
|
+
name: args.name,
|
|
188
|
+
cards: args.cards,
|
|
189
|
+
channels: args.channels,
|
|
190
|
+
skip_if_empty: args.skip_if_empty !== false,
|
|
191
|
+
collection_id: args.collection_id
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const pulse = await this.metabaseClient.request('POST', '/api/pulse', pulseData);
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
content: [{
|
|
198
|
+
type: 'text',
|
|
199
|
+
text: `✅ **Scheduled Report (Pulse) Created!**\\n\\n` +
|
|
200
|
+
`🆔 Pulse ID: ${pulse.id}\\n` +
|
|
201
|
+
`📋 Name: ${pulse.name}\\n` +
|
|
202
|
+
`📊 Cards: ${args.cards.length}\\n` +
|
|
203
|
+
`📧 Channels: ${args.channels.length}`
|
|
204
|
+
}]
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
} catch (error) {
|
|
208
|
+
return {
|
|
209
|
+
content: [{ type: 'text', text: `❌ Pulse creation failed: ${error.message}` }]
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|