better-supabase 0.1.0 → 0.2.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.
Files changed (115) hide show
  1. package/bin/better-supabase.js +2 -1
  2. package/dist/claims-CoTobNC-.js +25 -0
  3. package/dist/cli/bin.js +1 -1
  4. package/dist/cli/index.d.ts +2 -2
  5. package/dist/cli/index.js +1 -1
  6. package/dist/{cli-CoByWYja.js → cli-DrSgDOQC.js} +333 -159
  7. package/dist/client/index.d.ts +1 -1
  8. package/dist/{compiler-DL18ukPe.d.ts → compiler-CZ9nIuqy.d.ts} +2 -2
  9. package/dist/config/index.d.ts +3 -3
  10. package/dist/config/index.js +2 -2
  11. package/dist/{config-D-aR2ZoN.js → config-ClDXmVtY.js} +11 -10
  12. package/dist/{define-QGkq04oe.js → define-BXMYD9AY.js} +65 -62
  13. package/dist/{define-CTfWw-SS.d.ts → define-e3zI4_4g.d.ts} +27 -10
  14. package/dist/edge/index.d.ts +4 -4
  15. package/dist/edge/index.js +3 -3
  16. package/dist/entitlements-CTEBm7IB.js +20 -0
  17. package/dist/entitlements-Dng-ctuT.d.ts +42 -0
  18. package/dist/events/index.d.ts +1 -1
  19. package/dist/{events-2OJsVG_d.d.ts → events-DOt3nSO-.d.ts} +2 -2
  20. package/dist/{executor-Bje4suQF.d.ts → executor-BYXQ9AGn.d.ts} +1 -1
  21. package/dist/{executor-Bazhgsgq.d.ts → executor-CGdNQ27g.d.ts} +1 -1
  22. package/dist/{executor-Ceq60hPM.js → executor-DeppvirK.js} +1 -1
  23. package/dist/guards-BnzDvFrz.js +10 -0
  24. package/dist/hono/index.d.ts +5 -5
  25. package/dist/hono/index.js +3 -3
  26. package/dist/{index-JXHBuS53.d.ts → index-Bw1bwtC0.d.ts} +3 -3
  27. package/dist/{index-Hf8PP9sP.d.ts → index-CBDufH1M.d.ts} +15 -11
  28. package/dist/{index-cEeweTeM.d.ts → index-CG69s15v.d.ts} +3 -3
  29. package/dist/{index-DQVvHzjW.d.ts → index-Ciu5Xlml.d.ts} +3 -3
  30. package/dist/{index-Sw43J8HG.d.ts → index-D4v7sncu.d.ts} +6 -6
  31. package/dist/{index-BD6wKthp.d.ts → index-DOsjFvMf.d.ts} +28 -7
  32. package/dist/{index-DRid0qIv.d.ts → index-DbxFr7XG.d.ts} +2 -2
  33. package/dist/{index-Nxo3TJcm.d.ts → index-DuP14VHo.d.ts} +11 -3
  34. package/dist/{index-Dup-nSe9.d.ts → index-K-3WM2Hp.d.ts} +2 -2
  35. package/dist/index.d.ts +9 -9
  36. package/dist/index.js +7 -6
  37. package/dist/jobs/index.d.ts +1 -1
  38. package/dist/jobs/index.js +6 -5
  39. package/dist/{json-schema-BbwO2mZg.js → json-schema-BpYqkCKf.js} +2 -1
  40. package/dist/list/index.d.ts +1 -1
  41. package/dist/{live-6b9i72Ux.d.ts → live-DJyZMD6w.d.ts} +14 -3
  42. package/dist/logger-glM28NVN.js +21 -0
  43. package/dist/mcp/index.d.ts +41 -4
  44. package/dist/mcp/index.js +194 -61
  45. package/dist/{mfa-CpogP66z.js → mfa-CpLfJSde.js} +23 -3
  46. package/dist/next/index.d.ts +23 -23
  47. package/dist/next/index.js +9 -8
  48. package/dist/openapi/index.d.ts +1 -1
  49. package/dist/openapi/index.js +3 -3
  50. package/dist/orpc/index.d.ts +4 -4
  51. package/dist/orpc/index.js +2 -2
  52. package/dist/otel/index.d.ts +3 -3
  53. package/dist/otel/index.js +1 -1
  54. package/dist/{template-BeOGnynM.js → permdock-sql-CghlHPuH.js} +25 -1
  55. package/dist/{plugin-HvGqCurB.d.ts → plugin-C4TR9J0Z.d.ts} +2 -2
  56. package/dist/plugins/actor/index.d.ts +1 -1
  57. package/dist/plugins/rules/index.d.ts +4 -3
  58. package/dist/plugins/rules/index.js +4 -11
  59. package/dist/plugins/soft-delete/index.d.ts +2 -2
  60. package/dist/plugins/tenant/index.d.ts +10 -4
  61. package/dist/plugins/tenant/index.js +15 -13
  62. package/dist/plugins/timestamps/index.d.ts +1 -1
  63. package/dist/plugins/validation/index.d.ts +1 -1
  64. package/dist/{pool-BFeqzwQS.d.ts → pool-R2m_DkXi.d.ts} +1 -1
  65. package/dist/postgres/index.d.ts +4 -4
  66. package/dist/postgres/index.js +2 -2
  67. package/dist/{postgrest-Dm-vhY6R.d.ts → postgrest-cAEQ_IQN.d.ts} +1 -1
  68. package/dist/{problem-DPu6Fh-t.js → problem-BTkTtRV9.js} +4 -4
  69. package/dist/query/index.d.ts +1 -1
  70. package/dist/react/index.d.ts +6 -6
  71. package/dist/react/index.js +3 -3
  72. package/dist/react/server.d.ts +5 -5
  73. package/dist/react/server.js +1 -1
  74. package/dist/react/session.d.ts +1 -1
  75. package/dist/react/session.js +1 -1
  76. package/dist/{react-CrOvrnIP.js → react-C83BdBGt.js} +9 -8
  77. package/dist/{read-set-DhX2d04c.js → read-set-4lI3W_gZ.js} +1 -1
  78. package/dist/realtime/index.d.ts +3 -2
  79. package/dist/realtime/index.js +26 -11
  80. package/dist/{resolve-DW8h9NPm.d.ts → resolve-CjHILk2w.d.ts} +21 -6
  81. package/dist/{resource-CYiu6Ygi.js → resource-BHEWmQJK.js} +1 -1
  82. package/dist/{resource-SkPTRJDD.d.ts → resource-CgZa3YG_.d.ts} +4 -4
  83. package/dist/{respond-4-dhoV7x.js → respond-DMcbQwfN.js} +7 -4
  84. package/dist/{respond-PoTpy5DT.d.ts → respond-Dne1ARSa.d.ts} +14 -14
  85. package/dist/server/index.d.ts +7 -7
  86. package/dist/server/index.js +5 -5
  87. package/dist/{session-C3osL21k.d.ts → session-C3LxnQ0e.d.ts} +4 -3
  88. package/dist/{session-CD-3orO_.js → session-CqEwzVup.js} +2 -1
  89. package/dist/{simplify-oHiDKRVQ.js → simplify-bwJpLnaS.js} +12 -12
  90. package/dist/{spec-pins-t6sPaHvl.js → spec-pins-C3oc-Tdp.js} +1 -1
  91. package/dist/{spec-pins-CJPVghHy.d.ts → spec-pins-CqorWDnO.d.ts} +1 -1
  92. package/dist/{sql-M8l3fnS0.js → sql-DTjA1LPj.js} +1 -1
  93. package/dist/ssr/index.d.ts +5 -5
  94. package/dist/ssr/index.js +3 -3
  95. package/dist/{stats-3iy8u3_P.js → stats-C5W-rBfR.js} +1 -1
  96. package/dist/storage/index.d.ts +3 -2
  97. package/dist/storage/index.js +1 -1
  98. package/dist/{storage-BWXVUXGk.js → storage-DuGn38fr.js} +72 -6
  99. package/dist/testing/index.d.ts +46 -15
  100. package/dist/testing/index.js +111 -43
  101. package/dist/{types-C2fslP1z.d.ts → types-C83v5SQz.d.ts} +55 -2
  102. package/dist/{view-B0ltUmTi.d.ts → view-D1s0wa0T.d.ts} +9 -3
  103. package/dist/{view-B6F9QbG-.js → view-aOYizTeE.js} +2 -1
  104. package/dist/webhooks/index.d.ts +3 -3
  105. package/dist/webhooks/index.js +1 -1
  106. package/dist/{webhooks-DbMTJJX2.js → webhooks-DMipjhSP.js} +1 -1
  107. package/package.json +8 -4
  108. package/schemas/config-v1.json +49 -4
  109. package/schemas/doctor-report-v1.json +1 -0
  110. package/schemas/snapshot-v2.json +4 -0
  111. package/skills/better-supabase/references/plugins.md +9 -0
  112. package/skills/better-supabase-api/SKILL.md +2 -2
  113. package/skills/better-supabase-testing/SKILL.md +4 -3
  114. package/dist/entitlements-BBg61DQZ.d.ts +0 -20
  115. package/dist/entitlements-BC_khwRy.js +0 -17
package/dist/mcp/index.js CHANGED
@@ -1,15 +1,21 @@
1
1
  import { n as dbError } from "../errors-Bl3rkOzo.js";
2
2
  import { t as validate } from "../standard-FUi3Z3-7.js";
3
- import { o as toProblem } from "../problem-DPu6Fh-t.js";
4
- import { t as SPEC_PINS } from "../spec-pins-t6sPaHvl.js";
5
- import { t as buildJsonSchema } from "../json-schema-BbwO2mZg.js";
6
- import { a as settle, c as withExtra, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-4-dhoV7x.js";
7
- import { t as defineResource } from "../resource-CYiu6Ygi.js";
8
- import { fromSupabaseUrl, resourceMetadataResponse, unauthorizedResponse } from "@supabase/server/oauth-protected-resource";
3
+ import { o as toProblem } from "../problem-BTkTtRV9.js";
4
+ import { t as SPEC_PINS } from "../spec-pins-C3oc-Tdp.js";
5
+ import { t as buildJsonSchema } from "../json-schema-BpYqkCKf.js";
6
+ import { a as settle, c as withExtra, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-DMcbQwfN.js";
7
+ import { t as defineResource } from "../resource-BHEWmQJK.js";
8
+ import { fromSupabaseUrl, unauthorizedResponse } from "@supabase/server/oauth-protected-resource";
9
9
  //#region src/mcp/index.ts
10
10
  const MCP_PROTOCOL_VERSION = SPEC_PINS.mcp;
11
11
  const WELL_KNOWN = "/.well-known/oauth-protected-resource";
12
- const SUPPORTED_VERSIONS = /* @__PURE__ */ new Set([MCP_PROTOCOL_VERSION, "2025-03-26"]);
12
+ /** Revisions before 2026-07-28 open with an `initialize` handshake. */
13
+ const LEGACY_VERSIONS = [
14
+ "2025-11-25",
15
+ "2025-06-18",
16
+ "2025-03-26"
17
+ ];
18
+ const SUPPORTED_VERSIONS = [MCP_PROTOCOL_VERSION, ...LEGACY_VERSIONS];
13
19
  /** A custom tool. Pass it in `createMcp({ tools: [...] })`. */
14
20
  function defineTool(definition) {
15
21
  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 +36,7 @@ function defineTool(definition) {
30
36
  ...definition.annotations ? { annotations: definition.annotations } : {}
31
37
  },
32
38
  ...definition.input ? { input: definition.input } : {},
39
+ ...definition.meta === void 0 ? {} : { meta: definition.meta },
33
40
  run: (args, ctx) => definition.run(args, ctx)
34
41
  };
35
42
  }
@@ -143,18 +150,38 @@ const PARSE_ERROR = -32700;
143
150
  const INVALID_REQUEST = -32600;
144
151
  const METHOD_NOT_FOUND = -32601;
145
152
  const INVALID_PARAMS = -32602;
146
- function rpcError(id, code, message, status = 200) {
153
+ const HEADER_MISMATCH = -32020;
154
+ const UNSUPPORTED_PROTOCOL_VERSION = -32022;
155
+ const META_VERSION = "io.modelcontextprotocol/protocolVersion";
156
+ const META_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
157
+ const META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";
158
+ /** How long clients may cache `tools/list` and `server/discover` (per token). */
159
+ const LIST_TTL_MS = 3e5;
160
+ function rpcError(id, code, message, status = 200, data) {
147
161
  return Response.json({
148
162
  jsonrpc: "2.0",
149
163
  id: id ?? null,
150
164
  error: {
151
165
  code,
152
- message
166
+ message,
167
+ ...data ? { data } : {}
153
168
  }
154
169
  }, { status });
155
170
  }
171
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
172
+ /** An `Mcp-Name` value, decoding the `=?base64?...?=` sentinel. */
173
+ function headerName(value) {
174
+ const encoded = /^=\?base64\?(.*)\?=$/.exec(value)?.[1];
175
+ if (encoded === void 0) return value;
176
+ try {
177
+ return new TextDecoder().decode(Uint8Array.from(atob(encoded), (char) => char.charCodeAt(0)));
178
+ } catch {
179
+ return value;
180
+ }
181
+ }
182
+ const quoted = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
156
183
  function isRpcRequest(value) {
157
- return typeof value === "object" && value !== null && value["jsonrpc"] === "2.0" && typeof value["method"] === "string";
184
+ return isObject(value) && value["jsonrpc"] === "2.0" && typeof value["method"] === "string" && (value["params"] === void 0 || isObject(value["params"]));
158
185
  }
159
186
  function textResult(value, isError = false) {
160
187
  const structured = typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
@@ -185,43 +212,75 @@ function createMcp(sb, options) {
185
212
  config: { json: options.json ?? {} }
186
213
  })["$defs"];
187
214
  const registry = /* @__PURE__ */ new Map();
188
- const register = (info, call) => {
189
- if (registry.has(info.name)) throw new TypeError(`Duplicate MCP tool "${info.name}"`);
190
- registry.set(info.name, {
191
- info,
192
- call
193
- });
215
+ const register = (entry) => {
216
+ if (registry.has(entry.info.name)) throw new TypeError(`Duplicate MCP tool "${entry.info.name}"`);
217
+ registry.set(entry.info.name, entry);
194
218
  };
195
219
  const failure = (error) => textResult(toProblem(error, { expose }), true);
220
+ const refused = async (tool, args, ctx) => {
221
+ if (!options.authorize) return void 0;
222
+ const decision = await options.authorize(ctx, tool, args);
223
+ if (decision.allowed) return void 0;
224
+ return {
225
+ refusal: decision.reason ?? `Not allowed to call ${tool.info.name}`,
226
+ scopes: decision.scopes ?? []
227
+ };
228
+ };
229
+ const isVisible = async (tool, ctx) => !options.visible || options.visible(ctx, tool);
196
230
  for (const [table, raw] of Object.entries(options.resources ?? {})) {
197
231
  if (!raw) continue;
198
232
  const resource = defineResource(sb, table, raw === true ? {} : raw);
199
- for (const { info, operation } of tableTools(resource, defs)) register(info, async (args, ctx) => {
200
- const input = args ?? {};
201
- const id = resource.keyParam ? input[resource.keyParam] : void 0;
202
- const result = await resource.execute(ctx.db, operation, {
203
- id,
204
- query: input,
205
- data: operation === "update" ? input["patch"] : input
233
+ for (const { info, operation } of tableTools(resource, defs)) {
234
+ const ref = {
235
+ info,
236
+ meta: void 0
237
+ };
238
+ register({
239
+ ...ref,
240
+ async call(args, ctx) {
241
+ const input = args ?? {};
242
+ const refusal = await refused(ref, input, ctx);
243
+ if (refusal) return refusal;
244
+ const id = resource.keyParam ? input[resource.keyParam] : void 0;
245
+ const result = await resource.execute(ctx.db, operation, {
246
+ id,
247
+ query: input,
248
+ data: operation === "update" ? input["patch"] : input
249
+ });
250
+ if (!result.ok) return failure(result.error);
251
+ return textResult(operation === "delete" ? { deleted: true } : result.data);
252
+ }
206
253
  });
207
- if (!result.ok) return failure(result.error);
208
- return textResult(operation === "delete" ? { deleted: true } : result.data);
209
- });
254
+ }
210
255
  }
211
256
  const addTool = (tool) => {
212
- register(tool.info, async (args, ctx) => {
213
- let value = args ?? {};
214
- if (tool.input) {
215
- const parsed = await validate(tool.input, value, "arguments");
216
- if (!parsed.ok) return failure(parsed.error);
217
- value = parsed.data;
257
+ const ref = {
258
+ info: tool.info,
259
+ meta: tool.meta
260
+ };
261
+ register({
262
+ ...ref,
263
+ async call(args, ctx) {
264
+ let value = args ?? {};
265
+ if (tool.input) {
266
+ const parsed = await validate(tool.input, value, "arguments");
267
+ if (!parsed.ok) return failure(parsed.error);
268
+ value = parsed.data;
269
+ }
270
+ const refusal = await refused(ref, value, ctx);
271
+ if (refusal) return refusal;
272
+ const outcome = await settle(() => tool.run(value, ctx));
273
+ return outcome.ok ? textResult(outcome.data) : failure(outcome.error);
218
274
  }
219
- const outcome = await settle(() => tool.run(value, ctx));
220
- return outcome.ok ? textResult(outcome.data) : failure(outcome.error);
221
275
  });
222
276
  };
223
277
  for (const tool of options.tools ?? []) addTool(tool);
224
278
  const listTools = () => [...registry.values()].map((entry) => entry.info);
279
+ const visibleTools = async (ctx) => {
280
+ const entries = [...registry.values()];
281
+ const shown = await Promise.all(entries.map((entry) => isVisible(entry, ctx)));
282
+ return entries.filter((_, index) => shown[index]).map((e) => e.info);
283
+ };
225
284
  const authorizationServers = () => options.authorizationServers ?? [fromSupabaseUrl(server.env.url)];
226
285
  const resourceOf = (request) => {
227
286
  if (typeof options.resource === "function") return options.resource(request);
@@ -233,15 +292,63 @@ function createMcp(sb, options) {
233
292
  const resource = new URL(resourceOf(request));
234
293
  return `${resource.origin}${WELL_KNOWN}${resource.pathname.replace(/\/$/, "")}`;
235
294
  };
236
- const call = async (name, args, ctx) => {
295
+ const invoke = async (name, args, ctx, checkVisible = true) => {
237
296
  const entry = registry.get(name);
238
- if (!entry) return failure(dbError("not_found", `Unknown tool "${name}"`));
239
297
  try {
298
+ if (!entry || checkVisible && !await isVisible(entry, ctx)) return failure(dbError("not_found", `Unknown tool "${name}"`));
240
299
  return await entry.call(args, ctx);
241
300
  } catch (cause) {
242
301
  return failure(dbError("unexpected", expose && cause instanceof Error ? cause.message : "The tool failed"));
243
302
  }
244
303
  };
304
+ const isRefusal = (value) => "refusal" in value;
305
+ const call = async (name, args, ctx) => {
306
+ const outcome = await invoke(name, args, ctx);
307
+ return isRefusal(outcome) ? failure(dbError("forbidden", outcome.refusal)) : outcome;
308
+ };
309
+ const scopes = (options.scopes ?? []).filter((scope) => scope !== "offline_access");
310
+ const serverInfo = {
311
+ name: options.name,
312
+ version: options.version,
313
+ ...options.title ? { title: options.title } : {}
314
+ };
315
+ const capabilities = { tools: { listChanged: false } };
316
+ const unauthorized = (request) => {
317
+ const response = unauthorizedResponse(request, { resourceMetadataUrl: metadataUrl(request) });
318
+ if (scopes.length > 0) response.headers.set("www-authenticate", `${response.headers.get("www-authenticate") ?? "Bearer"}, scope=${quoted(scopes.join(" "))}`);
319
+ return response;
320
+ };
321
+ const forbidden = (request, message, needed = [], id = null) => {
322
+ const all = [.../* @__PURE__ */ new Set([...scopes, ...needed])];
323
+ const challenge = [
324
+ "Bearer error=\"insufficient_scope\"",
325
+ `error_description=${quoted(message)}`,
326
+ ...all.length > 0 ? [`scope=${quoted(all.join(" "))}`] : [],
327
+ `resource_metadata=${quoted(metadataUrl(request))}`
328
+ ].join(", ");
329
+ const response = rpcError(id, INVALID_REQUEST, message, 403);
330
+ response.headers.set("www-authenticate", challenge);
331
+ return response;
332
+ };
333
+ const unsupportedVersion = (id, requested) => rpcError(id, UNSUPPORTED_PROTOCOL_VERSION, "Unsupported protocol version", 400, {
334
+ supported: [...SUPPORTED_VERSIONS],
335
+ requested
336
+ });
337
+ /** 2026-07-28 requests carry their version and capabilities, mirrored in headers. */
338
+ const checkModern = (message, header, headers) => {
339
+ const meta = message.params?.["_meta"];
340
+ const version = isObject(meta) ? meta[META_VERSION] : void 0;
341
+ 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);
342
+ if (version !== MCP_PROTOCOL_VERSION) return unsupportedVersion(message.id, version);
343
+ const mismatch = (detail) => rpcError(message.id, HEADER_MISMATCH, `Header mismatch: ${detail}`, 400);
344
+ if (header !== version) return mismatch(`MCP-Protocol-Version must be ${version}`);
345
+ if (headers.get("mcp-method") !== message.method) return mismatch(`Mcp-Method must be ${message.method}`);
346
+ const name = message.params?.["name"];
347
+ if (message.method === "tools/call" && typeof name === "string") {
348
+ const sent = headers.get("mcp-name");
349
+ if (sent === null || headerName(sent) !== name) return mismatch(`Mcp-Name must be ${name}`);
350
+ }
351
+ };
245
352
  const handler = async (request) => {
246
353
  const origin = request.headers.get("origin");
247
354
  if (origin && options.allowedOrigins && !options.allowedOrigins.includes(origin)) return rpcError(null, INVALID_REQUEST, "Origin not allowed", 403);
@@ -249,14 +356,11 @@ function createMcp(sb, options) {
249
356
  status: 405,
250
357
  headers: { allow: "POST" }
251
358
  });
252
- const version = request.headers.get("mcp-protocol-version");
253
- if (version && !SUPPORTED_VERSIONS.has(version)) return rpcError(null, INVALID_REQUEST, `Unsupported MCP-Protocol-Version ${version}`, 400);
359
+ const header = request.headers.get("mcp-protocol-version");
360
+ if (header && !SUPPORTED_VERSIONS.includes(header)) return unsupportedVersion(null, header);
254
361
  const ctx = await server.context(request);
255
362
  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
- }
363
+ if (denied) return denied.kind === "unauthorized" ? unauthorized(request) : forbidden(request, denied.message);
260
364
  let message;
261
365
  try {
262
366
  message = await request.json();
@@ -264,43 +368,72 @@ function createMcp(sb, options) {
264
368
  return rpcError(null, PARSE_ERROR, "Parse error", 400);
265
369
  }
266
370
  if (!isRpcRequest(message)) return rpcError(null, INVALID_REQUEST, "Expected one JSON-RPC 2.0 message", 400);
371
+ const meta = message.params?.["_meta"];
372
+ const modern = isObject(meta) && meta[META_VERSION] !== void 0 || header === MCP_PROTOCOL_VERSION;
373
+ if (modern) {
374
+ const invalid = checkModern(message, header, request.headers);
375
+ if (invalid) return invalid;
376
+ }
267
377
  if (message.id === void 0) return new Response(null, { status: 202 });
378
+ const toolContext = () => withExtra(ctx, {
379
+ request,
380
+ signal: request.signal
381
+ });
268
382
  const reply = (result) => Response.json({
269
383
  jsonrpc: "2.0",
270
384
  id: message.id,
271
- result
385
+ result: modern ? {
386
+ ...result,
387
+ resultType: "complete",
388
+ _meta: { [META_SERVER_INFO]: serverInfo }
389
+ } : result
272
390
  });
391
+ const notFound = () => rpcError(message.id, METHOD_NOT_FOUND, `Method not found: ${message.method}`, modern ? 404 : 200);
273
392
  switch (message.method) {
393
+ case "server/discover": return reply({
394
+ supportedVersions: [...SUPPORTED_VERSIONS],
395
+ capabilities,
396
+ ...options.instructions ? { instructions: options.instructions } : {},
397
+ ttlMs: LIST_TTL_MS,
398
+ cacheScope: "private"
399
+ });
274
400
  case "initialize": {
401
+ if (modern) return notFound();
275
402
  const requested = message.params?.["protocolVersion"];
276
403
  return reply({
277
- protocolVersion: typeof requested === "string" && SUPPORTED_VERSIONS.has(requested) ? requested : MCP_PROTOCOL_VERSION,
278
- capabilities: { tools: { listChanged: false } },
279
- serverInfo: {
280
- name: options.name,
281
- version: options.version,
282
- ...options.title ? { title: options.title } : {}
283
- },
404
+ protocolVersion: typeof requested === "string" && LEGACY_VERSIONS.includes(requested) ? requested : LEGACY_VERSIONS[0],
405
+ capabilities,
406
+ serverInfo,
284
407
  ...options.instructions ? { instructions: options.instructions } : {}
285
408
  });
286
409
  }
287
- case "ping": return reply({});
288
- case "tools/list": return reply({ tools: listTools() });
410
+ case "ping": return modern ? notFound() : reply({});
411
+ case "tools/list": return reply({
412
+ tools: await visibleTools(toolContext()),
413
+ ...modern ? {
414
+ ttlMs: LIST_TTL_MS,
415
+ cacheScope: "private"
416
+ } : {}
417
+ });
289
418
  case "tools/call": {
290
419
  const name = message.params?.["name"];
291
- if (typeof name !== "string" || !registry.has(name)) return rpcError(message.id, INVALID_PARAMS, `Unknown tool ${JSON.stringify(name)}`);
292
- return reply(await call(name, message.params?.["arguments"], withExtra(ctx, {
293
- request,
294
- signal: request.signal
295
- })));
420
+ const entry = typeof name === "string" ? registry.get(name) : void 0;
421
+ const context = toolContext();
422
+ if (!entry || !await isVisible(entry, context)) return rpcError(message.id, INVALID_PARAMS, `Unknown tool ${JSON.stringify(name)}`);
423
+ const outcome = await invoke(entry.info.name, message.params?.["arguments"], context, false);
424
+ if (!isRefusal(outcome)) return reply(outcome);
425
+ if (outcome.scopes.length > 0) return forbidden(request, outcome.refusal, outcome.scopes, message.id);
426
+ return reply(failure(dbError("forbidden", outcome.refusal)));
296
427
  }
297
- default: return rpcError(message.id, METHOD_NOT_FOUND, `Method not found: ${message.method}`);
428
+ default: return notFound();
298
429
  }
299
430
  };
300
- const metadata = (request) => resourceMetadataResponse(request, {
431
+ const metadata = (request) => Response.json({
301
432
  resource: resourceOf(request),
302
- authorizationServers: [...authorizationServers()]
303
- });
433
+ authorization_servers: [...authorizationServers()],
434
+ bearer_methods_supported: ["header"],
435
+ ...scopes.length > 0 ? { scopes_supported: scopes } : {}
436
+ }, { headers: { "access-control-allow-origin": "*" } });
304
437
  const mcp = extendServer(server, {
305
438
  get tools() {
306
439
  return listTools();
@@ -1,4 +1,5 @@
1
1
  import { n as dbError } from "./errors-Bl3rkOzo.js";
2
+ import { t as consoleLogger } from "./logger-glM28NVN.js";
2
3
  import { DEFAULT_COOKIE_OPTIONS, createChunks, isChunkLike, parseCookieHeader, serializeCookieHeader, stringFromBase64URL, stringToBase64URL } from "@supabase/ssr";
3
4
  import { extractCredentials, verifyCredentials } from "@supabase/server/core";
4
5
  //#region src/auth/impersonation.ts
@@ -257,7 +258,7 @@ async function verify(credentials, modes, options, source) {
257
258
  const memo = token ? memoFor(options) : void 0;
258
259
  if (token && memo) {
259
260
  const hit = remembered(memo, token, Math.floor((options.now ?? Date.now)() / 1e3));
260
- if (hit) return checkClaims({
261
+ if (hit) return checkUser({
261
262
  ...hit,
262
263
  source
263
264
  }, options);
@@ -270,7 +271,7 @@ async function verify(credentials, modes, options, source) {
270
271
  user: state.user,
271
272
  expiresAt: state.expiresAt
272
273
  });
273
- return checkClaims(state, options);
274
+ return checkUser(state, options);
274
275
  }
275
276
  /** Validates a user's claims against `options.claims`, keeping every JWT claim. */
276
277
  async function checkClaims(state, options) {
@@ -297,6 +298,25 @@ async function checkClaims(state, options) {
297
298
  }
298
299
  } : state;
299
300
  }
301
+ const warnedMetadata = /* @__PURE__ */ new WeakSet();
302
+ /** Claims first, then `user_metadata`, whose failure never invalidates the session. */
303
+ async function checkUser(state, options) {
304
+ const checked = await checkClaims(state, options);
305
+ const schema = options.userMetadata;
306
+ if (checked.kind !== "user" || !schema) return checked;
307
+ const { profile: _untrusted, ...user } = checked;
308
+ let outcome = schema["~standard"].validate(checked.user.userMetadata ?? checked.claims.user_metadata ?? {});
309
+ if (outcome instanceof Promise) outcome = await outcome;
310
+ if (!outcome.issues) return {
311
+ ...user,
312
+ profile: outcome.value
313
+ };
314
+ if (!warnedMetadata.has(schema)) {
315
+ warnedMetadata.add(schema);
316
+ (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(".") ?? "") });
317
+ }
318
+ return user;
319
+ }
300
320
  async function verifyOnce(credentials, modes, options, source) {
301
321
  const { data, error } = await verifyCredentials(credentials, {
302
322
  auth: modes,
@@ -373,7 +393,7 @@ async function resolveAuth(request, options) {
373
393
  kind: "invalid",
374
394
  reason: state.reason ?? "token",
375
395
  error: state.error
376
- } : await checkClaims(state, options), cookies);
396
+ } : await checkUser(state, options), cookies);
377
397
  }
378
398
  const credentials = extractCredentials(request);
379
399
  if (credentials.token) return resolution(await verify({
@@ -1,12 +1,12 @@
1
- import { n as AnyModels, t as AnyFunctions } from "../types-C2fslP1z.js";
1
+ import { n as AnyModels, t as AnyFunctions } from "../types-C83v5SQz.js";
2
2
  import { p as DbError } from "../result-CTkCZ6JA.js";
3
- import { B as ReadSet, P as DbStats, Z as QuerySpec, l as CacheAdapter, t as BetterSupabase } from "../define-CTfWw-SS.js";
3
+ import { B as ReadSet, P as DbStats, Z as QuerySpec, l as CacheAdapter, t as BetterSupabase } from "../define-e3zI4_4g.js";
4
4
  import { n as AmrEntry, s as Impersonator, t as Aal } from "../mfa-TVia554c.js";
5
- import { r as AuthState } from "../resolve-DW8h9NPm.js";
6
- import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions, t as AuthKind } from "../respond-PoTpy5DT.js";
7
- import { n as toSession, t as AuthSession } from "../view-B0ltUmTi.js";
8
- import { r as LiveCountSeed, t as CountRunner } from "../live-6b9i72Ux.js";
9
- import { n as hasEntitlement, t as EntitlementKey } from "../entitlements-BBg61DQZ.js";
5
+ import { r as AuthState } from "../resolve-CjHILk2w.js";
6
+ import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions, t as AuthKind } from "../respond-Dne1ARSa.js";
7
+ import { n as toSession, t as AuthSession } from "../view-D1s0wa0T.js";
8
+ import { r as LiveCountSeed, t as CountRunner } from "../live-DJyZMD6w.js";
9
+ import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-Dng-ctuT.js";
10
10
  import { NextRequest } from "next/server.js";
11
11
  import { StandardSchemaV1 } from "@standard-schema/spec";
12
12
  //#region src/next/collector.d.ts
@@ -38,7 +38,7 @@ export interface NextDebugOptions {
38
38
  }
39
39
  /** Response header with the URL of the request's totals on `next.debugRoute()`. */
40
40
  export declare const STATS_URL_HEADER = "x-bs-stats";
41
- export interface ProxyOptions<C = unknown> {
41
+ export interface ProxyOptions<C = unknown, U = unknown> {
42
42
  /**
43
43
  * Another middleware to compose with, such as next-intl's. It runs on the
44
44
  * original request, alongside auth; its rewrite, redirect or request
@@ -46,9 +46,9 @@ export interface ProxyOptions<C = unknown> {
46
46
  */
47
47
  readonly before?: (request: NextRequest) => Response | undefined | Promise<Response | undefined>;
48
48
  /** Answer before rendering, e.g. redirect signed-out users. Refreshed cookies are kept. */
49
- readonly protect?: (auth: AuthState<C>, request: NextRequest) => Response | undefined | Promise<Response | undefined>;
49
+ readonly protect?: (auth: AuthState<C, U>, request: NextRequest) => Response | undefined | Promise<Response | undefined>;
50
50
  /** Post-processes the final response; return a new one to replace it. */
51
- readonly after?: (response: Response, auth: AuthState<C>) => Response | undefined | void | Promise<Response | undefined | void>;
51
+ readonly after?: (response: Response, auth: AuthState<C, U>) => Response | undefined | void | Promise<Response | undefined | void>;
52
52
  /**
53
53
  * Adds `Server-Timing: bs-proxy;dur=..., bs-verify;dur=...` (ms): the whole
54
54
  * proxy, and resolving the session (a local verify, or a refresh).
@@ -76,25 +76,25 @@ type Unwrapped<T> = T extends {
76
76
  } | {
77
77
  readonly ok: false;
78
78
  } ? D : T;
79
- export interface BetterNext<M extends AnyModels, F extends AnyFunctions, E, C = unknown> extends BetterServer<M, F, E, C> {
79
+ export interface BetterNext<M extends AnyModels, F extends AnyFunctions, E, C = unknown, U = unknown> extends BetterServer<M, F, E, C, U> {
80
80
  /** `proxy.ts`: refreshes sessions for page loads and server actions. */
81
- proxy(request: NextRequest, options?: ProxyOptions<C>): Promise<Response>;
81
+ proxy(request: NextRequest, options?: ProxyOptions<C, U>): Promise<Response>;
82
82
  /** Server Components and actions: the caller's context, memoized per request. */
83
- server(): Promise<ServerContext<M, F, E, C>>;
83
+ server(): Promise<ServerContext<M, F, E, C, U>>;
84
84
  /**
85
85
  * The verified caller as serializable data (no token, no clients), memoized
86
86
  * per request. Wrap it in a `'use cache: private'` function with Cache
87
87
  * Components and pass the promise to `<SessionProvider>`.
88
88
  */
89
- session(): Promise<AuthSession<C>>;
89
+ session(): Promise<AuthSession<C, U>>;
90
90
  /**
91
91
  * The context for a session and its token, without reading the request.
92
92
  * The token is verified again (memoized, no network call); a token for
93
93
  * another user than `session` gives an `invalid` context.
94
94
  */
95
- serverFor(session: AuthSession<C>, options: {
95
+ serverFor(session: AuthSession<C, U>, options: {
96
96
  readonly token: string | null;
97
- }): Promise<ServerContext<M, F, E, C>>;
97
+ }): Promise<ServerContext<M, F, E, C, U>>;
98
98
  /**
99
99
  * First statement of an app-authored `'use cache: private'` function: sets
100
100
  * `cacheLife` from the session's expiry (`sessionStale`), tags the entry
@@ -108,7 +108,7 @@ export interface BetterNext<M extends AnyModels, F extends AnyFunctions, E, C =
108
108
  * }
109
109
  * ```
110
110
  */
111
- cached(options?: CachedOptions): Promise<CachedContext<M, F, E, C>>;
111
+ cached(options?: CachedOptions): Promise<CachedContext<M, F, E, C, U>>;
112
112
  /** Drops every `next.cached()` entry of a user, e.g. after a role change. */
113
113
  invalidateSession(userId: string): void;
114
114
  /**
@@ -120,13 +120,13 @@ export interface BetterNext<M extends AnyModels, F extends AnyFunctions, E, C =
120
120
  */
121
121
  liveCount<T extends Extract<keyof M, string>>(spec: QuerySpec<T, "count", number>, db?: CountRunner): Promise<LiveCountSeed<T>>;
122
122
  /** Route handler with auth, Result unwrapping and Problem Details errors. */
123
- route<P = Record<string, string | string[]>>(handler: (request: NextRequest, ctx: ServerContext<M, F, E, C> & {
123
+ route<P = Record<string, string | string[]>>(handler: (request: NextRequest, ctx: ServerContext<M, F, E, C, U> & {
124
124
  readonly params: P;
125
125
  }) => unknown, options?: GuardOptions): (request: NextRequest, segment: {
126
126
  readonly params: Promise<P>;
127
127
  }) => Promise<Response>;
128
128
  /** Server action returning a serializable `ActionResult`. */
129
- action<S extends StandardSchemaV1 | undefined, T>(options: ActionOptions<S>, fn: (input: ActionParsed<S>, ctx: ServerContext<M, F, E, C>) => Promise<T> | T): (input: ActionInput<S>) => Promise<ActionResult<Unwrapped<Awaited<T>>>>;
129
+ action<S extends StandardSchemaV1 | undefined, T>(options: ActionOptions<S>, fn: (input: ActionParsed<S>, ctx: ServerContext<M, F, E, C, U>) => Promise<T> | T): (input: ActionInput<S>) => Promise<ActionResult<Unwrapped<Awaited<T>>>>;
130
130
  /** Tags the current `"use cache"` scope with a table (and row) tag. */
131
131
  cacheTag(table: Extract<keyof M, string>, id?: string | number): void;
132
132
  /**
@@ -159,8 +159,8 @@ export interface CachedOptions {
159
159
  readonly expire?: number;
160
160
  };
161
161
  }
162
- export type CachedContext<M extends AnyModels, F extends AnyFunctions, E, C = unknown> = ServerContext<M, F, E, C> & {
163
- readonly session: AuthSession<C>;
162
+ export type CachedContext<M extends AnyModels, F extends AnyFunctions, E, C = unknown, U = unknown> = ServerContext<M, F, E, C, U> & {
163
+ readonly session: AuthSession<C, U>;
164
164
  };
165
165
  /** The tag `next.cached()` puts on a user's entries. */
166
166
  export declare function sessionTag(userId: string): string;
@@ -205,6 +205,6 @@ export declare function shouldRefresh(request: Request): boolean;
205
205
  * export const next = createNext(sb);
206
206
  * ```
207
207
  */
208
- export declare function createNext<M extends AnyModels, D, F extends AnyFunctions, E, C = unknown>(sb: BetterSupabase<M, D, F, E, C>, options?: NextOptions): BetterNext<M, F, E, C>;
208
+ export declare function createNext<M extends AnyModels, D, F extends AnyFunctions, E, C = unknown, U = unknown>(sb: BetterSupabase<M, D, F, E, C, U>, options?: NextOptions): BetterNext<M, F, E, C, U>;
209
209
  //#endregion
210
- export { type Aal, type AmrEntry, type AuthKind, type AuthSession, type DbBudget, type DbStats, type EntitlementKey, type GuardOptions, type Impersonator, hasEntitlement, toSession };
210
+ export { type Aal, type AmrEntry, type AuthKind, type AuthSession, type DbBudget, type DbStats, type EntitlementKey, type GuardOptions, type Impersonator, type MembershipClaim, hasEntitlement, toSession };
@@ -1,12 +1,13 @@
1
1
  import { n as dbError } from "../errors-Bl3rkOzo.js";
2
- import { n as StatsRecorder, t as EMPTY_STATS } from "../stats-3iy8u3_P.js";
3
- import { o as isReadSet } from "../read-set-DhX2d04c.js";
2
+ import { n as StatsRecorder, t as EMPTY_STATS } from "../stats-C5W-rBfR.js";
3
+ import { o as isReadSet } from "../read-set-4lI3W_gZ.js";
4
4
  import { t as validate } from "../standard-FUi3Z3-7.js";
5
- import { a as problemResponse } from "../problem-DPu6Fh-t.js";
6
- import { t as hasEntitlement } from "../entitlements-BC_khwRy.js";
7
- import { a as settle, c as withExtra, d as primaryCookie, i as respond, n as guard, o as createServer, s as extendServer, t as defaultExpose, u as PRIMARY_COOKIE } from "../respond-4-dhoV7x.js";
8
- import { r as checkAal } from "../mfa-CpogP66z.js";
9
- import { t as toSession } from "../view-B6F9QbG-.js";
5
+ import { a as problemResponse } from "../problem-BTkTtRV9.js";
6
+ import { t as isList } from "../guards-BnzDvFrz.js";
7
+ import { t as hasEntitlement } from "../entitlements-CTEBm7IB.js";
8
+ import { a as settle, c as withExtra, d as primaryCookie, i as respond, n as guard, o as createServer, s as extendServer, t as defaultExpose, u as PRIMARY_COOKIE } from "../respond-DMcbQwfN.js";
9
+ import { r as checkAal } from "../mfa-CpLfJSde.js";
10
+ import { t as toSession } from "../view-aOYizTeE.js";
10
11
  import { cache } from "react";
11
12
  import { cacheLife, cacheTag, revalidateTag, updateTag } from "next/cache.js";
12
13
  import { cookies, headers } from "next/headers.js";
@@ -411,7 +412,7 @@ function createNext(sb, options = {}) {
411
412
  cacheTag(tagFor(table), ...id === void 0 ? [] : [tagFor(table, id)]);
412
413
  },
413
414
  cacheTags(target) {
414
- const specs = isReadSet(target) ? Object.values(target.specs) : Array.isArray(target) ? target : [target];
415
+ const specs = isReadSet(target) ? Object.values(target.specs) : isList(target) ? target : [target];
415
416
  const tags = /* @__PURE__ */ new Set();
416
417
  for (const spec of specs) {
417
418
  for (const table of sb.tablesOf(spec)) tags.add(tagFor(table));
@@ -1,2 +1,2 @@
1
- import { a as ResourceOptions, i as ResourceOperation, n as OpenApiOptions, o as createOpenApi, r as PROBLEM_SCHEMA, t as OpenApiDocument } from "../index-DRid0qIv.js";
1
+ import { a as ResourceOptions, i as ResourceOperation, n as OpenApiOptions, o as createOpenApi, r as PROBLEM_SCHEMA, t as OpenApiDocument } from "../index-DbxFr7XG.js";
2
2
  export { OpenApiDocument, OpenApiOptions, PROBLEM_SCHEMA, ResourceOperation, ResourceOptions, createOpenApi };
@@ -1,6 +1,6 @@
1
- import { n as PROBLEM_TYPE_BASE, t as PROBLEM_CONTENT_TYPE } from "../problem-DPu6Fh-t.js";
2
- import { t as SPEC_PINS } from "../spec-pins-t6sPaHvl.js";
3
- import { t as buildJsonSchema } from "../json-schema-BbwO2mZg.js";
1
+ import { n as PROBLEM_TYPE_BASE, t as PROBLEM_CONTENT_TYPE } from "../problem-BTkTtRV9.js";
2
+ import { t as SPEC_PINS } from "../spec-pins-C3oc-Tdp.js";
3
+ import { t as buildJsonSchema } from "../json-schema-BpYqkCKf.js";
4
4
  //#region src/openapi/index.ts
5
5
  /** JSON Schema for RFC 9457 Problem Details as better-supabase sends them. */
6
6
  const PROBLEM_SCHEMA = {
@@ -1,8 +1,8 @@
1
- import { n as AnyModels, t as AnyFunctions } from "../types-C2fslP1z.js";
1
+ import { n as AnyModels, t as AnyFunctions } from "../types-C83v5SQz.js";
2
2
  import { o as Result, p as DbError } from "../result-CTkCZ6JA.js";
3
- import { t as BetterSupabase } from "../define-CTfWw-SS.js";
4
- import { r as AuthState } from "../resolve-DW8h9NPm.js";
5
- import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-PoTpy5DT.js";
3
+ import { t as BetterSupabase } from "../define-e3zI4_4g.js";
4
+ import { r as AuthState } from "../resolve-CjHILk2w.js";
5
+ import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-Dne1ARSa.js";
6
6
  import { r as ProblemDetails } from "../problem-DlgB0lYl.js";
7
7
  import { DecoratedMiddleware, ORPCError } from "@orpc/server";
8
8
  //#region src/orpc/index.d.ts
@@ -1,6 +1,6 @@
1
1
  import { r as dbErrorOf } from "../errors-Bl3rkOzo.js";
2
- import { o as toProblem } from "../problem-DPu6Fh-t.js";
3
- import { a as settle, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-4-dhoV7x.js";
2
+ import { o as toProblem } from "../problem-BTkTtRV9.js";
3
+ import { a as settle, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-DMcbQwfN.js";
4
4
  import { COMMON_ERROR_STATUS_MAP, ORPCError, os } from "@orpc/server";
5
5
  //#region src/orpc/index.ts
6
6
  const CODES = new Map(Object.entries(COMMON_ERROR_STATUS_MAP).map(([code, status]) => [status, code]));
@@ -1,6 +1,6 @@
1
- import { a as EventHub } from "../events-2OJsVG_d.js";
2
- import { f as Plugin } from "../plugin-HvGqCurB.js";
3
- import { t as SPEC_PINS } from "../spec-pins-CJPVghHy.js";
1
+ import { a as EventHub } from "../events-DOt3nSO-.js";
2
+ import { f as Plugin } from "../plugin-C4TR9J0Z.js";
3
+ import { t as SPEC_PINS } from "../spec-pins-CqorWDnO.js";
4
4
  import { Attributes, Meter, Tracer } from "@opentelemetry/api";
5
5
  //#region src/otel/index.d.ts
6
6
  export declare const INSTRUMENTATION_NAME = "better-supabase";
@@ -1,5 +1,5 @@
1
1
  import { n as definePlugin } from "../plugin-BqR6wKMB.js";
2
- import { t as SPEC_PINS } from "../spec-pins-t6sPaHvl.js";
2
+ import { t as SPEC_PINS } from "../spec-pins-C3oc-Tdp.js";
3
3
  import "../version-Cqq1m3UY.js";
4
4
  import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
5
5
  //#region src/otel/index.ts