@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.
- package/README.md +207 -10
- package/dist/index.js +1254 -353
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
233
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
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
|
-
|
|
1178
|
-
|
|
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
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
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
|
|
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
|
|
1224
|
-
throw new Error("
|
|
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
|
|
1228
|
-
|
|
1229
|
-
|
|
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:
|
|
1237
|
-
message: `
|
|
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:
|
|
1242
|
-
message: `
|
|
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
|
-
|
|
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
|
|
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
|
|
1294
|
-
|
|
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
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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
|
-
|
|
1325
|
-
//
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
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
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
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
|
|
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
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1455
|
-
|
|
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
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
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
|
|
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
|
-
|
|
1538
|
-
|
|
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
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
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
|
-
|
|
1686
|
-
|
|
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,
|
|
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
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
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 (
|
|
1707
|
-
console.error("
|
|
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
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
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
|
|
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;
|