@universal-mcp-toolkit/server-supabase 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/json",
3
+ "name": "supabase",
4
+ "title": "Supabase MCP Server",
5
+ "description": "Database, auth, storage, and function tools for Supabase.",
6
+ "version": "0.1.0",
7
+ "transports": [
8
+ "stdio",
9
+ "http+sse"
10
+ ],
11
+ "authentication": {
12
+ "mode": "environment-variables",
13
+ "required": [
14
+ "SUPABASE_URL",
15
+ "SUPABASE_KEY"
16
+ ]
17
+ },
18
+ "capabilities": {
19
+ "tools": true,
20
+ "resources": true,
21
+ "prompts": true
22
+ },
23
+ "packageName": "@universal-mcp-toolkit/server-supabase",
24
+ "homepage": "https://github.com/universal-mcp-toolkit/universal-mcp-toolkit#readme",
25
+ "tools": [
26
+ "list-tables",
27
+ "query-table",
28
+ "list-storage-buckets"
29
+ ],
30
+ "resources": [
31
+ "project-overview"
32
+ ],
33
+ "prompts": [
34
+ "incident-response"
35
+ ]
36
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 universal-mcp-toolkit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,77 @@
1
+ import * as _universal_mcp_toolkit_core from '@universal-mcp-toolkit/core';
2
+ import { ToolkitServer, ToolkitServerMetadata } from '@universal-mcp-toolkit/core';
3
+ import { z } from 'zod';
4
+
5
+ type JsonPrimitive = boolean | number | string | null;
6
+ interface JsonObject {
7
+ [key: string]: JsonValue;
8
+ }
9
+ type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
10
+ declare const supabaseFilterValueSchema: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>]>;
11
+ declare const supabaseEnvShape: {
12
+ SUPABASE_URL: z.ZodString;
13
+ SUPABASE_KEY: z.ZodString;
14
+ SUPABASE_SCHEMA: z.ZodDefault<z.ZodString>;
15
+ SUPABASE_MAX_ROWS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
16
+ SUPABASE_BUCKET_LIMIT: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
17
+ };
18
+ type SupabaseEnv = z.infer<z.ZodObject<typeof supabaseEnvShape>>;
19
+ type SupabaseFilterValue = z.infer<typeof supabaseFilterValueSchema>;
20
+ interface SupabaseFilter {
21
+ column: string;
22
+ operator: "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "like" | "ilike" | "in" | "is";
23
+ value: SupabaseFilterValue;
24
+ }
25
+ interface SupabaseTableSummary {
26
+ schema: string;
27
+ name: string;
28
+ type: string;
29
+ }
30
+ interface SupabaseBucketSummary {
31
+ id: string;
32
+ name: string;
33
+ public: boolean;
34
+ fileSizeLimit: number | null;
35
+ allowedMimeTypes: string[] | null;
36
+ createdAt: string | null;
37
+ }
38
+ interface SupabaseClient {
39
+ listTables(input: {
40
+ schema: string;
41
+ limit: number;
42
+ }): Promise<{
43
+ schema: string;
44
+ tables: SupabaseTableSummary[];
45
+ }>;
46
+ queryTable(input: {
47
+ schema: string;
48
+ table: string;
49
+ select: string;
50
+ filters: readonly SupabaseFilter[];
51
+ limit: number;
52
+ orderBy?: string;
53
+ ascending: boolean;
54
+ }): Promise<{
55
+ schema: string;
56
+ table: string;
57
+ rows: JsonObject[];
58
+ }>;
59
+ listStorageBuckets(input: {
60
+ limit: number;
61
+ }): Promise<SupabaseBucketSummary[]>;
62
+ }
63
+ declare const metadata: ToolkitServerMetadata;
64
+ declare const serverCard: _universal_mcp_toolkit_core.ToolkitServerCard;
65
+ declare class SupabaseServer extends ToolkitServer {
66
+ private readonly env;
67
+ private readonly client;
68
+ constructor(env: SupabaseEnv, client: SupabaseClient);
69
+ }
70
+ interface CreateSupabaseServerOptions {
71
+ env?: SupabaseEnv;
72
+ client?: SupabaseClient;
73
+ }
74
+ declare function createServer(options?: CreateSupabaseServerOptions): SupabaseServer;
75
+ declare function main(argv?: readonly string[]): Promise<void>;
76
+
77
+ export { type CreateSupabaseServerOptions, type JsonObject, type JsonValue, type SupabaseBucketSummary, type SupabaseClient, type SupabaseEnv, type SupabaseFilter, type SupabaseFilterValue, SupabaseServer, type SupabaseTableSummary, createServer, main, metadata, serverCard };
package/dist/index.js ADDED
@@ -0,0 +1,394 @@
1
+ // src/index.ts
2
+ import { pathToFileURL } from "url";
3
+ import {
4
+ HttpServiceClient,
5
+ ToolkitServer,
6
+ ValidationError,
7
+ createLogger,
8
+ createServerCard,
9
+ defineTool,
10
+ loadEnv,
11
+ parseRuntimeOptions,
12
+ runToolkitServer
13
+ } from "@universal-mcp-toolkit/core";
14
+ import { z } from "zod";
15
+ var jsonValueSchema = z.lazy(
16
+ () => z.union([z.string(), z.number().finite(), z.boolean(), z.null(), z.array(jsonValueSchema), z.record(z.string(), jsonValueSchema)])
17
+ );
18
+ var jsonObjectSchema = z.record(z.string(), jsonValueSchema);
19
+ var supabaseFilterValueSchema = z.union([
20
+ z.string(),
21
+ z.number().finite(),
22
+ z.boolean(),
23
+ z.null(),
24
+ z.array(z.union([z.string(), z.number().finite(), z.boolean(), z.null()]))
25
+ ]);
26
+ var supabaseEnvShape = {
27
+ SUPABASE_URL: z.string().url(),
28
+ SUPABASE_KEY: z.string().min(1),
29
+ SUPABASE_SCHEMA: z.string().min(1).default("public"),
30
+ SUPABASE_MAX_ROWS: z.coerce.number().int().positive().max(200).default(50),
31
+ SUPABASE_BUCKET_LIMIT: z.coerce.number().int().positive().max(100).default(25)
32
+ };
33
+ var TOOL_NAMES = ["list-storage-buckets", "list-tables", "query-table"];
34
+ var RESOURCE_NAMES = ["project-overview"];
35
+ var PROMPT_NAMES = ["incident-response"];
36
+ var tableRowSchema = z.object({
37
+ table_schema: z.string(),
38
+ table_name: z.string(),
39
+ table_type: z.string()
40
+ }).passthrough();
41
+ var listTablesResponseSchema = z.array(tableRowSchema);
42
+ var queryRowsResponseSchema = z.array(jsonObjectSchema);
43
+ var bucketRowSchema = z.object({
44
+ id: z.string(),
45
+ name: z.string(),
46
+ public: z.boolean().nullable().optional(),
47
+ file_size_limit: z.number().int().nonnegative().nullable().optional(),
48
+ allowed_mime_types: z.array(z.string()).nullable().optional(),
49
+ created_at: z.string().nullable().optional()
50
+ }).passthrough();
51
+ var listBucketsResponseSchema = z.array(bucketRowSchema);
52
+ var metadata = {
53
+ id: "supabase",
54
+ title: "Supabase MCP Server",
55
+ description: "Database, auth, storage, and function tools for Supabase.",
56
+ version: "0.1.0",
57
+ packageName: "@universal-mcp-toolkit/server-supabase",
58
+ homepage: "https://github.com/universal-mcp-toolkit/universal-mcp-toolkit#readme",
59
+ repositoryUrl: "https://github.com/universal-mcp-toolkit/universal-mcp-toolkit",
60
+ envVarNames: ["SUPABASE_URL", "SUPABASE_KEY"],
61
+ transports: ["stdio", "sse"],
62
+ toolNames: TOOL_NAMES,
63
+ resourceNames: RESOURCE_NAMES,
64
+ promptNames: PROMPT_NAMES
65
+ };
66
+ var serverCard = createServerCard(metadata);
67
+ function getProjectReference(urlValue) {
68
+ try {
69
+ const url = new URL(urlValue);
70
+ return url.hostname.split(".")[0] ?? url.hostname;
71
+ } catch {
72
+ return urlValue;
73
+ }
74
+ }
75
+ function formatScalarFilterValue(value) {
76
+ if (value === null) {
77
+ return "null";
78
+ }
79
+ if (typeof value === "boolean") {
80
+ return value ? "true" : "false";
81
+ }
82
+ return String(value);
83
+ }
84
+ function formatInMember(value) {
85
+ if (value === null) {
86
+ return "null";
87
+ }
88
+ if (typeof value === "string") {
89
+ const escaped = value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"');
90
+ return `"${escaped}"`;
91
+ }
92
+ if (typeof value === "boolean") {
93
+ return value ? "true" : "false";
94
+ }
95
+ return String(value);
96
+ }
97
+ function formatFilterExpression(filter) {
98
+ if (filter.operator === "in") {
99
+ if (!Array.isArray(filter.value)) {
100
+ throw new ValidationError("Supabase 'in' filters require an array value.");
101
+ }
102
+ return `in.(${filter.value.map((value) => formatInMember(value)).join(",")})`;
103
+ }
104
+ if (Array.isArray(filter.value)) {
105
+ throw new ValidationError(`Supabase filter operator '${filter.operator}' does not accept array values.`);
106
+ }
107
+ return `${filter.operator}.${formatScalarFilterValue(filter.value)}`;
108
+ }
109
+ var SupabaseRestClient = class extends HttpServiceClient {
110
+ constructor(env) {
111
+ super({
112
+ serviceName: "Supabase",
113
+ baseUrl: env.SUPABASE_URL,
114
+ logger: createLogger({ name: metadata.packageName }),
115
+ defaultHeaders: () => ({
116
+ apikey: env.SUPABASE_KEY,
117
+ authorization: `Bearer ${env.SUPABASE_KEY}`
118
+ })
119
+ });
120
+ this.env = env;
121
+ }
122
+ async listTables(input) {
123
+ const rows = await this.getJson("/rest/v1/tables", listTablesResponseSchema, {
124
+ headers: {
125
+ "accept-profile": "information_schema"
126
+ },
127
+ query: {
128
+ select: "table_schema,table_name,table_type",
129
+ table_schema: `eq.${input.schema}`,
130
+ order: "table_name.asc",
131
+ limit: input.limit
132
+ }
133
+ });
134
+ return {
135
+ schema: input.schema,
136
+ tables: rows.map((row) => ({
137
+ schema: row.table_schema,
138
+ name: row.table_name,
139
+ type: row.table_type
140
+ }))
141
+ };
142
+ }
143
+ async queryTable(input) {
144
+ const query = {
145
+ select: input.select,
146
+ limit: input.limit
147
+ };
148
+ if (input.orderBy) {
149
+ query.order = `${input.orderBy}.${input.ascending ? "asc" : "desc"}`;
150
+ }
151
+ for (const filter of input.filters) {
152
+ query[filter.column] = formatFilterExpression(filter);
153
+ }
154
+ const rows = await this.getJson(`/rest/v1/${encodeURIComponent(input.table)}`, queryRowsResponseSchema, {
155
+ headers: {
156
+ "accept-profile": input.schema
157
+ },
158
+ query
159
+ });
160
+ return {
161
+ schema: input.schema,
162
+ table: input.table,
163
+ rows
164
+ };
165
+ }
166
+ async listStorageBuckets(input) {
167
+ const rows = await this.getJson("/storage/v1/bucket", listBucketsResponseSchema);
168
+ return rows.slice(0, input.limit).map((row) => ({
169
+ id: row.id,
170
+ name: row.name,
171
+ public: row.public ?? false,
172
+ fileSizeLimit: row.file_size_limit ?? null,
173
+ allowedMimeTypes: row.allowed_mime_types ?? null,
174
+ createdAt: row.created_at ?? null
175
+ }));
176
+ }
177
+ };
178
+ var SupabaseServer = class extends ToolkitServer {
179
+ constructor(env, client) {
180
+ super(metadata);
181
+ this.env = env;
182
+ this.client = client;
183
+ this.registerTool(
184
+ defineTool({
185
+ name: "list-tables",
186
+ title: "List Supabase tables",
187
+ description: "List tables exposed through the Supabase REST API for a given schema.",
188
+ inputSchema: {
189
+ schema: z.string().min(1).default(this.env.SUPABASE_SCHEMA),
190
+ limit: z.number().int().positive().max(100).default(25)
191
+ },
192
+ outputSchema: {
193
+ schema: z.string(),
194
+ tableCount: z.number().int().nonnegative(),
195
+ tables: z.array(
196
+ z.object({
197
+ schema: z.string(),
198
+ name: z.string(),
199
+ type: z.string()
200
+ })
201
+ )
202
+ },
203
+ handler: async ({ limit, schema }, context) => {
204
+ const cappedLimit = Math.min(limit, this.env.SUPABASE_MAX_ROWS);
205
+ await context.log("info", `Listing Supabase tables for schema '${schema}'.`);
206
+ const result = await this.client.listTables({
207
+ schema,
208
+ limit: cappedLimit
209
+ });
210
+ return {
211
+ schema: result.schema,
212
+ tableCount: result.tables.length,
213
+ tables: result.tables
214
+ };
215
+ },
216
+ renderText: (output) => `${output.tableCount} tables found in Supabase schema ${output.schema}.`
217
+ })
218
+ );
219
+ this.registerTool(
220
+ defineTool({
221
+ name: "query-table",
222
+ title: "Query Supabase table",
223
+ description: "Query a Supabase table with select, filters, ordering, and row limits.",
224
+ inputSchema: {
225
+ schema: z.string().min(1).default(this.env.SUPABASE_SCHEMA),
226
+ table: z.string().min(1),
227
+ select: z.string().min(1).default("*"),
228
+ filters: z.array(
229
+ z.object({
230
+ column: z.string().min(1),
231
+ operator: z.enum(["eq", "neq", "gt", "gte", "lt", "lte", "like", "ilike", "in", "is"]),
232
+ value: supabaseFilterValueSchema
233
+ })
234
+ ).default([]),
235
+ limit: z.number().int().positive().max(200).default(25),
236
+ orderBy: z.string().min(1).optional(),
237
+ ascending: z.boolean().default(true)
238
+ },
239
+ outputSchema: {
240
+ schema: z.string(),
241
+ table: z.string(),
242
+ rowCount: z.number().int().nonnegative(),
243
+ rows: z.array(jsonObjectSchema)
244
+ },
245
+ handler: async ({ ascending, filters, limit, orderBy, schema, select, table }, context) => {
246
+ const cappedLimit = Math.min(limit, this.env.SUPABASE_MAX_ROWS);
247
+ await context.log("info", `Querying Supabase table ${schema}.${table}.`);
248
+ const request = {
249
+ schema,
250
+ table,
251
+ select,
252
+ filters,
253
+ limit: cappedLimit,
254
+ ascending
255
+ };
256
+ if (orderBy) {
257
+ request.orderBy = orderBy;
258
+ }
259
+ const result = await this.client.queryTable(request);
260
+ return {
261
+ schema: result.schema,
262
+ table: result.table,
263
+ rowCount: result.rows.length,
264
+ rows: result.rows
265
+ };
266
+ },
267
+ renderText: (output) => `${output.rowCount} row(s) returned from ${output.schema}.${output.table}.`
268
+ })
269
+ );
270
+ this.registerTool(
271
+ defineTool({
272
+ name: "list-storage-buckets",
273
+ title: "List Supabase storage buckets",
274
+ description: "List storage buckets available in the configured Supabase project.",
275
+ inputSchema: {
276
+ limit: z.number().int().positive().max(100).default(25)
277
+ },
278
+ outputSchema: {
279
+ bucketCount: z.number().int().nonnegative(),
280
+ buckets: z.array(
281
+ z.object({
282
+ id: z.string(),
283
+ name: z.string(),
284
+ public: z.boolean(),
285
+ fileSizeLimit: z.number().int().nonnegative().nullable(),
286
+ allowedMimeTypes: z.array(z.string()).nullable(),
287
+ createdAt: z.string().nullable()
288
+ })
289
+ )
290
+ },
291
+ handler: async ({ limit }, context) => {
292
+ const cappedLimit = Math.min(limit, this.env.SUPABASE_BUCKET_LIMIT);
293
+ await context.log("info", "Listing Supabase storage buckets.");
294
+ const buckets = await this.client.listStorageBuckets({ limit: cappedLimit });
295
+ return {
296
+ bucketCount: buckets.length,
297
+ buckets
298
+ };
299
+ },
300
+ renderText: (output) => `${output.bucketCount} storage bucket(s) available in Supabase.`
301
+ })
302
+ );
303
+ this.registerStaticResource(
304
+ "project-overview",
305
+ "supabase://project-overview",
306
+ {
307
+ title: "Supabase project overview",
308
+ description: "Project-level context including schema and storage bucket visibility.",
309
+ mimeType: "application/json"
310
+ },
311
+ async () => {
312
+ const tables = await this.client.listTables({
313
+ schema: this.env.SUPABASE_SCHEMA,
314
+ limit: Math.min(this.env.SUPABASE_MAX_ROWS, 20)
315
+ });
316
+ const buckets = await this.client.listStorageBuckets({
317
+ limit: Math.min(this.env.SUPABASE_BUCKET_LIMIT, 20)
318
+ });
319
+ return this.createJsonResource("supabase://project-overview", {
320
+ projectRef: getProjectReference(this.env.SUPABASE_URL),
321
+ url: this.env.SUPABASE_URL,
322
+ schema: this.env.SUPABASE_SCHEMA,
323
+ maxRows: this.env.SUPABASE_MAX_ROWS,
324
+ tables: tables.tables,
325
+ buckets
326
+ });
327
+ }
328
+ );
329
+ this.registerPrompt(
330
+ "incident-response",
331
+ {
332
+ title: "Supabase incident response",
333
+ description: "Prepare a structured response plan for a Supabase production incident.",
334
+ argsSchema: {
335
+ incident: z.string().min(1),
336
+ impactedArea: z.string().min(1).optional(),
337
+ recentErrors: z.string().min(1).optional()
338
+ }
339
+ },
340
+ async ({ impactedArea, incident, recentErrors }) => this.createTextPrompt(
341
+ [
342
+ "Prepare a Supabase incident response plan.",
343
+ `Incident: ${incident}`,
344
+ `Impacted area: ${impactedArea ?? "unknown"}`,
345
+ `Recent errors: ${recentErrors ?? "none provided"}`,
346
+ `Primary schema: ${this.env.SUPABASE_SCHEMA}`,
347
+ `Project ref: ${getProjectReference(this.env.SUPABASE_URL)}`,
348
+ "Use the Supabase tools to:",
349
+ "- confirm which tables and buckets are involved",
350
+ "- capture a bounded sample of affected rows",
351
+ "- distinguish schema, data, and storage symptoms",
352
+ "- document the safest immediate mitigation and follow-up checks"
353
+ ].join("\n")
354
+ )
355
+ );
356
+ }
357
+ };
358
+ function createServer(options = {}) {
359
+ const env = options.env ?? loadEnv(supabaseEnvShape);
360
+ const client = options.client ?? new SupabaseRestClient(env);
361
+ return new SupabaseServer(env, client);
362
+ }
363
+ async function main(argv = process.argv.slice(2)) {
364
+ const env = loadEnv(supabaseEnvShape);
365
+ const runtimeOptions = parseRuntimeOptions(argv);
366
+ await runToolkitServer(
367
+ {
368
+ createServer: () => createServer({ env }),
369
+ serverCard
370
+ },
371
+ runtimeOptions
372
+ );
373
+ }
374
+ function isMainModule() {
375
+ const entryPoint = process.argv[1];
376
+ if (!entryPoint) {
377
+ return false;
378
+ }
379
+ return import.meta.url === pathToFileURL(entryPoint).href;
380
+ }
381
+ if (isMainModule()) {
382
+ void main().catch((error) => {
383
+ const message = error instanceof Error ? error.message : "Unknown startup error.";
384
+ console.error(`Failed to start Supabase MCP server: ${message}`);
385
+ process.exitCode = 1;
386
+ });
387
+ }
388
+ export {
389
+ SupabaseServer,
390
+ createServer,
391
+ main,
392
+ metadata,
393
+ serverCard
394
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@universal-mcp-toolkit/server-supabase",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Database, auth, storage, and function tools for Supabase.",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "server-supabase": "./dist/index.js",
9
+ "umt-supabase": "./dist/index.js"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/Markgatcha/universal-mcp-toolkit.git"
14
+ },
15
+ "homepage": "https://github.com/Markgatcha/universal-mcp-toolkit#readme",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ ".well-known"
26
+ ],
27
+ "keywords": [
28
+ "mcp",
29
+ "model-context-protocol",
30
+ "ai",
31
+ "developer-tools",
32
+ "typescript",
33
+ "supabase",
34
+ "database",
35
+ "auth",
36
+ "storage"
37
+ ],
38
+ "dependencies": {
39
+ "@supabase/supabase-js": "^2.99.0",
40
+ "@universal-mcp-toolkit/core": "0.1.0",
41
+ "zod": "^4.3.6"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup src/index.ts --format esm --dts --clean",
48
+ "dev": "tsx watch src/index.ts",
49
+ "lint": "tsc --noEmit",
50
+ "typecheck": "tsc --noEmit",
51
+ "test": "vitest run --passWithNoTests",
52
+ "clean": "rimraf dist"
53
+ }
54
+ }