@aliyun-rds/supabase-mcp-server 1.0.6 → 1.0.8
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 +222 -39
- package/dist/index.js +805 -99
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -12,14 +12,403 @@ import {
|
|
|
12
12
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
13
13
|
|
|
14
14
|
// src/client/index.ts
|
|
15
|
-
import { createClient } from "@supabase/supabase-js";
|
|
15
|
+
import { createClient as createClient2 } from "@supabase/supabase-js";
|
|
16
16
|
import { Pool } from "pg";
|
|
17
|
+
|
|
18
|
+
// src/auth/user-auth-client.ts
|
|
19
|
+
import { createClient } from "@supabase/supabase-js";
|
|
20
|
+
var UserAuthClient = class _UserAuthClient {
|
|
21
|
+
supabase;
|
|
22
|
+
session = null;
|
|
23
|
+
refreshTimer = null;
|
|
24
|
+
config;
|
|
25
|
+
authStateCallbacks = [];
|
|
26
|
+
isDestroyed = false;
|
|
27
|
+
/**
|
|
28
|
+
* Private constructor - use create() factory method instead
|
|
29
|
+
*/
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.supabase = createClient(config.supabaseUrl, config.supabaseAnonKey, {
|
|
33
|
+
auth: {
|
|
34
|
+
autoRefreshToken: true,
|
|
35
|
+
persistSession: false,
|
|
36
|
+
// Don't persist to localStorage in server environment
|
|
37
|
+
detectSessionInUrl: false
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
this.supabase.auth.onAuthStateChange((event, session) => {
|
|
41
|
+
this.session = session;
|
|
42
|
+
this.notifyAuthStateChange(event, session);
|
|
43
|
+
if (event === "TOKEN_REFRESHED" && session) {
|
|
44
|
+
console.error("[UserAuthClient] Token refreshed successfully");
|
|
45
|
+
this.scheduleTokenRefresh(session);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Factory method to create and initialize UserAuthClient
|
|
51
|
+
*
|
|
52
|
+
* @throws Error if authentication fails
|
|
53
|
+
*/
|
|
54
|
+
static async create(config) {
|
|
55
|
+
const client = new _UserAuthClient(config);
|
|
56
|
+
await client.signIn();
|
|
57
|
+
return client;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Sign in with email and password
|
|
61
|
+
*
|
|
62
|
+
* @throws Error if authentication fails
|
|
63
|
+
*/
|
|
64
|
+
async signIn() {
|
|
65
|
+
if (this.isDestroyed) {
|
|
66
|
+
throw new Error("UserAuthClient has been destroyed");
|
|
67
|
+
}
|
|
68
|
+
console.error(`[UserAuthClient] Signing in as ${this.config.userEmail}...`);
|
|
69
|
+
const { data, error } = await this.supabase.auth.signInWithPassword({
|
|
70
|
+
email: this.config.userEmail,
|
|
71
|
+
password: this.config.userPassword
|
|
72
|
+
});
|
|
73
|
+
if (error) {
|
|
74
|
+
console.error(`[UserAuthClient] Sign in failed: ${error.message}`);
|
|
75
|
+
throw new Error(`Authentication failed: ${error.message}`);
|
|
76
|
+
}
|
|
77
|
+
if (!data.session) {
|
|
78
|
+
throw new Error("Authentication succeeded but no session returned");
|
|
79
|
+
}
|
|
80
|
+
this.session = data.session;
|
|
81
|
+
console.error(`[UserAuthClient] Successfully signed in as ${data.user?.email} (ID: ${data.user?.id})`);
|
|
82
|
+
this.scheduleTokenRefresh(data.session);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Manually refresh the current session
|
|
86
|
+
*
|
|
87
|
+
* @throws Error if refresh fails
|
|
88
|
+
*/
|
|
89
|
+
async refreshToken() {
|
|
90
|
+
if (this.isDestroyed) {
|
|
91
|
+
throw new Error("UserAuthClient has been destroyed");
|
|
92
|
+
}
|
|
93
|
+
if (!this.session?.refresh_token) {
|
|
94
|
+
console.error("[UserAuthClient] No refresh token available, re-authenticating...");
|
|
95
|
+
await this.signIn();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
console.error("[UserAuthClient] Refreshing token...");
|
|
99
|
+
const { data, error } = await this.supabase.auth.refreshSession({
|
|
100
|
+
refresh_token: this.session.refresh_token
|
|
101
|
+
});
|
|
102
|
+
if (error) {
|
|
103
|
+
console.error(`[UserAuthClient] Token refresh failed: ${error.message}`);
|
|
104
|
+
console.error("[UserAuthClient] Attempting to re-authenticate...");
|
|
105
|
+
await this.signIn();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (data.session) {
|
|
109
|
+
this.session = data.session;
|
|
110
|
+
this.scheduleTokenRefresh(data.session);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Schedule automatic token refresh before expiry
|
|
115
|
+
*/
|
|
116
|
+
scheduleTokenRefresh(session) {
|
|
117
|
+
if (this.refreshTimer) {
|
|
118
|
+
clearTimeout(this.refreshTimer);
|
|
119
|
+
this.refreshTimer = null;
|
|
120
|
+
}
|
|
121
|
+
if (!session.expires_at) {
|
|
122
|
+
console.error("[UserAuthClient] Session has no expiry time, skipping auto-refresh scheduling");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const expiresAt = session.expires_at * 1e3;
|
|
126
|
+
const now = Date.now();
|
|
127
|
+
const refreshBeforeExpiry = this.config.refreshBeforeExpiry || 6e4;
|
|
128
|
+
const refreshIn = expiresAt - now - refreshBeforeExpiry;
|
|
129
|
+
if (refreshIn <= 0) {
|
|
130
|
+
console.error("[UserAuthClient] Token near expiry, refreshing immediately...");
|
|
131
|
+
this.refreshToken().catch((err) => {
|
|
132
|
+
console.error("[UserAuthClient] Auto-refresh failed:", err);
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
console.error(`[UserAuthClient] Token refresh scheduled in ${Math.round(refreshIn / 1e3)} seconds`);
|
|
137
|
+
this.refreshTimer = setTimeout(() => {
|
|
138
|
+
if (!this.isDestroyed) {
|
|
139
|
+
this.refreshToken().catch((err) => {
|
|
140
|
+
console.error("[UserAuthClient] Scheduled token refresh failed:", err);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}, refreshIn);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get the current access token
|
|
147
|
+
*/
|
|
148
|
+
getAccessToken() {
|
|
149
|
+
return this.session?.access_token || null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get the current user ID
|
|
153
|
+
*/
|
|
154
|
+
getUserId() {
|
|
155
|
+
return this.session?.user?.id || null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get the current user email
|
|
159
|
+
*/
|
|
160
|
+
getUserEmail() {
|
|
161
|
+
return this.session?.user?.email || null;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Check if the client is currently authenticated
|
|
165
|
+
*/
|
|
166
|
+
isAuthenticated() {
|
|
167
|
+
if (!this.session) return false;
|
|
168
|
+
if (this.session.expires_at) {
|
|
169
|
+
const expiresAt = this.session.expires_at * 1e3;
|
|
170
|
+
if (Date.now() >= expiresAt) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get the current session
|
|
178
|
+
*/
|
|
179
|
+
getSession() {
|
|
180
|
+
return this.session;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get the underlying Supabase client
|
|
184
|
+
*
|
|
185
|
+
* Note: This client uses the authenticated user's session,
|
|
186
|
+
* so all operations are subject to RLS policies.
|
|
187
|
+
*/
|
|
188
|
+
getSupabaseClient() {
|
|
189
|
+
return this.supabase;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Register a callback for auth state changes
|
|
193
|
+
*/
|
|
194
|
+
onAuthStateChange(callback) {
|
|
195
|
+
this.authStateCallbacks.push(callback);
|
|
196
|
+
return () => {
|
|
197
|
+
const index = this.authStateCallbacks.indexOf(callback);
|
|
198
|
+
if (index > -1) {
|
|
199
|
+
this.authStateCallbacks.splice(index, 1);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Notify all registered callbacks of auth state change
|
|
205
|
+
*/
|
|
206
|
+
notifyAuthStateChange(event, session) {
|
|
207
|
+
for (const callback of this.authStateCallbacks) {
|
|
208
|
+
try {
|
|
209
|
+
callback(event, session);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
console.error("[UserAuthClient] Auth state callback error:", err);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Sign out and clean up resources
|
|
217
|
+
*/
|
|
218
|
+
async signOut() {
|
|
219
|
+
if (this.refreshTimer) {
|
|
220
|
+
clearTimeout(this.refreshTimer);
|
|
221
|
+
this.refreshTimer = null;
|
|
222
|
+
}
|
|
223
|
+
if (this.session) {
|
|
224
|
+
try {
|
|
225
|
+
await this.supabase.auth.signOut();
|
|
226
|
+
} catch (err) {
|
|
227
|
+
console.error("[UserAuthClient] Sign out error:", err);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
this.session = null;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Destroy the client and clean up all resources
|
|
234
|
+
*/
|
|
235
|
+
async destroy() {
|
|
236
|
+
this.isDestroyed = true;
|
|
237
|
+
await this.signOut();
|
|
238
|
+
this.authStateCallbacks = [];
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/config/types.ts
|
|
243
|
+
var ConfigValidationError = class extends Error {
|
|
244
|
+
constructor(message, missingParams, conflictingModes) {
|
|
245
|
+
super(message);
|
|
246
|
+
this.missingParams = missingParams;
|
|
247
|
+
this.conflictingModes = conflictingModes;
|
|
248
|
+
this.name = "ConfigValidationError";
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
function isAliyunConfig(config) {
|
|
252
|
+
return config.mode === "aliyun" /* ALIYUN_MULTI_INSTANCE */;
|
|
253
|
+
}
|
|
254
|
+
function isSingleInstanceAdminConfig(config) {
|
|
255
|
+
return config.mode === "admin" /* SINGLE_INSTANCE_ADMIN */;
|
|
256
|
+
}
|
|
257
|
+
function isSingleInstanceUserConfig(config) {
|
|
258
|
+
return config.mode === "user" /* SINGLE_INSTANCE_USER */;
|
|
259
|
+
}
|
|
260
|
+
function getPermissionLevel(mode) {
|
|
261
|
+
switch (mode) {
|
|
262
|
+
case "aliyun" /* ALIYUN_MULTI_INSTANCE */:
|
|
263
|
+
return "full" /* FULL */;
|
|
264
|
+
case "admin" /* SINGLE_INSTANCE_ADMIN */:
|
|
265
|
+
return "admin" /* ADMIN */;
|
|
266
|
+
case "user" /* SINGLE_INSTANCE_USER */:
|
|
267
|
+
return "user" /* USER */;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/config/parser.ts
|
|
272
|
+
function detectModes(options) {
|
|
273
|
+
const supabaseUrl = options.supabaseUrl || options.url;
|
|
274
|
+
const supabaseAnonKey = options.supabaseAnonKey || options.anonKey;
|
|
275
|
+
const supabaseServiceRoleKey = options.supabaseServiceRoleKey || options.serviceKey;
|
|
276
|
+
return {
|
|
277
|
+
aliyun: {
|
|
278
|
+
hasRequired: !!(options.aliyunAk && options.aliyunSk),
|
|
279
|
+
hasAny: !!(options.aliyunAk || options.aliyunSk || options.aliyunRegion)
|
|
280
|
+
},
|
|
281
|
+
admin: {
|
|
282
|
+
hasRequired: !!(supabaseUrl && supabaseAnonKey && supabaseServiceRoleKey),
|
|
283
|
+
hasAny: !!(supabaseUrl || supabaseAnonKey || supabaseServiceRoleKey)
|
|
284
|
+
},
|
|
285
|
+
user: {
|
|
286
|
+
hasRequired: !!(supabaseUrl && supabaseAnonKey && options.supabaseUserEmail && options.supabaseUserPassword),
|
|
287
|
+
hasAny: !!(options.supabaseUserEmail || options.supabaseUserPassword)
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function generateHelpMessage(modes, options) {
|
|
292
|
+
const lines = [
|
|
293
|
+
"Error: No valid authentication configuration found.",
|
|
294
|
+
"",
|
|
295
|
+
"Please provide ONE of the following configurations:",
|
|
296
|
+
""
|
|
297
|
+
];
|
|
298
|
+
lines.push("Mode 1 - Alibaba Cloud Multi-Instance Management:");
|
|
299
|
+
lines.push(" --aliyun-ak <key> --aliyun-sk <secret> [--aliyun-region <region>]");
|
|
300
|
+
lines.push(" Or: ALIYUN_ACCESS_KEY_ID, ALIYUN_ACCESS_KEY_SECRET");
|
|
301
|
+
if (modes.aliyun.hasAny && !modes.aliyun.hasRequired) {
|
|
302
|
+
const missing = [];
|
|
303
|
+
if (!options.aliyunAk) missing.push("--aliyun-ak");
|
|
304
|
+
if (!options.aliyunSk) missing.push("--aliyun-sk");
|
|
305
|
+
lines.push(` \u26A0\uFE0F Missing: ${missing.join(", ")}`);
|
|
306
|
+
}
|
|
307
|
+
lines.push("");
|
|
308
|
+
const supabaseUrl = options.supabaseUrl || options.url;
|
|
309
|
+
const supabaseAnonKey = options.supabaseAnonKey || options.anonKey;
|
|
310
|
+
const supabaseServiceRoleKey = options.supabaseServiceRoleKey || options.serviceKey;
|
|
311
|
+
lines.push("Mode 2 - Single Instance Admin Access:");
|
|
312
|
+
lines.push(" --supabase-url <url> --supabase-anon-key <key> --supabase-service-role-key <key>");
|
|
313
|
+
lines.push(" Or: SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY");
|
|
314
|
+
if (modes.admin.hasAny && !modes.admin.hasRequired) {
|
|
315
|
+
const missing = [];
|
|
316
|
+
if (!supabaseUrl) missing.push("--supabase-url");
|
|
317
|
+
if (!supabaseAnonKey) missing.push("--supabase-anon-key");
|
|
318
|
+
if (!supabaseServiceRoleKey) missing.push("--supabase-service-role-key");
|
|
319
|
+
lines.push(` \u26A0\uFE0F Missing: ${missing.join(", ")}`);
|
|
320
|
+
}
|
|
321
|
+
lines.push("");
|
|
322
|
+
lines.push("Mode 3 - Single Instance User Access (RLS restricted):");
|
|
323
|
+
lines.push(" --supabase-url <url> --supabase-anon-key <key> --supabase-user-email <email> --supabase-user-password <password>");
|
|
324
|
+
lines.push(" Or: SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_USER_EMAIL, SUPABASE_USER_PASSWORD");
|
|
325
|
+
if (modes.user.hasAny && !modes.user.hasRequired) {
|
|
326
|
+
const missing = [];
|
|
327
|
+
if (!supabaseUrl) missing.push("--supabase-url");
|
|
328
|
+
if (!supabaseAnonKey) missing.push("--supabase-anon-key");
|
|
329
|
+
if (!options.supabaseUserEmail) missing.push("--supabase-user-email");
|
|
330
|
+
if (!options.supabaseUserPassword) missing.push("--supabase-user-password");
|
|
331
|
+
lines.push(` \u26A0\uFE0F Missing: ${missing.join(", ")}`);
|
|
332
|
+
}
|
|
333
|
+
return lines.join("\n");
|
|
334
|
+
}
|
|
335
|
+
function parseConfig(options) {
|
|
336
|
+
const modes = detectModes(options);
|
|
337
|
+
const supabaseUrl = options.supabaseUrl || options.url;
|
|
338
|
+
const supabaseAnonKey = options.supabaseAnonKey || options.anonKey;
|
|
339
|
+
const supabaseServiceRoleKey = options.supabaseServiceRoleKey || options.serviceKey;
|
|
340
|
+
const completeModes = [];
|
|
341
|
+
if (modes.aliyun.hasRequired) completeModes.push("aliyun" /* ALIYUN_MULTI_INSTANCE */);
|
|
342
|
+
if (modes.admin.hasRequired) completeModes.push("admin" /* SINGLE_INSTANCE_ADMIN */);
|
|
343
|
+
if (modes.user.hasRequired) completeModes.push("user" /* SINGLE_INSTANCE_USER */);
|
|
344
|
+
if (completeModes.length > 1) {
|
|
345
|
+
console.error(`Warning: Multiple authentication modes detected: ${completeModes.join(", ")}`);
|
|
346
|
+
console.error("Using highest priority mode based on: Aliyun > Admin > User");
|
|
347
|
+
}
|
|
348
|
+
if (modes.aliyun.hasRequired) {
|
|
349
|
+
const config = {
|
|
350
|
+
mode: "aliyun" /* ALIYUN_MULTI_INSTANCE */,
|
|
351
|
+
accessKeyId: options.aliyunAk,
|
|
352
|
+
accessKeySecret: options.aliyunSk,
|
|
353
|
+
regionId: options.aliyunRegion,
|
|
354
|
+
enableRagAgent: options.enableRagAgent,
|
|
355
|
+
workspacePath: options.workspacePath,
|
|
356
|
+
toolsConfig: options.toolsConfig
|
|
357
|
+
};
|
|
358
|
+
return config;
|
|
359
|
+
}
|
|
360
|
+
if (modes.user.hasRequired) {
|
|
361
|
+
const config = {
|
|
362
|
+
mode: "user" /* SINGLE_INSTANCE_USER */,
|
|
363
|
+
supabaseUrl,
|
|
364
|
+
supabaseAnonKey,
|
|
365
|
+
userEmail: options.supabaseUserEmail,
|
|
366
|
+
userPassword: options.supabaseUserPassword,
|
|
367
|
+
enableRagAgent: options.enableRagAgent,
|
|
368
|
+
workspacePath: options.workspacePath,
|
|
369
|
+
toolsConfig: options.toolsConfig
|
|
370
|
+
};
|
|
371
|
+
return config;
|
|
372
|
+
}
|
|
373
|
+
if (modes.admin.hasRequired) {
|
|
374
|
+
const config = {
|
|
375
|
+
mode: "admin" /* SINGLE_INSTANCE_ADMIN */,
|
|
376
|
+
supabaseUrl,
|
|
377
|
+
supabaseAnonKey,
|
|
378
|
+
supabaseServiceRoleKey,
|
|
379
|
+
databaseUrl: options.dbUrl,
|
|
380
|
+
jwtSecret: options.jwtSecret,
|
|
381
|
+
enableRagAgent: options.enableRagAgent,
|
|
382
|
+
workspacePath: options.workspacePath,
|
|
383
|
+
toolsConfig: options.toolsConfig
|
|
384
|
+
};
|
|
385
|
+
return config;
|
|
386
|
+
}
|
|
387
|
+
const helpMessage = generateHelpMessage(modes, options);
|
|
388
|
+
throw new ConfigValidationError(helpMessage);
|
|
389
|
+
}
|
|
390
|
+
function getAuthModeDescription(mode) {
|
|
391
|
+
switch (mode) {
|
|
392
|
+
case "aliyun" /* ALIYUN_MULTI_INSTANCE */:
|
|
393
|
+
return "Alibaba Cloud Multi-Instance Management";
|
|
394
|
+
case "admin" /* SINGLE_INSTANCE_ADMIN */:
|
|
395
|
+
return "Single Instance Admin Access";
|
|
396
|
+
case "user" /* SINGLE_INSTANCE_USER */:
|
|
397
|
+
return "Single Instance User Access (RLS restricted)";
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/client/index.ts
|
|
17
402
|
var SelfhostedSupabaseClient = class _SelfhostedSupabaseClient {
|
|
18
403
|
options;
|
|
19
404
|
supabase;
|
|
20
405
|
pgPool = null;
|
|
21
406
|
// Lazy initialized pool for direct DB access
|
|
22
407
|
rpcFunctionExists = false;
|
|
408
|
+
/** The authentication mode this client was created with */
|
|
409
|
+
authMode;
|
|
410
|
+
/** User authentication client (only set for AuthMode.SINGLE_INSTANCE_USER) */
|
|
411
|
+
userAuthClient = null;
|
|
23
412
|
// SQL definition for the helper function
|
|
24
413
|
static CREATE_EXECUTE_SQL_FUNCTION = `
|
|
25
414
|
CREATE OR REPLACE FUNCTION public.execute_sql(query text, read_only boolean DEFAULT false)
|
|
@@ -53,24 +442,81 @@ var SelfhostedSupabaseClient = class _SelfhostedSupabaseClient {
|
|
|
53
442
|
* Creates an instance of SelfhostedSupabaseClient.
|
|
54
443
|
* Note: Call initialize() after creating the instance to check for RPC functions.
|
|
55
444
|
* @param options - Configuration options for the client.
|
|
445
|
+
* @param authMode - The authentication mode (defaults to SINGLE_INSTANCE_ADMIN)
|
|
446
|
+
* @param userAuthClient - Optional UserAuthClient for user-level authentication
|
|
56
447
|
*/
|
|
57
|
-
constructor(options) {
|
|
448
|
+
constructor(options, authMode = "admin" /* SINGLE_INSTANCE_ADMIN */, userAuthClient = null) {
|
|
58
449
|
this.options = options;
|
|
59
|
-
|
|
60
|
-
this.
|
|
450
|
+
this.authMode = authMode;
|
|
451
|
+
this.userAuthClient = userAuthClient;
|
|
61
452
|
if (!options.supabaseUrl || !options.supabaseAnonKey) {
|
|
62
453
|
throw new Error("Supabase URL and Anon Key are required.");
|
|
63
454
|
}
|
|
455
|
+
if (userAuthClient) {
|
|
456
|
+
this.supabase = userAuthClient.getSupabaseClient();
|
|
457
|
+
} else {
|
|
458
|
+
const apiKey = options.supabaseServiceRoleKey || options.supabaseAnonKey;
|
|
459
|
+
this.supabase = createClient2(options.supabaseUrl, apiKey, options.supabaseClientOptions);
|
|
460
|
+
}
|
|
64
461
|
}
|
|
65
462
|
/**
|
|
66
463
|
* Factory function to create and asynchronously initialize the client.
|
|
67
464
|
* Checks for the existence of the helper RPC function.
|
|
465
|
+
*
|
|
466
|
+
* @param options - Configuration options for the client
|
|
467
|
+
* @param authMode - The authentication mode (defaults to SINGLE_INSTANCE_ADMIN for backward compatibility)
|
|
68
468
|
*/
|
|
69
|
-
static async create(options) {
|
|
70
|
-
const client = new _SelfhostedSupabaseClient(options);
|
|
469
|
+
static async create(options, authMode = "admin" /* SINGLE_INSTANCE_ADMIN */) {
|
|
470
|
+
const client = new _SelfhostedSupabaseClient(options, authMode);
|
|
71
471
|
await client.initialize();
|
|
72
472
|
return client;
|
|
73
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Factory function to create a client with user-level authentication.
|
|
476
|
+
* This creates a client that operates under the user's RLS permissions.
|
|
477
|
+
*
|
|
478
|
+
* @param config - User authentication configuration
|
|
479
|
+
* @returns A SelfhostedSupabaseClient using the authenticated user's session
|
|
480
|
+
* @throws Error if user authentication fails
|
|
481
|
+
*/
|
|
482
|
+
static async createWithUserAuth(config) {
|
|
483
|
+
const userAuthClient = await UserAuthClient.create(config);
|
|
484
|
+
const options = {
|
|
485
|
+
supabaseUrl: config.supabaseUrl,
|
|
486
|
+
supabaseAnonKey: config.supabaseAnonKey
|
|
487
|
+
// Note: No service role key for user-level auth
|
|
488
|
+
};
|
|
489
|
+
const client = new _SelfhostedSupabaseClient(
|
|
490
|
+
options,
|
|
491
|
+
"user" /* SINGLE_INSTANCE_USER */,
|
|
492
|
+
userAuthClient
|
|
493
|
+
);
|
|
494
|
+
await client.initializeForUserAuth();
|
|
495
|
+
return client;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Initialize client for user-level authentication.
|
|
499
|
+
* Skips RPC function creation since user doesn't have admin permissions.
|
|
500
|
+
*/
|
|
501
|
+
async initializeForUserAuth() {
|
|
502
|
+
console.error("Initializing SelfhostedSupabaseClient for user-level authentication...");
|
|
503
|
+
try {
|
|
504
|
+
const { error } = await this.supabase.rpc("execute_sql", {
|
|
505
|
+
query: "SELECT 1",
|
|
506
|
+
read_only: true
|
|
507
|
+
});
|
|
508
|
+
this.rpcFunctionExists = !error;
|
|
509
|
+
if (error) {
|
|
510
|
+
console.error("RPC function not available for user auth mode:", error.message);
|
|
511
|
+
} else {
|
|
512
|
+
console.error("RPC function available for user auth mode.");
|
|
513
|
+
}
|
|
514
|
+
} catch (err) {
|
|
515
|
+
console.error("RPC function check failed:", err);
|
|
516
|
+
this.rpcFunctionExists = false;
|
|
517
|
+
}
|
|
518
|
+
console.error("User auth initialization complete.");
|
|
519
|
+
}
|
|
74
520
|
/**
|
|
75
521
|
* Initializes the client by checking for the required RPC function.
|
|
76
522
|
* Attempts to create the function if it doesn't exist and a service role key is provided.
|
|
@@ -367,6 +813,55 @@ var SelfhostedSupabaseClient = class _SelfhostedSupabaseClient {
|
|
|
367
813
|
isRpcAvailable() {
|
|
368
814
|
return this.rpcFunctionExists;
|
|
369
815
|
}
|
|
816
|
+
/**
|
|
817
|
+
* Sets the RPC function availability status.
|
|
818
|
+
* Called after successfully installing the execute_sql function.
|
|
819
|
+
*/
|
|
820
|
+
setRpcAvailable(available) {
|
|
821
|
+
this.rpcFunctionExists = available;
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Gets the authentication mode this client was created with.
|
|
825
|
+
*/
|
|
826
|
+
getAuthMode() {
|
|
827
|
+
return this.authMode;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Checks if this client is using user-level authentication.
|
|
831
|
+
*/
|
|
832
|
+
isUserAuth() {
|
|
833
|
+
return this.authMode === "user" /* SINGLE_INSTANCE_USER */;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Gets the user ID if using user-level authentication.
|
|
837
|
+
*/
|
|
838
|
+
getUserId() {
|
|
839
|
+
return this.userAuthClient?.getUserId() || null;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Gets the user email if using user-level authentication.
|
|
843
|
+
*/
|
|
844
|
+
getUserEmail() {
|
|
845
|
+
return this.userAuthClient?.getUserEmail() || null;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Gets the UserAuthClient if using user-level authentication.
|
|
849
|
+
*/
|
|
850
|
+
getUserAuthClient() {
|
|
851
|
+
return this.userAuthClient;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Cleanup resources when done with the client.
|
|
855
|
+
*/
|
|
856
|
+
async destroy() {
|
|
857
|
+
if (this.userAuthClient) {
|
|
858
|
+
await this.userAuthClient.destroy();
|
|
859
|
+
}
|
|
860
|
+
if (this.pgPool) {
|
|
861
|
+
await this.pgPool.end();
|
|
862
|
+
this.pgPool = null;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
370
865
|
};
|
|
371
866
|
|
|
372
867
|
// src/tools/list_tables.ts
|
|
@@ -377,6 +872,9 @@ import { z } from "zod";
|
|
|
377
872
|
import { exec } from "node:child_process";
|
|
378
873
|
import { promisify } from "node:util";
|
|
379
874
|
var execAsync = promisify(exec);
|
|
875
|
+
function isSqlErrorResponse(result) {
|
|
876
|
+
return result.error !== void 0;
|
|
877
|
+
}
|
|
380
878
|
function handleSqlResponse(result, schema) {
|
|
381
879
|
if ("error" in result) {
|
|
382
880
|
throw new Error(`SQL Error (${result.error.code}): ${result.error.message}`);
|
|
@@ -789,7 +1287,22 @@ var getDatabaseStatsTool = {
|
|
|
789
1287
|
stats_reset::text
|
|
790
1288
|
FROM pg_stat_database
|
|
791
1289
|
`;
|
|
792
|
-
const
|
|
1290
|
+
const getBgWriterStatsSqlPg17 = `
|
|
1291
|
+
SELECT
|
|
1292
|
+
(SELECT num_timed::text FROM pg_stat_checkpointer) as checkpoints_timed,
|
|
1293
|
+
(SELECT num_requested::text FROM pg_stat_checkpointer) as checkpoints_req,
|
|
1294
|
+
(SELECT write_time FROM pg_stat_checkpointer) as checkpoint_write_time,
|
|
1295
|
+
(SELECT sync_time FROM pg_stat_checkpointer) as checkpoint_sync_time,
|
|
1296
|
+
(SELECT buffers_written::text FROM pg_stat_checkpointer) as buffers_checkpoint,
|
|
1297
|
+
buffers_clean::text,
|
|
1298
|
+
maxwritten_clean::text,
|
|
1299
|
+
NULL::text as buffers_backend,
|
|
1300
|
+
NULL::text as buffers_backend_fsync,
|
|
1301
|
+
buffers_alloc::text,
|
|
1302
|
+
stats_reset::text
|
|
1303
|
+
FROM pg_stat_bgwriter
|
|
1304
|
+
`;
|
|
1305
|
+
const getBgWriterStatsSqlPg16 = `
|
|
793
1306
|
SELECT
|
|
794
1307
|
checkpoints_timed::text,
|
|
795
1308
|
checkpoints_req::text,
|
|
@@ -804,10 +1317,12 @@ var getDatabaseStatsTool = {
|
|
|
804
1317
|
stats_reset::text
|
|
805
1318
|
FROM pg_stat_bgwriter
|
|
806
1319
|
`;
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1320
|
+
const dbStatsResult = await executeSqlWithFallback(client, getDbStatsSql, true);
|
|
1321
|
+
let bgWriterStatsResult = await executeSqlWithFallback(client, getBgWriterStatsSqlPg17, true);
|
|
1322
|
+
if (isSqlErrorResponse(bgWriterStatsResult)) {
|
|
1323
|
+
console.error("PostgreSQL 17+ query failed, trying PostgreSQL 16 query...");
|
|
1324
|
+
bgWriterStatsResult = await executeSqlWithFallback(client, getBgWriterStatsSqlPg16, true);
|
|
1325
|
+
}
|
|
811
1326
|
const dbStats = handleSqlResponse(dbStatsResult, GetDbStatsOutputSchema.shape.database_stats);
|
|
812
1327
|
const bgWriterStats = handleSqlResponse(bgWriterStatsResult, GetDbStatsOutputSchema.shape.bgwriter_stats);
|
|
813
1328
|
return {
|
|
@@ -1475,6 +1990,48 @@ var updateAuthUserTool = {
|
|
|
1475
1990
|
// src/index.ts
|
|
1476
1991
|
import { z as z30 } from "zod";
|
|
1477
1992
|
|
|
1993
|
+
// src/tools/types.ts
|
|
1994
|
+
var ADMIN_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
1995
|
+
// Auth management tools
|
|
1996
|
+
"create_auth_user",
|
|
1997
|
+
"delete_auth_user",
|
|
1998
|
+
"update_auth_user",
|
|
1999
|
+
"list_auth_users",
|
|
2000
|
+
"get_auth_user",
|
|
2001
|
+
// Admin configuration tools
|
|
2002
|
+
"get_service_key",
|
|
2003
|
+
"verify_jwt_secret",
|
|
2004
|
+
"install_execute_sql_function",
|
|
2005
|
+
"rebuild_hooks"
|
|
2006
|
+
]);
|
|
2007
|
+
var ALIYUN_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
2008
|
+
"list_aliyun_supabase_instances",
|
|
2009
|
+
"connect_to_supabase_instance",
|
|
2010
|
+
"get_current_supabase_instance",
|
|
2011
|
+
"disconnect_supabase_instance"
|
|
2012
|
+
]);
|
|
2013
|
+
function isToolAllowed(toolName, permissionLevel) {
|
|
2014
|
+
switch (permissionLevel) {
|
|
2015
|
+
case "full" /* FULL */:
|
|
2016
|
+
return true;
|
|
2017
|
+
case "admin" /* ADMIN */:
|
|
2018
|
+
return !ALIYUN_ONLY_TOOLS.has(toolName);
|
|
2019
|
+
case "user" /* USER */:
|
|
2020
|
+
return !ALIYUN_ONLY_TOOLS.has(toolName) && !ADMIN_ONLY_TOOLS.has(toolName);
|
|
2021
|
+
default:
|
|
2022
|
+
return false;
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
function getToolNotAllowedMessage(toolName, permissionLevel) {
|
|
2026
|
+
if (ALIYUN_ONLY_TOOLS.has(toolName)) {
|
|
2027
|
+
return `Tool "${toolName}" is only available in Alibaba Cloud multi-instance management mode. Please use --aliyun-ak and --aliyun-sk to enable this mode.`;
|
|
2028
|
+
}
|
|
2029
|
+
if (ADMIN_ONLY_TOOLS.has(toolName) && permissionLevel === "user" /* USER */) {
|
|
2030
|
+
return `Tool "${toolName}" requires admin permissions and is not available in user-level access mode. Please use --supabase-service-role-key for admin access.`;
|
|
2031
|
+
}
|
|
2032
|
+
return `Tool "${toolName}" is not allowed for the current permission level (${permissionLevel}).`;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
1478
2035
|
// src/tools/list_storage_buckets.ts
|
|
1479
2036
|
import { z as z20 } from "zod";
|
|
1480
2037
|
var BucketSchema = z20.object({
|
|
@@ -1707,14 +2264,18 @@ SET search_path = public
|
|
|
1707
2264
|
AS $$
|
|
1708
2265
|
DECLARE
|
|
1709
2266
|
result jsonb;
|
|
2267
|
+
cleaned_query text;
|
|
1710
2268
|
BEGIN
|
|
2269
|
+
-- Remove trailing semicolons and whitespace from query
|
|
2270
|
+
cleaned_query := regexp_replace(trim(query), ';\\s*$', '');
|
|
2271
|
+
|
|
1711
2272
|
-- Execute query and convert result to JSON
|
|
1712
2273
|
IF read_only THEN
|
|
1713
2274
|
-- Read-only query
|
|
1714
|
-
EXECUTE 'SELECT COALESCE(jsonb_agg(row_to_json(t)), ''[]''::jsonb) FROM (' ||
|
|
2275
|
+
EXECUTE 'SELECT COALESCE(jsonb_agg(row_to_json(t)), ''[]''::jsonb) FROM (' || cleaned_query || ') t' INTO result;
|
|
1715
2276
|
ELSE
|
|
1716
2277
|
-- Write query (INSERT/UPDATE/DELETE)
|
|
1717
|
-
EXECUTE
|
|
2278
|
+
EXECUTE cleaned_query;
|
|
1718
2279
|
result := '[]'::jsonb;
|
|
1719
2280
|
END IF;
|
|
1720
2281
|
|
|
@@ -1722,10 +2283,7 @@ BEGIN
|
|
|
1722
2283
|
EXCEPTION
|
|
1723
2284
|
WHEN OTHERS THEN
|
|
1724
2285
|
-- Return error information
|
|
1725
|
-
|
|
1726
|
-
'error', SQLERRM,
|
|
1727
|
-
'code', SQLSTATE
|
|
1728
|
-
);
|
|
2286
|
+
RAISE EXCEPTION 'Error executing SQL (SQLSTATE: %): %', SQLSTATE, SQLERRM;
|
|
1729
2287
|
END;
|
|
1730
2288
|
$$;
|
|
1731
2289
|
`;
|
|
@@ -1784,6 +2342,7 @@ async function execute(input, context) {
|
|
|
1784
2342
|
}
|
|
1785
2343
|
context.log("execute_sql function installed successfully!", "info");
|
|
1786
2344
|
context.log("You may need to wait a few seconds for PostgREST to reload the schema.", "info");
|
|
2345
|
+
client.setRpcAvailable(true);
|
|
1787
2346
|
return {
|
|
1788
2347
|
success: true,
|
|
1789
2348
|
message: "execute_sql function installed successfully. You can now use tools that require SQL execution (list_tables, execute_sql, etc.)."
|
|
@@ -1947,18 +2506,30 @@ import * as path from "node:path";
|
|
|
1947
2506
|
// src/integrations/rag-agent-client.ts
|
|
1948
2507
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1949
2508
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2509
|
+
function isUserConfig(config) {
|
|
2510
|
+
return "userEmail" in config && "userPassword" in config;
|
|
2511
|
+
}
|
|
1950
2512
|
var RagAgentClient = class {
|
|
1951
2513
|
config;
|
|
1952
2514
|
client = null;
|
|
1953
2515
|
tools = [];
|
|
2516
|
+
authMode;
|
|
1954
2517
|
constructor(config) {
|
|
1955
2518
|
this.config = config;
|
|
2519
|
+
this.authMode = isUserConfig(config) ? "jwt" /* JWT */ : "service_key" /* SERVICE_KEY */;
|
|
2520
|
+
}
|
|
2521
|
+
/**
|
|
2522
|
+
* Get the authentication mode being used
|
|
2523
|
+
*/
|
|
2524
|
+
getAuthMode() {
|
|
2525
|
+
return this.authMode;
|
|
1956
2526
|
}
|
|
1957
2527
|
/**
|
|
1958
2528
|
* Initialize connection to rag-agent MCP server and fetch tools
|
|
1959
2529
|
*/
|
|
1960
2530
|
async initialize() {
|
|
1961
|
-
|
|
2531
|
+
const modeDescription = this.authMode === "jwt" /* JWT */ ? "JWT mode (user-level access)" : "Service Key mode (admin access)";
|
|
2532
|
+
console.error(`Initializing RAG Agent MCP client in ${modeDescription}...`);
|
|
1962
2533
|
try {
|
|
1963
2534
|
this.client = new Client(
|
|
1964
2535
|
{
|
|
@@ -1977,19 +2548,45 @@ var RagAgentClient = class {
|
|
|
1977
2548
|
}
|
|
1978
2549
|
env.NO_PROXY = "*";
|
|
1979
2550
|
env.no_proxy = "*";
|
|
2551
|
+
const args = [
|
|
2552
|
+
"--from",
|
|
2553
|
+
"rag-agent-mcp",
|
|
2554
|
+
"rag-agent",
|
|
2555
|
+
"--host",
|
|
2556
|
+
this.config.host,
|
|
2557
|
+
"--api-key",
|
|
2558
|
+
this.config.apiKey
|
|
2559
|
+
];
|
|
2560
|
+
console.error("[RAG Agent] Configuration:");
|
|
2561
|
+
console.error(` Host: ${this.config.host}`);
|
|
2562
|
+
console.error(` API Key: ${this.config.apiKey.substring(0, 20)}...${this.config.apiKey.substring(this.config.apiKey.length - 10)}`);
|
|
2563
|
+
console.error(` API Key length: ${this.config.apiKey.length}`);
|
|
2564
|
+
try {
|
|
2565
|
+
const payloadBase64 = this.config.apiKey.split(".")[1];
|
|
2566
|
+
if (payloadBase64) {
|
|
2567
|
+
const payload = JSON.parse(Buffer.from(payloadBase64, "base64").toString("utf-8"));
|
|
2568
|
+
console.error(` JWT Role: ${payload.role || "unknown"}`);
|
|
2569
|
+
console.error(` JWT Issuer: ${payload.iss || "unknown"}`);
|
|
2570
|
+
}
|
|
2571
|
+
} catch (e) {
|
|
2572
|
+
console.error(` (Could not decode API key as JWT)`);
|
|
2573
|
+
}
|
|
2574
|
+
if (this.authMode === "jwt" /* JWT */ && isUserConfig(this.config)) {
|
|
2575
|
+
args.push("--user", this.config.userEmail);
|
|
2576
|
+
args.push("--user-password", this.config.userPassword);
|
|
2577
|
+
console.error(` User: ${this.config.userEmail}`);
|
|
2578
|
+
console.error(` Password: ${"*".repeat(this.config.userPassword.length)}`);
|
|
2579
|
+
}
|
|
2580
|
+
const maskedArgs = args.map((arg, i) => {
|
|
2581
|
+
if (args[i - 1] === "--api-key") return "<API_KEY>";
|
|
2582
|
+
if (args[i - 1] === "--user-password") return "<PASSWORD>";
|
|
2583
|
+
return arg;
|
|
2584
|
+
});
|
|
2585
|
+
console.error(`[RAG Agent] Command: uvx ${maskedArgs.join(" ")}`);
|
|
2586
|
+
console.error(`[RAG Agent] Auth mode: ${this.authMode}`);
|
|
1980
2587
|
const transport = new StdioClientTransport({
|
|
1981
2588
|
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
|
-
],
|
|
2589
|
+
args,
|
|
1993
2590
|
env
|
|
1994
2591
|
});
|
|
1995
2592
|
await this.client.connect(transport);
|
|
@@ -2094,32 +2691,67 @@ function wrapAllRagAgentTools(ragClient) {
|
|
|
2094
2691
|
import RdsAiPkg from "@alicloud/rdsai20250507/dist/client.js";
|
|
2095
2692
|
import * as $RdsAi from "@alicloud/rdsai20250507";
|
|
2096
2693
|
var RdsAiClient = RdsAiPkg.default || RdsAiPkg;
|
|
2694
|
+
var RDSAI_DEFAULT_ENDPOINT = "rdsai.aliyuncs.com";
|
|
2695
|
+
var RDSAI_CENTER_UNIT_REGIONS = /* @__PURE__ */ new Set([
|
|
2696
|
+
"cn-beijing",
|
|
2697
|
+
"cn-wulanchabu",
|
|
2698
|
+
"cn-hangzhou",
|
|
2699
|
+
"cn-shanghai",
|
|
2700
|
+
"cn-shenzhen",
|
|
2701
|
+
"cn-guangzhou"
|
|
2702
|
+
]);
|
|
2703
|
+
function resolveRdsAiEndpoint(regionId, explicitEndpoint) {
|
|
2704
|
+
if (explicitEndpoint) {
|
|
2705
|
+
return explicitEndpoint;
|
|
2706
|
+
}
|
|
2707
|
+
if (!regionId || RDSAI_CENTER_UNIT_REGIONS.has(regionId)) {
|
|
2708
|
+
return RDSAI_DEFAULT_ENDPOINT;
|
|
2709
|
+
}
|
|
2710
|
+
return `rdsai.${regionId}.aliyuncs.com`;
|
|
2711
|
+
}
|
|
2097
2712
|
var AliyunRdsAiClient = class {
|
|
2098
|
-
|
|
2099
|
-
// RdsAi Client instance
|
|
2713
|
+
clientCache;
|
|
2100
2714
|
config;
|
|
2101
2715
|
constructor(config) {
|
|
2102
2716
|
this.config = config;
|
|
2717
|
+
this.clientCache = /* @__PURE__ */ new Map();
|
|
2718
|
+
const defaultEndpoint = resolveRdsAiEndpoint(config.regionId, config.endpoint);
|
|
2719
|
+
this.clientCache.set(defaultEndpoint, this.createClient(defaultEndpoint, config.regionId));
|
|
2720
|
+
}
|
|
2721
|
+
createClient(endpoint, regionId) {
|
|
2103
2722
|
const openApiConfig = {
|
|
2104
|
-
accessKeyId: config.accessKeyId,
|
|
2105
|
-
accessKeySecret: config.accessKeySecret,
|
|
2106
|
-
endpoint
|
|
2723
|
+
accessKeyId: this.config.accessKeyId,
|
|
2724
|
+
accessKeySecret: this.config.accessKeySecret,
|
|
2725
|
+
endpoint,
|
|
2726
|
+
regionId
|
|
2107
2727
|
};
|
|
2108
|
-
|
|
2728
|
+
return new RdsAiClient(openApiConfig);
|
|
2729
|
+
}
|
|
2730
|
+
getClientForRegion(regionId) {
|
|
2731
|
+
const effectiveRegionId = regionId || this.config.regionId;
|
|
2732
|
+
const endpoint = resolveRdsAiEndpoint(effectiveRegionId, this.config.endpoint);
|
|
2733
|
+
const cached = this.clientCache.get(endpoint);
|
|
2734
|
+
if (cached) {
|
|
2735
|
+
return cached;
|
|
2736
|
+
}
|
|
2737
|
+
const client = this.createClient(endpoint, effectiveRegionId);
|
|
2738
|
+
this.clientCache.set(endpoint, client);
|
|
2739
|
+
return client;
|
|
2109
2740
|
}
|
|
2110
2741
|
/**
|
|
2111
2742
|
* List all Supabase instances
|
|
2112
2743
|
*/
|
|
2113
2744
|
async listSupabaseInstances(options) {
|
|
2745
|
+
const regionId = options?.regionId || this.config.regionId;
|
|
2114
2746
|
const request = new $RdsAi.DescribeAppInstancesRequest({
|
|
2115
2747
|
appType: "supabase",
|
|
2116
|
-
regionId
|
|
2748
|
+
regionId,
|
|
2117
2749
|
DBInstanceName: options?.dbInstanceName,
|
|
2118
2750
|
pageSize: options?.pageSize || 50,
|
|
2119
2751
|
pageNumber: options?.pageNumber || 1
|
|
2120
2752
|
});
|
|
2121
2753
|
try {
|
|
2122
|
-
const response = await this.
|
|
2754
|
+
const response = await this.getClientForRegion(regionId).describeAppInstances(request);
|
|
2123
2755
|
if (!response.body) {
|
|
2124
2756
|
throw new Error("Empty response from DescribeAppInstances API");
|
|
2125
2757
|
}
|
|
@@ -2145,19 +2777,21 @@ var AliyunRdsAiClient = class {
|
|
|
2145
2777
|
instances
|
|
2146
2778
|
};
|
|
2147
2779
|
} catch (error) {
|
|
2148
|
-
console.error("Error calling DescribeAppInstances:", error);
|
|
2780
|
+
console.error("Error calling DescribeAppInstances:", JSON.stringify(error, null, 2));
|
|
2781
|
+
console.error("Error code:", error?.code);
|
|
2782
|
+
console.error("Error data:", error?.data);
|
|
2149
2783
|
throw new Error(`Failed to list Supabase instances: ${error instanceof Error ? error.message : String(error)}`);
|
|
2150
2784
|
}
|
|
2151
2785
|
}
|
|
2152
2786
|
/**
|
|
2153
2787
|
* Get authentication information for a specific instance
|
|
2154
2788
|
*/
|
|
2155
|
-
async getInstanceAuthInfo(instanceName) {
|
|
2789
|
+
async getInstanceAuthInfo(instanceName, regionId) {
|
|
2156
2790
|
const request = new $RdsAi.DescribeInstanceAuthInfoRequest({
|
|
2157
2791
|
instanceName
|
|
2158
2792
|
});
|
|
2159
2793
|
try {
|
|
2160
|
-
const response = await this.
|
|
2794
|
+
const response = await this.getClientForRegion(regionId || this.config.regionId).describeInstanceAuthInfo(request);
|
|
2161
2795
|
if (!response.body) {
|
|
2162
2796
|
throw new Error("Empty response from DescribeInstanceAuthInfo API");
|
|
2163
2797
|
}
|
|
@@ -2191,7 +2825,7 @@ var AliyunRdsAiClient = class {
|
|
|
2191
2825
|
if (!instance) {
|
|
2192
2826
|
throw new Error(`Instance ${instanceName} not found. Available instances: ${instancesResponse.instances.map((i) => i.instanceName).join(", ")}`);
|
|
2193
2827
|
}
|
|
2194
|
-
const authInfo = await this.getInstanceAuthInfo(instanceName);
|
|
2828
|
+
const authInfo = await this.getInstanceAuthInfo(instanceName, instance.regionId || queryRegion);
|
|
2195
2829
|
const connectionString = useVpc ? instance.vpcConnectionString : instance.publicConnectionString;
|
|
2196
2830
|
const supabaseUrl = connectionString.startsWith("http") ? connectionString : `http://${connectionString}`;
|
|
2197
2831
|
return {
|
|
@@ -2285,7 +2919,8 @@ import { z as z27 } from "zod";
|
|
|
2285
2919
|
import { zodToJsonSchema as zodToJsonSchema3 } from "zod-to-json-schema";
|
|
2286
2920
|
var ConnectInstanceInputZodSchema = z27.object({
|
|
2287
2921
|
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")
|
|
2922
|
+
use_vpc: z27.boolean().optional().default(false).describe("Whether to use VPC connection instead of public connection"),
|
|
2923
|
+
region_id: z27.string().optional().describe("Region ID of the instance (e.g., cn-hangzhou, cn-chengdu). Required when connecting to an instance in a different region than the default startup region.")
|
|
2289
2924
|
});
|
|
2290
2925
|
var ConnectInstanceOutputZodSchema = z27.object({
|
|
2291
2926
|
success: z27.boolean(),
|
|
@@ -2300,7 +2935,7 @@ async function execute3(input, aliyunClient, createSupabaseClient, setCurrentIns
|
|
|
2300
2935
|
}
|
|
2301
2936
|
log(`Connecting to Supabase instance: ${input.instance_name}...`, "info");
|
|
2302
2937
|
try {
|
|
2303
|
-
const credentials = await aliyunClient.getSupabaseCredentials(input.instance_name, input.use_vpc);
|
|
2938
|
+
const credentials = await aliyunClient.getSupabaseCredentials(input.instance_name, input.use_vpc, input.region_id);
|
|
2304
2939
|
log(`Retrieved credentials for instance ${input.instance_name}`, "info");
|
|
2305
2940
|
log(`Supabase URL: ${credentials.supabaseUrl}`, "info");
|
|
2306
2941
|
const supabaseClient = await createSupabaseClient(
|
|
@@ -2410,42 +3045,63 @@ var disconnect_instance_default = disconnectSupabaseInstanceTool;
|
|
|
2410
3045
|
// src/index.ts
|
|
2411
3046
|
async function main() {
|
|
2412
3047
|
const program = new Command();
|
|
2413
|
-
program.name("self-hosted-supabase-mcp").description("MCP Server for self-hosted Supabase instances").option("--url <url>", "Supabase project URL (
|
|
3048
|
+
program.name("self-hosted-supabase-mcp").description("MCP Server for self-hosted Supabase instances with three-tier authentication").option("--url <url>", "Supabase project URL (alias for --supabase-url)", process.env.SUPABASE_URL).option("--anon-key <key>", "Supabase anonymous key (alias for --supabase-anon-key)", process.env.SUPABASE_ANON_KEY).option("--service-key <key>", "Supabase service role key (alias for --supabase-service-role-key)", process.env.SUPABASE_SERVICE_ROLE_KEY).option("--supabase-url <url>", "Supabase project URL", process.env.SUPABASE_URL).option("--supabase-anon-key <key>", "Supabase anonymous key", process.env.SUPABASE_ANON_KEY).option("--supabase-service-role-key <key>", "Supabase service role key", process.env.SUPABASE_SERVICE_ROLE_KEY).option("--supabase-user-email <email>", "User email for Supabase Auth login", process.env.SUPABASE_USER_EMAIL).option("--supabase-user-password <password>", "User password for Supabase Auth login", process.env.SUPABASE_USER_PASSWORD).option("--db-url <url>", "Direct database connection string (optional, for pg fallback)", process.env.DATABASE_URL).option("--jwt-secret <secret>", "Supabase JWT secret (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", process.env.ENABLE_RAG_AGENT === "true").parse(process.argv);
|
|
2414
3049
|
const options = program.opts();
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
3050
|
+
let config;
|
|
3051
|
+
try {
|
|
3052
|
+
config = parseConfig(options);
|
|
3053
|
+
} catch (error) {
|
|
3054
|
+
if (error instanceof ConfigValidationError) {
|
|
3055
|
+
console.error(error.message);
|
|
3056
|
+
process.exit(1);
|
|
3057
|
+
}
|
|
3058
|
+
throw error;
|
|
2423
3059
|
}
|
|
2424
|
-
|
|
3060
|
+
const authMode = config.mode;
|
|
3061
|
+
const permissionLevel = getPermissionLevel(authMode);
|
|
3062
|
+
console.error(`Initializing Self-Hosted Supabase MCP Server in ${getAuthModeDescription(authMode)} mode...`);
|
|
2425
3063
|
try {
|
|
2426
3064
|
let aliyunClient = null;
|
|
2427
|
-
|
|
3065
|
+
let selfhostedClient = null;
|
|
3066
|
+
let userInfo;
|
|
3067
|
+
let currentInstanceName = null;
|
|
3068
|
+
let currentSupabaseClient = null;
|
|
3069
|
+
if (isAliyunConfig(config)) {
|
|
2428
3070
|
console.error("Alibaba Cloud mode enabled, initializing client...");
|
|
2429
3071
|
aliyunClient = createAliyunClient({
|
|
2430
|
-
accessKeyId:
|
|
2431
|
-
accessKeySecret:
|
|
2432
|
-
regionId:
|
|
3072
|
+
accessKeyId: config.accessKeyId,
|
|
3073
|
+
accessKeySecret: config.accessKeySecret,
|
|
3074
|
+
regionId: config.regionId
|
|
2433
3075
|
});
|
|
2434
3076
|
console.error("Alibaba Cloud client initialized successfully.");
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
databaseUrl: options.dbUrl,
|
|
2443
|
-
jwtSecret: options.jwtSecret
|
|
3077
|
+
} else if (isSingleInstanceUserConfig(config)) {
|
|
3078
|
+
console.error("Single Instance User mode enabled, authenticating...");
|
|
3079
|
+
selfhostedClient = await SelfhostedSupabaseClient.createWithUserAuth({
|
|
3080
|
+
supabaseUrl: config.supabaseUrl,
|
|
3081
|
+
supabaseAnonKey: config.supabaseAnonKey,
|
|
3082
|
+
userEmail: config.userEmail,
|
|
3083
|
+
userPassword: config.userPassword
|
|
2444
3084
|
});
|
|
2445
|
-
|
|
3085
|
+
currentSupabaseClient = selfhostedClient;
|
|
3086
|
+
const userId = selfhostedClient.getUserId();
|
|
3087
|
+
const email = selfhostedClient.getUserEmail();
|
|
3088
|
+
if (userId && email) {
|
|
3089
|
+
userInfo = { userId, email };
|
|
3090
|
+
}
|
|
3091
|
+
console.error(`Authenticated as user: ${email} (ID: ${userId})`);
|
|
3092
|
+
console.error("Note: Access is restricted by Row Level Security (RLS) policies.");
|
|
3093
|
+
} else if (isSingleInstanceAdminConfig(config)) {
|
|
3094
|
+
console.error("Single Instance Admin mode enabled, initializing client...");
|
|
3095
|
+
selfhostedClient = await SelfhostedSupabaseClient.create({
|
|
3096
|
+
supabaseUrl: config.supabaseUrl,
|
|
3097
|
+
supabaseAnonKey: config.supabaseAnonKey,
|
|
3098
|
+
supabaseServiceRoleKey: config.supabaseServiceRoleKey,
|
|
3099
|
+
databaseUrl: config.databaseUrl,
|
|
3100
|
+
jwtSecret: config.jwtSecret
|
|
3101
|
+
}, "admin" /* SINGLE_INSTANCE_ADMIN */);
|
|
3102
|
+
currentSupabaseClient = selfhostedClient;
|
|
3103
|
+
console.error("Supabase client initialized successfully (admin mode).");
|
|
2446
3104
|
}
|
|
2447
|
-
let currentInstanceName = null;
|
|
2448
|
-
let currentSupabaseClient = selfhostedClient;
|
|
2449
3105
|
const getCurrentInstanceName = () => currentInstanceName;
|
|
2450
3106
|
const setCurrentInstance = (instanceName, client) => {
|
|
2451
3107
|
currentInstanceName = instanceName;
|
|
@@ -2462,25 +3118,53 @@ async function main() {
|
|
|
2462
3118
|
supabaseServiceRoleKey: serviceKey,
|
|
2463
3119
|
databaseUrl: options.dbUrl,
|
|
2464
3120
|
jwtSecret
|
|
2465
|
-
});
|
|
3121
|
+
}, "aliyun" /* ALIYUN_MULTI_INSTANCE */);
|
|
2466
3122
|
};
|
|
2467
3123
|
let ragAgentClient = null;
|
|
2468
3124
|
const initializeRagAgent = async (supabaseClient) => {
|
|
2469
|
-
if (!
|
|
3125
|
+
if (!config.enableRagAgent) {
|
|
2470
3126
|
return null;
|
|
2471
3127
|
}
|
|
2472
3128
|
try {
|
|
2473
3129
|
console.error("Initializing RAG Agent client...");
|
|
2474
|
-
const
|
|
2475
|
-
const
|
|
2476
|
-
const urlObj = new URL(urlToUse);
|
|
2477
|
-
const host = urlObj.hostname;
|
|
3130
|
+
const supabaseUrl = supabaseClient.getSupabaseUrl();
|
|
3131
|
+
const urlObj = new URL(supabaseUrl);
|
|
2478
3132
|
const port = urlObj.port ? parseInt(urlObj.port, 10) : urlObj.protocol === "https:" ? 443 : 80;
|
|
2479
|
-
const
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
3133
|
+
const hostUrl = `${urlObj.protocol}//${urlObj.hostname}:${port}`;
|
|
3134
|
+
console.error(`RAG Agent host: ${hostUrl}`);
|
|
3135
|
+
let client;
|
|
3136
|
+
if (isSingleInstanceUserConfig(config)) {
|
|
3137
|
+
const anonKey = supabaseClient.getAnonKey();
|
|
3138
|
+
console.error("[initializeRagAgent] Tier 3: User mode detected");
|
|
3139
|
+
console.error(`[initializeRagAgent] Using anon key for JWT auth`);
|
|
3140
|
+
console.error(`[initializeRagAgent] Anon key preview: ${anonKey.substring(0, 20)}...`);
|
|
3141
|
+
console.error(`[initializeRagAgent] User email: ${config.userEmail}`);
|
|
3142
|
+
client = await createRagAgentClient({
|
|
3143
|
+
host: hostUrl,
|
|
3144
|
+
apiKey: anonKey,
|
|
3145
|
+
userEmail: config.userEmail,
|
|
3146
|
+
userPassword: config.userPassword
|
|
3147
|
+
});
|
|
3148
|
+
} else {
|
|
3149
|
+
const serviceKey = supabaseClient.getServiceRoleKey();
|
|
3150
|
+
const anonKey = supabaseClient.getAnonKey();
|
|
3151
|
+
console.error("[initializeRagAgent] Tier 1/2: Admin mode detected");
|
|
3152
|
+
console.error(`[initializeRagAgent] Service key available: ${!!serviceKey}`);
|
|
3153
|
+
if (serviceKey) {
|
|
3154
|
+
console.error(`[initializeRagAgent] Service key preview: ${serviceKey.substring(0, 20)}...`);
|
|
3155
|
+
}
|
|
3156
|
+
console.error(`[initializeRagAgent] Anon key preview: ${anonKey.substring(0, 20)}...`);
|
|
3157
|
+
if (!serviceKey) {
|
|
3158
|
+
console.error("[initializeRagAgent] WARNING: Service role key not available!");
|
|
3159
|
+
console.error("[initializeRagAgent] This may cause 401 errors if RAG Agent requires admin access");
|
|
3160
|
+
}
|
|
3161
|
+
const apiKey = serviceKey || anonKey;
|
|
3162
|
+
console.error(`[initializeRagAgent] Using: ${serviceKey ? "service_role key" : "anon key (fallback)"}`);
|
|
3163
|
+
client = await createRagAgentClient({
|
|
3164
|
+
host: hostUrl,
|
|
3165
|
+
apiKey
|
|
3166
|
+
});
|
|
3167
|
+
}
|
|
2484
3168
|
console.error("RAG Agent client initialized successfully.");
|
|
2485
3169
|
return client;
|
|
2486
3170
|
} catch (error) {
|
|
@@ -2489,14 +3173,14 @@ async function main() {
|
|
|
2489
3173
|
return null;
|
|
2490
3174
|
}
|
|
2491
3175
|
};
|
|
2492
|
-
if (
|
|
2493
|
-
ragAgentClient = await initializeRagAgent(
|
|
2494
|
-
} else if (
|
|
3176
|
+
if (config.enableRagAgent && currentSupabaseClient) {
|
|
3177
|
+
ragAgentClient = await initializeRagAgent(currentSupabaseClient);
|
|
3178
|
+
} else if (config.enableRagAgent && isAliyunConfig(config)) {
|
|
2495
3179
|
console.error("RAG Agent integration enabled.");
|
|
2496
3180
|
console.error("RAG Agent tools will be available after connecting to a Supabase instance.");
|
|
2497
3181
|
}
|
|
2498
3182
|
const wrappedAliyunTools = {};
|
|
2499
|
-
if (
|
|
3183
|
+
if (isAliyunConfig(config)) {
|
|
2500
3184
|
wrappedAliyunTools[list_instances_default.name] = {
|
|
2501
3185
|
...list_instances_default,
|
|
2502
3186
|
execute: async (input, context) => {
|
|
@@ -2517,7 +3201,7 @@ async function main() {
|
|
|
2517
3201
|
setCurrentInstance,
|
|
2518
3202
|
context.log
|
|
2519
3203
|
);
|
|
2520
|
-
if (result.success && currentSupabaseClient &&
|
|
3204
|
+
if (result.success && currentSupabaseClient && config.enableRagAgent && !ragAgentClient) {
|
|
2521
3205
|
ragAgentClient = await initializeRagAgent(currentSupabaseClient);
|
|
2522
3206
|
}
|
|
2523
3207
|
return result;
|
|
@@ -2573,17 +3257,16 @@ async function main() {
|
|
|
2573
3257
|
[install_execute_sql_function_default.name]: install_execute_sql_function_default,
|
|
2574
3258
|
[searchDocsTool.name]: searchDocsTool
|
|
2575
3259
|
};
|
|
2576
|
-
if (
|
|
3260
|
+
if (config.enableRagAgent) {
|
|
2577
3261
|
if (ragAgentClient) {
|
|
2578
3262
|
const ragTools = wrapAllRagAgentTools(ragAgentClient);
|
|
2579
3263
|
Object.assign(availableTools, ragTools);
|
|
2580
3264
|
console.error(`Added ${Object.keys(ragTools).length} RAG Agent tools to available tools.`);
|
|
2581
|
-
} else if (
|
|
3265
|
+
} else if (isAliyunConfig(config)) {
|
|
2582
3266
|
const dummyRagTools = await (async () => {
|
|
2583
3267
|
try {
|
|
2584
3268
|
const tempClient = await createRagAgentClient({
|
|
2585
|
-
host: "localhost",
|
|
2586
|
-
port: 80,
|
|
3269
|
+
host: "http://localhost:80",
|
|
2587
3270
|
apiKey: "dummy"
|
|
2588
3271
|
});
|
|
2589
3272
|
const tools = wrapAllRagAgentTools(tempClient);
|
|
@@ -2609,8 +3292,16 @@ async function main() {
|
|
|
2609
3292
|
console.error(`Added ${Object.keys(dummyRagTools).length} RAG Agent tools (will be initialized after connection).`);
|
|
2610
3293
|
}
|
|
2611
3294
|
}
|
|
2612
|
-
|
|
2613
|
-
const
|
|
3295
|
+
const permissionFilteredTools = {};
|
|
3296
|
+
for (const [toolName, tool] of Object.entries(availableTools)) {
|
|
3297
|
+
if (isToolAllowed(toolName, permissionLevel)) {
|
|
3298
|
+
permissionFilteredTools[toolName] = tool;
|
|
3299
|
+
} else {
|
|
3300
|
+
console.error(`Tool ${toolName} disabled (not allowed for ${permissionLevel} permission level).`);
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
let registeredTools = { ...permissionFilteredTools };
|
|
3304
|
+
const toolsConfigPath = config.toolsConfig;
|
|
2614
3305
|
let enabledToolNames = null;
|
|
2615
3306
|
if (toolsConfigPath) {
|
|
2616
3307
|
try {
|
|
@@ -2631,27 +3322,31 @@ async function main() {
|
|
|
2631
3322
|
enabledToolNames = new Set(toolNames.map((name) => name.trim()).filter((name) => name.length > 0));
|
|
2632
3323
|
} catch (error) {
|
|
2633
3324
|
console.error(`Error loading or parsing tool config file '${toolsConfigPath}':`, error instanceof Error ? error.message : String(error));
|
|
2634
|
-
console.error("Falling back to enabling all tools due to config error.");
|
|
3325
|
+
console.error("Falling back to enabling all permission-allowed tools due to config error.");
|
|
2635
3326
|
enabledToolNames = null;
|
|
2636
3327
|
}
|
|
2637
3328
|
}
|
|
2638
3329
|
if (enabledToolNames !== null) {
|
|
2639
3330
|
console.error(`Whitelisting tools based on config: ${Array.from(enabledToolNames).join(", ")}`);
|
|
2640
3331
|
registeredTools = {};
|
|
2641
|
-
for (const toolName in
|
|
3332
|
+
for (const toolName in permissionFilteredTools) {
|
|
2642
3333
|
if (enabledToolNames.has(toolName)) {
|
|
2643
|
-
registeredTools[toolName] =
|
|
3334
|
+
registeredTools[toolName] = permissionFilteredTools[toolName];
|
|
2644
3335
|
} else {
|
|
2645
3336
|
console.error(`Tool ${toolName} disabled (not in config whitelist).`);
|
|
2646
3337
|
}
|
|
2647
3338
|
}
|
|
2648
3339
|
for (const requestedName of enabledToolNames) {
|
|
2649
|
-
if (!
|
|
2650
|
-
|
|
3340
|
+
if (!permissionFilteredTools[requestedName]) {
|
|
3341
|
+
if (availableTools[requestedName]) {
|
|
3342
|
+
console.warn(`Warning: Tool "${requestedName}" is not available for the current permission level.`);
|
|
3343
|
+
} else {
|
|
3344
|
+
console.warn(`Warning: Tool "${requestedName}" specified in config file not found.`);
|
|
3345
|
+
}
|
|
2651
3346
|
}
|
|
2652
3347
|
}
|
|
2653
3348
|
} else {
|
|
2654
|
-
console.error("No valid --tools-config specified or error loading config, enabling all
|
|
3349
|
+
console.error("No valid --tools-config specified or error loading config, enabling all permission-allowed tools.");
|
|
2655
3350
|
}
|
|
2656
3351
|
const capabilitiesTools = {};
|
|
2657
3352
|
for (const tool of Object.values(registeredTools)) {
|
|
@@ -2666,6 +3361,7 @@ async function main() {
|
|
|
2666
3361
|
};
|
|
2667
3362
|
}
|
|
2668
3363
|
const capabilities = { tools: capabilitiesTools };
|
|
3364
|
+
console.error(`Registered ${Object.keys(registeredTools).length} tools.`);
|
|
2669
3365
|
console.error("Initializing MCP Server...");
|
|
2670
3366
|
const server = new Server(
|
|
2671
3367
|
{
|
|
@@ -2684,6 +3380,10 @@ async function main() {
|
|
|
2684
3380
|
const tool = registeredTools[toolName];
|
|
2685
3381
|
if (!tool) {
|
|
2686
3382
|
if (availableTools[toolName]) {
|
|
3383
|
+
const message = getToolNotAllowedMessage(toolName, permissionLevel);
|
|
3384
|
+
throw new McpError(ErrorCode.MethodNotFound, message);
|
|
3385
|
+
}
|
|
3386
|
+
if (permissionFilteredTools[toolName]) {
|
|
2687
3387
|
throw new McpError(ErrorCode.MethodNotFound, `Tool "${toolName}" is available but not enabled by the current server configuration.`);
|
|
2688
3388
|
}
|
|
2689
3389
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${toolName}`);
|
|
@@ -2709,7 +3409,7 @@ async function main() {
|
|
|
2709
3409
|
"get_current_supabase_instance",
|
|
2710
3410
|
"disconnect_supabase_instance"
|
|
2711
3411
|
].includes(toolName);
|
|
2712
|
-
if (
|
|
3412
|
+
if (isAliyunConfig(config) && !isAliyunManagementTool && !currentSupabaseClient) {
|
|
2713
3413
|
throw new McpError(
|
|
2714
3414
|
ErrorCode.InvalidRequest,
|
|
2715
3415
|
"Not connected to any Supabase instance. Please use connect_to_supabase_instance tool first."
|
|
@@ -2717,7 +3417,10 @@ async function main() {
|
|
|
2717
3417
|
}
|
|
2718
3418
|
const context = {
|
|
2719
3419
|
selfhostedClient: currentSupabaseClient,
|
|
2720
|
-
workspacePath:
|
|
3420
|
+
workspacePath: config.workspacePath || process.cwd(),
|
|
3421
|
+
authMode,
|
|
3422
|
+
permissionLevel,
|
|
3423
|
+
userInfo,
|
|
2721
3424
|
log: (message, level = "info") => {
|
|
2722
3425
|
console.error(`[${level.toUpperCase()}] ${message}`);
|
|
2723
3426
|
}
|
|
@@ -2756,6 +3459,9 @@ async function main() {
|
|
|
2756
3459
|
if (ragAgentClient) {
|
|
2757
3460
|
await ragAgentClient.close();
|
|
2758
3461
|
}
|
|
3462
|
+
if (selfhostedClient) {
|
|
3463
|
+
await selfhostedClient.destroy();
|
|
3464
|
+
}
|
|
2759
3465
|
};
|
|
2760
3466
|
process.on("SIGINT", async () => {
|
|
2761
3467
|
await cleanup();
|