mcp-supabase-selfhosted 1.0.0 → 1.2.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/.gemini/skills/supabase-selfhosted/SKILL.md +11 -10
- package/CHANGELOG.md +14 -1
- package/CONTRIBUTING.md +2 -2
- package/README.md +37 -49
- package/SECURITY.md +4 -4
- package/dist/config/env.js +4 -7
- package/dist/db/postgres.js +9 -9
- package/dist/index.js +68 -3
- package/dist/supabase/client.js +5 -5
- package/dist/tools/index.js +55 -55
- package/package.json +2 -2
- package/src/config/env.ts +4 -7
- package/src/db/postgres.ts +9 -11
- package/src/index.ts +79 -3
- package/src/supabase/client.ts +5 -5
- package/src/tools/index.ts +55 -58
package/dist/tools/index.js
CHANGED
|
@@ -3,26 +3,26 @@ import { getSupabaseClient } from '../supabase/client.js';
|
|
|
3
3
|
export const toolsDefinitions = [
|
|
4
4
|
{
|
|
5
5
|
name: 'list_tables',
|
|
6
|
-
description: '
|
|
6
|
+
description: 'List all tables in a specific PostgreSQL database schema.',
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: 'object',
|
|
9
9
|
properties: {
|
|
10
10
|
schema: {
|
|
11
11
|
type: 'string',
|
|
12
|
-
description: "
|
|
12
|
+
description: "The name of the schema (e.g. 'public', 'auth'). Defaults to 'public'.",
|
|
13
13
|
},
|
|
14
14
|
},
|
|
15
15
|
},
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
name: 'execute_sql',
|
|
19
|
-
description: '
|
|
19
|
+
description: 'Execute a raw SQL query on the Supabase PostgreSQL database. Useful for reading data, modifying schemas, or managing the database. WARNING: This tool has direct access, bypassing RLS.',
|
|
20
20
|
inputSchema: {
|
|
21
21
|
type: 'object',
|
|
22
22
|
properties: {
|
|
23
23
|
sql: {
|
|
24
24
|
type: 'string',
|
|
25
|
-
description: '
|
|
25
|
+
description: 'The SQL query to execute.',
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
28
|
required: ['sql'],
|
|
@@ -30,32 +30,32 @@ export const toolsDefinitions = [
|
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
name: 'list_users',
|
|
33
|
-
description: '
|
|
33
|
+
description: 'List users registered in the Supabase Authentication service (auth.users). Returns basic user information.',
|
|
34
34
|
inputSchema: {
|
|
35
35
|
type: 'object',
|
|
36
36
|
properties: {
|
|
37
37
|
page: {
|
|
38
38
|
type: 'number',
|
|
39
|
-
description: '
|
|
39
|
+
description: 'The page number for pagination (defaults to 1).',
|
|
40
40
|
},
|
|
41
41
|
perPage: {
|
|
42
42
|
type: 'number',
|
|
43
|
-
description: '
|
|
43
|
+
description: 'The amount of users per page (defaults to 50).',
|
|
44
44
|
},
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
49
|
name: 'create_user',
|
|
50
|
-
description: '
|
|
50
|
+
description: 'Create a new user in Supabase Auth. Useful for initializing administrative or test accounts.',
|
|
51
51
|
inputSchema: {
|
|
52
52
|
type: 'object',
|
|
53
53
|
properties: {
|
|
54
|
-
email: { type: 'string', description: '
|
|
55
|
-
password: { type: 'string', description: '
|
|
54
|
+
email: { type: 'string', description: 'User email address.' },
|
|
55
|
+
password: { type: 'string', description: 'User password (minimum 6 characters).' },
|
|
56
56
|
email_confirm: {
|
|
57
57
|
type: 'boolean',
|
|
58
|
-
description: '
|
|
58
|
+
description: 'If true, auto-confirms the email (defaults to true).',
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
61
|
required: ['email', 'password'],
|
|
@@ -63,14 +63,14 @@ export const toolsDefinitions = [
|
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
name: 'delete_user',
|
|
66
|
-
description: '
|
|
66
|
+
description: 'Delete a Supabase Auth user by their ID.',
|
|
67
67
|
inputSchema: {
|
|
68
68
|
type: 'object',
|
|
69
69
|
properties: {
|
|
70
|
-
user_id: { type: 'string', description: '
|
|
70
|
+
user_id: { type: 'string', description: 'The UUID of the user to delete.' },
|
|
71
71
|
confirm: {
|
|
72
72
|
type: 'boolean',
|
|
73
|
-
description: '
|
|
73
|
+
description: 'Must be true to confirm the destructive deletion.',
|
|
74
74
|
},
|
|
75
75
|
},
|
|
76
76
|
required: ['user_id', 'confirm'],
|
|
@@ -78,7 +78,7 @@ export const toolsDefinitions = [
|
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
name: 'list_buckets',
|
|
81
|
-
description: '
|
|
81
|
+
description: 'List all storage buckets configured in the Supabase project.',
|
|
82
82
|
inputSchema: {
|
|
83
83
|
type: 'object',
|
|
84
84
|
properties: {},
|
|
@@ -86,14 +86,14 @@ export const toolsDefinitions = [
|
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
name: 'create_bucket',
|
|
89
|
-
description: '
|
|
89
|
+
description: 'Create a new storage bucket in Supabase.',
|
|
90
90
|
inputSchema: {
|
|
91
91
|
type: 'object',
|
|
92
92
|
properties: {
|
|
93
|
-
bucket: { type: 'string', description: '
|
|
93
|
+
bucket: { type: 'string', description: 'Name of the new bucket.' },
|
|
94
94
|
public: {
|
|
95
95
|
type: 'boolean',
|
|
96
|
-
description: '
|
|
96
|
+
description: 'Whether the bucket should be public (defaults to false).',
|
|
97
97
|
},
|
|
98
98
|
},
|
|
99
99
|
required: ['bucket'],
|
|
@@ -101,14 +101,14 @@ export const toolsDefinitions = [
|
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
name: 'delete_bucket',
|
|
104
|
-
description: '
|
|
104
|
+
description: 'Delete a storage bucket in Supabase. The bucket must be empty or it will fail.',
|
|
105
105
|
inputSchema: {
|
|
106
106
|
type: 'object',
|
|
107
107
|
properties: {
|
|
108
|
-
bucket: { type: 'string', description: '
|
|
108
|
+
bucket: { type: 'string', description: 'Name of the bucket to delete.' },
|
|
109
109
|
confirm: {
|
|
110
110
|
type: 'boolean',
|
|
111
|
-
description: '
|
|
111
|
+
description: 'Must be true to confirm the destructive deletion.',
|
|
112
112
|
},
|
|
113
113
|
},
|
|
114
114
|
required: ['bucket', 'confirm'],
|
|
@@ -116,24 +116,24 @@ export const toolsDefinitions = [
|
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
118
|
name: 'get_schema',
|
|
119
|
-
description: '
|
|
119
|
+
description: 'Retrieve the database schema or a specific table structure. Useful for understanding columns before executing SQL.',
|
|
120
120
|
inputSchema: {
|
|
121
121
|
type: 'object',
|
|
122
122
|
properties: {
|
|
123
123
|
table_name: {
|
|
124
124
|
type: 'string',
|
|
125
|
-
description: '
|
|
125
|
+
description: 'Table name to get columns for. If omitted, returns all tables with their columns.',
|
|
126
126
|
},
|
|
127
127
|
schema: {
|
|
128
128
|
type: 'string',
|
|
129
|
-
description: "
|
|
129
|
+
description: "The name of the schema (e.g. 'public'). Defaults to 'public'.",
|
|
130
130
|
},
|
|
131
131
|
},
|
|
132
132
|
},
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
name: 'get_advisors',
|
|
136
|
-
description: '
|
|
136
|
+
description: 'Get performance and security alerts/recommendations directly from the database (similar to Supabase dashboard alerts). Analyzes unused indexes, missing RLS policies, and cache hit ratio.',
|
|
137
137
|
inputSchema: {
|
|
138
138
|
type: 'object',
|
|
139
139
|
properties: {},
|
|
@@ -141,17 +141,17 @@ export const toolsDefinitions = [
|
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
name: 'list_files',
|
|
144
|
-
description: '
|
|
144
|
+
description: 'List files and folders within a specific storage bucket.',
|
|
145
145
|
inputSchema: {
|
|
146
146
|
type: 'object',
|
|
147
147
|
properties: {
|
|
148
148
|
bucket: {
|
|
149
149
|
type: 'string',
|
|
150
|
-
description: '
|
|
150
|
+
description: 'The name of the bucket to query.',
|
|
151
151
|
},
|
|
152
152
|
path: {
|
|
153
153
|
type: 'string',
|
|
154
|
-
description: '
|
|
154
|
+
description: 'The folder path within the bucket (optional).',
|
|
155
155
|
},
|
|
156
156
|
},
|
|
157
157
|
required: ['bucket'],
|
|
@@ -159,20 +159,20 @@ export const toolsDefinitions = [
|
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
name: 'list_rls_policies',
|
|
162
|
-
description: '
|
|
162
|
+
description: 'List all Row Level Security (RLS) policies active in the database. Useful for auditing access rules.',
|
|
163
163
|
inputSchema: {
|
|
164
164
|
type: 'object',
|
|
165
165
|
properties: {
|
|
166
166
|
schema: {
|
|
167
167
|
type: 'string',
|
|
168
|
-
description: "
|
|
168
|
+
description: "The schema to audit. Defaults to 'public'.",
|
|
169
169
|
},
|
|
170
170
|
},
|
|
171
171
|
},
|
|
172
172
|
},
|
|
173
173
|
{
|
|
174
174
|
name: 'get_active_connections',
|
|
175
|
-
description: '
|
|
175
|
+
description: 'Show current active database connections and their running queries. Excellent for debugging performance issues, locks, or connection pool saturation.',
|
|
176
176
|
inputSchema: {
|
|
177
177
|
type: 'object',
|
|
178
178
|
properties: {},
|
|
@@ -202,7 +202,7 @@ export async function handleGetSchema(params) {
|
|
|
202
202
|
catch (error) {
|
|
203
203
|
return {
|
|
204
204
|
isError: true,
|
|
205
|
-
content: [{ type: 'text', text: `Error
|
|
205
|
+
content: [{ type: 'text', text: `Error retrieving schema: ${error.message}` }],
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
208
|
}
|
|
@@ -223,7 +223,7 @@ export async function handleListTables(params) {
|
|
|
223
223
|
catch (error) {
|
|
224
224
|
return {
|
|
225
225
|
isError: true,
|
|
226
|
-
content: [{ type: 'text', text: `Error
|
|
226
|
+
content: [{ type: 'text', text: `Error listing tables: ${error.message}` }],
|
|
227
227
|
};
|
|
228
228
|
}
|
|
229
229
|
}
|
|
@@ -232,7 +232,7 @@ export async function handleExecuteSql(params) {
|
|
|
232
232
|
if (!sql) {
|
|
233
233
|
return {
|
|
234
234
|
isError: true,
|
|
235
|
-
content: [{ type: 'text', text: "
|
|
235
|
+
content: [{ type: 'text', text: "The 'sql' parameter is required." }],
|
|
236
236
|
};
|
|
237
237
|
}
|
|
238
238
|
try {
|
|
@@ -244,7 +244,7 @@ export async function handleExecuteSql(params) {
|
|
|
244
244
|
catch (error) {
|
|
245
245
|
return {
|
|
246
246
|
isError: true,
|
|
247
|
-
content: [{ type: 'text', text: `Error
|
|
247
|
+
content: [{ type: 'text', text: `Error executing SQL: ${error.message}` }],
|
|
248
248
|
};
|
|
249
249
|
}
|
|
250
250
|
}
|
|
@@ -266,7 +266,7 @@ export async function handleListUsers(params) {
|
|
|
266
266
|
catch (error) {
|
|
267
267
|
return {
|
|
268
268
|
isError: true,
|
|
269
|
-
content: [{ type: 'text', text: `Error
|
|
269
|
+
content: [{ type: 'text', text: `Error listing users: ${error.message}` }],
|
|
270
270
|
};
|
|
271
271
|
}
|
|
272
272
|
}
|
|
@@ -276,7 +276,7 @@ export async function handleCreateUser(params) {
|
|
|
276
276
|
if (!email || !password) {
|
|
277
277
|
return {
|
|
278
278
|
isError: true,
|
|
279
|
-
content: [{ type: 'text', text: "
|
|
279
|
+
content: [{ type: 'text', text: "Both 'email' and 'password' parameters are required." }],
|
|
280
280
|
};
|
|
281
281
|
}
|
|
282
282
|
try {
|
|
@@ -294,7 +294,7 @@ export async function handleCreateUser(params) {
|
|
|
294
294
|
catch (error) {
|
|
295
295
|
return {
|
|
296
296
|
isError: true,
|
|
297
|
-
content: [{ type: 'text', text: `Error
|
|
297
|
+
content: [{ type: 'text', text: `Error creating user: ${error.message}` }],
|
|
298
298
|
};
|
|
299
299
|
}
|
|
300
300
|
}
|
|
@@ -318,7 +318,7 @@ export async function handleDeleteUser(params) {
|
|
|
318
318
|
catch (error) {
|
|
319
319
|
return {
|
|
320
320
|
isError: true,
|
|
321
|
-
content: [{ type: 'text', text: `Error
|
|
321
|
+
content: [{ type: 'text', text: `Error deleting user: ${error.message}` }],
|
|
322
322
|
};
|
|
323
323
|
}
|
|
324
324
|
}
|
|
@@ -335,7 +335,7 @@ export async function handleListBuckets() {
|
|
|
335
335
|
catch (error) {
|
|
336
336
|
return {
|
|
337
337
|
isError: true,
|
|
338
|
-
content: [{ type: 'text', text: `Error
|
|
338
|
+
content: [{ type: 'text', text: `Error listing buckets: ${error.message}` }],
|
|
339
339
|
};
|
|
340
340
|
}
|
|
341
341
|
}
|
|
@@ -345,7 +345,7 @@ export async function handleCreateBucket(params) {
|
|
|
345
345
|
if (!bucket) {
|
|
346
346
|
return {
|
|
347
347
|
isError: true,
|
|
348
|
-
content: [{ type: 'text', text: "
|
|
348
|
+
content: [{ type: 'text', text: "The 'bucket' parameter is required." }],
|
|
349
349
|
};
|
|
350
350
|
}
|
|
351
351
|
try {
|
|
@@ -361,7 +361,7 @@ export async function handleCreateBucket(params) {
|
|
|
361
361
|
catch (error) {
|
|
362
362
|
return {
|
|
363
363
|
isError: true,
|
|
364
|
-
content: [{ type: 'text', text: `Error
|
|
364
|
+
content: [{ type: 'text', text: `Error creating bucket: ${error.message}` }],
|
|
365
365
|
};
|
|
366
366
|
}
|
|
367
367
|
}
|
|
@@ -372,7 +372,7 @@ export async function handleDeleteBucket(params) {
|
|
|
372
372
|
return {
|
|
373
373
|
isError: true,
|
|
374
374
|
content: [
|
|
375
|
-
{ type: 'text', text: "
|
|
375
|
+
{ type: 'text', text: "The 'bucket' parameter is required and 'confirm' must be true." },
|
|
376
376
|
],
|
|
377
377
|
};
|
|
378
378
|
}
|
|
@@ -387,7 +387,7 @@ export async function handleDeleteBucket(params) {
|
|
|
387
387
|
catch (error) {
|
|
388
388
|
return {
|
|
389
389
|
isError: true,
|
|
390
|
-
content: [{ type: 'text', text: `Error
|
|
390
|
+
content: [{ type: 'text', text: `Error deleting bucket: ${error.message}` }],
|
|
391
391
|
};
|
|
392
392
|
}
|
|
393
393
|
}
|
|
@@ -417,19 +417,19 @@ export async function handleGetAdvisors() {
|
|
|
417
417
|
const cacheRows = await query(cacheSql);
|
|
418
418
|
const report = {
|
|
419
419
|
security: {
|
|
420
|
-
issue: '
|
|
421
|
-
description: '
|
|
420
|
+
issue: 'Tables without Row Level Security (RLS) enabled',
|
|
421
|
+
description: 'These tables are exposed to the anonymous API if RLS is not configured.',
|
|
422
422
|
tables_affected: rlsRows.map((r) => r.table_name),
|
|
423
423
|
},
|
|
424
424
|
performance: {
|
|
425
425
|
unused_indexes: {
|
|
426
|
-
issue: '
|
|
427
|
-
description: '
|
|
426
|
+
issue: 'Unused indexes',
|
|
427
|
+
description: 'Indexes that occupy space and slow down writes but are not being used in reads.',
|
|
428
428
|
indexes: unusedIndexesRows,
|
|
429
429
|
},
|
|
430
430
|
cache_health: {
|
|
431
|
-
issue: '
|
|
432
|
-
description: '
|
|
431
|
+
issue: 'Cache hit ratio',
|
|
432
|
+
description: 'Should be as close to 99% as possible.',
|
|
433
433
|
ratio_percentage: cacheRows[0]?.cache_hit_ratio || 'N/A',
|
|
434
434
|
},
|
|
435
435
|
},
|
|
@@ -441,7 +441,7 @@ export async function handleGetAdvisors() {
|
|
|
441
441
|
catch (error) {
|
|
442
442
|
return {
|
|
443
443
|
isError: true,
|
|
444
|
-
content: [{ type: 'text', text: `Error
|
|
444
|
+
content: [{ type: 'text', text: `Error retrieving alerts: ${error.message}` }],
|
|
445
445
|
};
|
|
446
446
|
}
|
|
447
447
|
}
|
|
@@ -451,7 +451,7 @@ export async function handleListFiles(params) {
|
|
|
451
451
|
if (!bucket) {
|
|
452
452
|
return {
|
|
453
453
|
isError: true,
|
|
454
|
-
content: [{ type: 'text', text: "
|
|
454
|
+
content: [{ type: 'text', text: "The 'bucket' parameter is required." }],
|
|
455
455
|
};
|
|
456
456
|
}
|
|
457
457
|
try {
|
|
@@ -465,7 +465,7 @@ export async function handleListFiles(params) {
|
|
|
465
465
|
catch (error) {
|
|
466
466
|
return {
|
|
467
467
|
isError: true,
|
|
468
|
-
content: [{ type: 'text', text: `Error
|
|
468
|
+
content: [{ type: 'text', text: `Error listing files in bucket: ${error.message}` }],
|
|
469
469
|
};
|
|
470
470
|
}
|
|
471
471
|
}
|
|
@@ -486,7 +486,7 @@ export async function handleListRlsPolicies(params) {
|
|
|
486
486
|
catch (error) {
|
|
487
487
|
return {
|
|
488
488
|
isError: true,
|
|
489
|
-
content: [{ type: 'text', text: `Error
|
|
489
|
+
content: [{ type: 'text', text: `Error listing RLS policies: ${error.message}` }],
|
|
490
490
|
};
|
|
491
491
|
}
|
|
492
492
|
}
|
|
@@ -515,7 +515,7 @@ export async function handleGetActiveConnections() {
|
|
|
515
515
|
catch (error) {
|
|
516
516
|
return {
|
|
517
517
|
isError: true,
|
|
518
|
-
content: [{ type: 'text', text: `Error
|
|
518
|
+
content: [{ type: 'text', text: `Error retrieving active connections: ${error.message}` }],
|
|
519
519
|
};
|
|
520
520
|
}
|
|
521
521
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-supabase-selfhosted",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "An open-source Model Context Protocol (MCP) server designed specifically for self-hosted Supabase instances.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"mcp-supabase-selfhosted": "
|
|
8
|
+
"mcp-supabase-selfhosted": "dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "tsx --test tests/tools.test.ts",
|
package/src/config/env.ts
CHANGED
|
@@ -7,12 +7,9 @@ dotenv.config();
|
|
|
7
7
|
// Esquema de validación para las variables de entorno
|
|
8
8
|
const envSchema = z
|
|
9
9
|
.object({
|
|
10
|
-
SUPABASE_URL: z.string().url('
|
|
10
|
+
SUPABASE_URL: z.string().url('Supabase URL must be a valid URL').optional(),
|
|
11
11
|
SUPABASE_SERVICE_ROLE_KEY: z.string().optional(),
|
|
12
|
-
DATABASE_URL: z
|
|
13
|
-
.string()
|
|
14
|
-
.url('La URL de la base de datos (Postgres) debe ser válida')
|
|
15
|
-
.optional(),
|
|
12
|
+
DATABASE_URL: z.string().url('Database URL (Postgres) must be a valid URL').optional(),
|
|
16
13
|
})
|
|
17
14
|
.refine(
|
|
18
15
|
(data) => {
|
|
@@ -23,7 +20,7 @@ const envSchema = z
|
|
|
23
20
|
},
|
|
24
21
|
{
|
|
25
22
|
message:
|
|
26
|
-
'
|
|
23
|
+
'You must configure at least DATABASE_URL or (SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY)',
|
|
27
24
|
},
|
|
28
25
|
);
|
|
29
26
|
|
|
@@ -33,7 +30,7 @@ export function getConfig() {
|
|
|
33
30
|
return config;
|
|
34
31
|
} catch (error: unknown) {
|
|
35
32
|
if (error instanceof z.ZodError) {
|
|
36
|
-
console.error('
|
|
33
|
+
console.error(' Configuration error. Environment variables are missing or invalid:');
|
|
37
34
|
error.issues.forEach((e: z.ZodIssue) => console.error(` - ${e.message}`));
|
|
38
35
|
process.exit(1);
|
|
39
36
|
}
|
package/src/db/postgres.ts
CHANGED
|
@@ -14,29 +14,27 @@ export async function getDbPool(): Promise<Pool> {
|
|
|
14
14
|
const config = getConfig();
|
|
15
15
|
|
|
16
16
|
if (!config.DATABASE_URL) {
|
|
17
|
-
throw new Error(
|
|
18
|
-
'DATABASE_URL no está configurada. Las herramientas de base de datos directa no funcionarán.',
|
|
19
|
-
);
|
|
17
|
+
throw new Error('DATABASE_URL is not configured. Direct database tools will not work.');
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
pgPool = new Pool({
|
|
23
21
|
connectionString: config.DATABASE_URL,
|
|
24
|
-
max: 10, //
|
|
25
|
-
idleTimeoutMillis: 30000, //
|
|
26
|
-
connectionTimeoutMillis: 5000, //
|
|
27
|
-
statement_timeout: 10000, // (10s)
|
|
22
|
+
max: 10, // Maximum active connections for this MCP
|
|
23
|
+
idleTimeoutMillis: 30000, // Close idle connections after 30s
|
|
24
|
+
connectionTimeoutMillis: 5000, // Abort slow connection attempts
|
|
25
|
+
statement_timeout: 10000, // (10s) Abort heavy/incorrect queries generated by the AI
|
|
28
26
|
});
|
|
29
27
|
|
|
30
28
|
try {
|
|
31
|
-
//
|
|
29
|
+
// Verify connection with a simple query
|
|
32
30
|
const client = await pgPool.connect();
|
|
33
31
|
client.release();
|
|
34
|
-
console.error('
|
|
32
|
+
console.error(' Successfully connected to PostgreSQL (Pool with timeouts configured).');
|
|
35
33
|
return pgPool;
|
|
36
34
|
} catch (error: unknown) {
|
|
37
35
|
pgPool = null;
|
|
38
36
|
const msg = error instanceof Error ? error.message : String(error);
|
|
39
|
-
console.error(' Error
|
|
37
|
+
console.error(' Error connecting to PostgreSQL:', msg);
|
|
40
38
|
throw error;
|
|
41
39
|
}
|
|
42
40
|
}
|
|
@@ -51,7 +49,7 @@ export async function query(sql: string, params: unknown[] = []) {
|
|
|
51
49
|
return result.rows;
|
|
52
50
|
} catch (error: unknown) {
|
|
53
51
|
const msg = error instanceof Error ? error.message : String(error);
|
|
54
|
-
console.error(' Error
|
|
52
|
+
console.error(' Error executing query:', msg);
|
|
55
53
|
throw error;
|
|
56
54
|
}
|
|
57
55
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CallToolRequestSchema,
|
|
6
|
+
ListToolsRequestSchema,
|
|
7
|
+
ListResourcesRequestSchema,
|
|
8
|
+
ReadResourceRequestSchema,
|
|
9
|
+
ListPromptsRequestSchema,
|
|
10
|
+
GetPromptRequestSchema,
|
|
11
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
5
12
|
import { getConfig } from './config/env.js';
|
|
6
13
|
import {
|
|
7
14
|
toolsDefinitions,
|
|
@@ -19,6 +26,7 @@ import {
|
|
|
19
26
|
handleListRlsPolicies,
|
|
20
27
|
handleGetActiveConnections,
|
|
21
28
|
} from './tools/index.js';
|
|
29
|
+
import { query } from './db/postgres.js';
|
|
22
30
|
|
|
23
31
|
async function main() {
|
|
24
32
|
// 1. Validar la configuración al inicio
|
|
@@ -33,10 +41,78 @@ async function main() {
|
|
|
33
41
|
{
|
|
34
42
|
capabilities: {
|
|
35
43
|
tools: {},
|
|
44
|
+
resources: {},
|
|
45
|
+
prompts: {},
|
|
36
46
|
},
|
|
37
47
|
},
|
|
38
48
|
);
|
|
39
49
|
|
|
50
|
+
// --- RECURSOS (Resources) ---
|
|
51
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
52
|
+
return {
|
|
53
|
+
resources: [
|
|
54
|
+
{
|
|
55
|
+
uri: 'supabase://database/schema',
|
|
56
|
+
name: 'Complete database schema',
|
|
57
|
+
description: 'Returns the structure of all tables and columns in the public schema.',
|
|
58
|
+
mimeType: 'application/json',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
65
|
+
if (request.params.uri === 'supabase://database/schema') {
|
|
66
|
+
const sql = `
|
|
67
|
+
SELECT table_name, column_name, data_type
|
|
68
|
+
FROM information_schema.columns
|
|
69
|
+
WHERE table_schema = 'public'
|
|
70
|
+
ORDER BY table_name, ordinal_position;
|
|
71
|
+
`;
|
|
72
|
+
const rows = await query(sql);
|
|
73
|
+
return {
|
|
74
|
+
contents: [
|
|
75
|
+
{
|
|
76
|
+
uri: request.params.uri,
|
|
77
|
+
mimeType: 'application/json',
|
|
78
|
+
text: JSON.stringify(rows, null, 2),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
throw new Error('Resource not found');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// --- PROMPTS ---
|
|
87
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
88
|
+
return {
|
|
89
|
+
prompts: [
|
|
90
|
+
{
|
|
91
|
+
name: 'audit-security',
|
|
92
|
+
description: 'Performs a complete security audit of the Supabase instance.',
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
99
|
+
if (request.params.name === 'audit-security') {
|
|
100
|
+
return {
|
|
101
|
+
description: 'Supabase Security Audit',
|
|
102
|
+
messages: [
|
|
103
|
+
{
|
|
104
|
+
role: 'user',
|
|
105
|
+
content: {
|
|
106
|
+
type: 'text',
|
|
107
|
+
text: 'Please perform the following steps to audit my instance:\n1. Use get_advisors to detect performance and RLS issues.\n2. Use list_rls_policies to review all active access rules.\n3. Use get_active_connections to see if there are suspicious accesses or blocks.\n4. Finally, provide a detailed report with security recommendations.',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
throw new Error('Prompt not found');
|
|
114
|
+
});
|
|
115
|
+
|
|
40
116
|
// 3. Registrar el manejador para listar herramientas (Tools)
|
|
41
117
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
42
118
|
return {
|
|
@@ -84,10 +160,10 @@ async function main() {
|
|
|
84
160
|
const transport = new StdioServerTransport();
|
|
85
161
|
await server.connect(transport);
|
|
86
162
|
|
|
87
|
-
console.error('
|
|
163
|
+
console.error(' Supabase Self-Hosted MCP Server started successfully.');
|
|
88
164
|
}
|
|
89
165
|
|
|
90
166
|
main().catch((error) => {
|
|
91
|
-
console.error('
|
|
167
|
+
console.error(' Fatal error starting MCP server:', error);
|
|
92
168
|
process.exit(1);
|
|
93
169
|
});
|
package/src/supabase/client.ts
CHANGED
|
@@ -15,13 +15,13 @@ export function getSupabaseClient(): SupabaseClient {
|
|
|
15
15
|
|
|
16
16
|
if (!config.SUPABASE_URL || !config.SUPABASE_SERVICE_ROLE_KEY) {
|
|
17
17
|
throw new Error(
|
|
18
|
-
'SUPABASE_URL
|
|
18
|
+
'SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are required to use Auth and Storage tools.',
|
|
19
19
|
);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
22
|
+
// Create client using the service role key.
|
|
23
|
+
// IMPORTANT: In an MCP context (acting as super admin), it is safe
|
|
24
|
+
// and necessary to use the service role key, but the user should be aware of this.
|
|
25
25
|
supabaseClient = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE_KEY, {
|
|
26
26
|
auth: {
|
|
27
27
|
autoRefreshToken: false,
|
|
@@ -29,6 +29,6 @@ export function getSupabaseClient(): SupabaseClient {
|
|
|
29
29
|
},
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
console.error('
|
|
32
|
+
console.error(' Supabase API client initialized.');
|
|
33
33
|
return supabaseClient;
|
|
34
34
|
}
|