@aliyun-rds/supabase-mcp-server 1.0.3 → 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 +151 -16
  2. package/dist/index.js +1060 -364
  3. package/package.json +5 -2
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
  }
@@ -246,61 +247,90 @@ var SelfhostedSupabaseClient = class _SelfhostedSupabaseClient {
246
247
  }
247
248
  }
248
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
+ }
249
271
  async checkAndCreateRpcFunction() {
250
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
+ }
251
278
  try {
252
- const { error } = await this.supabase.rpc("execute_sql", { query: "SELECT 1" });
253
- 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) {
254
291
  console.error("'public.execute_sql' function found.");
255
292
  this.rpcFunctionExists = true;
256
293
  return;
257
294
  }
258
- const UNDEFINED_FUNCTION_ERROR_CODE = "42883";
259
- const POSTGREST_FUNCTION_NOT_FOUND_CODE = "PGRST202";
260
- if (error.code === UNDEFINED_FUNCTION_ERROR_CODE || error.code === POSTGREST_FUNCTION_NOT_FOUND_CODE) {
261
- console.error(
262
- `'public.execute_sql' function not found (Code: ${error.code}). Attempting creation...`
263
- );
264
- if (!this.options.supabaseServiceRoleKey) {
265
- console.error("Cannot create 'public.execute_sql': supabaseServiceRoleKey not provided.");
266
- this.rpcFunctionExists = false;
267
- return;
268
- }
269
- if (!this.options.databaseUrl) {
270
- console.error("Cannot create 'public.execute_sql' reliably without databaseUrl for direct connection.");
271
- this.rpcFunctionExists = false;
272
- return;
273
- }
274
- try {
275
- console.error("Creating 'public.execute_sql' function using direct DB connection...");
276
- await this.executeSqlWithPg(_SelfhostedSupabaseClient.CREATE_EXECUTE_SQL_FUNCTION);
277
- await this.executeSqlWithPg(_SelfhostedSupabaseClient.GRANT_EXECUTE_SQL_FUNCTION);
278
- console.error("'public.execute_sql' function created and permissions granted successfully.");
279
- console.error("Notifying PostgREST to reload schema cache...");
280
- await this.executeSqlWithPg("NOTIFY pgrst, 'reload schema'");
281
- console.error("PostgREST schema reload notification sent.");
282
- this.rpcFunctionExists = true;
283
- } catch (creationError) {
284
- const errorMessage = creationError instanceof Error ? creationError.message : String(creationError);
285
- 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.");
286
325
  this.rpcFunctionExists = false;
287
- throw new Error(`Failed to create execute_sql function/notify: ${errorMessage}`);
288
326
  }
289
- } else {
290
- console.error(
291
- "Unexpected error checking for 'public.execute_sql' function:",
292
- error
293
- );
294
- this.rpcFunctionExists = false;
295
- throw new Error(
296
- `Error checking for execute_sql function: ${error.message}`
297
- );
298
327
  }
299
328
  } catch (err) {
300
329
  const errorMessage = err instanceof Error ? err.message : String(err);
301
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.");
302
333
  this.rpcFunctionExists = false;
303
- throw new Error(`Exception during RPC function check/creation: ${errorMessage}`);
304
334
  }
305
335
  }
306
336
  // --- Getters ---
@@ -331,6 +361,12 @@ var SelfhostedSupabaseClient = class _SelfhostedSupabaseClient {
331
361
  isPgAvailable() {
332
362
  return !!this.options.databaseUrl;
333
363
  }
364
+ /**
365
+ * Checks if the execute_sql RPC function is available.
366
+ */
367
+ isRpcAvailable() {
368
+ return this.rpcFunctionExists;
369
+ }
334
370
  };
335
371
 
336
372
  // src/tools/list_tables.ts
@@ -405,32 +441,36 @@ var listTablesTool = {
405
441
  // Use explicit types for input and context
406
442
  execute: async (input, context) => {
407
443
  const client = context.selfhostedClient;
408
- const listTablesSql = `
409
- SELECT
410
- n.nspname as schema,
411
- c.relname as name,
412
- pgd.description as comment
413
- FROM
414
- pg_catalog.pg_class c
415
- JOIN
416
- pg_catalog.pg_namespace n ON n.oid = c.relnamespace
417
- LEFT JOIN
418
- pg_catalog.pg_description pgd ON pgd.objoid = c.oid AND pgd.objsubid = 0
419
- WHERE
420
- c.relkind = 'r' -- r = ordinary table
421
- AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
422
- AND n.nspname NOT LIKE 'pg_temp_%'
423
- AND n.nspname NOT LIKE 'pg_toast_temp_%'
424
- -- Exclude Supabase internal schemas
425
- AND n.nspname NOT IN ('auth', 'storage', 'extensions', 'graphql', 'graphql_public', 'pgbouncer', 'realtime', 'supabase_functions', 'supabase_migrations', '_realtime')
426
- AND has_schema_privilege(n.oid, 'USAGE')
427
- AND has_table_privilege(c.oid, 'SELECT')
428
- ORDER BY
429
- n.nspname,
430
- c.relname
431
- `;
432
- const result = await executeSqlWithFallback(client, listTablesSql, true);
433
- 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
+ }
434
474
  }
435
475
  };
436
476
 
@@ -538,7 +578,7 @@ var mcpInputSchema4 = {
538
578
  };
539
579
  var applyMigrationTool = {
540
580
  name: "apply_migration",
541
- 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).",
542
582
  inputSchema: ApplyMigrationInputSchema,
543
583
  mcpInputSchema: mcpInputSchema4,
544
584
  outputSchema: ApplyMigrationOutputSchema,
@@ -1105,32 +1145,37 @@ var listAuthUsersTool = {
1105
1145
  execute: async (input, context) => {
1106
1146
  const client = context.selfhostedClient;
1107
1147
  const { limit, offset } = input;
1108
- if (!client.isPgAvailable()) {
1109
- context.log("Direct database connection (DATABASE_URL) is required to list auth users.", "error");
1110
- 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;
1111
1178
  }
1112
- const listUsersSql = `
1113
- SELECT
1114
- id,
1115
- email,
1116
- role,
1117
- raw_app_meta_data,
1118
- raw_user_meta_data,
1119
- created_at::text, -- Cast timestamp to text for JSON
1120
- last_sign_in_at::text -- Cast timestamp to text for JSON
1121
- FROM
1122
- auth.users
1123
- ORDER BY
1124
- created_at DESC
1125
- LIMIT ${limit}
1126
- OFFSET ${offset}
1127
- `;
1128
- console.error("Attempting to list auth users using direct DB connection...");
1129
- const result = await client.executeSqlWithPg(listUsersSql);
1130
- const validatedUsers = handleSqlResponse(result, ListAuthUsersOutputSchema);
1131
- console.error(`Found ${validatedUsers.length} users.`);
1132
- context.log(`Found ${validatedUsers.length} users.`);
1133
- return validatedUsers;
1134
1179
  }
1135
1180
  };
1136
1181
 
@@ -1171,43 +1216,33 @@ var getAuthUserTool = {
1171
1216
  execute: async (input, context) => {
1172
1217
  const client = context.selfhostedClient;
1173
1218
  const { user_id } = input;
1174
- if (!client.isPgAvailable()) {
1175
- context.log("Direct database connection (DATABASE_URL) is required to get auth user details.", "error");
1176
- throw new Error("Direct database connection (DATABASE_URL) is required to get auth user details.");
1177
- }
1178
- const sql = `
1179
- SELECT
1180
- id,
1181
- email,
1182
- role,
1183
- raw_app_meta_data,
1184
- raw_user_meta_data,
1185
- created_at::text,
1186
- last_sign_in_at::text
1187
- FROM auth.users
1188
- WHERE id = $1
1189
- `;
1190
- const params = [user_id];
1191
- console.error(`Attempting to get auth user ${user_id} using direct DB connection...`);
1192
- const user = await client.executeTransactionWithPg(async (pgClient) => {
1193
- const result = await pgClient.query(sql, params);
1194
- if (result.rows.length === 0) {
1195
- 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}`);
1196
1225
  }
1197
- try {
1198
- const singleUser = AuthUserZodSchema2.parse(result.rows[0]);
1199
- return singleUser;
1200
- } catch (validationError) {
1201
- if (validationError instanceof z16.ZodError) {
1202
- console.error("Zod validation failed:", validationError.errors);
1203
- throw new Error(`Output validation failed: ${validationError.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")}`);
1204
- }
1205
- throw validationError;
1226
+ if (!data || !data.user) {
1227
+ throw new Error(`User with ID ${user_id} not found.`);
1206
1228
  }
1207
- });
1208
- console.error(`Found user ${user_id}.`);
1209
- context.log(`Found user ${user_id}.`);
1210
- 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
+ }
1211
1246
  }
1212
1247
  };
1213
1248
 
@@ -1233,37 +1268,34 @@ var mcpInputSchema16 = {
1233
1268
  };
1234
1269
  var deleteAuthUserTool = {
1235
1270
  name: "delete_auth_user",
1236
- 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.",
1237
1272
  inputSchema: DeleteAuthUserInputSchema,
1238
1273
  mcpInputSchema: mcpInputSchema16,
1239
1274
  outputSchema: DeleteAuthUserOutputSchema,
1240
1275
  execute: async (input, context) => {
1241
1276
  const client = context.selfhostedClient;
1242
1277
  const { user_id } = input;
1243
- if (!client.isPgAvailable()) {
1244
- 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.");
1245
1280
  }
1281
+ context.log(`Deleting user ${user_id} using Supabase Admin API...`, "info");
1246
1282
  try {
1247
- const result = await client.executeTransactionWithPg(async (pgClient) => {
1248
- const deleteResult = await pgClient.query(
1249
- "DELETE FROM auth.users WHERE id = $1",
1250
- [user_id]
1251
- );
1252
- return deleteResult;
1253
- });
1254
- 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");
1255
1286
  return {
1256
- success: true,
1257
- message: `Successfully deleted user with ID: ${user_id}`
1287
+ success: false,
1288
+ message: `Failed to delete user: ${error.message}`
1258
1289
  };
1259
1290
  }
1291
+ context.log(`User ${user_id} deleted successfully`, "info");
1260
1292
  return {
1261
- success: false,
1262
- 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}`
1263
1295
  };
1264
1296
  } catch (error) {
1265
1297
  const errorMessage = error instanceof Error ? error.message : String(error);
1266
- console.error(`Error deleting user ${user_id}:`, errorMessage);
1298
+ context.log(`Error deleting user ${user_id}: ${errorMessage}`, "error");
1267
1299
  throw new Error(`Failed to delete user ${user_id}: ${errorMessage}`);
1268
1300
  }
1269
1301
  }
@@ -1302,80 +1334,48 @@ var mcpInputSchema17 = {
1302
1334
  };
1303
1335
  var createAuthUserTool = {
1304
1336
  name: "create_auth_user",
1305
- 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.",
1306
1338
  inputSchema: CreateAuthUserInputSchema,
1307
1339
  mcpInputSchema: mcpInputSchema17,
1308
- // Ensure defined above
1309
1340
  outputSchema: CreatedAuthUserZodSchema,
1310
1341
  execute: async (input, context) => {
1311
1342
  const client = context.selfhostedClient;
1312
1343
  const { email, password, role, app_metadata, user_metadata } = input;
1313
- if (!client.isPgAvailable()) {
1314
- context.log("Direct database connection (DATABASE_URL) is required to create an auth user directly.", "error");
1315
- 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.");
1316
1346
  }
1317
- console.warn(`SECURITY WARNING: Creating user ${email} with plain text password via direct DB insert.`);
1318
- context.log(`Attempting to create user ${email}...`, "warn");
1319
- const createdUser = await client.executeTransactionWithPg(async (pgClient) => {
1320
- try {
1321
- await pgClient.query("SELECT crypt('test', gen_salt('bf'))");
1322
- } catch (err) {
1323
- throw new Error("Failed to execute crypt function. Ensure pgcrypto extension is enabled in the database.");
1324
- }
1325
- const sql = `
1326
- INSERT INTO auth.users (
1327
- instance_id, email, encrypted_password, role,
1328
- raw_app_meta_data, raw_user_meta_data,
1329
- aud, email_confirmed_at, confirmation_sent_at -- Set required defaults
1330
- )
1331
- VALUES (
1332
- COALESCE(current_setting('app.instance_id', TRUE), '00000000-0000-0000-0000-000000000000')::uuid,
1333
- $1, crypt($2, gen_salt('bf')),
1334
- $3,
1335
- $4::jsonb,
1336
- $5::jsonb,
1337
- 'authenticated', now(), now()
1338
- )
1339
- RETURNING id, email, role, raw_app_meta_data, raw_user_meta_data, created_at::text, last_sign_in_at::text;
1340
- `;
1341
- 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({
1342
1350
  email,
1343
1351
  password,
1344
- role || "authenticated",
1345
- // Default role
1346
- JSON.stringify(app_metadata || {}),
1347
- JSON.stringify(user_metadata || {})
1348
- ];
1349
- try {
1350
- const result = await pgClient.query(sql, params);
1351
- if (result.rows.length === 0) {
1352
- throw new Error("User creation failed, no user returned after insert.");
1353
- }
1354
- return CreatedAuthUserZodSchema.parse(result.rows[0]);
1355
- } catch (dbError) {
1356
- let errorMessage = "Unknown database error during user creation";
1357
- let isUniqueViolation = false;
1358
- if (typeof dbError === "object" && dbError !== null && "code" in dbError) {
1359
- if (dbError.code === "23505") {
1360
- isUniqueViolation = true;
1361
- errorMessage = `User creation failed: Email '${email}' likely already exists.`;
1362
- } else if ("message" in dbError && typeof dbError.message === "string") {
1363
- errorMessage = `Database error (${dbError.code}): ${dbError.message}`;
1364
- } else {
1365
- errorMessage = `Database error code: ${dbError.code}`;
1366
- }
1367
- } else if (dbError instanceof Error) {
1368
- errorMessage = `Database error during user creation: ${dbError.message}`;
1369
- } else {
1370
- errorMessage = `Database error during user creation: ${String(dbError)}`;
1371
- }
1372
- console.error("Error creating user in DB:", dbError);
1373
- 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}`);
1374
1360
  }
1375
- });
1376
- console.error(`Successfully created user ${email} with ID ${createdUser.id}.`);
1377
- context.log(`Successfully created user ${email} with ID ${createdUser.id}.`);
1378
- 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
+ }
1379
1379
  }
1380
1380
  };
1381
1381
 
@@ -1417,95 +1417,63 @@ var mcpInputSchema18 = {
1417
1417
  };
1418
1418
  var updateAuthUserTool = {
1419
1419
  name: "update_auth_user",
1420
- 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.",
1421
1421
  inputSchema: UpdateAuthUserInputSchema,
1422
1422
  mcpInputSchema: mcpInputSchema18,
1423
- // Ensure defined
1424
1423
  outputSchema: UpdatedAuthUserZodSchema,
1425
1424
  execute: async (input, context) => {
1426
1425
  const client = context.selfhostedClient;
1427
1426
  const { user_id, email, password, role, app_metadata, user_metadata } = input;
1428
- if (!client.isPgAvailable()) {
1429
- context.log("Direct database connection (DATABASE_URL) is required to update auth user details.", "error");
1430
- throw new Error("Direct database connection (DATABASE_URL) is required to update auth user details.");
1431
- }
1432
- const updates = [];
1433
- const params = [];
1434
- let paramIndex = 1;
1435
- if (email !== void 0) {
1436
- updates.push(`email = $${paramIndex++}`);
1437
- params.push(email);
1438
- }
1439
- if (password !== void 0) {
1440
- updates.push(`encrypted_password = crypt($${paramIndex++}, gen_salt('bf'))`);
1441
- params.push(password);
1442
- console.warn(`SECURITY WARNING: Updating password for user ${user_id} with plain text password via direct DB update.`);
1443
- }
1444
- if (role !== void 0) {
1445
- updates.push(`role = $${paramIndex++}`);
1446
- params.push(role);
1447
- }
1448
- if (app_metadata !== void 0) {
1449
- updates.push(`raw_app_meta_data = $${paramIndex++}::jsonb`);
1450
- params.push(JSON.stringify(app_metadata));
1451
- }
1452
- if (user_metadata !== void 0) {
1453
- updates.push(`raw_user_meta_data = $${paramIndex++}::jsonb`);
1454
- params.push(JSON.stringify(user_metadata));
1455
- }
1456
- params.push(user_id);
1457
- const userIdParamIndex = paramIndex;
1458
- const sql = `
1459
- UPDATE auth.users
1460
- SET ${updates.join(", ")}, updated_at = NOW()
1461
- WHERE id = $${userIdParamIndex}
1462
- RETURNING id, email, role, raw_app_meta_data, raw_user_meta_data, created_at::text, updated_at::text, last_sign_in_at::text;
1463
- `;
1464
- console.error(`Attempting to update auth user ${user_id}...`);
1465
- context.log(`Attempting to update auth user ${user_id}...`);
1466
- 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
+ }
1467
1436
  if (password !== void 0) {
1468
- try {
1469
- await pgClient.query("SELECT crypt('test', gen_salt('bf'))");
1470
- } catch (err) {
1471
- throw new Error("Failed to execute crypt function for password update. Ensure pgcrypto extension is enabled.");
1472
- }
1437
+ updateAttributes.password = password;
1473
1438
  }
1474
- try {
1475
- const result = await pgClient.query(sql, params);
1476
- if (result.rows.length === 0) {
1477
- throw new Error(`User update failed: User with ID ${user_id} not found or no rows affected.`);
1478
- }
1479
- return UpdatedAuthUserZodSchema.parse(result.rows[0]);
1480
- } catch (dbError) {
1481
- let errorMessage = "Unknown database error during user update";
1482
- let isUniqueViolation = false;
1483
- if (typeof dbError === "object" && dbError !== null && "code" in dbError) {
1484
- if (email !== void 0 && dbError.code === "23505") {
1485
- isUniqueViolation = true;
1486
- errorMessage = `User update failed: Email '${email}' likely already exists for another user.`;
1487
- } else if ("message" in dbError && typeof dbError.message === "string") {
1488
- errorMessage = `Database error (${dbError.code}): ${dbError.message}`;
1489
- } else {
1490
- errorMessage = `Database error code: ${dbError.code}`;
1491
- }
1492
- } else if (dbError instanceof Error) {
1493
- errorMessage = `Database error during user update: ${dbError.message}`;
1494
- } else {
1495
- errorMessage = `Database error during user update: ${String(dbError)}`;
1496
- }
1497
- console.error("Error updating user in DB:", dbError);
1498
- throw new Error(errorMessage);
1439
+ if (user_metadata !== void 0) {
1440
+ updateAttributes.user_metadata = user_metadata;
1499
1441
  }
1500
- });
1501
- console.error(`Successfully updated user ${user_id}.`);
1502
- context.log(`Successfully updated user ${user_id}.`);
1503
- 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
+ }
1504
1472
  }
1505
1473
  };
1506
1474
 
1507
1475
  // src/index.ts
1508
- import { z as z24 } from "zod";
1476
+ import { z as z30 } from "zod";
1509
1477
 
1510
1478
  // src/tools/list_storage_buckets.ts
1511
1479
  import { z as z20 } from "zod";
@@ -1537,10 +1505,6 @@ var listStorageBucketsTool = {
1537
1505
  execute: async (input, context) => {
1538
1506
  const client = context.selfhostedClient;
1539
1507
  console.error("Listing storage buckets...");
1540
- if (!client.isPgAvailable()) {
1541
- context.log("Direct database connection (DATABASE_URL) is required to list storage buckets.", "error");
1542
- throw new Error("Direct database connection (DATABASE_URL) is required to list storage buckets.");
1543
- }
1544
1508
  const sql = `
1545
1509
  SELECT
1546
1510
  id,
@@ -1554,8 +1518,17 @@ var listStorageBucketsTool = {
1554
1518
  updated_at::text -- Cast to text
1555
1519
  FROM storage.buckets;
1556
1520
  `;
1557
- console.error("Attempting to list storage buckets using direct DB connection...");
1558
- 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
+ }
1559
1532
  const validatedBuckets = handleSqlResponse(result, ListStorageBucketsOutputSchema);
1560
1533
  console.error(`Found ${validatedBuckets.length} buckets.`);
1561
1534
  context.log(`Found ${validatedBuckets.length} buckets.`);
@@ -1610,43 +1583,43 @@ var listStorageObjectsTool = {
1610
1583
  const client = context.selfhostedClient;
1611
1584
  const { bucket_id, limit, offset, prefix } = input;
1612
1585
  console.error(`Listing objects for bucket ${bucket_id} (Prefix: ${prefix || "N/A"})...`);
1613
- if (!client.isPgAvailable()) {
1614
- context.log("Direct database connection (DATABASE_URL) is required to list storage objects.", "error");
1615
- throw new Error("Direct database connection (DATABASE_URL) is required to list storage objects.");
1616
- }
1617
- const objects = await client.executeTransactionWithPg(async (pgClient) => {
1618
- let sql = `
1619
- SELECT
1620
- id,
1621
- name,
1622
- bucket_id,
1623
- owner,
1624
- version,
1625
- metadata ->> 'mimetype' AS mimetype,
1626
- metadata ->> 'size' AS size, -- Extract size from metadata
1627
- metadata,
1628
- created_at::text,
1629
- updated_at::text,
1630
- last_accessed_at::text
1631
- FROM storage.objects
1632
- WHERE bucket_id = $1
1633
- `;
1634
- const params = [bucket_id];
1635
- let paramIndex = 2;
1636
- if (prefix) {
1637
- sql += ` AND name LIKE $${paramIndex++}`;
1638
- params.push(`${prefix}%`);
1639
- }
1640
- sql += " ORDER BY name ASC NULLS FIRST";
1641
- sql += ` LIMIT $${paramIndex++}`;
1642
- params.push(limit);
1643
- sql += ` OFFSET $${paramIndex++}`;
1644
- params.push(offset);
1645
- sql += ";";
1646
- console.error("Executing parameterized SQL to list storage objects within transaction...");
1647
- const result = await pgClient.query(sql, params);
1648
- return handleSqlResponse(result.rows, ListStorageObjectsOutputSchema);
1649
- });
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);
1650
1623
  console.error(`Found ${objects.length} objects.`);
1651
1624
  context.log(`Found ${objects.length} objects.`);
1652
1625
  return objects;
@@ -1685,10 +1658,6 @@ var listRealtimePublicationsTool = {
1685
1658
  execute: async (input, context) => {
1686
1659
  const client = context.selfhostedClient;
1687
1660
  console.error("Listing Realtime publications...");
1688
- if (!client.isPgAvailable()) {
1689
- context.log("Direct database connection (DATABASE_URL) is required to list publications.", "error");
1690
- throw new Error("Direct database connection (DATABASE_URL) is required to list publications.");
1691
- }
1692
1661
  const sql = `
1693
1662
  SELECT
1694
1663
  oid,
@@ -1702,8 +1671,17 @@ var listRealtimePublicationsTool = {
1702
1671
  pubviaroot
1703
1672
  FROM pg_catalog.pg_publication;
1704
1673
  `;
1705
- console.error("Attempting to list publications using direct DB connection...");
1706
- 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
+ }
1707
1685
  const validatedPublications = handleSqlResponse(result, ListRealtimePublicationsOutputSchema);
1708
1686
  console.error(`Found ${validatedPublications.length} publications.`);
1709
1687
  context.log(`Found ${validatedPublications.length} publications.`);
@@ -1712,6 +1690,256 @@ var listRealtimePublicationsTool = {
1712
1690
  };
1713
1691
  var list_realtime_publications_default = listRealtimePublicationsTool;
1714
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
+
1715
1943
  // src/index.ts
1716
1944
  import * as fs from "node:fs";
1717
1945
  import * as path from "node:path";
@@ -1829,16 +2057,16 @@ async function createRagAgentClient(config) {
1829
2057
  }
1830
2058
 
1831
2059
  // src/integrations/rag-agent-tools.ts
1832
- import { z as z23 } from "zod";
2060
+ import { z as z25 } from "zod";
1833
2061
  function wrapRagAgentTool(ragTool, ragClient) {
1834
- const inputSchema2 = z23.any();
2062
+ const inputSchema2 = z25.any();
1835
2063
  return {
1836
2064
  name: `rag_${ragTool.name}`,
1837
2065
  // Prefix with 'rag_' to avoid naming conflicts
1838
2066
  description: ragTool.description || `RAG Agent tool: ${ragTool.name}`,
1839
2067
  inputSchema: inputSchema2,
1840
2068
  mcpInputSchema: ragTool.inputSchema,
1841
- outputSchema: z23.any(),
2069
+ outputSchema: z25.any(),
1842
2070
  execute: async (input, context) => {
1843
2071
  context.log(`Calling RAG Agent tool: ${ragTool.name}`, "info");
1844
2072
  try {
@@ -1862,48 +2090,464 @@ function wrapAllRagAgentTools(ragClient) {
1862
2090
  return wrappedTools;
1863
2091
  }
1864
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
+
1865
2410
  // src/index.ts
1866
2411
  async function main() {
1867
2412
  const program = new Command();
1868
- 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.').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);
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);
1869
2414
  const options = program.opts();
1870
- if (!options.url) {
1871
- console.error("Error: Supabase URL is required. Use --url or SUPABASE_URL.");
1872
- 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.");
1873
2420
  }
1874
- if (!options.anonKey) {
1875
- console.error("Error: Supabase Anon Key is required. Use --anon-key or SUPABASE_ANON_KEY.");
1876
- 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.");
1877
2423
  }
1878
2424
  console.error("Initializing Self-Hosted Supabase MCP Server...");
1879
2425
  try {
1880
- const selfhostedClient = await SelfhostedSupabaseClient.create({
1881
- supabaseUrl: options.url,
1882
- supabaseAnonKey: options.anonKey,
1883
- supabaseServiceRoleKey: options.serviceKey,
1884
- databaseUrl: options.dbUrl,
1885
- jwtSecret: options.jwtSecret
1886
- });
1887
- 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
+ };
1888
2467
  let ragAgentClient = null;
1889
- if (options.enableRagAgent) {
2468
+ const initializeRagAgent = async (supabaseClient) => {
2469
+ if (!options.enableRagAgent) {
2470
+ return null;
2471
+ }
1890
2472
  try {
1891
- console.error("RAG Agent integration enabled, initializing...");
1892
- const urlObj = new URL(options.url);
2473
+ console.error("Initializing RAG Agent client...");
2474
+ const urlToUse = supabaseClient.getSupabaseUrl();
2475
+ const anonKeyToUse = supabaseClient.getAnonKey();
2476
+ const urlObj = new URL(urlToUse);
1893
2477
  const host = urlObj.hostname;
1894
2478
  const port = urlObj.port ? parseInt(urlObj.port, 10) : urlObj.protocol === "https:" ? 443 : 80;
1895
- ragAgentClient = await createRagAgentClient({
2479
+ const client = await createRagAgentClient({
1896
2480
  host,
1897
2481
  port,
1898
- apiKey: options.anonKey
2482
+ apiKey: anonKeyToUse
1899
2483
  });
1900
2484
  console.error("RAG Agent client initialized successfully.");
2485
+ return client;
1901
2486
  } catch (error) {
1902
2487
  console.error("Failed to initialize RAG Agent client:", error);
1903
2488
  console.error("Continuing without RAG Agent integration...");
2489
+ return null;
1904
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
+ };
1905
2547
  }
1906
2548
  const availableTools = {
2549
+ // Alibaba Cloud tools (if in Aliyun mode)
2550
+ ...wrappedAliyunTools,
1907
2551
  // Cast here assumes tools will implement AppTool structure
1908
2552
  [listTablesTool.name]: listTablesTool,
1909
2553
  [listExtensionsTool.name]: listExtensionsTool,
@@ -1925,12 +2569,45 @@ async function main() {
1925
2569
  [updateAuthUserTool.name]: updateAuthUserTool,
1926
2570
  [list_storage_buckets_default.name]: list_storage_buckets_default,
1927
2571
  [list_storage_objects_default.name]: list_storage_objects_default,
1928
- [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
1929
2575
  };
1930
- if (ragAgentClient) {
1931
- const ragTools = wrapAllRagAgentTools(ragAgentClient);
1932
- Object.assign(availableTools, ragTools);
1933
- console.error(`Added ${Object.keys(ragTools).length} RAG Agent tools to available tools.`);
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
+ }
1934
2611
  }
1935
2612
  let registeredTools = { ...availableTools };
1936
2613
  const toolsConfigPath = options.toolsConfig;
@@ -2015,12 +2692,31 @@ async function main() {
2015
2692
  if (typeof tool.execute !== "function") {
2016
2693
  throw new Error(`Tool ${toolName} does not have an execute method.`);
2017
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
+ }
2018
2699
  let parsedArgs = request.params.arguments;
2019
2700
  if (tool.inputSchema && typeof tool.inputSchema.parse === "function") {
2020
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
+ );
2021
2717
  }
2022
2718
  const context = {
2023
- selfhostedClient,
2719
+ selfhostedClient: currentSupabaseClient,
2024
2720
  workspacePath: options.workspacePath,
2025
2721
  log: (message, level = "info") => {
2026
2722
  console.error(`[${level.toUpperCase()}] ${message}`);
@@ -2038,7 +2734,7 @@ async function main() {
2038
2734
  } catch (error) {
2039
2735
  console.error(`Error executing tool ${toolName}:`, error);
2040
2736
  let errorMessage = `Error executing tool ${toolName}: `;
2041
- if (error instanceof z24.ZodError) {
2737
+ if (error instanceof z30.ZodError) {
2042
2738
  errorMessage += `Input validation failed: ${error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")}`;
2043
2739
  } else if (error instanceof Error) {
2044
2740
  errorMessage += error.message;