better-supabase 0.1.0 → 0.3.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/README.md +34 -31
- package/dist/base64-CQeB1eSf.js +42 -0
- package/dist/claims-CoTobNC-.js +25 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +4 -14
- package/dist/{compiler-DL18ukPe.d.ts → compiler-2zqGzxVZ.d.ts} +2 -2
- package/dist/config/index.d.ts +4 -3
- package/dist/config/index.js +5 -3
- package/dist/config-Be4GwsqI.js +129 -0
- package/dist/{index-BD6wKthp.d.ts → config-C1CYcm6o.d.ts} +75 -54
- package/dist/{config-D-aR2ZoN.js → config-DOsGYSiT.js} +14 -136
- package/dist/{define-QGkq04oe.js → define-DF341GDj.js} +133 -110
- package/dist/{define-CTfWw-SS.d.ts → define-DUbydmJa.d.ts} +32 -14
- package/dist/edge/index.d.ts +4 -4
- package/dist/edge/index.js +4 -4
- package/dist/entitlements-CTEBm7IB.js +20 -0
- package/dist/entitlements-DvJXoI02.d.ts +42 -0
- package/dist/env/index.js +203 -1
- package/dist/events/index.d.ts +1 -1
- package/dist/events/index.js +139 -1
- package/dist/{events-2OJsVG_d.d.ts → events-Do8jXh4D.d.ts} +3 -3
- package/dist/{executor-Bje4suQF.d.ts → executor-B26lgbHD.d.ts} +2 -2
- package/dist/{executor-Ceq60hPM.js → executor-C57jx2pn.js} +7 -2
- package/dist/{executor-Bazhgsgq.d.ts → executor-DynmIWhq.d.ts} +2 -2
- package/dist/guards-BnzDvFrz.js +10 -0
- package/dist/hono/index.d.ts +5 -5
- package/dist/hono/index.js +4 -4
- package/dist/{index-Hf8PP9sP.d.ts → index-BNsABq7P.d.ts} +17 -13
- package/dist/{index-JXHBuS53.d.ts → index-CADlNHK-.d.ts} +27 -14
- package/dist/{index-Nxo3TJcm.d.ts → index-CTTMS6_1.d.ts} +27 -8
- package/dist/index-Cf-D2jk1.d.ts +50 -0
- package/dist/{index-Dup-nSe9.d.ts → index-CriFFJ_p.d.ts} +4 -4
- package/dist/{index-DQVvHzjW.d.ts → index-GV77NpRP.d.ts} +5 -4
- package/dist/{index-Sw43J8HG.d.ts → index-UJ-LUxtI.d.ts} +6 -6
- package/dist/{index-cEeweTeM.d.ts → index-kpgopLNv.d.ts} +4 -3
- package/dist/{index-DRid0qIv.d.ts → index-qriRM9cZ.d.ts} +7 -2
- package/dist/index.d.ts +13 -13
- package/dist/index.js +18 -8
- package/dist/jobs/index.d.ts +25 -24
- package/dist/jobs/index.js +19 -12
- package/dist/{json-schema-BbwO2mZg.js → json-schema--aOXAGfU.js} +9 -3
- package/dist/lint/index.js +1 -1
- package/dist/list/index.d.ts +2 -2
- package/dist/list/index.js +128 -93
- package/dist/{live-6b9i72Ux.d.ts → live-QE3UPbGm.d.ts} +27 -6
- package/dist/logger-glM28NVN.js +21 -0
- package/dist/mcp/index.d.ts +59 -15
- package/dist/mcp/index.js +229 -67
- package/dist/{mfa-CpogP66z.js → mfa-BxoR1RkN.js} +97 -22
- package/dist/{mfa-TVia554c.d.ts → mfa-x3V3vGh-.d.ts} +1 -1
- package/dist/next/index.d.ts +51 -38
- package/dist/next/index.js +31 -25
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +58 -35
- package/dist/orpc/index.d.ts +6 -6
- package/dist/orpc/index.js +3 -3
- package/dist/otel/index.d.ts +3 -3
- package/dist/otel/index.js +5 -3
- package/dist/{template-BeOGnynM.js → permdock-sql-Bta_G0v-.js} +32 -3
- package/dist/permdock-sql-DImJULY-.d.ts +17 -0
- package/dist/{plugin-HvGqCurB.d.ts → plugin-DVRB8qNE.d.ts} +5 -4
- package/dist/plugins/actor/index.d.ts +1 -1
- package/dist/plugins/rules/index.d.ts +4 -3
- package/dist/plugins/rules/index.js +8 -15
- package/dist/plugins/soft-delete/index.d.ts +3 -3
- package/dist/plugins/soft-delete/index.js +2 -2
- package/dist/plugins/tenant/index.d.ts +10 -4
- package/dist/plugins/tenant/index.js +15 -13
- package/dist/plugins/timestamps/index.d.ts +1 -1
- package/dist/plugins/timestamps/index.js +1 -1
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/pool-B9qsLyFR.d.ts +71 -0
- package/dist/postgres/index.d.ts +10 -5
- package/dist/postgres/index.js +36 -16
- package/dist/{postgrest-Dm-vhY6R.d.ts → postgrest-DmBlevh-.d.ts} +1 -1
- package/dist/{problem-DPu6Fh-t.js → problem-CGt1ADix.js} +14 -8
- package/dist/{problem-DlgB0lYl.d.ts → problem-Z2RTQjZD.d.ts} +6 -4
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +1 -1
- package/dist/{query-ChvDbsDE.js → query-BdKLTPlj.js} +2 -2
- package/dist/react/index.d.ts +7 -7
- package/dist/react/index.js +3 -3
- package/dist/react/server.d.ts +5 -5
- package/dist/react/server.js +1 -1
- package/dist/react/session.d.ts +21 -2
- package/dist/react/session.js +19 -1
- package/dist/{react-CrOvrnIP.js → react-DNBYNazu.js} +28 -23
- package/dist/{read-set-DhX2d04c.js → read-set-D0qBcboM.js} +3 -3
- package/dist/realtime/index.d.ts +4 -2
- package/dist/realtime/index.js +30 -14
- package/dist/{resolve-DW8h9NPm.d.ts → resolve-BRciX2jK.d.ts} +25 -9
- package/dist/{resource-SkPTRJDD.d.ts → resource-CwVgiI5q.d.ts} +16 -7
- package/dist/{resource-CYiu6Ygi.js → resource-D30rp0GN.js} +38 -16
- package/dist/{respond-4-dhoV7x.js → respond-C44EZAJo.js} +38 -19
- package/dist/{respond-PoTpy5DT.d.ts → respond-DAkPIdTH.d.ts} +23 -17
- package/dist/{result-CTkCZ6JA.d.ts → result-c2OYgCV2.d.ts} +13 -8
- package/dist/server/index.d.ts +35 -10
- package/dist/server/index.js +38 -9
- package/dist/{simplify-oHiDKRVQ.js → simplify-LakqK1vH.js} +22 -28
- package/dist/{spec-pins-t6sPaHvl.js → spec-pins-C3oc-Tdp.js} +1 -1
- package/dist/{spec-pins-CJPVghHy.d.ts → spec-pins-CqorWDnO.d.ts} +1 -1
- package/dist/sql/index.d.ts +139 -0
- package/dist/sql/index.js +1983 -0
- package/dist/{sql-M8l3fnS0.js → sql-BIgk12ew.js} +72 -16
- package/dist/ssr/index.d.ts +5 -5
- package/dist/ssr/index.js +3 -3
- package/dist/{standard-CyhRzctb.d.ts → standard-eudBiRk1.d.ts} +1 -1
- package/dist/{stats-3iy8u3_P.js → stats-D78UQ8Yu.js} +8 -7
- package/dist/storage/index.d.ts +4 -2
- package/dist/storage/index.js +530 -1
- package/dist/{tables-rZxRxNPW.js → tables-Dhha3BXM.js} +5 -5
- package/dist/temporal-BwshQk0W.js +27 -0
- package/dist/temporal-required-CMxeSVPo.js +19 -0
- package/dist/testing/index.d.ts +49 -16
- package/dist/testing/index.js +233 -66
- package/dist/{types-C2fslP1z.d.ts → types-DONv9SHN.d.ts} +68 -12
- package/dist/{version-Cqq1m3UY.js → version-BfpG2gks.js} +1 -1
- package/dist/view-3Po_PVlr.d.ts +79 -0
- package/dist/{view-B6F9QbG-.js → view-wZ-9qIUz.js} +12 -2
- package/dist/webhooks/index.d.ts +19 -17
- package/dist/webhooks/index.js +1 -1
- package/dist/{webhooks-DbMTJJX2.js → webhooks-CAIFQSTM.js} +12 -10
- package/package.json +30 -30
- package/schemas/config-v1.json +80 -5
- package/schemas/doctor-report-v1.json +18 -1
- package/schemas/snapshot-v2.json +39 -0
- package/skills/better-supabase/SKILL.md +23 -11
- package/skills/better-supabase/references/plugins.md +28 -16
- package/skills/better-supabase/references/troubleshooting.md +15 -15
- package/skills/better-supabase-api/SKILL.md +9 -9
- package/skills/better-supabase-api/references/adapters.md +30 -25
- package/skills/better-supabase-testing/SKILL.md +15 -11
- package/bin/better-supabase.js +0 -3
- package/dist/casing-da0uRTqt.js +0 -14
- package/dist/cli/bin.d.ts +0 -1
- package/dist/cli/bin.js +0 -15
- package/dist/cli/index.d.ts +0 -226
- package/dist/cli/index.js +0 -3
- package/dist/cli-CoByWYja.js +0 -6472
- package/dist/entitlements-BBg61DQZ.d.ts +0 -20
- package/dist/entitlements-BC_khwRy.js +0 -17
- package/dist/env-BEToab0I.js +0 -204
- package/dist/events-BuzTgn2D.js +0 -135
- package/dist/pool-BFeqzwQS.d.ts +0 -37
- package/dist/seed-CzecODcJ.js +0 -101
- package/dist/session-C3osL21k.d.ts +0 -21
- package/dist/session-CD-3orO_.js +0 -19
- package/dist/storage-BWXVUXGk.js +0 -461
- package/dist/view-B0ltUmTi.d.ts +0 -43
package/dist/mcp/index.js
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import { n as dbError } from "../errors-Bl3rkOzo.js";
|
|
2
|
+
import { t as base64ToText } from "../base64-CQeB1eSf.js";
|
|
2
3
|
import { t as validate } from "../standard-FUi3Z3-7.js";
|
|
3
|
-
import { o as toProblem } from "../problem-
|
|
4
|
-
import { t as SPEC_PINS } from "../spec-pins-
|
|
5
|
-
import { t as buildJsonSchema } from "../json-schema
|
|
6
|
-
import { a as settle, c as withExtra, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-
|
|
7
|
-
import { t as defineResource } from "../resource-
|
|
8
|
-
import { fromSupabaseUrl,
|
|
9
|
-
//#region src/mcp/
|
|
4
|
+
import { o as toProblem } from "../problem-CGt1ADix.js";
|
|
5
|
+
import { t as SPEC_PINS } from "../spec-pins-C3oc-Tdp.js";
|
|
6
|
+
import { t as buildJsonSchema } from "../json-schema--aOXAGfU.js";
|
|
7
|
+
import { a as settle, c as withExtra, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-C44EZAJo.js";
|
|
8
|
+
import { t as defineResource } from "../resource-D30rp0GN.js";
|
|
9
|
+
import { fromSupabaseUrl, unauthorizedResponse } from "@supabase/server/oauth-protected-resource";
|
|
10
|
+
//#region src/mcp/mcp.ts
|
|
10
11
|
const MCP_PROTOCOL_VERSION = SPEC_PINS.mcp;
|
|
11
12
|
const WELL_KNOWN = "/.well-known/oauth-protected-resource";
|
|
12
|
-
|
|
13
|
+
/** Revisions before 2026-07-28 open with an `initialize` handshake. */
|
|
14
|
+
const LEGACY_VERSIONS = [
|
|
15
|
+
"2025-11-25",
|
|
16
|
+
"2025-06-18",
|
|
17
|
+
"2025-03-26"
|
|
18
|
+
];
|
|
19
|
+
const SUPPORTED_VERSIONS = [MCP_PROTOCOL_VERSION, ...LEGACY_VERSIONS];
|
|
20
|
+
/** The `Host` header's hostname is in the list; a missing or malformed header is not. */
|
|
21
|
+
function hostAllowed(request, allowed) {
|
|
22
|
+
const host = request.headers.get("host");
|
|
23
|
+
if (!host) return false;
|
|
24
|
+
try {
|
|
25
|
+
return allowed.includes(new URL(`http://${host}`).hostname);
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
13
30
|
/** A custom tool. Pass it in `createMcp({ tools: [...] })`. */
|
|
14
31
|
function defineTool(definition) {
|
|
15
32
|
if (!/^[A-Za-z0-9_.-]{1,64}$/.test(definition.name)) throw new TypeError(`Invalid tool name "${definition.name}": use 1-64 letters, digits, "_", "-" or "."`);
|
|
@@ -30,6 +47,7 @@ function defineTool(definition) {
|
|
|
30
47
|
...definition.annotations ? { annotations: definition.annotations } : {}
|
|
31
48
|
},
|
|
32
49
|
...definition.input ? { input: definition.input } : {},
|
|
50
|
+
...definition.meta === void 0 ? {} : { meta: definition.meta },
|
|
33
51
|
run: (args, ctx) => definition.run(args, ctx)
|
|
34
52
|
};
|
|
35
53
|
}
|
|
@@ -60,15 +78,15 @@ const ANNOTATIONS = {
|
|
|
60
78
|
openWorldHint: false
|
|
61
79
|
}
|
|
62
80
|
};
|
|
63
|
-
function pageSchema(max) {
|
|
81
|
+
function pageSchema(max, cursor) {
|
|
64
82
|
return {
|
|
65
83
|
type: "object",
|
|
66
84
|
properties: {
|
|
67
|
-
page: {
|
|
85
|
+
...cursor ? { after: { type: "string" } } : { page: {
|
|
68
86
|
type: "integer",
|
|
69
87
|
minimum: 1,
|
|
70
88
|
default: 1
|
|
71
|
-
},
|
|
89
|
+
} },
|
|
72
90
|
size: {
|
|
73
91
|
type: "integer",
|
|
74
92
|
minimum: 1,
|
|
@@ -92,7 +110,23 @@ function tableTools(resource, defs) {
|
|
|
92
110
|
required: [keyParam, ...Object.keys(more)],
|
|
93
111
|
additionalProperties: false
|
|
94
112
|
});
|
|
95
|
-
const
|
|
113
|
+
const cursor = resource.pagination === "cursor";
|
|
114
|
+
const page = cursor ? {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
items: {
|
|
118
|
+
type: "array",
|
|
119
|
+
items: row
|
|
120
|
+
},
|
|
121
|
+
nextCursor: { type: ["string", "null"] },
|
|
122
|
+
hasMore: { type: "boolean" }
|
|
123
|
+
},
|
|
124
|
+
required: [
|
|
125
|
+
"items",
|
|
126
|
+
"nextCursor",
|
|
127
|
+
"hasMore"
|
|
128
|
+
]
|
|
129
|
+
} : {
|
|
96
130
|
type: "object",
|
|
97
131
|
properties: {
|
|
98
132
|
items: {
|
|
@@ -119,7 +153,7 @@ function tableTools(resource, defs) {
|
|
|
119
153
|
};
|
|
120
154
|
for (const operation of resource.operations) switch (operation) {
|
|
121
155
|
case "list": {
|
|
122
|
-
const { $schema: _, ...listSchema } = resource.list?.jsonSchema ?? pageSchema(resource.maxPageSize);
|
|
156
|
+
const { $schema: _, ...listSchema } = resource.list?.jsonSchema ?? pageSchema(resource.maxPageSize, cursor);
|
|
123
157
|
add("list", `List ${table} rows visible to the caller, one page at a time.`, listSchema, page);
|
|
124
158
|
break;
|
|
125
159
|
}
|
|
@@ -143,18 +177,38 @@ const PARSE_ERROR = -32700;
|
|
|
143
177
|
const INVALID_REQUEST = -32600;
|
|
144
178
|
const METHOD_NOT_FOUND = -32601;
|
|
145
179
|
const INVALID_PARAMS = -32602;
|
|
146
|
-
|
|
180
|
+
const HEADER_MISMATCH = -32020;
|
|
181
|
+
const UNSUPPORTED_PROTOCOL_VERSION = -32022;
|
|
182
|
+
const META_VERSION = "io.modelcontextprotocol/protocolVersion";
|
|
183
|
+
const META_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
|
|
184
|
+
const META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";
|
|
185
|
+
/** How long clients may cache `tools/list` and `server/discover` (per token). */
|
|
186
|
+
const LIST_TTL_MS = 3e5;
|
|
187
|
+
function rpcError(id, code, message, status = 200, data) {
|
|
147
188
|
return Response.json({
|
|
148
189
|
jsonrpc: "2.0",
|
|
149
190
|
id: id ?? null,
|
|
150
191
|
error: {
|
|
151
192
|
code,
|
|
152
|
-
message
|
|
193
|
+
message,
|
|
194
|
+
...data ? { data } : {}
|
|
153
195
|
}
|
|
154
196
|
}, { status });
|
|
155
197
|
}
|
|
198
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
199
|
+
/** An `Mcp-Name` value, decoding the `=?base64?...?=` sentinel. */
|
|
200
|
+
function headerName(value) {
|
|
201
|
+
const encoded = /^=\?base64\?(.*)\?=$/.exec(value)?.[1];
|
|
202
|
+
if (encoded === void 0) return value;
|
|
203
|
+
try {
|
|
204
|
+
return base64ToText(encoded);
|
|
205
|
+
} catch {
|
|
206
|
+
return value;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const quoted = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
|
|
156
210
|
function isRpcRequest(value) {
|
|
157
|
-
return
|
|
211
|
+
return isObject(value) && value["jsonrpc"] === "2.0" && typeof value["method"] === "string" && (value["params"] === void 0 || isObject(value["params"]));
|
|
158
212
|
}
|
|
159
213
|
function textResult(value, isError = false) {
|
|
160
214
|
const structured = typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
@@ -185,43 +239,75 @@ function createMcp(sb, options) {
|
|
|
185
239
|
config: { json: options.json ?? {} }
|
|
186
240
|
})["$defs"];
|
|
187
241
|
const registry = /* @__PURE__ */ new Map();
|
|
188
|
-
const register = (
|
|
189
|
-
if (registry.has(info.name)) throw new TypeError(`Duplicate MCP tool "${info.name}"`);
|
|
190
|
-
registry.set(info.name,
|
|
191
|
-
info,
|
|
192
|
-
call
|
|
193
|
-
});
|
|
242
|
+
const register = (entry) => {
|
|
243
|
+
if (registry.has(entry.info.name)) throw new TypeError(`Duplicate MCP tool "${entry.info.name}"`);
|
|
244
|
+
registry.set(entry.info.name, entry);
|
|
194
245
|
};
|
|
195
246
|
const failure = (error) => textResult(toProblem(error, { expose }), true);
|
|
247
|
+
const refused = async (tool, args, ctx) => {
|
|
248
|
+
if (!options.authorize) return void 0;
|
|
249
|
+
const decision = await options.authorize(ctx, tool, args);
|
|
250
|
+
if (decision.allowed) return void 0;
|
|
251
|
+
return {
|
|
252
|
+
refusal: decision.reason ?? `Not allowed to call ${tool.info.name}`,
|
|
253
|
+
scopes: decision.scopes ?? []
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
const isVisible = async (tool, ctx) => !options.visible || options.visible(ctx, tool);
|
|
196
257
|
for (const [table, raw] of Object.entries(options.resources ?? {})) {
|
|
197
258
|
if (!raw) continue;
|
|
198
259
|
const resource = defineResource(sb, table, raw === true ? {} : raw);
|
|
199
|
-
for (const { info, operation } of tableTools(resource, defs))
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
260
|
+
for (const { info, operation } of tableTools(resource, defs)) {
|
|
261
|
+
const ref = {
|
|
262
|
+
info,
|
|
263
|
+
meta: void 0
|
|
264
|
+
};
|
|
265
|
+
register({
|
|
266
|
+
...ref,
|
|
267
|
+
async call(args, ctx) {
|
|
268
|
+
const input = args ?? {};
|
|
269
|
+
const refusal = await refused(ref, input, ctx);
|
|
270
|
+
if (refusal) return refusal;
|
|
271
|
+
const id = resource.keyParam ? input[resource.keyParam] : void 0;
|
|
272
|
+
const result = await resource.execute(ctx.db, operation, {
|
|
273
|
+
id,
|
|
274
|
+
query: input,
|
|
275
|
+
data: operation === "update" ? input["patch"] : input
|
|
276
|
+
});
|
|
277
|
+
if (!result.ok) return failure(result.error);
|
|
278
|
+
return textResult(operation === "delete" ? { deleted: true } : result.data);
|
|
279
|
+
}
|
|
206
280
|
});
|
|
207
|
-
|
|
208
|
-
return textResult(operation === "delete" ? { deleted: true } : result.data);
|
|
209
|
-
});
|
|
281
|
+
}
|
|
210
282
|
}
|
|
211
283
|
const addTool = (tool) => {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
284
|
+
const ref = {
|
|
285
|
+
info: tool.info,
|
|
286
|
+
meta: tool.meta
|
|
287
|
+
};
|
|
288
|
+
register({
|
|
289
|
+
...ref,
|
|
290
|
+
async call(args, ctx) {
|
|
291
|
+
let value = args ?? {};
|
|
292
|
+
if (tool.input) {
|
|
293
|
+
const parsed = await validate(tool.input, value, "arguments");
|
|
294
|
+
if (!parsed.ok) return failure(parsed.error);
|
|
295
|
+
value = parsed.data;
|
|
296
|
+
}
|
|
297
|
+
const refusal = await refused(ref, value, ctx);
|
|
298
|
+
if (refusal) return refusal;
|
|
299
|
+
const outcome = await settle(() => tool.run(value, ctx));
|
|
300
|
+
return outcome.ok ? textResult(outcome.data) : failure(outcome.error);
|
|
218
301
|
}
|
|
219
|
-
const outcome = await settle(() => tool.run(value, ctx));
|
|
220
|
-
return outcome.ok ? textResult(outcome.data) : failure(outcome.error);
|
|
221
302
|
});
|
|
222
303
|
};
|
|
223
304
|
for (const tool of options.tools ?? []) addTool(tool);
|
|
224
305
|
const listTools = () => [...registry.values()].map((entry) => entry.info);
|
|
306
|
+
const visibleTools = async (ctx) => {
|
|
307
|
+
const entries = [...registry.values()];
|
|
308
|
+
const shown = await Promise.all(entries.map((entry) => isVisible(entry, ctx)));
|
|
309
|
+
return entries.filter((_, index) => shown[index]).map((e) => e.info);
|
|
310
|
+
};
|
|
225
311
|
const authorizationServers = () => options.authorizationServers ?? [fromSupabaseUrl(server.env.url)];
|
|
226
312
|
const resourceOf = (request) => {
|
|
227
313
|
if (typeof options.resource === "function") return options.resource(request);
|
|
@@ -233,30 +319,76 @@ function createMcp(sb, options) {
|
|
|
233
319
|
const resource = new URL(resourceOf(request));
|
|
234
320
|
return `${resource.origin}${WELL_KNOWN}${resource.pathname.replace(/\/$/, "")}`;
|
|
235
321
|
};
|
|
236
|
-
const
|
|
322
|
+
const invoke = async (name, args, ctx, checkVisible = true) => {
|
|
237
323
|
const entry = registry.get(name);
|
|
238
|
-
if (!entry) return failure(dbError("not_found", `Unknown tool "${name}"`));
|
|
239
324
|
try {
|
|
325
|
+
if (!entry || checkVisible && !await isVisible(entry, ctx)) return failure(dbError("not_found", `Unknown tool "${name}"`));
|
|
240
326
|
return await entry.call(args, ctx);
|
|
241
327
|
} catch (cause) {
|
|
242
328
|
return failure(dbError("unexpected", expose && cause instanceof Error ? cause.message : "The tool failed"));
|
|
243
329
|
}
|
|
244
330
|
};
|
|
331
|
+
const isRefusal = (value) => "refusal" in value;
|
|
332
|
+
const call = async (name, args, ctx) => {
|
|
333
|
+
const outcome = await invoke(name, args, ctx);
|
|
334
|
+
return isRefusal(outcome) ? failure(dbError("forbidden", outcome.refusal)) : outcome;
|
|
335
|
+
};
|
|
336
|
+
const scopes = (options.scopes ?? []).filter((scope) => scope !== "offline_access");
|
|
337
|
+
const serverInfo = {
|
|
338
|
+
name: options.name,
|
|
339
|
+
version: options.version,
|
|
340
|
+
...options.title ? { title: options.title } : {}
|
|
341
|
+
};
|
|
342
|
+
const capabilities = { tools: { listChanged: false } };
|
|
343
|
+
const unauthorized = (request) => {
|
|
344
|
+
const response = unauthorizedResponse(request, { resourceMetadataUrl: metadataUrl(request) });
|
|
345
|
+
if (scopes.length > 0) response.headers.set("www-authenticate", `${response.headers.get("www-authenticate") ?? "Bearer"}, scope=${quoted(scopes.join(" "))}`);
|
|
346
|
+
return response;
|
|
347
|
+
};
|
|
348
|
+
const forbidden = (request, message, needed = [], id = null) => {
|
|
349
|
+
const all = [.../* @__PURE__ */ new Set([...scopes, ...needed])];
|
|
350
|
+
const challenge = [
|
|
351
|
+
"Bearer error=\"insufficient_scope\"",
|
|
352
|
+
`error_description=${quoted(message)}`,
|
|
353
|
+
...all.length > 0 ? [`scope=${quoted(all.join(" "))}`] : [],
|
|
354
|
+
`resource_metadata=${quoted(metadataUrl(request))}`
|
|
355
|
+
].join(", ");
|
|
356
|
+
const response = rpcError(id, INVALID_REQUEST, message, 403);
|
|
357
|
+
response.headers.set("www-authenticate", challenge);
|
|
358
|
+
return response;
|
|
359
|
+
};
|
|
360
|
+
const unsupportedVersion = (id, requested) => rpcError(id, UNSUPPORTED_PROTOCOL_VERSION, "Unsupported protocol version", 400, {
|
|
361
|
+
supported: [...SUPPORTED_VERSIONS],
|
|
362
|
+
requested
|
|
363
|
+
});
|
|
364
|
+
/** 2026-07-28 requests carry their version and capabilities, mirrored in headers. */
|
|
365
|
+
const checkModern = (message, header, headers) => {
|
|
366
|
+
const meta = message.params?.["_meta"];
|
|
367
|
+
const version = isObject(meta) ? meta[META_VERSION] : void 0;
|
|
368
|
+
if (!isObject(meta) || typeof version !== "string" || !isObject(meta[META_CAPABILITIES])) return rpcError(message.id, INVALID_PARAMS, `Requests need _meta["${META_VERSION}"] and _meta["${META_CAPABILITIES}"]`, 400);
|
|
369
|
+
if (version !== MCP_PROTOCOL_VERSION) return unsupportedVersion(message.id, version);
|
|
370
|
+
const mismatch = (detail) => rpcError(message.id, HEADER_MISMATCH, `Header mismatch: ${detail}`, 400);
|
|
371
|
+
if (header !== version) return mismatch(`MCP-Protocol-Version must be ${version}`);
|
|
372
|
+
if (headers.get("mcp-method") !== message.method) return mismatch(`Mcp-Method must be ${message.method}`);
|
|
373
|
+
const name = message.params?.["name"];
|
|
374
|
+
if (message.method === "tools/call" && typeof name === "string") {
|
|
375
|
+
const sent = headers.get("mcp-name");
|
|
376
|
+
if (sent === null || headerName(sent) !== name) return mismatch(`Mcp-Name must be ${name}`);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
245
379
|
const handler = async (request) => {
|
|
246
380
|
const origin = request.headers.get("origin");
|
|
247
381
|
if (origin && options.allowedOrigins && !options.allowedOrigins.includes(origin)) return rpcError(null, INVALID_REQUEST, "Origin not allowed", 403);
|
|
382
|
+
if (options.allowedHosts && !hostAllowed(request, options.allowedHosts)) return rpcError(null, INVALID_REQUEST, "Host not allowed", 403);
|
|
248
383
|
if (request.method !== "POST") return new Response(null, {
|
|
249
384
|
status: 405,
|
|
250
385
|
headers: { allow: "POST" }
|
|
251
386
|
});
|
|
252
|
-
const
|
|
253
|
-
if (
|
|
387
|
+
const header = request.headers.get("mcp-protocol-version");
|
|
388
|
+
if (header && !SUPPORTED_VERSIONS.includes(header)) return unsupportedVersion(null, header);
|
|
254
389
|
const ctx = await server.context(request);
|
|
255
390
|
const denied = guard(ctx.auth, options.allow, options.aal);
|
|
256
|
-
if (denied)
|
|
257
|
-
if (denied.kind === "unauthorized") return unauthorizedResponse(request, { resourceMetadataUrl: metadataUrl(request) });
|
|
258
|
-
return rpcError(null, INVALID_REQUEST, denied.message, 403);
|
|
259
|
-
}
|
|
391
|
+
if (denied) return denied.kind === "unauthorized" ? unauthorized(request) : forbidden(request, denied.message);
|
|
260
392
|
let message;
|
|
261
393
|
try {
|
|
262
394
|
message = await request.json();
|
|
@@ -264,43 +396,73 @@ function createMcp(sb, options) {
|
|
|
264
396
|
return rpcError(null, PARSE_ERROR, "Parse error", 400);
|
|
265
397
|
}
|
|
266
398
|
if (!isRpcRequest(message)) return rpcError(null, INVALID_REQUEST, "Expected one JSON-RPC 2.0 message", 400);
|
|
399
|
+
const meta = message.params?.["_meta"];
|
|
400
|
+
const modern = isObject(meta) && meta[META_VERSION] !== void 0 || header === MCP_PROTOCOL_VERSION;
|
|
401
|
+
if (modern) {
|
|
402
|
+
const invalid = checkModern(message, header, request.headers);
|
|
403
|
+
if (invalid) return invalid;
|
|
404
|
+
}
|
|
267
405
|
if (message.id === void 0) return new Response(null, { status: 202 });
|
|
406
|
+
const toolContext = () => withExtra(ctx, {
|
|
407
|
+
request,
|
|
408
|
+
signal: request.signal
|
|
409
|
+
});
|
|
268
410
|
const reply = (result) => Response.json({
|
|
269
411
|
jsonrpc: "2.0",
|
|
270
412
|
id: message.id,
|
|
271
|
-
result
|
|
413
|
+
result: modern ? {
|
|
414
|
+
...result,
|
|
415
|
+
resultType: "complete",
|
|
416
|
+
_meta: { [META_SERVER_INFO]: serverInfo }
|
|
417
|
+
} : result
|
|
272
418
|
});
|
|
419
|
+
const notFound = () => rpcError(message.id, METHOD_NOT_FOUND, `Method not found: ${message.method}`, modern ? 404 : 200);
|
|
273
420
|
switch (message.method) {
|
|
421
|
+
case "server/discover": return reply({
|
|
422
|
+
supportedVersions: [...SUPPORTED_VERSIONS],
|
|
423
|
+
capabilities,
|
|
424
|
+
...options.instructions ? { instructions: options.instructions } : {},
|
|
425
|
+
ttlMs: LIST_TTL_MS,
|
|
426
|
+
cacheScope: "private"
|
|
427
|
+
});
|
|
274
428
|
case "initialize": {
|
|
429
|
+
if (modern) return notFound();
|
|
275
430
|
const requested = message.params?.["protocolVersion"];
|
|
276
431
|
return reply({
|
|
277
|
-
protocolVersion: typeof requested === "string" &&
|
|
278
|
-
capabilities
|
|
279
|
-
serverInfo
|
|
280
|
-
name: options.name,
|
|
281
|
-
version: options.version,
|
|
282
|
-
...options.title ? { title: options.title } : {}
|
|
283
|
-
},
|
|
432
|
+
protocolVersion: typeof requested === "string" && LEGACY_VERSIONS.includes(requested) ? requested : LEGACY_VERSIONS[0],
|
|
433
|
+
capabilities,
|
|
434
|
+
serverInfo,
|
|
284
435
|
...options.instructions ? { instructions: options.instructions } : {}
|
|
285
436
|
});
|
|
286
437
|
}
|
|
287
|
-
case "ping": return reply({});
|
|
288
|
-
case "tools/list": return reply({
|
|
438
|
+
case "ping": return modern ? notFound() : reply({});
|
|
439
|
+
case "tools/list": return reply({
|
|
440
|
+
tools: await visibleTools(toolContext()),
|
|
441
|
+
...modern ? {
|
|
442
|
+
ttlMs: LIST_TTL_MS,
|
|
443
|
+
cacheScope: "private"
|
|
444
|
+
} : {}
|
|
445
|
+
});
|
|
289
446
|
case "tools/call": {
|
|
290
447
|
const name = message.params?.["name"];
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
448
|
+
const entry = typeof name === "string" ? registry.get(name) : void 0;
|
|
449
|
+
const context = toolContext();
|
|
450
|
+
if (!entry || !await isVisible(entry, context)) return rpcError(message.id, INVALID_PARAMS, `Unknown tool ${JSON.stringify(name)}`);
|
|
451
|
+
const outcome = await invoke(entry.info.name, message.params?.["arguments"], context, false);
|
|
452
|
+
if (!isRefusal(outcome)) return reply(outcome);
|
|
453
|
+
if (outcome.scopes.length > 0) return forbidden(request, outcome.refusal, outcome.scopes, message.id);
|
|
454
|
+
return reply(failure(dbError("forbidden", outcome.refusal)));
|
|
296
455
|
}
|
|
297
|
-
default: return
|
|
456
|
+
default: return notFound();
|
|
298
457
|
}
|
|
299
458
|
};
|
|
300
|
-
const metadata = (request) =>
|
|
459
|
+
const metadata = (request) => Response.json({
|
|
301
460
|
resource: resourceOf(request),
|
|
302
|
-
|
|
303
|
-
|
|
461
|
+
authorization_servers: [...authorizationServers()],
|
|
462
|
+
bearer_methods_supported: ["header"],
|
|
463
|
+
...scopes.length > 0 ? { scopes_supported: scopes } : {},
|
|
464
|
+
...options.resourceDocumentation === void 0 ? {} : { resource_documentation: options.resourceDocumentation }
|
|
465
|
+
}, { headers: { "access-control-allow-origin": "*" } });
|
|
304
466
|
const mcp = extendServer(server, {
|
|
305
467
|
get tools() {
|
|
306
468
|
return listTools();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { n as dbError } from "./errors-Bl3rkOzo.js";
|
|
2
|
+
import { t as consoleLogger } from "./logger-glM28NVN.js";
|
|
3
|
+
import { n as decodeJwtPayload } from "./base64-CQeB1eSf.js";
|
|
2
4
|
import { DEFAULT_COOKIE_OPTIONS, createChunks, isChunkLike, parseCookieHeader, serializeCookieHeader, stringFromBase64URL, stringToBase64URL } from "@supabase/ssr";
|
|
3
5
|
import { extractCredentials, verifyCredentials } from "@supabase/server/core";
|
|
4
6
|
//#region src/auth/impersonation.ts
|
|
@@ -21,6 +23,65 @@ function impersonatorOf(claims) {
|
|
|
21
23
|
} : { id: sub };
|
|
22
24
|
}
|
|
23
25
|
//#endregion
|
|
26
|
+
//#region src/auth/actor.ts
|
|
27
|
+
function isRecord(value) {
|
|
28
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29
|
+
}
|
|
30
|
+
function copyAct(level) {
|
|
31
|
+
const entries = Object.entries(level).map(([key, value]) => [key, key === "act" && isRecord(value) ? copyAct(value) : value]);
|
|
32
|
+
return Object.fromEntries(entries);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reads only `act` and `client_id`, the way PermDock's `actorOf` does. An
|
|
36
|
+
* `act` that is not a chain of objects each with a non-empty `sub` is
|
|
37
|
+
* `{ ok: false }`: the session must not pass as the user alone.
|
|
38
|
+
*/
|
|
39
|
+
function actorOf(claims) {
|
|
40
|
+
const outer = claims["act"];
|
|
41
|
+
if (outer !== void 0) {
|
|
42
|
+
let level = outer;
|
|
43
|
+
while (level !== void 0) {
|
|
44
|
+
if (!isRecord(level)) return {
|
|
45
|
+
ok: false,
|
|
46
|
+
reason: "invalid-chain"
|
|
47
|
+
};
|
|
48
|
+
const sub = level["sub"];
|
|
49
|
+
if (typeof sub !== "string" || sub === "") return {
|
|
50
|
+
ok: false,
|
|
51
|
+
reason: "invalid-chain"
|
|
52
|
+
};
|
|
53
|
+
level = level["act"];
|
|
54
|
+
}
|
|
55
|
+
if (!isRecord(outer) || typeof outer["sub"] !== "string") return {
|
|
56
|
+
ok: false,
|
|
57
|
+
reason: "invalid-chain"
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
actor: {
|
|
62
|
+
id: outer["sub"],
|
|
63
|
+
kind: "oauth-client",
|
|
64
|
+
chain: copyAct(outer)
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const client = claims["client_id"];
|
|
69
|
+
if (typeof client === "string" && client !== "") return {
|
|
70
|
+
ok: true,
|
|
71
|
+
actor: {
|
|
72
|
+
id: client,
|
|
73
|
+
kind: "oauth-client"
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
return { ok: true };
|
|
77
|
+
}
|
|
78
|
+
/** The OAuth `scope` claim (a space-separated string or a list); `undefined` when empty. */
|
|
79
|
+
function delegationOf(claims) {
|
|
80
|
+
const scope = claims["scope"];
|
|
81
|
+
const scopes = typeof scope === "string" ? scope.split(/\s+/u).filter(Boolean) : Array.isArray(scope) ? scope.filter((item) => typeof item === "string") : [];
|
|
82
|
+
return scopes.length > 0 ? { scopes } : void 0;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
24
85
|
//#region src/auth/refresh.ts
|
|
25
86
|
/**
|
|
26
87
|
* How long a finished refresh is reused for requests still carrying the old
|
|
@@ -190,20 +251,9 @@ function applyCookieWrites(cookies, writes) {
|
|
|
190
251
|
}
|
|
191
252
|
//#endregion
|
|
192
253
|
//#region src/auth/resolve.ts
|
|
193
|
-
function decodePayload(token) {
|
|
194
|
-
const part = token.split(".")[1];
|
|
195
|
-
if (!part) return void 0;
|
|
196
|
-
try {
|
|
197
|
-
const json = atob(part.replace(/-/g, "+").replace(/_/g, "/"));
|
|
198
|
-
const parsed = JSON.parse(json);
|
|
199
|
-
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
200
|
-
} catch {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
254
|
function expiryOf(session) {
|
|
205
255
|
if (typeof session.expires_at === "number") return session.expires_at;
|
|
206
|
-
const exp =
|
|
256
|
+
const exp = decodeJwtPayload(session.access_token)?.["exp"];
|
|
207
257
|
return typeof exp === "number" ? exp : void 0;
|
|
208
258
|
}
|
|
209
259
|
function serverEnv(options) {
|
|
@@ -257,7 +307,7 @@ async function verify(credentials, modes, options, source) {
|
|
|
257
307
|
const memo = token ? memoFor(options) : void 0;
|
|
258
308
|
if (token && memo) {
|
|
259
309
|
const hit = remembered(memo, token, Math.floor((options.now ?? Date.now)() / 1e3));
|
|
260
|
-
if (hit) return
|
|
310
|
+
if (hit) return checkUser({
|
|
261
311
|
...hit,
|
|
262
312
|
source
|
|
263
313
|
}, options);
|
|
@@ -270,11 +320,17 @@ async function verify(credentials, modes, options, source) {
|
|
|
270
320
|
user: state.user,
|
|
271
321
|
expiresAt: state.expiresAt
|
|
272
322
|
});
|
|
273
|
-
return
|
|
323
|
+
return checkUser(state, options);
|
|
274
324
|
}
|
|
275
325
|
/** Validates a user's claims against `options.claims`, keeping every JWT claim. */
|
|
276
326
|
async function checkClaims(state, options) {
|
|
277
|
-
if (state.kind !== "user"
|
|
327
|
+
if (state.kind !== "user") return state;
|
|
328
|
+
if (!actorOf(state.claims).ok) return {
|
|
329
|
+
kind: "invalid",
|
|
330
|
+
reason: "actor",
|
|
331
|
+
error: dbError("unauthorized", "The token's act claim is not a chain of actors with a sub", { code: "ACTOR_INVALID" })
|
|
332
|
+
};
|
|
333
|
+
if (!options.claims) return state;
|
|
278
334
|
let outcome = options.claims["~standard"].validate(state.claims);
|
|
279
335
|
if (outcome instanceof Promise) outcome = await outcome;
|
|
280
336
|
if (outcome.issues) {
|
|
@@ -297,12 +353,31 @@ async function checkClaims(state, options) {
|
|
|
297
353
|
}
|
|
298
354
|
} : state;
|
|
299
355
|
}
|
|
356
|
+
const warnedMetadata = /* @__PURE__ */ new WeakSet();
|
|
357
|
+
/** Claims first, then `user_metadata`, whose failure never invalidates the session. */
|
|
358
|
+
async function checkUser(state, options) {
|
|
359
|
+
const checked = await checkClaims(state, options);
|
|
360
|
+
const schema = options.userMetadata;
|
|
361
|
+
if (checked.kind !== "user" || !schema) return checked;
|
|
362
|
+
const { profile: _untrusted, ...user } = checked;
|
|
363
|
+
let outcome = schema["~standard"].validate(checked.user.userMetadata ?? checked.claims.user_metadata ?? {});
|
|
364
|
+
if (outcome instanceof Promise) outcome = await outcome;
|
|
365
|
+
if (!outcome.issues) return {
|
|
366
|
+
...user,
|
|
367
|
+
profile: outcome.value
|
|
368
|
+
};
|
|
369
|
+
if (!warnedMetadata.has(schema)) {
|
|
370
|
+
warnedMetadata.add(schema);
|
|
371
|
+
(options.logger ?? consoleLogger).warn("user_metadata does not match sb.userMetadata(schema); session.profile is undefined", { paths: outcome.issues.map((issue) => issue.path?.map((segment) => typeof segment === "object" ? String(segment.key) : String(segment)).join(".") ?? "") });
|
|
372
|
+
}
|
|
373
|
+
return user;
|
|
374
|
+
}
|
|
300
375
|
async function verifyOnce(credentials, modes, options, source) {
|
|
301
376
|
const { data, error } = await verifyCredentials(credentials, {
|
|
302
377
|
auth: modes,
|
|
303
378
|
env: serverEnv(options),
|
|
304
|
-
...options.audience
|
|
305
|
-
...options.issuer
|
|
379
|
+
...options.audience === void 0 ? {} : { audience: options.audience },
|
|
380
|
+
...options.issuer === void 0 ? {} : { issuer: options.issuer }
|
|
306
381
|
});
|
|
307
382
|
if (error) {
|
|
308
383
|
const kind = error.code === "JWKS_FETCH_FAILED" ? "network" : error.status >= 500 ? "unexpected" : "unauthorized";
|
|
@@ -373,7 +448,7 @@ async function resolveAuth(request, options) {
|
|
|
373
448
|
kind: "invalid",
|
|
374
449
|
reason: state.reason ?? "token",
|
|
375
450
|
error: state.error
|
|
376
|
-
} : await
|
|
451
|
+
} : await checkUser(state, options), cookies);
|
|
377
452
|
}
|
|
378
453
|
const credentials = extractCredentials(request);
|
|
379
454
|
if (credentials.token) return resolution(await verify({
|
|
@@ -401,7 +476,7 @@ async function resolveAuth(request, options) {
|
|
|
401
476
|
token: session.access_token,
|
|
402
477
|
apikey: null
|
|
403
478
|
}, ["user"], options, "cookie");
|
|
404
|
-
if (state.kind !== "invalid" || state.reason
|
|
479
|
+
if (state.kind !== "invalid" || state.reason !== "token" || state.error.kind === "network") return resolution(state, cookies);
|
|
405
480
|
if (!options.refresh) return resolution({
|
|
406
481
|
kind: "anon",
|
|
407
482
|
reason: "signed_out"
|
|
@@ -453,8 +528,8 @@ function authContext(auth) {
|
|
|
453
528
|
actor: {
|
|
454
529
|
id: auth.user.id,
|
|
455
530
|
kind: "user",
|
|
456
|
-
...auth.user.role
|
|
457
|
-
...auth.user.email
|
|
531
|
+
...auth.user.role === void 0 ? {} : { role: auth.user.role },
|
|
532
|
+
...auth.user.email === void 0 ? {} : { email: auth.user.email },
|
|
458
533
|
...impersonator ? { impersonator: impersonator.id } : {}
|
|
459
534
|
},
|
|
460
535
|
claims: auth.claims
|
|
@@ -518,4 +593,4 @@ function checkAal(auth, required) {
|
|
|
518
593
|
});
|
|
519
594
|
}
|
|
520
595
|
//#endregion
|
|
521
|
-
export { clientIp as a, applyCookieWrites as c, serializeCookie as d, sessionCookieName as f,
|
|
596
|
+
export { actClaim as _, clientIp as a, applyCookieWrites as c, serializeCookie as d, sessionCookieName as f, delegationOf as g, actorOf as h, authContext as i, parseCookies as l, refreshSession as m, amrOf as n, resolveAuth as o, writeSession as p, checkAal as r, AUTH_CACHE_HEADERS as s, aalOf as t, readSession as u, impersonatorOf as v };
|