@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.
- package/.well-known/mcp-server.json +36 -0
- package/LICENSE +21 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +394 -0
- package/package.json +54 -0
|
@@ -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.
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|