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.
@@ -3,26 +3,26 @@ import { getSupabaseClient } from '../supabase/client.js';
3
3
  export const toolsDefinitions = [
4
4
  {
5
5
  name: 'list_tables',
6
- description: 'Lista todas las tablas en un esquema específico de la base de datos PostgreSQL.',
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: "El nombre del esquema (ej. 'public', 'auth'). Por defecto es 'public'.",
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: 'Ejecuta una consulta SQL cruda en la base de datos PostgreSQL de Supabase. Útil para leer datos, modificar esquemas o administrar la base de datos. ATENCIÓN: Esta herramienta tiene acceso directo, sin pasar por RLS.',
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: 'La consulta SQL a ejecutar.',
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: 'Lista los usuarios registrados en el servicio de Autenticación de Supabase (auth.users). Devuelve información básica de los usuarios.',
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: 'El número de página para paginación (por defecto 1).',
39
+ description: 'The page number for pagination (defaults to 1).',
40
40
  },
41
41
  perPage: {
42
42
  type: 'number',
43
- description: 'La cantidad de usuarios por página (por defecto 50).',
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: 'Crea un nuevo usuario en Supabase Auth. Útil para inicializar cuentas administrativas o de prueba.',
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: 'Correo electrónico del usuario.' },
55
- password: { type: 'string', description: 'Contraseña del usuario (mínimo 6 caracteres).' },
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: 'Si es true, autoconfirma el email (por defecto true).',
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: 'Elimina un usuario de Supabase Auth por su ID.',
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: 'El UUID del usuario a eliminar.' },
70
+ user_id: { type: 'string', description: 'The UUID of the user to delete.' },
71
71
  confirm: {
72
72
  type: 'boolean',
73
- description: 'Debe ser true para confirmar la eliminación destructiva.',
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: 'Lista todos los buckets de almacenamiento (Storage) configurados en el proyecto de Supabase.',
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: 'Crea un nuevo bucket de almacenamiento (Storage) en Supabase.',
89
+ description: 'Create a new storage bucket in Supabase.',
90
90
  inputSchema: {
91
91
  type: 'object',
92
92
  properties: {
93
- bucket: { type: 'string', description: 'Nombre del nuevo bucket.' },
93
+ bucket: { type: 'string', description: 'Name of the new bucket.' },
94
94
  public: {
95
95
  type: 'boolean',
96
- description: 'Si el bucket debe ser público (por defecto false).',
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: 'Elimina un bucket de almacenamiento (Storage) en Supabase. El bucket debe estar vacío o fallará.',
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: 'Nombre del bucket a eliminar.' },
108
+ bucket: { type: 'string', description: 'Name of the bucket to delete.' },
109
109
  confirm: {
110
110
  type: 'boolean',
111
- description: 'Debe ser true para confirmar la eliminación destructiva.',
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: 'Obtiene el esquema de la base de datos o de una tabla específica. Útil para entender la estructura antes de ejecutar SQL.',
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: 'Nombre de la tabla para obtener sus columnas. Si se omite, devuelve una lista de todas las tablas con sus columnas.',
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: "El nombre del esquema (ej. 'public'). Por defecto es 'public'.",
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: 'Obtiene alertas y recomendaciones de rendimiento y seguridad directamente de la base de datos (similar a las alertas del panel de Supabase). Analiza índices sin uso, políticas RLS faltantes y ratio de caché.',
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: 'Lista los archivos y carpetas dentro de un bucket de almacenamiento (Storage) específico.',
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: 'El nombre del bucket a consultar.',
150
+ description: 'The name of the bucket to query.',
151
151
  },
152
152
  path: {
153
153
  type: 'string',
154
- description: 'La ruta de la carpeta dentro del bucket (opcional).',
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: 'Lista todas las políticas de seguridad a nivel de fila (Row Level Security - RLS) activas en la base de datos. Útil para auditar reglas de acceso.',
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: "El esquema a auditar. Por defecto 'public'.",
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: 'Muestra las conexiones activas actuales a la base de datos y qué consultas están ejecutando. Excelente para depurar problemas de rendimiento, bloqueos o saturación del pool de conexiones.',
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 obteniendo esquema: ${error.message}` }],
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 listando tablas: ${error.message}` }],
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: "El parámetro 'sql' es obligatorio." }],
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 ejecutando SQL: ${error.message}` }],
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 listando usuarios: ${error.message}` }],
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: "Los parámetros 'email' y 'password' son obligatorios." }],
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 creando usuario: ${error.message}` }],
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 eliminando usuario: ${error.message}` }],
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 listando buckets: ${error.message}` }],
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: "El parámetro 'bucket' es obligatorio." }],
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 creando bucket: ${error.message}` }],
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: "El parámetro 'bucket' es obligatorio y 'confirm' debe ser true." },
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 eliminando bucket: ${error.message}` }],
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: 'Tablas sin Row Level Security (RLS) habilitado',
421
- description: 'Estas tablas están expuestas a la API anónima si no configuras RLS.',
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: 'Índices sin uso',
427
- description: 'Índices que ocupan espacio y ralentizan escrituras pero no se están usando en lecturas.',
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: 'Ratio de acierto en caché',
432
- description: 'Debe estar lo más cerca posible al 99%.',
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 obteniendo alertas: ${error.message}` }],
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: "El parámetro 'bucket' es obligatorio." }],
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 listando archivos en el bucket: ${error.message}` }],
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 listando políticas RLS: ${error.message}` }],
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 obteniendo conexiones activas: ${error.message}` }],
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.0.0",
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": "./dist/index.js"
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('La URL de Supabase debe ser válida').optional(),
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
- 'Debes configurar al menos DATABASE_URL o (SUPABASE_URL y SUPABASE_SERVICE_ROLE_KEY)',
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(' Error de configuración. Faltan variables de entorno o son inválidas:');
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
  }
@@ -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, // Límite máximo de conexiones activas para este MCP
25
- idleTimeoutMillis: 30000, // Cerrar conexiones inactivas después de 30s
26
- connectionTimeoutMillis: 5000, // Abortar intentos de conexión lentos
27
- statement_timeout: 10000, // (10s) Abortar consultas pesadas/erróneas generadas por la IA
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
- // Verificamos la conexión con una consulta sencilla
29
+ // Verify connection with a simple query
32
30
  const client = await pgPool.connect();
33
31
  client.release();
34
- console.error(' Conectado exitosamente a PostgreSQL (Pool con timeouts configurados).');
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 conectando a PostgreSQL:', msg);
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 ejecutando query:', msg);
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 { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
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(' Servidor MCP de Supabase Self-Hosted iniciado correctamente.');
163
+ console.error(' Supabase Self-Hosted MCP Server started successfully.');
88
164
  }
89
165
 
90
166
  main().catch((error) => {
91
- console.error(' Error fatal al iniciar el servidor MCP:', error);
167
+ console.error(' Fatal error starting MCP server:', error);
92
168
  process.exit(1);
93
169
  });
@@ -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 y SUPABASE_SERVICE_ROLE_KEY son obligatorias para utilizar las herramientas de Auth y Storage.',
18
+ 'SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are required to use Auth and Storage tools.',
19
19
  );
20
20
  }
21
21
 
22
- // Creamos el cliente usando la service role key.
23
- // IMPORTANTE: En el contexto de un MCP (que actúa como un super admin), es seguro
24
- // y necesario usar la service role key, pero el usuario debe estar consciente de esto.
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(' Cliente Supabase API inicializado.');
32
+ console.error(' Supabase API client initialized.');
33
33
  return supabaseClient;
34
34
  }