@aliyun-rds/supabase-mcp-server 1.0.2 → 1.0.4

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.
Files changed (3) hide show
  1. package/README.md +207 -10
  2. package/dist/index.js +1254 -353
  3. package/package.json +6 -3
package/dist/index.js CHANGED
@@ -56,7 +56,8 @@ var SelfhostedSupabaseClient = class _SelfhostedSupabaseClient {
56
56
  */
57
57
  constructor(options) {
58
58
  this.options = options;
59
- this.supabase = createClient(options.supabaseUrl, options.supabaseAnonKey, options.supabaseClientOptions);
59
+ const apiKey = options.supabaseServiceRoleKey || options.supabaseAnonKey;
60
+ this.supabase = createClient(options.supabaseUrl, apiKey, options.supabaseClientOptions);
60
61
  if (!options.supabaseUrl || !options.supabaseAnonKey) {
61
62
  throw new Error("Supabase URL and Anon Key are required.");
62
63
  }
@@ -167,6 +168,25 @@ var SelfhostedSupabaseClient = class _SelfhostedSupabaseClient {
167
168
  client?.release();
168
169
  }
169
170
  }
171
+ /**
172
+ * Encodes special characters in database URL to prevent parsing errors
173
+ * Handles characters like #, $ that may cause issues with pg library
174
+ */
175
+ encodeDatabaseUrl(url) {
176
+ try {
177
+ const parsedUrl = new URL(url);
178
+ if (parsedUrl.username) {
179
+ parsedUrl.username = encodeURIComponent(parsedUrl.username);
180
+ }
181
+ if (parsedUrl.password) {
182
+ parsedUrl.password = encodeURIComponent(parsedUrl.password);
183
+ }
184
+ return parsedUrl.toString();
185
+ } catch (error) {
186
+ console.error("Database URL contains special characters. Applying basic encoding (excluding @).");
187
+ return url.replace(/#/g, "%23").replace(/\$/g, "%24");
188
+ }
189
+ }
170
190
  /**
171
191
  * Ensures the pg connection pool is initialized.
172
192
  * Should be called before accessing this.pgPool.
@@ -177,7 +197,8 @@ var SelfhostedSupabaseClient = class _SelfhostedSupabaseClient {
177
197
  throw new Error("DATABASE_URL is not configured. Cannot initialize pg pool.");
178
198
  }
179
199
  console.error("Initializing pg pool...");
180
- this.pgPool = new Pool({ connectionString: this.options.databaseUrl });
200
+ const encodedDbUrl = this.encodeDatabaseUrl(this.options.databaseUrl);
201
+ this.pgPool = new Pool({ connectionString: encodedDbUrl });
181
202
  this.pgPool.on("error", (err, client) => {
182
203
  console.error("PG Pool Error: Unexpected error on idle client", err);
183
204
  });
@@ -226,61 +247,90 @@ var SelfhostedSupabaseClient = class _SelfhostedSupabaseClient {
226
247
  }
227
248
  }
228
249
  // --- Helper/Private Methods (to be implemented) ---
250
+ /**
251
+ * Executes SQL using postgres-meta API (/pg/query endpoint)
252
+ * This is available in Supabase instances and doesn't require direct database connection
253
+ */
254
+ async executeSqlViaPostgresMeta(query) {
255
+ const url = `${this.options.supabaseUrl}/pg/query`;
256
+ const apiKey = this.options.supabaseServiceRoleKey || this.options.supabaseAnonKey;
257
+ const response = await fetch(url, {
258
+ method: "POST",
259
+ headers: {
260
+ "Content-Type": "application/json",
261
+ "apikey": apiKey
262
+ },
263
+ body: JSON.stringify({ query })
264
+ });
265
+ if (!response.ok) {
266
+ const errorText = await response.text();
267
+ throw new Error(`postgres-meta API error: ${response.status} ${errorText}`);
268
+ }
269
+ return await response.json();
270
+ }
229
271
  async checkAndCreateRpcFunction() {
230
272
  console.error("Checking for public.execute_sql RPC function...");
273
+ if (!this.options.supabaseServiceRoleKey) {
274
+ console.error("Cannot check/create 'public.execute_sql': supabaseServiceRoleKey not provided.");
275
+ this.rpcFunctionExists = false;
276
+ return;
277
+ }
231
278
  try {
232
- const { error } = await this.supabase.rpc("execute_sql", { query: "SELECT 1" });
233
- if (!error) {
279
+ console.error("Checking if execute_sql function exists using postgres-meta API...");
280
+ const checkQuery = `
281
+ SELECT EXISTS (
282
+ SELECT 1
283
+ FROM pg_proc p
284
+ JOIN pg_namespace n ON p.pronamespace = n.oid
285
+ WHERE n.nspname = 'public'
286
+ AND p.proname = 'execute_sql'
287
+ ) as function_exists;
288
+ `;
289
+ const checkResult = await this.executeSqlViaPostgresMeta(checkQuery);
290
+ if (checkResult && checkResult.length > 0 && checkResult[0].function_exists) {
234
291
  console.error("'public.execute_sql' function found.");
235
292
  this.rpcFunctionExists = true;
236
293
  return;
237
294
  }
238
- const UNDEFINED_FUNCTION_ERROR_CODE = "42883";
239
- const POSTGREST_FUNCTION_NOT_FOUND_CODE = "PGRST202";
240
- if (error.code === UNDEFINED_FUNCTION_ERROR_CODE || error.code === POSTGREST_FUNCTION_NOT_FOUND_CODE) {
241
- console.error(
242
- `'public.execute_sql' function not found (Code: ${error.code}). Attempting creation...`
243
- );
244
- if (!this.options.supabaseServiceRoleKey) {
245
- console.error("Cannot create 'public.execute_sql': supabaseServiceRoleKey not provided.");
246
- this.rpcFunctionExists = false;
247
- return;
248
- }
249
- if (!this.options.databaseUrl) {
250
- console.error("Cannot create 'public.execute_sql' reliably without databaseUrl for direct connection.");
251
- this.rpcFunctionExists = false;
252
- return;
253
- }
254
- try {
255
- console.error("Creating 'public.execute_sql' function using direct DB connection...");
256
- await this.executeSqlWithPg(_SelfhostedSupabaseClient.CREATE_EXECUTE_SQL_FUNCTION);
257
- await this.executeSqlWithPg(_SelfhostedSupabaseClient.GRANT_EXECUTE_SQL_FUNCTION);
258
- console.error("'public.execute_sql' function created and permissions granted successfully.");
259
- console.error("Notifying PostgREST to reload schema cache...");
260
- await this.executeSqlWithPg("NOTIFY pgrst, 'reload schema'");
261
- console.error("PostgREST schema reload notification sent.");
262
- this.rpcFunctionExists = true;
263
- } catch (creationError) {
264
- const errorMessage = creationError instanceof Error ? creationError.message : String(creationError);
265
- console.error("Failed to create 'public.execute_sql' function or notify PostgREST:", creationError);
295
+ console.error("'public.execute_sql' function not found. Creating...");
296
+ try {
297
+ console.error("Creating 'public.execute_sql' function using postgres-meta API...");
298
+ await this.executeSqlViaPostgresMeta(_SelfhostedSupabaseClient.CREATE_EXECUTE_SQL_FUNCTION);
299
+ await this.executeSqlViaPostgresMeta(_SelfhostedSupabaseClient.GRANT_EXECUTE_SQL_FUNCTION);
300
+ console.error("'public.execute_sql' function created and permissions granted successfully.");
301
+ console.error("Notifying PostgREST to reload schema cache...");
302
+ await this.executeSqlViaPostgresMeta("NOTIFY pgrst, 'reload schema'");
303
+ console.error("PostgREST schema reload notification sent.");
304
+ this.rpcFunctionExists = true;
305
+ } catch (postgresMetaError) {
306
+ console.error("Failed to create function via postgres-meta API:", postgresMetaError);
307
+ if (this.options.databaseUrl) {
308
+ try {
309
+ console.error("Falling back to direct DB connection...");
310
+ await this.executeSqlWithPg(_SelfhostedSupabaseClient.CREATE_EXECUTE_SQL_FUNCTION);
311
+ await this.executeSqlWithPg(_SelfhostedSupabaseClient.GRANT_EXECUTE_SQL_FUNCTION);
312
+ await this.executeSqlWithPg("NOTIFY pgrst, 'reload schema'");
313
+ console.error("'public.execute_sql' function created via direct DB connection.");
314
+ this.rpcFunctionExists = true;
315
+ } catch (pgError) {
316
+ const errorMessage = pgError instanceof Error ? pgError.message : String(pgError);
317
+ console.error("Failed to create function via direct DB connection:", pgError);
318
+ this.rpcFunctionExists = false;
319
+ console.error("RPC function creation failed. You can manually install using the install_execute_sql_function tool.");
320
+ }
321
+ } else {
322
+ const errorMessage = postgresMetaError instanceof Error ? postgresMetaError.message : String(postgresMetaError);
323
+ console.error("No fallback available (databaseUrl not provided)");
324
+ console.error("RPC function creation failed. You can manually install using the install_execute_sql_function tool.");
266
325
  this.rpcFunctionExists = false;
267
- throw new Error(`Failed to create execute_sql function/notify: ${errorMessage}`);
268
326
  }
269
- } else {
270
- console.error(
271
- "Unexpected error checking for 'public.execute_sql' function:",
272
- error
273
- );
274
- this.rpcFunctionExists = false;
275
- throw new Error(
276
- `Error checking for execute_sql function: ${error.message}`
277
- );
278
327
  }
279
328
  } catch (err) {
280
329
  const errorMessage = err instanceof Error ? err.message : String(err);
281
330
  console.error("Exception during RPC function check/creation:", err);
331
+ console.error("RPC function check failed, but continuing initialization...");
332
+ console.error("You can manually install the execute_sql function using the install_execute_sql_function tool.");
282
333
  this.rpcFunctionExists = false;
283
- throw new Error(`Exception during RPC function check/creation: ${errorMessage}`);
284
334
  }
285
335
  }
286
336
  // --- Getters ---
@@ -311,6 +361,12 @@ var SelfhostedSupabaseClient = class _SelfhostedSupabaseClient {
311
361
  isPgAvailable() {
312
362
  return !!this.options.databaseUrl;
313
363
  }
364
+ /**
365
+ * Checks if the execute_sql RPC function is available.
366
+ */
367
+ isRpcAvailable() {
368
+ return this.rpcFunctionExists;
369
+ }
314
370
  };
315
371
 
316
372
  // src/tools/list_tables.ts
@@ -385,32 +441,36 @@ var listTablesTool = {
385
441
  // Use explicit types for input and context
386
442
  execute: async (input, context) => {
387
443
  const client = context.selfhostedClient;
388
- const listTablesSql = `
389
- SELECT
390
- n.nspname as schema,
391
- c.relname as name,
392
- pgd.description as comment
393
- FROM
394
- pg_catalog.pg_class c
395
- JOIN
396
- pg_catalog.pg_namespace n ON n.oid = c.relnamespace
397
- LEFT JOIN
398
- pg_catalog.pg_description pgd ON pgd.objoid = c.oid AND pgd.objsubid = 0
399
- WHERE
400
- c.relkind = 'r' -- r = ordinary table
401
- AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
402
- AND n.nspname NOT LIKE 'pg_temp_%'
403
- AND n.nspname NOT LIKE 'pg_toast_temp_%'
404
- -- Exclude Supabase internal schemas
405
- AND n.nspname NOT IN ('auth', 'storage', 'extensions', 'graphql', 'graphql_public', 'pgbouncer', 'realtime', 'supabase_functions', 'supabase_migrations', '_realtime')
406
- AND has_schema_privilege(n.oid, 'USAGE')
407
- AND has_table_privilege(c.oid, 'SELECT')
408
- ORDER BY
409
- n.nspname,
410
- c.relname
411
- `;
412
- const result = await executeSqlWithFallback(client, listTablesSql, true);
413
- return handleSqlResponse(result, ListTablesOutputSchema);
444
+ console.error("Listing tables using Supabase REST API introspection...");
445
+ try {
446
+ if (client.isPgAvailable() || client.isRpcAvailable()) {
447
+ const listTablesSql = `
448
+ SELECT
449
+ table_schema as schema,
450
+ table_name as name,
451
+ NULL as comment
452
+ FROM
453
+ information_schema.tables
454
+ WHERE
455
+ table_type = 'BASE TABLE'
456
+ AND table_schema NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
457
+ AND table_schema NOT LIKE 'pg_temp_%'
458
+ AND table_schema NOT LIKE 'pg_toast_temp_%'
459
+ AND table_schema NOT IN ('auth', 'storage', 'extensions', 'graphql', 'graphql_public', 'pgbouncer', 'realtime', 'supabase_functions', 'supabase_migrations', '_realtime')
460
+ ORDER BY
461
+ table_schema,
462
+ table_name
463
+ `;
464
+ const result = await executeSqlWithFallback(client, listTablesSql, true);
465
+ return handleSqlResponse(result, ListTablesOutputSchema);
466
+ }
467
+ context.log("Cannot list tables: requires either direct database access (DATABASE_URL) or execute_sql RPC function", "error");
468
+ throw new Error("Cannot list tables: requires either direct database access (DATABASE_URL) or execute_sql RPC function. Please provide DATABASE_URL or install the execute_sql RPC function in your Supabase instance.");
469
+ } catch (error) {
470
+ const errorMessage = error instanceof Error ? error.message : String(error);
471
+ context.log(`Error listing tables: ${errorMessage}`, "error");
472
+ throw error;
473
+ }
414
474
  }
415
475
  };
416
476
 
@@ -518,7 +578,7 @@ var mcpInputSchema4 = {
518
578
  };
519
579
  var applyMigrationTool = {
520
580
  name: "apply_migration",
521
- description: "Applies a SQL migration script and records it in the supabase_migrations.schema_migrations table within a transaction.",
581
+ description: "Applies a SQL migration script and records it in the supabase_migrations.schema_migrations table within a transaction. Requires direct database connection (DATABASE_URL).",
522
582
  inputSchema: ApplyMigrationInputSchema,
523
583
  mcpInputSchema: mcpInputSchema4,
524
584
  outputSchema: ApplyMigrationOutputSchema,
@@ -1085,32 +1145,37 @@ var listAuthUsersTool = {
1085
1145
  execute: async (input, context) => {
1086
1146
  const client = context.selfhostedClient;
1087
1147
  const { limit, offset } = input;
1088
- if (!client.isPgAvailable()) {
1089
- context.log("Direct database connection (DATABASE_URL) is required to list auth users.", "error");
1090
- throw new Error("Direct database connection (DATABASE_URL) is required to list auth users.");
1148
+ console.error("Listing auth users using Supabase Admin API...");
1149
+ try {
1150
+ const { data, error } = await client.supabase.auth.admin.listUsers({
1151
+ page: Math.floor(offset / limit) + 1,
1152
+ perPage: limit
1153
+ });
1154
+ if (error) {
1155
+ context.log(`Error listing users: ${error.message}`, "error");
1156
+ throw new Error(`Failed to list users: ${error.message}`);
1157
+ }
1158
+ if (!data || !data.users) {
1159
+ context.log("No users found or invalid response", "warning");
1160
+ return [];
1161
+ }
1162
+ const users = data.users.map((user) => ({
1163
+ id: user.id,
1164
+ email: user.email || null,
1165
+ role: user.role || null,
1166
+ created_at: user.created_at || null,
1167
+ last_sign_in_at: user.last_sign_in_at || null,
1168
+ raw_app_meta_data: user.app_metadata || null,
1169
+ raw_user_meta_data: user.user_metadata || null
1170
+ }));
1171
+ console.error(`Found ${users.length} users.`);
1172
+ context.log(`Found ${users.length} users.`);
1173
+ return ListAuthUsersOutputSchema.parse(users);
1174
+ } catch (error) {
1175
+ const errorMessage = error instanceof Error ? error.message : String(error);
1176
+ context.log(`Error listing users: ${errorMessage}`, "error");
1177
+ throw error;
1091
1178
  }
1092
- const listUsersSql = `
1093
- SELECT
1094
- id,
1095
- email,
1096
- role,
1097
- raw_app_meta_data,
1098
- raw_user_meta_data,
1099
- created_at::text, -- Cast timestamp to text for JSON
1100
- last_sign_in_at::text -- Cast timestamp to text for JSON
1101
- FROM
1102
- auth.users
1103
- ORDER BY
1104
- created_at DESC
1105
- LIMIT ${limit}
1106
- OFFSET ${offset}
1107
- `;
1108
- console.error("Attempting to list auth users using direct DB connection...");
1109
- const result = await client.executeSqlWithPg(listUsersSql);
1110
- const validatedUsers = handleSqlResponse(result, ListAuthUsersOutputSchema);
1111
- console.error(`Found ${validatedUsers.length} users.`);
1112
- context.log(`Found ${validatedUsers.length} users.`);
1113
- return validatedUsers;
1114
1179
  }
1115
1180
  };
1116
1181
 
@@ -1151,43 +1216,33 @@ var getAuthUserTool = {
1151
1216
  execute: async (input, context) => {
1152
1217
  const client = context.selfhostedClient;
1153
1218
  const { user_id } = input;
1154
- if (!client.isPgAvailable()) {
1155
- context.log("Direct database connection (DATABASE_URL) is required to get auth user details.", "error");
1156
- throw new Error("Direct database connection (DATABASE_URL) is required to get auth user details.");
1157
- }
1158
- const sql = `
1159
- SELECT
1160
- id,
1161
- email,
1162
- role,
1163
- raw_app_meta_data,
1164
- raw_user_meta_data,
1165
- created_at::text,
1166
- last_sign_in_at::text
1167
- FROM auth.users
1168
- WHERE id = $1
1169
- `;
1170
- const params = [user_id];
1171
- console.error(`Attempting to get auth user ${user_id} using direct DB connection...`);
1172
- const user = await client.executeTransactionWithPg(async (pgClient) => {
1173
- const result = await pgClient.query(sql, params);
1174
- if (result.rows.length === 0) {
1175
- throw new Error(`User with ID ${user_id} not found.`);
1219
+ console.error(`Getting auth user ${user_id} using Supabase Admin API...`);
1220
+ try {
1221
+ const { data, error } = await client.supabase.auth.admin.getUserById(user_id);
1222
+ if (error) {
1223
+ context.log(`Error getting user: ${error.message}`, "error");
1224
+ throw new Error(`Failed to get user: ${error.message}`);
1176
1225
  }
1177
- try {
1178
- const singleUser = AuthUserZodSchema2.parse(result.rows[0]);
1179
- return singleUser;
1180
- } catch (validationError) {
1181
- if (validationError instanceof z16.ZodError) {
1182
- console.error("Zod validation failed:", validationError.errors);
1183
- throw new Error(`Output validation failed: ${validationError.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")}`);
1184
- }
1185
- throw validationError;
1226
+ if (!data || !data.user) {
1227
+ throw new Error(`User with ID ${user_id} not found.`);
1186
1228
  }
1187
- });
1188
- console.error(`Found user ${user_id}.`);
1189
- context.log(`Found user ${user_id}.`);
1190
- return user;
1229
+ const user = {
1230
+ id: data.user.id,
1231
+ email: data.user.email || null,
1232
+ role: data.user.role || null,
1233
+ created_at: data.user.created_at || null,
1234
+ last_sign_in_at: data.user.last_sign_in_at || null,
1235
+ raw_app_meta_data: data.user.app_metadata || null,
1236
+ raw_user_meta_data: data.user.user_metadata || null
1237
+ };
1238
+ console.error(`Found user ${user_id}.`);
1239
+ context.log(`Found user ${user_id}.`);
1240
+ return AuthUserZodSchema2.parse(user);
1241
+ } catch (error) {
1242
+ const errorMessage = error instanceof Error ? error.message : String(error);
1243
+ context.log(`Error getting user: ${errorMessage}`, "error");
1244
+ throw error;
1245
+ }
1191
1246
  }
1192
1247
  };
1193
1248
 
@@ -1213,37 +1268,34 @@ var mcpInputSchema16 = {
1213
1268
  };
1214
1269
  var deleteAuthUserTool = {
1215
1270
  name: "delete_auth_user",
1216
- description: "Deletes a user from auth.users by their ID. Requires service_role key and direct DB connection.",
1271
+ description: "Deletes a user using Supabase Admin API. Requires service role key.",
1217
1272
  inputSchema: DeleteAuthUserInputSchema,
1218
1273
  mcpInputSchema: mcpInputSchema16,
1219
1274
  outputSchema: DeleteAuthUserOutputSchema,
1220
1275
  execute: async (input, context) => {
1221
1276
  const client = context.selfhostedClient;
1222
1277
  const { user_id } = input;
1223
- if (!client.isPgAvailable()) {
1224
- throw new Error("Direct database connection (DATABASE_URL) is required for deleting users but is not configured or available.");
1278
+ if (!client) {
1279
+ throw new Error("Supabase client is not initialized.");
1225
1280
  }
1281
+ context.log(`Deleting user ${user_id} using Supabase Admin API...`, "info");
1226
1282
  try {
1227
- const result = await client.executeTransactionWithPg(async (pgClient) => {
1228
- const deleteResult = await pgClient.query(
1229
- "DELETE FROM auth.users WHERE id = $1",
1230
- [user_id]
1231
- );
1232
- return deleteResult;
1233
- });
1234
- if (result.rowCount === 1) {
1283
+ const { error } = await client.supabase.auth.admin.deleteUser(user_id);
1284
+ if (error) {
1285
+ context.log(`Error deleting user: ${error.message}`, "error");
1235
1286
  return {
1236
- success: true,
1237
- message: `Successfully deleted user with ID: ${user_id}`
1287
+ success: false,
1288
+ message: `Failed to delete user: ${error.message}`
1238
1289
  };
1239
1290
  }
1291
+ context.log(`User ${user_id} deleted successfully`, "info");
1240
1292
  return {
1241
- success: false,
1242
- message: `User with ID ${user_id} not found or could not be deleted.`
1293
+ success: true,
1294
+ message: `Successfully deleted user with ID: ${user_id}`
1243
1295
  };
1244
1296
  } catch (error) {
1245
1297
  const errorMessage = error instanceof Error ? error.message : String(error);
1246
- console.error(`Error deleting user ${user_id}:`, errorMessage);
1298
+ context.log(`Error deleting user ${user_id}: ${errorMessage}`, "error");
1247
1299
  throw new Error(`Failed to delete user ${user_id}: ${errorMessage}`);
1248
1300
  }
1249
1301
  }
@@ -1282,80 +1334,48 @@ var mcpInputSchema17 = {
1282
1334
  };
1283
1335
  var createAuthUserTool = {
1284
1336
  name: "create_auth_user",
1285
- description: "Creates a new user directly in auth.users. WARNING: Requires plain password, insecure. Use with extreme caution.",
1337
+ description: "Creates a new user using Supabase Admin API. Requires service role key.",
1286
1338
  inputSchema: CreateAuthUserInputSchema,
1287
1339
  mcpInputSchema: mcpInputSchema17,
1288
- // Ensure defined above
1289
1340
  outputSchema: CreatedAuthUserZodSchema,
1290
1341
  execute: async (input, context) => {
1291
1342
  const client = context.selfhostedClient;
1292
1343
  const { email, password, role, app_metadata, user_metadata } = input;
1293
- if (!client.isPgAvailable()) {
1294
- context.log("Direct database connection (DATABASE_URL) is required to create an auth user directly.", "error");
1295
- throw new Error("Direct database connection (DATABASE_URL) is required to create an auth user directly.");
1344
+ if (!client) {
1345
+ throw new Error("Supabase client is not initialized.");
1296
1346
  }
1297
- console.warn(`SECURITY WARNING: Creating user ${email} with plain text password via direct DB insert.`);
1298
- context.log(`Attempting to create user ${email}...`, "warn");
1299
- const createdUser = await client.executeTransactionWithPg(async (pgClient) => {
1300
- try {
1301
- await pgClient.query("SELECT crypt('test', gen_salt('bf'))");
1302
- } catch (err) {
1303
- throw new Error("Failed to execute crypt function. Ensure pgcrypto extension is enabled in the database.");
1304
- }
1305
- const sql = `
1306
- INSERT INTO auth.users (
1307
- instance_id, email, encrypted_password, role,
1308
- raw_app_meta_data, raw_user_meta_data,
1309
- aud, email_confirmed_at, confirmation_sent_at -- Set required defaults
1310
- )
1311
- VALUES (
1312
- COALESCE(current_setting('app.instance_id', TRUE), '00000000-0000-0000-0000-000000000000')::uuid,
1313
- $1, crypt($2, gen_salt('bf')),
1314
- $3,
1315
- $4::jsonb,
1316
- $5::jsonb,
1317
- 'authenticated', now(), now()
1318
- )
1319
- RETURNING id, email, role, raw_app_meta_data, raw_user_meta_data, created_at::text, last_sign_in_at::text;
1320
- `;
1321
- const params = [
1347
+ context.log(`Creating user ${email} using Supabase Admin API...`, "info");
1348
+ try {
1349
+ const { data, error } = await client.supabase.auth.admin.createUser({
1322
1350
  email,
1323
1351
  password,
1324
- role || "authenticated",
1325
- // Default role
1326
- JSON.stringify(app_metadata || {}),
1327
- JSON.stringify(user_metadata || {})
1328
- ];
1329
- try {
1330
- const result = await pgClient.query(sql, params);
1331
- if (result.rows.length === 0) {
1332
- throw new Error("User creation failed, no user returned after insert.");
1333
- }
1334
- return CreatedAuthUserZodSchema.parse(result.rows[0]);
1335
- } catch (dbError) {
1336
- let errorMessage = "Unknown database error during user creation";
1337
- let isUniqueViolation = false;
1338
- if (typeof dbError === "object" && dbError !== null && "code" in dbError) {
1339
- if (dbError.code === "23505") {
1340
- isUniqueViolation = true;
1341
- errorMessage = `User creation failed: Email '${email}' likely already exists.`;
1342
- } else if ("message" in dbError && typeof dbError.message === "string") {
1343
- errorMessage = `Database error (${dbError.code}): ${dbError.message}`;
1344
- } else {
1345
- errorMessage = `Database error code: ${dbError.code}`;
1346
- }
1347
- } else if (dbError instanceof Error) {
1348
- errorMessage = `Database error during user creation: ${dbError.message}`;
1349
- } else {
1350
- errorMessage = `Database error during user creation: ${String(dbError)}`;
1351
- }
1352
- console.error("Error creating user in DB:", dbError);
1353
- throw new Error(errorMessage);
1352
+ email_confirm: true,
1353
+ // Auto-confirm email
1354
+ user_metadata: user_metadata || {},
1355
+ app_metadata: app_metadata || {}
1356
+ });
1357
+ if (error) {
1358
+ context.log(`Error creating user: ${error.message}`, "error");
1359
+ throw new Error(`Failed to create user: ${error.message}`);
1354
1360
  }
1355
- });
1356
- console.error(`Successfully created user ${email} with ID ${createdUser.id}.`);
1357
- context.log(`Successfully created user ${email} with ID ${createdUser.id}.`);
1358
- return createdUser;
1361
+ if (!data.user) {
1362
+ throw new Error("User creation succeeded but no user data returned");
1363
+ }
1364
+ context.log(`User ${email} created successfully with ID: ${data.user.id}`, "info");
1365
+ return {
1366
+ id: data.user.id,
1367
+ email: data.user.email || null,
1368
+ role: data.user.role || role || "authenticated",
1369
+ created_at: data.user.created_at || null,
1370
+ last_sign_in_at: data.user.last_sign_in_at || null,
1371
+ raw_app_meta_data: data.user.app_metadata || null,
1372
+ raw_user_meta_data: data.user.user_metadata || null
1373
+ };
1374
+ } catch (error) {
1375
+ const errorMessage = error instanceof Error ? error.message : String(error);
1376
+ context.log(`Error creating user: ${errorMessage}`, "error");
1377
+ throw error;
1378
+ }
1359
1379
  }
1360
1380
  };
1361
1381
 
@@ -1397,95 +1417,63 @@ var mcpInputSchema18 = {
1397
1417
  };
1398
1418
  var updateAuthUserTool = {
1399
1419
  name: "update_auth_user",
1400
- description: "Updates fields for a user in auth.users. WARNING: Password handling is insecure. Requires service_role key and direct DB connection.",
1420
+ description: "Updates fields for a user using Supabase Admin API. Requires service role key.",
1401
1421
  inputSchema: UpdateAuthUserInputSchema,
1402
1422
  mcpInputSchema: mcpInputSchema18,
1403
- // Ensure defined
1404
1423
  outputSchema: UpdatedAuthUserZodSchema,
1405
1424
  execute: async (input, context) => {
1406
1425
  const client = context.selfhostedClient;
1407
1426
  const { user_id, email, password, role, app_metadata, user_metadata } = input;
1408
- if (!client.isPgAvailable()) {
1409
- context.log("Direct database connection (DATABASE_URL) is required to update auth user details.", "error");
1410
- throw new Error("Direct database connection (DATABASE_URL) is required to update auth user details.");
1411
- }
1412
- const updates = [];
1413
- const params = [];
1414
- let paramIndex = 1;
1415
- if (email !== void 0) {
1416
- updates.push(`email = $${paramIndex++}`);
1417
- params.push(email);
1418
- }
1419
- if (password !== void 0) {
1420
- updates.push(`encrypted_password = crypt($${paramIndex++}, gen_salt('bf'))`);
1421
- params.push(password);
1422
- console.warn(`SECURITY WARNING: Updating password for user ${user_id} with plain text password via direct DB update.`);
1423
- }
1424
- if (role !== void 0) {
1425
- updates.push(`role = $${paramIndex++}`);
1426
- params.push(role);
1427
- }
1428
- if (app_metadata !== void 0) {
1429
- updates.push(`raw_app_meta_data = $${paramIndex++}::jsonb`);
1430
- params.push(JSON.stringify(app_metadata));
1431
- }
1432
- if (user_metadata !== void 0) {
1433
- updates.push(`raw_user_meta_data = $${paramIndex++}::jsonb`);
1434
- params.push(JSON.stringify(user_metadata));
1435
- }
1436
- params.push(user_id);
1437
- const userIdParamIndex = paramIndex;
1438
- const sql = `
1439
- UPDATE auth.users
1440
- SET ${updates.join(", ")}, updated_at = NOW()
1441
- WHERE id = $${userIdParamIndex}
1442
- RETURNING id, email, role, raw_app_meta_data, raw_user_meta_data, created_at::text, updated_at::text, last_sign_in_at::text;
1443
- `;
1444
- console.error(`Attempting to update auth user ${user_id}...`);
1445
- context.log(`Attempting to update auth user ${user_id}...`);
1446
- const updatedUser = await client.executeTransactionWithPg(async (pgClient) => {
1427
+ if (!client) {
1428
+ throw new Error("Supabase client is not initialized.");
1429
+ }
1430
+ context.log(`Updating user ${user_id} using Supabase Admin API...`, "info");
1431
+ try {
1432
+ const updateAttributes = {};
1433
+ if (email !== void 0) {
1434
+ updateAttributes.email = email;
1435
+ }
1447
1436
  if (password !== void 0) {
1448
- try {
1449
- await pgClient.query("SELECT crypt('test', gen_salt('bf'))");
1450
- } catch (err) {
1451
- throw new Error("Failed to execute crypt function for password update. Ensure pgcrypto extension is enabled.");
1452
- }
1437
+ updateAttributes.password = password;
1453
1438
  }
1454
- try {
1455
- const result = await pgClient.query(sql, params);
1456
- if (result.rows.length === 0) {
1457
- throw new Error(`User update failed: User with ID ${user_id} not found or no rows affected.`);
1458
- }
1459
- return UpdatedAuthUserZodSchema.parse(result.rows[0]);
1460
- } catch (dbError) {
1461
- let errorMessage = "Unknown database error during user update";
1462
- let isUniqueViolation = false;
1463
- if (typeof dbError === "object" && dbError !== null && "code" in dbError) {
1464
- if (email !== void 0 && dbError.code === "23505") {
1465
- isUniqueViolation = true;
1466
- errorMessage = `User update failed: Email '${email}' likely already exists for another user.`;
1467
- } else if ("message" in dbError && typeof dbError.message === "string") {
1468
- errorMessage = `Database error (${dbError.code}): ${dbError.message}`;
1469
- } else {
1470
- errorMessage = `Database error code: ${dbError.code}`;
1471
- }
1472
- } else if (dbError instanceof Error) {
1473
- errorMessage = `Database error during user update: ${dbError.message}`;
1474
- } else {
1475
- errorMessage = `Database error during user update: ${String(dbError)}`;
1476
- }
1477
- console.error("Error updating user in DB:", dbError);
1478
- throw new Error(errorMessage);
1439
+ if (user_metadata !== void 0) {
1440
+ updateAttributes.user_metadata = user_metadata;
1479
1441
  }
1480
- });
1481
- console.error(`Successfully updated user ${user_id}.`);
1482
- context.log(`Successfully updated user ${user_id}.`);
1483
- return updatedUser;
1442
+ if (app_metadata !== void 0) {
1443
+ updateAttributes.app_metadata = app_metadata;
1444
+ }
1445
+ const { data, error } = await client.supabase.auth.admin.updateUserById(
1446
+ user_id,
1447
+ updateAttributes
1448
+ );
1449
+ if (error) {
1450
+ context.log(`Error updating user: ${error.message}`, "error");
1451
+ throw new Error(`Failed to update user: ${error.message}`);
1452
+ }
1453
+ if (!data.user) {
1454
+ throw new Error("User update succeeded but no user data returned");
1455
+ }
1456
+ context.log(`User ${user_id} updated successfully`, "info");
1457
+ return {
1458
+ id: data.user.id,
1459
+ email: data.user.email || null,
1460
+ role: data.user.role || role || null,
1461
+ created_at: data.user.created_at || null,
1462
+ updated_at: data.user.updated_at || null,
1463
+ last_sign_in_at: data.user.last_sign_in_at || null,
1464
+ raw_app_meta_data: data.user.app_metadata || null,
1465
+ raw_user_meta_data: data.user.user_metadata || null
1466
+ };
1467
+ } catch (error) {
1468
+ const errorMessage = error instanceof Error ? error.message : String(error);
1469
+ context.log(`Error updating user ${user_id}: ${errorMessage}`, "error");
1470
+ throw error;
1471
+ }
1484
1472
  }
1485
1473
  };
1486
1474
 
1487
1475
  // src/index.ts
1488
- import { z as z23 } from "zod";
1476
+ import { z as z30 } from "zod";
1489
1477
 
1490
1478
  // src/tools/list_storage_buckets.ts
1491
1479
  import { z as z20 } from "zod";
@@ -1517,10 +1505,6 @@ var listStorageBucketsTool = {
1517
1505
  execute: async (input, context) => {
1518
1506
  const client = context.selfhostedClient;
1519
1507
  console.error("Listing storage buckets...");
1520
- if (!client.isPgAvailable()) {
1521
- context.log("Direct database connection (DATABASE_URL) is required to list storage buckets.", "error");
1522
- throw new Error("Direct database connection (DATABASE_URL) is required to list storage buckets.");
1523
- }
1524
1508
  const sql = `
1525
1509
  SELECT
1526
1510
  id,
@@ -1534,8 +1518,17 @@ var listStorageBucketsTool = {
1534
1518
  updated_at::text -- Cast to text
1535
1519
  FROM storage.buckets;
1536
1520
  `;
1537
- console.error("Attempting to list storage buckets using direct DB connection...");
1538
- const result = await client.executeSqlWithPg(sql);
1521
+ let result;
1522
+ if (client.isRpcAvailable()) {
1523
+ console.error("Listing storage buckets using execute_sql RPC function...");
1524
+ result = await client.executeSqlViaRpc(sql);
1525
+ } else if (client.isPgAvailable()) {
1526
+ console.error("Listing storage buckets using direct DB connection...");
1527
+ result = await client.executeSqlWithPg(sql);
1528
+ } else {
1529
+ context.log("Cannot list storage buckets: requires either direct database access (DATABASE_URL) or execute_sql RPC function.", "error");
1530
+ throw new Error("Cannot list storage buckets: requires either direct database access (DATABASE_URL) or execute_sql RPC function.");
1531
+ }
1539
1532
  const validatedBuckets = handleSqlResponse(result, ListStorageBucketsOutputSchema);
1540
1533
  console.error(`Found ${validatedBuckets.length} buckets.`);
1541
1534
  context.log(`Found ${validatedBuckets.length} buckets.`);
@@ -1590,43 +1583,43 @@ var listStorageObjectsTool = {
1590
1583
  const client = context.selfhostedClient;
1591
1584
  const { bucket_id, limit, offset, prefix } = input;
1592
1585
  console.error(`Listing objects for bucket ${bucket_id} (Prefix: ${prefix || "N/A"})...`);
1593
- if (!client.isPgAvailable()) {
1594
- context.log("Direct database connection (DATABASE_URL) is required to list storage objects.", "error");
1595
- throw new Error("Direct database connection (DATABASE_URL) is required to list storage objects.");
1596
- }
1597
- const objects = await client.executeTransactionWithPg(async (pgClient) => {
1598
- let sql = `
1599
- SELECT
1600
- id,
1601
- name,
1602
- bucket_id,
1603
- owner,
1604
- version,
1605
- metadata ->> 'mimetype' AS mimetype,
1606
- metadata ->> 'size' AS size, -- Extract size from metadata
1607
- metadata,
1608
- created_at::text,
1609
- updated_at::text,
1610
- last_accessed_at::text
1611
- FROM storage.objects
1612
- WHERE bucket_id = $1
1613
- `;
1614
- const params = [bucket_id];
1615
- let paramIndex = 2;
1616
- if (prefix) {
1617
- sql += ` AND name LIKE $${paramIndex++}`;
1618
- params.push(`${prefix}%`);
1619
- }
1620
- sql += " ORDER BY name ASC NULLS FIRST";
1621
- sql += ` LIMIT $${paramIndex++}`;
1622
- params.push(limit);
1623
- sql += ` OFFSET $${paramIndex++}`;
1624
- params.push(offset);
1625
- sql += ";";
1626
- console.error("Executing parameterized SQL to list storage objects within transaction...");
1627
- const result = await pgClient.query(sql, params);
1628
- return handleSqlResponse(result.rows, ListStorageObjectsOutputSchema);
1629
- });
1586
+ const escapeSqlString = (str) => {
1587
+ return str.replace(/'/g, "''");
1588
+ };
1589
+ let sql = `
1590
+ SELECT
1591
+ id,
1592
+ name,
1593
+ bucket_id,
1594
+ owner,
1595
+ version,
1596
+ metadata ->> 'mimetype' AS mimetype,
1597
+ metadata ->> 'size' AS size,
1598
+ metadata,
1599
+ created_at::text,
1600
+ updated_at::text,
1601
+ last_accessed_at::text
1602
+ FROM storage.objects
1603
+ WHERE bucket_id = '${escapeSqlString(bucket_id)}'
1604
+ `;
1605
+ if (prefix) {
1606
+ sql += ` AND name LIKE '${escapeSqlString(prefix)}%'`;
1607
+ }
1608
+ sql += " ORDER BY name ASC NULLS FIRST";
1609
+ sql += ` LIMIT ${limit}`;
1610
+ sql += ` OFFSET ${offset};`;
1611
+ let result;
1612
+ if (client.isRpcAvailable()) {
1613
+ console.error("Listing storage objects using execute_sql RPC function...");
1614
+ result = await client.executeSqlViaRpc(sql);
1615
+ } else if (client.isPgAvailable()) {
1616
+ console.error("Listing storage objects using direct DB connection...");
1617
+ result = await client.executeSqlWithPg(sql);
1618
+ } else {
1619
+ context.log("Cannot list storage objects: requires either direct database access (DATABASE_URL) or execute_sql RPC function.", "error");
1620
+ throw new Error("Cannot list storage objects: requires either direct database access (DATABASE_URL) or execute_sql RPC function.");
1621
+ }
1622
+ const objects = handleSqlResponse(result, ListStorageObjectsOutputSchema);
1630
1623
  console.error(`Found ${objects.length} objects.`);
1631
1624
  context.log(`Found ${objects.length} objects.`);
1632
1625
  return objects;
@@ -1665,10 +1658,6 @@ var listRealtimePublicationsTool = {
1665
1658
  execute: async (input, context) => {
1666
1659
  const client = context.selfhostedClient;
1667
1660
  console.error("Listing Realtime publications...");
1668
- if (!client.isPgAvailable()) {
1669
- context.log("Direct database connection (DATABASE_URL) is required to list publications.", "error");
1670
- throw new Error("Direct database connection (DATABASE_URL) is required to list publications.");
1671
- }
1672
1661
  const sql = `
1673
1662
  SELECT
1674
1663
  oid,
@@ -1682,8 +1671,17 @@ var listRealtimePublicationsTool = {
1682
1671
  pubviaroot
1683
1672
  FROM pg_catalog.pg_publication;
1684
1673
  `;
1685
- console.error("Attempting to list publications using direct DB connection...");
1686
- const result = await client.executeSqlWithPg(sql);
1674
+ let result;
1675
+ if (client.isRpcAvailable()) {
1676
+ console.error("Listing publications using execute_sql RPC function...");
1677
+ result = await client.executeSqlViaRpc(sql);
1678
+ } else if (client.isPgAvailable()) {
1679
+ console.error("Listing publications using direct DB connection...");
1680
+ result = await client.executeSqlWithPg(sql);
1681
+ } else {
1682
+ context.log("Cannot list publications: requires either direct database access (DATABASE_URL) or execute_sql RPC function.", "error");
1683
+ throw new Error("Cannot list publications: requires either direct database access (DATABASE_URL) or execute_sql RPC function.");
1684
+ }
1687
1685
  const validatedPublications = handleSqlResponse(result, ListRealtimePublicationsOutputSchema);
1688
1686
  console.error(`Found ${validatedPublications.length} publications.`);
1689
1687
  context.log(`Found ${validatedPublications.length} publications.`);
@@ -1692,32 +1690,864 @@ var listRealtimePublicationsTool = {
1692
1690
  };
1693
1691
  var list_realtime_publications_default = listRealtimePublicationsTool;
1694
1692
 
1693
+ // src/tools/install_execute_sql_function.ts
1694
+ import { z as z23 } from "zod";
1695
+ import { zodToJsonSchema } from "zod-to-json-schema";
1696
+ var InstallExecuteSqlFunctionInputZodSchema = z23.object({});
1697
+ var InstallExecuteSqlFunctionOutputZodSchema = z23.object({
1698
+ success: z23.boolean(),
1699
+ message: z23.string()
1700
+ });
1701
+ var CREATE_EXECUTE_SQL_FUNCTION = `
1702
+ CREATE OR REPLACE FUNCTION public.execute_sql(query text, read_only boolean DEFAULT true)
1703
+ RETURNS jsonb
1704
+ LANGUAGE plpgsql
1705
+ SECURITY DEFINER
1706
+ SET search_path = public
1707
+ AS $$
1708
+ DECLARE
1709
+ result jsonb;
1710
+ BEGIN
1711
+ -- Execute query and convert result to JSON
1712
+ IF read_only THEN
1713
+ -- Read-only query
1714
+ EXECUTE 'SELECT COALESCE(jsonb_agg(row_to_json(t)), ''[]''::jsonb) FROM (' || query || ') t' INTO result;
1715
+ ELSE
1716
+ -- Write query (INSERT/UPDATE/DELETE)
1717
+ EXECUTE query;
1718
+ result := '[]'::jsonb;
1719
+ END IF;
1720
+
1721
+ RETURN COALESCE(result, '[]'::jsonb);
1722
+ EXCEPTION
1723
+ WHEN OTHERS THEN
1724
+ -- Return error information
1725
+ RETURN jsonb_build_object(
1726
+ 'error', SQLERRM,
1727
+ 'code', SQLSTATE
1728
+ );
1729
+ END;
1730
+ $$;
1731
+ `;
1732
+ var GRANT_EXECUTE_SQL_FUNCTION = `
1733
+ GRANT EXECUTE ON FUNCTION public.execute_sql(text, boolean) TO anon, authenticated, service_role;
1734
+ `;
1735
+ var NOTIFY_POSTGREST = `NOTIFY pgrst, 'reload schema';`;
1736
+ async function execute(input, context) {
1737
+ const client = context.selfhostedClient;
1738
+ context.log("Installing execute_sql RPC function...", "info");
1739
+ try {
1740
+ const supabaseUrl = client.getSupabaseUrl();
1741
+ const serviceKey = client.getServiceRoleKey();
1742
+ if (!serviceKey) {
1743
+ throw new Error("Service role key is required to install the execute_sql function");
1744
+ }
1745
+ const postgresMetaUrl = `${supabaseUrl}/pg/query`;
1746
+ context.log("Creating execute_sql function...", "info");
1747
+ const createResponse = await fetch(postgresMetaUrl, {
1748
+ method: "POST",
1749
+ headers: {
1750
+ "Content-Type": "application/json",
1751
+ "apikey": serviceKey
1752
+ },
1753
+ body: JSON.stringify({ query: CREATE_EXECUTE_SQL_FUNCTION })
1754
+ });
1755
+ if (!createResponse.ok) {
1756
+ const errorText = await createResponse.text();
1757
+ throw new Error(`Failed to create function: ${createResponse.status} ${errorText}`);
1758
+ }
1759
+ context.log("Granting permissions...", "info");
1760
+ const grantResponse = await fetch(postgresMetaUrl, {
1761
+ method: "POST",
1762
+ headers: {
1763
+ "Content-Type": "application/json",
1764
+ "apikey": serviceKey
1765
+ },
1766
+ body: JSON.stringify({ query: GRANT_EXECUTE_SQL_FUNCTION })
1767
+ });
1768
+ if (!grantResponse.ok) {
1769
+ const errorText = await grantResponse.text();
1770
+ throw new Error(`Failed to grant permissions: ${grantResponse.status} ${errorText}`);
1771
+ }
1772
+ context.log("Notifying PostgREST to reload schema...", "info");
1773
+ const notifyResponse = await fetch(postgresMetaUrl, {
1774
+ method: "POST",
1775
+ headers: {
1776
+ "Content-Type": "application/json",
1777
+ "apikey": serviceKey
1778
+ },
1779
+ body: JSON.stringify({ query: NOTIFY_POSTGREST })
1780
+ });
1781
+ if (!notifyResponse.ok) {
1782
+ const errorText = await notifyResponse.text();
1783
+ console.error(`Warning: Failed to notify PostgREST: ${notifyResponse.status} ${errorText}`);
1784
+ }
1785
+ context.log("execute_sql function installed successfully!", "info");
1786
+ context.log("You may need to wait a few seconds for PostgREST to reload the schema.", "info");
1787
+ return {
1788
+ success: true,
1789
+ message: "execute_sql function installed successfully. You can now use tools that require SQL execution (list_tables, execute_sql, etc.)."
1790
+ };
1791
+ } catch (error) {
1792
+ const errorMessage = error instanceof Error ? error.message : String(error);
1793
+ context.log(`Error installing execute_sql function: ${errorMessage}`, "error");
1794
+ throw new Error(`Failed to install execute_sql function: ${errorMessage}`);
1795
+ }
1796
+ }
1797
+ var installExecuteSqlFunctionTool = {
1798
+ name: "install_execute_sql_function",
1799
+ description: "Install the execute_sql RPC function in the connected Supabase instance. This function is required for tools that need to execute SQL queries (list_tables, list_extensions, execute_sql, etc.). This tool uses the postgres-meta API and does not require direct database access.",
1800
+ inputSchema: InstallExecuteSqlFunctionInputZodSchema,
1801
+ mcpInputSchema: zodToJsonSchema(InstallExecuteSqlFunctionInputZodSchema),
1802
+ outputSchema: InstallExecuteSqlFunctionOutputZodSchema,
1803
+ execute
1804
+ };
1805
+ var install_execute_sql_function_default = installExecuteSqlFunctionTool;
1806
+
1807
+ // src/tools/search_docs.ts
1808
+ import { z as z24 } from "zod";
1809
+
1810
+ // src/integrations/content-api-client.ts
1811
+ var SupabaseDocsApiClient = class {
1812
+ schemaCache = null;
1813
+ graphqlEndpoint;
1814
+ constructor(baseUrl = "https://supabase.com/docs/api/graphql") {
1815
+ this.graphqlEndpoint = baseUrl;
1816
+ }
1817
+ /**
1818
+ * Load the GraphQL schema for Supabase documentation
1819
+ * Results are cached to avoid repeated requests
1820
+ */
1821
+ async loadSchema() {
1822
+ if (this.schemaCache) {
1823
+ console.error("Using cached GraphQL schema");
1824
+ return this.schemaCache;
1825
+ }
1826
+ console.error(`Fetching GraphQL schema from ${this.graphqlEndpoint}...`);
1827
+ try {
1828
+ const response = await fetch(this.graphqlEndpoint, {
1829
+ method: "POST",
1830
+ headers: {
1831
+ "Content-Type": "application/json",
1832
+ "Accept": "application/json"
1833
+ },
1834
+ body: JSON.stringify({
1835
+ query: "{ schema }"
1836
+ })
1837
+ });
1838
+ if (!response.ok) {
1839
+ throw new Error(`Failed to fetch schema: ${response.status} ${response.statusText}`);
1840
+ }
1841
+ const result = await response.json();
1842
+ if (result.errors) {
1843
+ throw new Error(
1844
+ `GraphQL errors: ${result.errors.map((e) => e.message).join(", ")}`
1845
+ );
1846
+ }
1847
+ if (!result.data || !result.data.schema) {
1848
+ throw new Error("Schema not found in GraphQL response");
1849
+ }
1850
+ this.schemaCache = result.data.schema;
1851
+ console.error("GraphQL schema loaded and cached successfully");
1852
+ return this.schemaCache;
1853
+ } catch (error) {
1854
+ const errorMessage = error instanceof Error ? error.message : String(error);
1855
+ console.error(`Error loading GraphQL schema: ${errorMessage}`);
1856
+ throw new Error(`Failed to load Supabase documentation schema: ${errorMessage}`);
1857
+ }
1858
+ }
1859
+ /**
1860
+ * Execute a GraphQL query against Supabase documentation
1861
+ */
1862
+ async query(params) {
1863
+ console.error(`Executing GraphQL query against Supabase docs...`);
1864
+ console.error(`Query string: ${params.query}`);
1865
+ const requestBody = {
1866
+ query: params.query
1867
+ };
1868
+ console.error(`Request body: ${JSON.stringify(requestBody)}`);
1869
+ try {
1870
+ const response = await fetch(this.graphqlEndpoint, {
1871
+ method: "POST",
1872
+ headers: {
1873
+ "Content-Type": "application/json",
1874
+ "Accept": "application/json"
1875
+ },
1876
+ body: JSON.stringify(requestBody)
1877
+ });
1878
+ if (!response.ok) {
1879
+ const errorText = await response.text();
1880
+ throw new Error(`GraphQL query failed: ${response.status} ${response.statusText} - ${errorText}`);
1881
+ }
1882
+ const result = await response.json();
1883
+ if (result.errors && Array.isArray(result.errors) && result.errors.length > 0) {
1884
+ const errorMessages = result.errors.map((err) => err.message).join(", ");
1885
+ throw new Error(`GraphQL errors: ${errorMessages}`);
1886
+ }
1887
+ console.error("GraphQL query executed successfully");
1888
+ return result;
1889
+ } catch (error) {
1890
+ const errorMessage = error instanceof Error ? error.message : String(error);
1891
+ console.error(`Error executing GraphQL query: ${errorMessage}`);
1892
+ throw new Error(`Failed to query Supabase documentation: ${errorMessage}`);
1893
+ }
1894
+ }
1895
+ };
1896
+ function createContentApiClient(baseUrl) {
1897
+ return new SupabaseDocsApiClient(baseUrl);
1898
+ }
1899
+
1900
+ // src/tools/search_docs.ts
1901
+ var SearchDocsInputSchema = z24.object({
1902
+ graphql_query: z24.string().describe("GraphQL query string to search Supabase documentation")
1903
+ });
1904
+ var SearchDocsOutputSchema = z24.any().describe("GraphQL query results from Supabase documentation");
1905
+ var mcpInputSchema22 = {
1906
+ type: "object",
1907
+ properties: {
1908
+ graphql_query: {
1909
+ type: "string",
1910
+ description: "GraphQL query string to search Supabase documentation. Must be a valid GraphQL query."
1911
+ }
1912
+ },
1913
+ required: ["graphql_query"]
1914
+ };
1915
+ var searchDocsTool = {
1916
+ name: "search_docs",
1917
+ description: `Search the Supabase official documentation using GraphQL. Must be a valid GraphQL query.
1918
+ You should default to calling this even if you think you already know the answer, since the documentation is always being updated.
1919
+ This tool queries the official Supabase documentation to help users understand Supabase features, APIs, and best practices.`,
1920
+ inputSchema: SearchDocsInputSchema,
1921
+ mcpInputSchema: mcpInputSchema22,
1922
+ outputSchema: SearchDocsOutputSchema,
1923
+ execute: async (input, context) => {
1924
+ context.log(`Raw input type: ${typeof input}`, "info");
1925
+ context.log(`Raw input: ${JSON.stringify(input)}`, "info");
1926
+ const { graphql_query } = input;
1927
+ context.log("Searching Supabase documentation...", "info");
1928
+ context.log(`GraphQL query type: ${typeof graphql_query}`, "info");
1929
+ context.log(`GraphQL query: ${graphql_query.substring(0, 100)}...`, "info");
1930
+ try {
1931
+ const contentApiClient = createContentApiClient();
1932
+ const result = await contentApiClient.query({ query: graphql_query });
1933
+ context.log("Documentation search completed successfully", "info");
1934
+ return result;
1935
+ } catch (error) {
1936
+ const errorMessage = error instanceof Error ? error.message : String(error);
1937
+ context.log(`Error searching documentation: ${errorMessage}`, "error");
1938
+ throw new Error(`Failed to search Supabase documentation: ${errorMessage}`);
1939
+ }
1940
+ }
1941
+ };
1942
+
1695
1943
  // src/index.ts
1696
1944
  import * as fs from "node:fs";
1697
1945
  import * as path from "node:path";
1946
+
1947
+ // src/integrations/rag-agent-client.ts
1948
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1949
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
1950
+ var RagAgentClient = class {
1951
+ config;
1952
+ client = null;
1953
+ tools = [];
1954
+ constructor(config) {
1955
+ this.config = config;
1956
+ }
1957
+ /**
1958
+ * Initialize connection to rag-agent MCP server and fetch tools
1959
+ */
1960
+ async initialize() {
1961
+ console.error("Initializing RAG Agent MCP client...");
1962
+ try {
1963
+ this.client = new Client(
1964
+ {
1965
+ name: "supabase-mcp-rag-agent-client",
1966
+ version: "1.0.0"
1967
+ },
1968
+ {
1969
+ capabilities: {}
1970
+ }
1971
+ );
1972
+ const env = {};
1973
+ for (const [key, value] of Object.entries(process.env)) {
1974
+ if (value !== void 0 && !["http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY", "all_proxy", "ALL_PROXY", "no_proxy", "NO_PROXY"].includes(key)) {
1975
+ env[key] = value;
1976
+ }
1977
+ }
1978
+ env.NO_PROXY = "*";
1979
+ env.no_proxy = "*";
1980
+ const transport = new StdioClientTransport({
1981
+ command: "uvx",
1982
+ args: [
1983
+ "--from",
1984
+ "rag-agent-mcp",
1985
+ "rag-agent",
1986
+ "--host",
1987
+ this.config.host,
1988
+ "--port",
1989
+ this.config.port.toString(),
1990
+ "--api-key",
1991
+ this.config.apiKey
1992
+ ],
1993
+ env
1994
+ });
1995
+ await this.client.connect(transport);
1996
+ console.error("Connected to RAG Agent MCP server");
1997
+ const response = await this.client.listTools();
1998
+ this.tools = response.tools.map((tool) => ({
1999
+ name: tool.name,
2000
+ description: tool.description,
2001
+ inputSchema: tool.inputSchema
2002
+ }));
2003
+ console.error(`Loaded ${this.tools.length} tools from RAG Agent MCP server`);
2004
+ console.error(`RAG Agent tools: ${this.tools.map((t) => t.name).join(", ")}`);
2005
+ } catch (error) {
2006
+ console.error("Failed to initialize RAG Agent MCP client:", error);
2007
+ throw error;
2008
+ }
2009
+ }
2010
+ /**
2011
+ * Get the list of available tools from rag-agent
2012
+ */
2013
+ getTools() {
2014
+ return this.tools;
2015
+ }
2016
+ /**
2017
+ * Call a tool on the rag-agent MCP server
2018
+ */
2019
+ async callTool(name, args) {
2020
+ if (!this.client) {
2021
+ throw new Error("RAG Agent client not initialized");
2022
+ }
2023
+ try {
2024
+ const response = await this.client.callTool({
2025
+ name,
2026
+ arguments: args
2027
+ });
2028
+ if (response.content && Array.isArray(response.content)) {
2029
+ const textContent = response.content.filter((item) => item.type === "text").map((item) => "text" in item ? item.text : "").join("\n");
2030
+ try {
2031
+ return JSON.parse(textContent);
2032
+ } catch {
2033
+ return textContent;
2034
+ }
2035
+ }
2036
+ return response;
2037
+ } catch (error) {
2038
+ console.error(`Error calling RAG Agent tool ${name}:`, error);
2039
+ throw error;
2040
+ }
2041
+ }
2042
+ /**
2043
+ * Close the connection to rag-agent MCP server
2044
+ */
2045
+ async close() {
2046
+ if (this.client) {
2047
+ await this.client.close();
2048
+ this.client = null;
2049
+ console.error("RAG Agent MCP client closed");
2050
+ }
2051
+ }
2052
+ };
2053
+ async function createRagAgentClient(config) {
2054
+ const client = new RagAgentClient(config);
2055
+ await client.initialize();
2056
+ return client;
2057
+ }
2058
+
2059
+ // src/integrations/rag-agent-tools.ts
2060
+ import { z as z25 } from "zod";
2061
+ function wrapRagAgentTool(ragTool, ragClient) {
2062
+ const inputSchema2 = z25.any();
2063
+ return {
2064
+ name: `rag_${ragTool.name}`,
2065
+ // Prefix with 'rag_' to avoid naming conflicts
2066
+ description: ragTool.description || `RAG Agent tool: ${ragTool.name}`,
2067
+ inputSchema: inputSchema2,
2068
+ mcpInputSchema: ragTool.inputSchema,
2069
+ outputSchema: z25.any(),
2070
+ execute: async (input, context) => {
2071
+ context.log(`Calling RAG Agent tool: ${ragTool.name}`, "info");
2072
+ try {
2073
+ const result = await ragClient.callTool(ragTool.name, input);
2074
+ return result;
2075
+ } catch (error) {
2076
+ const errorMessage = error instanceof Error ? error.message : String(error);
2077
+ context.log(`Error calling RAG Agent tool ${ragTool.name}: ${errorMessage}`, "error");
2078
+ throw new Error(`RAG Agent tool error: ${errorMessage}`);
2079
+ }
2080
+ }
2081
+ };
2082
+ }
2083
+ function wrapAllRagAgentTools(ragClient) {
2084
+ const tools = ragClient.getTools();
2085
+ const wrappedTools = {};
2086
+ for (const tool of tools) {
2087
+ const wrapped = wrapRagAgentTool(tool, ragClient);
2088
+ wrappedTools[wrapped.name] = wrapped;
2089
+ }
2090
+ return wrappedTools;
2091
+ }
2092
+
2093
+ // src/aliyun/client.ts
2094
+ import RdsAiPkg from "@alicloud/rdsai20250507/dist/client.js";
2095
+ import * as $RdsAi from "@alicloud/rdsai20250507";
2096
+ var RdsAiClient = RdsAiPkg.default || RdsAiPkg;
2097
+ var AliyunRdsAiClient = class {
2098
+ client;
2099
+ // RdsAi Client instance
2100
+ config;
2101
+ constructor(config) {
2102
+ this.config = config;
2103
+ const openApiConfig = {
2104
+ accessKeyId: config.accessKeyId,
2105
+ accessKeySecret: config.accessKeySecret,
2106
+ endpoint: config.endpoint || "rdsai.aliyuncs.com"
2107
+ };
2108
+ this.client = new RdsAiClient(openApiConfig);
2109
+ }
2110
+ /**
2111
+ * List all Supabase instances
2112
+ */
2113
+ async listSupabaseInstances(options) {
2114
+ const request = new $RdsAi.DescribeAppInstancesRequest({
2115
+ appType: "supabase",
2116
+ regionId: options?.regionId || this.config.regionId,
2117
+ DBInstanceName: options?.dbInstanceName,
2118
+ pageSize: options?.pageSize || 50,
2119
+ pageNumber: options?.pageNumber || 1
2120
+ });
2121
+ try {
2122
+ const response = await this.client.describeAppInstances(request);
2123
+ if (!response.body) {
2124
+ throw new Error("Empty response from DescribeAppInstances API");
2125
+ }
2126
+ const instances = (response.body.instances || []).map((inst) => ({
2127
+ instanceName: inst.instanceName || inst.instance_name || "",
2128
+ appType: inst.appType || inst.app_type || "supabase",
2129
+ instanceMinorVersion: inst.instanceMinorVersion || inst.instance_minor_version || 0,
2130
+ vSwitchId: inst.VSwitchId || inst.v_switch_id || "",
2131
+ instanceClass: inst.instanceClass || inst.instance_class || "",
2132
+ status: inst.status || "",
2133
+ dbInstanceName: inst.DBInstanceName || inst.db_instance_name || "",
2134
+ regionId: inst.regionId || inst.region_id || "",
2135
+ appName: inst.appName || inst.app_name || "",
2136
+ publicConnectionString: inst.publicConnectionString || inst.public_connection_string || inst.publicUrl || inst.public_url || "",
2137
+ vpcConnectionString: inst.vpcConnectionString || inst.vpc_connection_string || inst.vpcUrl || inst.vpc_url || ""
2138
+ }));
2139
+ return {
2140
+ requestId: response.body.requestId || "",
2141
+ totalCount: response.body.totalCount || 0,
2142
+ maxResults: response.body.maxResults || 0,
2143
+ pageNumber: response.body.pageNumber || 1,
2144
+ pageSize: response.body.pageSize || 0,
2145
+ instances
2146
+ };
2147
+ } catch (error) {
2148
+ console.error("Error calling DescribeAppInstances:", error);
2149
+ throw new Error(`Failed to list Supabase instances: ${error instanceof Error ? error.message : String(error)}`);
2150
+ }
2151
+ }
2152
+ /**
2153
+ * Get authentication information for a specific instance
2154
+ */
2155
+ async getInstanceAuthInfo(instanceName) {
2156
+ const request = new $RdsAi.DescribeInstanceAuthInfoRequest({
2157
+ instanceName
2158
+ });
2159
+ try {
2160
+ const response = await this.client.describeInstanceAuthInfo(request);
2161
+ if (!response.body) {
2162
+ throw new Error("Empty response from DescribeInstanceAuthInfo API");
2163
+ }
2164
+ return {
2165
+ requestId: response.body.requestId || "",
2166
+ instanceName: response.body.instanceName || "",
2167
+ jwtSecret: response.body.jwtSecret || "",
2168
+ apiKeys: {
2169
+ anonKey: response.body.apiKeys?.anonKey || "",
2170
+ serviceKey: response.body.apiKeys?.serviceKey || ""
2171
+ },
2172
+ configList: (response.body.configList || []).map((item) => ({
2173
+ name: item.name || "",
2174
+ value: item.value || ""
2175
+ }))
2176
+ };
2177
+ } catch (error) {
2178
+ console.error("Error calling DescribeInstanceAuthInfo:", error);
2179
+ throw new Error(`Failed to get instance auth info: ${error instanceof Error ? error.message : String(error)}`);
2180
+ }
2181
+ }
2182
+ /**
2183
+ * Get complete credentials for a Supabase instance
2184
+ */
2185
+ async getSupabaseCredentials(instanceName, useVpc = false, regionId) {
2186
+ const queryRegion = regionId || this.config.regionId;
2187
+ const instancesResponse = await this.listSupabaseInstances({
2188
+ regionId: queryRegion
2189
+ });
2190
+ const instance = instancesResponse.instances.find((inst) => inst.instanceName === instanceName);
2191
+ if (!instance) {
2192
+ throw new Error(`Instance ${instanceName} not found. Available instances: ${instancesResponse.instances.map((i) => i.instanceName).join(", ")}`);
2193
+ }
2194
+ const authInfo = await this.getInstanceAuthInfo(instanceName);
2195
+ const connectionString = useVpc ? instance.vpcConnectionString : instance.publicConnectionString;
2196
+ const supabaseUrl = connectionString.startsWith("http") ? connectionString : `http://${connectionString}`;
2197
+ return {
2198
+ instanceName,
2199
+ supabaseUrl,
2200
+ anonKey: authInfo.apiKeys.anonKey,
2201
+ serviceKey: authInfo.apiKeys.serviceKey,
2202
+ jwtSecret: authInfo.jwtSecret,
2203
+ useVpc
2204
+ };
2205
+ }
2206
+ };
2207
+ function createAliyunClient(config) {
2208
+ return new AliyunRdsAiClient(config);
2209
+ }
2210
+
2211
+ // src/tools/aliyun/list_instances.ts
2212
+ import { z as z26 } from "zod";
2213
+ import { zodToJsonSchema as zodToJsonSchema2 } from "zod-to-json-schema";
2214
+ var ListInstancesInputZodSchema = z26.object({
2215
+ region_id: z26.string().optional().describe("Region ID to filter instances (e.g., cn-beijing)"),
2216
+ db_instance_name: z26.string().optional().describe("RDS PostgreSQL database instance ID to filter"),
2217
+ page_size: z26.number().int().min(1).max(50).optional().default(50).describe("Number of results per page (1-50)"),
2218
+ page_number: z26.number().int().min(1).optional().default(1).describe("Page number")
2219
+ });
2220
+ var InstanceInfoZodSchema = z26.object({
2221
+ instance_name: z26.string(),
2222
+ app_name: z26.string(),
2223
+ status: z26.string(),
2224
+ instance_class: z26.string(),
2225
+ region_id: z26.string(),
2226
+ public_url: z26.string(),
2227
+ vpc_url: z26.string(),
2228
+ db_instance_name: z26.string()
2229
+ });
2230
+ var ListInstancesOutputZodSchema = z26.object({
2231
+ success: z26.boolean(),
2232
+ total_count: z26.number(),
2233
+ page_number: z26.number(),
2234
+ page_size: z26.number(),
2235
+ instances: z26.array(InstanceInfoZodSchema)
2236
+ });
2237
+ async function execute2(input, aliyunClient, log) {
2238
+ if (!aliyunClient) {
2239
+ throw new Error("Alibaba Cloud client is not initialized. Please provide --aliyun-ak and --aliyun-sk parameters.");
2240
+ }
2241
+ log("Fetching Supabase instances from Alibaba Cloud...", "info");
2242
+ try {
2243
+ const response = await aliyunClient.listSupabaseInstances({
2244
+ regionId: input.region_id,
2245
+ dbInstanceName: input.db_instance_name,
2246
+ pageSize: input.page_size,
2247
+ pageNumber: input.page_number
2248
+ });
2249
+ const instances = response.instances.map((inst) => ({
2250
+ instance_name: inst.instanceName,
2251
+ app_name: inst.appName,
2252
+ status: inst.status,
2253
+ instance_class: inst.instanceClass,
2254
+ region_id: inst.regionId,
2255
+ public_url: inst.publicConnectionString.startsWith("http") ? inst.publicConnectionString : `http://${inst.publicConnectionString}`,
2256
+ vpc_url: inst.vpcConnectionString.startsWith("http") ? inst.vpcConnectionString : `http://${inst.vpcConnectionString}`,
2257
+ db_instance_name: inst.dbInstanceName
2258
+ }));
2259
+ log(`Found ${instances.length} Supabase instance(s)`, "info");
2260
+ return {
2261
+ success: true,
2262
+ total_count: response.totalCount,
2263
+ page_number: response.pageNumber,
2264
+ page_size: response.pageSize,
2265
+ instances
2266
+ };
2267
+ } catch (error) {
2268
+ const errorMessage = error instanceof Error ? error.message : String(error);
2269
+ log(`Error listing instances: ${errorMessage}`, "error");
2270
+ throw error;
2271
+ }
2272
+ }
2273
+ var listAliyunSupabaseInstancesTool = {
2274
+ name: "list_aliyun_supabase_instances",
2275
+ description: "List all Supabase instances from Alibaba Cloud RDS AI. Returns instance names, status, connection URLs, and other metadata. Use this tool to discover available Supabase instances before connecting.",
2276
+ inputSchema: ListInstancesInputZodSchema,
2277
+ mcpInputSchema: zodToJsonSchema2(ListInstancesInputZodSchema),
2278
+ outputSchema: ListInstancesOutputZodSchema,
2279
+ execute: execute2
2280
+ };
2281
+ var list_instances_default = listAliyunSupabaseInstancesTool;
2282
+
2283
+ // src/tools/aliyun/connect_instance.ts
2284
+ import { z as z27 } from "zod";
2285
+ import { zodToJsonSchema as zodToJsonSchema3 } from "zod-to-json-schema";
2286
+ var ConnectInstanceInputZodSchema = z27.object({
2287
+ instance_name: z27.string().describe("The instance name (e.g., ra-supabase-8moov5lxba****)"),
2288
+ use_vpc: z27.boolean().optional().default(false).describe("Whether to use VPC connection instead of public connection")
2289
+ });
2290
+ var ConnectInstanceOutputZodSchema = z27.object({
2291
+ success: z27.boolean(),
2292
+ message: z27.string(),
2293
+ instance_name: z27.string(),
2294
+ supabase_url: z27.string(),
2295
+ connection_type: z27.string()
2296
+ });
2297
+ async function execute3(input, aliyunClient, createSupabaseClient, setCurrentInstance, log) {
2298
+ if (!aliyunClient) {
2299
+ throw new Error("Alibaba Cloud client is not initialized. Please provide --aliyun-ak and --aliyun-sk parameters.");
2300
+ }
2301
+ log(`Connecting to Supabase instance: ${input.instance_name}...`, "info");
2302
+ try {
2303
+ const credentials = await aliyunClient.getSupabaseCredentials(input.instance_name, input.use_vpc);
2304
+ log(`Retrieved credentials for instance ${input.instance_name}`, "info");
2305
+ log(`Supabase URL: ${credentials.supabaseUrl}`, "info");
2306
+ const supabaseClient = await createSupabaseClient(
2307
+ credentials.supabaseUrl,
2308
+ credentials.anonKey,
2309
+ credentials.serviceKey,
2310
+ credentials.jwtSecret
2311
+ );
2312
+ setCurrentInstance(input.instance_name, supabaseClient);
2313
+ log(`Successfully connected to instance ${input.instance_name}`, "info");
2314
+ return {
2315
+ success: true,
2316
+ message: `Successfully connected to Supabase instance ${input.instance_name}`,
2317
+ instance_name: input.instance_name,
2318
+ supabase_url: credentials.supabaseUrl,
2319
+ connection_type: input.use_vpc ? "VPC" : "Public"
2320
+ };
2321
+ } catch (error) {
2322
+ const errorMessage = error instanceof Error ? error.message : String(error);
2323
+ log(`Error connecting to instance: ${errorMessage}`, "error");
2324
+ throw new Error(`Failed to connect to instance ${input.instance_name}: ${errorMessage}`);
2325
+ }
2326
+ }
2327
+ var connectToSupabaseInstanceTool = {
2328
+ name: "connect_to_supabase_instance",
2329
+ description: "Connect to a specific Supabase instance by instance name. This tool fetches credentials from Alibaba Cloud and establishes a connection. After connecting, you can use all Supabase tools (list_tables, execute_sql, etc.).",
2330
+ inputSchema: ConnectInstanceInputZodSchema,
2331
+ mcpInputSchema: zodToJsonSchema3(ConnectInstanceInputZodSchema),
2332
+ outputSchema: ConnectInstanceOutputZodSchema,
2333
+ execute: execute3
2334
+ };
2335
+ var connect_instance_default = connectToSupabaseInstanceTool;
2336
+
2337
+ // src/tools/aliyun/get_current_instance.ts
2338
+ import { z as z28 } from "zod";
2339
+ import { zodToJsonSchema as zodToJsonSchema4 } from "zod-to-json-schema";
2340
+ var GetCurrentInstanceInputZodSchema = z28.object({});
2341
+ var GetCurrentInstanceOutputZodSchema = z28.object({
2342
+ connected: z28.boolean(),
2343
+ instance_name: z28.string().optional(),
2344
+ message: z28.string()
2345
+ });
2346
+ async function execute4(input, getCurrentInstanceName, log) {
2347
+ const currentInstance = getCurrentInstanceName();
2348
+ if (currentInstance) {
2349
+ log(`Currently connected to instance: ${currentInstance}`, "info");
2350
+ return {
2351
+ connected: true,
2352
+ instance_name: currentInstance,
2353
+ message: `Connected to Supabase instance: ${currentInstance}`
2354
+ };
2355
+ } else {
2356
+ log("Not connected to any Supabase instance", "info");
2357
+ return {
2358
+ connected: false,
2359
+ message: "Not connected to any Supabase instance. Use connect_to_supabase_instance to connect."
2360
+ };
2361
+ }
2362
+ }
2363
+ var getCurrentSupabaseInstanceTool = {
2364
+ name: "get_current_supabase_instance",
2365
+ description: "Get information about the currently connected Supabase instance. Returns the instance name if connected, or a message indicating no connection.",
2366
+ inputSchema: GetCurrentInstanceInputZodSchema,
2367
+ mcpInputSchema: zodToJsonSchema4(GetCurrentInstanceInputZodSchema),
2368
+ outputSchema: GetCurrentInstanceOutputZodSchema,
2369
+ execute: execute4
2370
+ };
2371
+ var get_current_instance_default = getCurrentSupabaseInstanceTool;
2372
+
2373
+ // src/tools/aliyun/disconnect_instance.ts
2374
+ import { z as z29 } from "zod";
2375
+ import { zodToJsonSchema as zodToJsonSchema5 } from "zod-to-json-schema";
2376
+ var DisconnectInstanceInputZodSchema = z29.object({});
2377
+ var DisconnectInstanceOutputZodSchema = z29.object({
2378
+ success: z29.boolean(),
2379
+ message: z29.string(),
2380
+ previous_instance: z29.string().optional()
2381
+ });
2382
+ async function execute5(input, getCurrentInstanceName, clearCurrentInstance, log) {
2383
+ const currentInstance = getCurrentInstanceName();
2384
+ if (!currentInstance) {
2385
+ log("No active connection to disconnect", "info");
2386
+ return {
2387
+ success: false,
2388
+ message: "Not connected to any Supabase instance"
2389
+ };
2390
+ }
2391
+ log(`Disconnecting from instance: ${currentInstance}`, "info");
2392
+ clearCurrentInstance();
2393
+ log("Disconnected successfully", "info");
2394
+ return {
2395
+ success: true,
2396
+ message: `Successfully disconnected from instance ${currentInstance}`,
2397
+ previous_instance: currentInstance
2398
+ };
2399
+ }
2400
+ var disconnectSupabaseInstanceTool = {
2401
+ name: "disconnect_supabase_instance",
2402
+ description: "Disconnect from the currently connected Supabase instance. After disconnecting, you need to connect to an instance again before using Supabase tools.",
2403
+ inputSchema: DisconnectInstanceInputZodSchema,
2404
+ mcpInputSchema: zodToJsonSchema5(DisconnectInstanceInputZodSchema),
2405
+ outputSchema: DisconnectInstanceOutputZodSchema,
2406
+ execute: execute5
2407
+ };
2408
+ var disconnect_instance_default = disconnectSupabaseInstanceTool;
2409
+
2410
+ // src/index.ts
1698
2411
  async function main() {
1699
2412
  const program = new Command();
1700
- program.name("self-hosted-supabase-mcp").description("MCP Server for self-hosted Supabase instances").option("--url <url>", "Supabase project URL", process.env.SUPABASE_URL).option("--anon-key <key>", "Supabase anonymous key", process.env.SUPABASE_ANON_KEY).option("--service-key <key>", "Supabase service role key (optional)", process.env.SUPABASE_SERVICE_ROLE_KEY).option("--db-url <url>", "Direct database connection string (optional, for pg fallback)", process.env.DATABASE_URL).option("--jwt-secret <secret>", "Supabase JWT secret (optional, needed for some tools)", process.env.SUPABASE_AUTH_JWT_SECRET).option("--workspace-path <path>", "Workspace root path (for file operations)", process.cwd()).option("--tools-config <path>", 'Path to a JSON file specifying which tools to enable (e.g., { "enabledTools": ["tool1", "tool2"] }). If omitted, all tools are enabled.').parse(process.argv);
2413
+ program.name("self-hosted-supabase-mcp").description("MCP Server for self-hosted Supabase instances").option("--url <url>", "Supabase project URL (legacy mode)", process.env.SUPABASE_URL).option("--anon-key <key>", "Supabase anonymous key (legacy mode)", process.env.SUPABASE_ANON_KEY).option("--service-key <key>", "Supabase service role key (legacy mode, optional)", process.env.SUPABASE_SERVICE_ROLE_KEY).option("--db-url <url>", "Direct database connection string (optional, for pg fallback)", process.env.DATABASE_URL).option("--jwt-secret <secret>", "Supabase JWT secret (legacy mode, optional)", process.env.SUPABASE_AUTH_JWT_SECRET).option("--aliyun-ak <key>", "Alibaba Cloud Access Key ID", process.env.ALIYUN_ACCESS_KEY_ID).option("--aliyun-sk <secret>", "Alibaba Cloud Access Key Secret", process.env.ALIYUN_ACCESS_KEY_SECRET).option("--aliyun-region <region>", "Alibaba Cloud region (optional)", process.env.ALIYUN_REGION).option("--workspace-path <path>", "Workspace root path (for file operations)", process.cwd()).option("--tools-config <path>", 'Path to a JSON file specifying which tools to enable (e.g., { "enabledTools": ["tool1", "tool2"] }). If omitted, all tools are enabled.').option("--enable-rag-agent", "Enable RAG Agent MCP integration (uses --url host:port and --anon-key as API key)", process.env.ENABLE_RAG_AGENT === "true").parse(process.argv);
1701
2414
  const options = program.opts();
1702
- if (!options.url) {
1703
- console.error("Error: Supabase URL is required. Use --url or SUPABASE_URL.");
1704
- throw new Error("Supabase URL is required.");
2415
+ const isAliyunMode = !!(options.aliyunAk && options.aliyunSk);
2416
+ const isLegacyMode = !!(options.url && options.anonKey);
2417
+ if (!isAliyunMode && !isLegacyMode) {
2418
+ console.error("Error: Either Alibaba Cloud credentials (--aliyun-ak, --aliyun-sk) or legacy Supabase credentials (--url, --anon-key) are required.");
2419
+ throw new Error("Missing required credentials.");
1705
2420
  }
1706
- if (!options.anonKey) {
1707
- console.error("Error: Supabase Anon Key is required. Use --anon-key or SUPABASE_ANON_KEY.");
1708
- throw new Error("Supabase Anon Key is required.");
2421
+ if (isAliyunMode && isLegacyMode) {
2422
+ console.error("Warning: Both Alibaba Cloud and legacy credentials provided. Using Alibaba Cloud mode.");
1709
2423
  }
1710
2424
  console.error("Initializing Self-Hosted Supabase MCP Server...");
1711
2425
  try {
1712
- const selfhostedClient = await SelfhostedSupabaseClient.create({
1713
- supabaseUrl: options.url,
1714
- supabaseAnonKey: options.anonKey,
1715
- supabaseServiceRoleKey: options.serviceKey,
1716
- databaseUrl: options.dbUrl,
1717
- jwtSecret: options.jwtSecret
1718
- });
1719
- console.error("Supabase client initialized successfully.");
2426
+ let aliyunClient = null;
2427
+ if (isAliyunMode) {
2428
+ console.error("Alibaba Cloud mode enabled, initializing client...");
2429
+ aliyunClient = createAliyunClient({
2430
+ accessKeyId: options.aliyunAk,
2431
+ accessKeySecret: options.aliyunSk,
2432
+ regionId: options.aliyunRegion
2433
+ });
2434
+ console.error("Alibaba Cloud client initialized successfully.");
2435
+ }
2436
+ let selfhostedClient = null;
2437
+ if (isLegacyMode && !isAliyunMode) {
2438
+ selfhostedClient = await SelfhostedSupabaseClient.create({
2439
+ supabaseUrl: options.url,
2440
+ supabaseAnonKey: options.anonKey,
2441
+ supabaseServiceRoleKey: options.serviceKey,
2442
+ databaseUrl: options.dbUrl,
2443
+ jwtSecret: options.jwtSecret
2444
+ });
2445
+ console.error("Supabase client initialized successfully (legacy mode).");
2446
+ }
2447
+ let currentInstanceName = null;
2448
+ let currentSupabaseClient = selfhostedClient;
2449
+ const getCurrentInstanceName = () => currentInstanceName;
2450
+ const setCurrentInstance = (instanceName, client) => {
2451
+ currentInstanceName = instanceName;
2452
+ currentSupabaseClient = client;
2453
+ };
2454
+ const clearCurrentInstance = () => {
2455
+ currentInstanceName = null;
2456
+ currentSupabaseClient = null;
2457
+ };
2458
+ const createSupabaseClientDynamic = async (url, anonKey, serviceKey, jwtSecret) => {
2459
+ return await SelfhostedSupabaseClient.create({
2460
+ supabaseUrl: url,
2461
+ supabaseAnonKey: anonKey,
2462
+ supabaseServiceRoleKey: serviceKey,
2463
+ databaseUrl: options.dbUrl,
2464
+ jwtSecret
2465
+ });
2466
+ };
2467
+ let ragAgentClient = null;
2468
+ const initializeRagAgent = async (supabaseClient) => {
2469
+ if (!options.enableRagAgent) {
2470
+ return null;
2471
+ }
2472
+ try {
2473
+ console.error("Initializing RAG Agent client...");
2474
+ const urlToUse = supabaseClient.getSupabaseUrl();
2475
+ const anonKeyToUse = supabaseClient.getAnonKey();
2476
+ const urlObj = new URL(urlToUse);
2477
+ const host = urlObj.hostname;
2478
+ const port = urlObj.port ? parseInt(urlObj.port, 10) : urlObj.protocol === "https:" ? 443 : 80;
2479
+ const client = await createRagAgentClient({
2480
+ host,
2481
+ port,
2482
+ apiKey: anonKeyToUse
2483
+ });
2484
+ console.error("RAG Agent client initialized successfully.");
2485
+ return client;
2486
+ } catch (error) {
2487
+ console.error("Failed to initialize RAG Agent client:", error);
2488
+ console.error("Continuing without RAG Agent integration...");
2489
+ return null;
2490
+ }
2491
+ };
2492
+ if (options.enableRagAgent && isLegacyMode && selfhostedClient) {
2493
+ ragAgentClient = await initializeRagAgent(selfhostedClient);
2494
+ } else if (options.enableRagAgent && isAliyunMode) {
2495
+ console.error("RAG Agent integration enabled.");
2496
+ console.error("RAG Agent tools will be available after connecting to a Supabase instance.");
2497
+ }
2498
+ const wrappedAliyunTools = {};
2499
+ if (isAliyunMode) {
2500
+ wrappedAliyunTools[list_instances_default.name] = {
2501
+ ...list_instances_default,
2502
+ execute: async (input, context) => {
2503
+ return await list_instances_default.execute(
2504
+ input,
2505
+ aliyunClient,
2506
+ context.log
2507
+ );
2508
+ }
2509
+ };
2510
+ wrappedAliyunTools[connect_instance_default.name] = {
2511
+ ...connect_instance_default,
2512
+ execute: async (input, context) => {
2513
+ const result = await connect_instance_default.execute(
2514
+ input,
2515
+ aliyunClient,
2516
+ createSupabaseClientDynamic,
2517
+ setCurrentInstance,
2518
+ context.log
2519
+ );
2520
+ if (result.success && currentSupabaseClient && options.enableRagAgent && !ragAgentClient) {
2521
+ ragAgentClient = await initializeRagAgent(currentSupabaseClient);
2522
+ }
2523
+ return result;
2524
+ }
2525
+ };
2526
+ wrappedAliyunTools[get_current_instance_default.name] = {
2527
+ ...get_current_instance_default,
2528
+ execute: async (input, context) => {
2529
+ return await get_current_instance_default.execute(
2530
+ input,
2531
+ getCurrentInstanceName,
2532
+ context.log
2533
+ );
2534
+ }
2535
+ };
2536
+ wrappedAliyunTools[disconnect_instance_default.name] = {
2537
+ ...disconnect_instance_default,
2538
+ execute: async (input, context) => {
2539
+ return await disconnect_instance_default.execute(
2540
+ input,
2541
+ getCurrentInstanceName,
2542
+ clearCurrentInstance,
2543
+ context.log
2544
+ );
2545
+ }
2546
+ };
2547
+ }
1720
2548
  const availableTools = {
2549
+ // Alibaba Cloud tools (if in Aliyun mode)
2550
+ ...wrappedAliyunTools,
1721
2551
  // Cast here assumes tools will implement AppTool structure
1722
2552
  [listTablesTool.name]: listTablesTool,
1723
2553
  [listExtensionsTool.name]: listExtensionsTool,
@@ -1739,8 +2569,46 @@ async function main() {
1739
2569
  [updateAuthUserTool.name]: updateAuthUserTool,
1740
2570
  [list_storage_buckets_default.name]: list_storage_buckets_default,
1741
2571
  [list_storage_objects_default.name]: list_storage_objects_default,
1742
- [list_realtime_publications_default.name]: list_realtime_publications_default
2572
+ [list_realtime_publications_default.name]: list_realtime_publications_default,
2573
+ [install_execute_sql_function_default.name]: install_execute_sql_function_default,
2574
+ [searchDocsTool.name]: searchDocsTool
1743
2575
  };
2576
+ if (options.enableRagAgent) {
2577
+ if (ragAgentClient) {
2578
+ const ragTools = wrapAllRagAgentTools(ragAgentClient);
2579
+ Object.assign(availableTools, ragTools);
2580
+ console.error(`Added ${Object.keys(ragTools).length} RAG Agent tools to available tools.`);
2581
+ } else if (isAliyunMode) {
2582
+ const dummyRagTools = await (async () => {
2583
+ try {
2584
+ const tempClient = await createRagAgentClient({
2585
+ host: "localhost",
2586
+ port: 80,
2587
+ apiKey: "dummy"
2588
+ });
2589
+ const tools = wrapAllRagAgentTools(tempClient);
2590
+ await tempClient.close();
2591
+ return tools;
2592
+ } catch (error) {
2593
+ console.error("Warning: Could not create RAG Agent tool schemas:", error);
2594
+ return {};
2595
+ }
2596
+ })();
2597
+ for (const [toolName, tool] of Object.entries(dummyRagTools)) {
2598
+ availableTools[toolName] = {
2599
+ ...tool,
2600
+ execute: async (input, context) => {
2601
+ if (!ragAgentClient) {
2602
+ throw new Error("RAG Agent is not initialized. Please connect to a Supabase instance first using connect_to_supabase_instance tool.");
2603
+ }
2604
+ const ragTools = wrapAllRagAgentTools(ragAgentClient);
2605
+ return await ragTools[toolName].execute(input, context);
2606
+ }
2607
+ };
2608
+ }
2609
+ console.error(`Added ${Object.keys(dummyRagTools).length} RAG Agent tools (will be initialized after connection).`);
2610
+ }
2611
+ }
1744
2612
  let registeredTools = { ...availableTools };
1745
2613
  const toolsConfigPath = options.toolsConfig;
1746
2614
  let enabledToolNames = null;
@@ -1824,12 +2692,31 @@ async function main() {
1824
2692
  if (typeof tool.execute !== "function") {
1825
2693
  throw new Error(`Tool ${toolName} does not have an execute method.`);
1826
2694
  }
2695
+ if (toolName === "search_docs") {
2696
+ console.error(`[DEBUG] request.params.arguments type: ${typeof request.params.arguments}`);
2697
+ console.error(`[DEBUG] request.params.arguments: ${JSON.stringify(request.params.arguments)}`);
2698
+ }
1827
2699
  let parsedArgs = request.params.arguments;
1828
2700
  if (tool.inputSchema && typeof tool.inputSchema.parse === "function") {
1829
2701
  parsedArgs = tool.inputSchema.parse(request.params.arguments);
2702
+ if (toolName === "search_docs") {
2703
+ console.error(`[DEBUG] parsedArgs after Zod: ${JSON.stringify(parsedArgs)}`);
2704
+ }
2705
+ }
2706
+ const isAliyunManagementTool = [
2707
+ "list_aliyun_supabase_instances",
2708
+ "connect_to_supabase_instance",
2709
+ "get_current_supabase_instance",
2710
+ "disconnect_supabase_instance"
2711
+ ].includes(toolName);
2712
+ if (isAliyunMode && !isAliyunManagementTool && !currentSupabaseClient) {
2713
+ throw new McpError(
2714
+ ErrorCode.InvalidRequest,
2715
+ "Not connected to any Supabase instance. Please use connect_to_supabase_instance tool first."
2716
+ );
1830
2717
  }
1831
2718
  const context = {
1832
- selfhostedClient,
2719
+ selfhostedClient: currentSupabaseClient,
1833
2720
  workspacePath: options.workspacePath,
1834
2721
  log: (message, level = "info") => {
1835
2722
  console.error(`[${level.toUpperCase()}] ${message}`);
@@ -1847,7 +2734,7 @@ async function main() {
1847
2734
  } catch (error) {
1848
2735
  console.error(`Error executing tool ${toolName}:`, error);
1849
2736
  let errorMessage = `Error executing tool ${toolName}: `;
1850
- if (error instanceof z23.ZodError) {
2737
+ if (error instanceof z30.ZodError) {
1851
2738
  errorMessage += `Input validation failed: ${error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")}`;
1852
2739
  } else if (error instanceof Error) {
1853
2740
  errorMessage += error.message;
@@ -1864,6 +2751,20 @@ async function main() {
1864
2751
  const transport = new StdioServerTransport();
1865
2752
  await server.connect(transport);
1866
2753
  console.error("MCP Server connected to stdio.");
2754
+ const cleanup = async () => {
2755
+ console.error("Shutting down...");
2756
+ if (ragAgentClient) {
2757
+ await ragAgentClient.close();
2758
+ }
2759
+ };
2760
+ process.on("SIGINT", async () => {
2761
+ await cleanup();
2762
+ process.exit(0);
2763
+ });
2764
+ process.on("SIGTERM", async () => {
2765
+ await cleanup();
2766
+ process.exit(0);
2767
+ });
1867
2768
  } catch (error) {
1868
2769
  console.error("Failed to initialize or start the MCP server:", error);
1869
2770
  throw error;