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.
Files changed (149) hide show
  1. package/README.md +34 -31
  2. package/dist/base64-CQeB1eSf.js +42 -0
  3. package/dist/claims-CoTobNC-.js +25 -0
  4. package/dist/client/index.d.ts +1 -1
  5. package/dist/client/index.js +4 -14
  6. package/dist/{compiler-DL18ukPe.d.ts → compiler-2zqGzxVZ.d.ts} +2 -2
  7. package/dist/config/index.d.ts +4 -3
  8. package/dist/config/index.js +5 -3
  9. package/dist/config-Be4GwsqI.js +129 -0
  10. package/dist/{index-BD6wKthp.d.ts → config-C1CYcm6o.d.ts} +75 -54
  11. package/dist/{config-D-aR2ZoN.js → config-DOsGYSiT.js} +14 -136
  12. package/dist/{define-QGkq04oe.js → define-DF341GDj.js} +133 -110
  13. package/dist/{define-CTfWw-SS.d.ts → define-DUbydmJa.d.ts} +32 -14
  14. package/dist/edge/index.d.ts +4 -4
  15. package/dist/edge/index.js +4 -4
  16. package/dist/entitlements-CTEBm7IB.js +20 -0
  17. package/dist/entitlements-DvJXoI02.d.ts +42 -0
  18. package/dist/env/index.js +203 -1
  19. package/dist/events/index.d.ts +1 -1
  20. package/dist/events/index.js +139 -1
  21. package/dist/{events-2OJsVG_d.d.ts → events-Do8jXh4D.d.ts} +3 -3
  22. package/dist/{executor-Bje4suQF.d.ts → executor-B26lgbHD.d.ts} +2 -2
  23. package/dist/{executor-Ceq60hPM.js → executor-C57jx2pn.js} +7 -2
  24. package/dist/{executor-Bazhgsgq.d.ts → executor-DynmIWhq.d.ts} +2 -2
  25. package/dist/guards-BnzDvFrz.js +10 -0
  26. package/dist/hono/index.d.ts +5 -5
  27. package/dist/hono/index.js +4 -4
  28. package/dist/{index-Hf8PP9sP.d.ts → index-BNsABq7P.d.ts} +17 -13
  29. package/dist/{index-JXHBuS53.d.ts → index-CADlNHK-.d.ts} +27 -14
  30. package/dist/{index-Nxo3TJcm.d.ts → index-CTTMS6_1.d.ts} +27 -8
  31. package/dist/index-Cf-D2jk1.d.ts +50 -0
  32. package/dist/{index-Dup-nSe9.d.ts → index-CriFFJ_p.d.ts} +4 -4
  33. package/dist/{index-DQVvHzjW.d.ts → index-GV77NpRP.d.ts} +5 -4
  34. package/dist/{index-Sw43J8HG.d.ts → index-UJ-LUxtI.d.ts} +6 -6
  35. package/dist/{index-cEeweTeM.d.ts → index-kpgopLNv.d.ts} +4 -3
  36. package/dist/{index-DRid0qIv.d.ts → index-qriRM9cZ.d.ts} +7 -2
  37. package/dist/index.d.ts +13 -13
  38. package/dist/index.js +18 -8
  39. package/dist/jobs/index.d.ts +25 -24
  40. package/dist/jobs/index.js +19 -12
  41. package/dist/{json-schema-BbwO2mZg.js → json-schema--aOXAGfU.js} +9 -3
  42. package/dist/lint/index.js +1 -1
  43. package/dist/list/index.d.ts +2 -2
  44. package/dist/list/index.js +128 -93
  45. package/dist/{live-6b9i72Ux.d.ts → live-QE3UPbGm.d.ts} +27 -6
  46. package/dist/logger-glM28NVN.js +21 -0
  47. package/dist/mcp/index.d.ts +59 -15
  48. package/dist/mcp/index.js +229 -67
  49. package/dist/{mfa-CpogP66z.js → mfa-BxoR1RkN.js} +97 -22
  50. package/dist/{mfa-TVia554c.d.ts → mfa-x3V3vGh-.d.ts} +1 -1
  51. package/dist/next/index.d.ts +51 -38
  52. package/dist/next/index.js +31 -25
  53. package/dist/openapi/index.d.ts +1 -1
  54. package/dist/openapi/index.js +58 -35
  55. package/dist/orpc/index.d.ts +6 -6
  56. package/dist/orpc/index.js +3 -3
  57. package/dist/otel/index.d.ts +3 -3
  58. package/dist/otel/index.js +5 -3
  59. package/dist/{template-BeOGnynM.js → permdock-sql-Bta_G0v-.js} +32 -3
  60. package/dist/permdock-sql-DImJULY-.d.ts +17 -0
  61. package/dist/{plugin-HvGqCurB.d.ts → plugin-DVRB8qNE.d.ts} +5 -4
  62. package/dist/plugins/actor/index.d.ts +1 -1
  63. package/dist/plugins/rules/index.d.ts +4 -3
  64. package/dist/plugins/rules/index.js +8 -15
  65. package/dist/plugins/soft-delete/index.d.ts +3 -3
  66. package/dist/plugins/soft-delete/index.js +2 -2
  67. package/dist/plugins/tenant/index.d.ts +10 -4
  68. package/dist/plugins/tenant/index.js +15 -13
  69. package/dist/plugins/timestamps/index.d.ts +1 -1
  70. package/dist/plugins/timestamps/index.js +1 -1
  71. package/dist/plugins/validation/index.d.ts +2 -2
  72. package/dist/pool-B9qsLyFR.d.ts +71 -0
  73. package/dist/postgres/index.d.ts +10 -5
  74. package/dist/postgres/index.js +36 -16
  75. package/dist/{postgrest-Dm-vhY6R.d.ts → postgrest-DmBlevh-.d.ts} +1 -1
  76. package/dist/{problem-DPu6Fh-t.js → problem-CGt1ADix.js} +14 -8
  77. package/dist/{problem-DlgB0lYl.d.ts → problem-Z2RTQjZD.d.ts} +6 -4
  78. package/dist/query/index.d.ts +2 -2
  79. package/dist/query/index.js +1 -1
  80. package/dist/{query-ChvDbsDE.js → query-BdKLTPlj.js} +2 -2
  81. package/dist/react/index.d.ts +7 -7
  82. package/dist/react/index.js +3 -3
  83. package/dist/react/server.d.ts +5 -5
  84. package/dist/react/server.js +1 -1
  85. package/dist/react/session.d.ts +21 -2
  86. package/dist/react/session.js +19 -1
  87. package/dist/{react-CrOvrnIP.js → react-DNBYNazu.js} +28 -23
  88. package/dist/{read-set-DhX2d04c.js → read-set-D0qBcboM.js} +3 -3
  89. package/dist/realtime/index.d.ts +4 -2
  90. package/dist/realtime/index.js +30 -14
  91. package/dist/{resolve-DW8h9NPm.d.ts → resolve-BRciX2jK.d.ts} +25 -9
  92. package/dist/{resource-SkPTRJDD.d.ts → resource-CwVgiI5q.d.ts} +16 -7
  93. package/dist/{resource-CYiu6Ygi.js → resource-D30rp0GN.js} +38 -16
  94. package/dist/{respond-4-dhoV7x.js → respond-C44EZAJo.js} +38 -19
  95. package/dist/{respond-PoTpy5DT.d.ts → respond-DAkPIdTH.d.ts} +23 -17
  96. package/dist/{result-CTkCZ6JA.d.ts → result-c2OYgCV2.d.ts} +13 -8
  97. package/dist/server/index.d.ts +35 -10
  98. package/dist/server/index.js +38 -9
  99. package/dist/{simplify-oHiDKRVQ.js → simplify-LakqK1vH.js} +22 -28
  100. package/dist/{spec-pins-t6sPaHvl.js → spec-pins-C3oc-Tdp.js} +1 -1
  101. package/dist/{spec-pins-CJPVghHy.d.ts → spec-pins-CqorWDnO.d.ts} +1 -1
  102. package/dist/sql/index.d.ts +139 -0
  103. package/dist/sql/index.js +1983 -0
  104. package/dist/{sql-M8l3fnS0.js → sql-BIgk12ew.js} +72 -16
  105. package/dist/ssr/index.d.ts +5 -5
  106. package/dist/ssr/index.js +3 -3
  107. package/dist/{standard-CyhRzctb.d.ts → standard-eudBiRk1.d.ts} +1 -1
  108. package/dist/{stats-3iy8u3_P.js → stats-D78UQ8Yu.js} +8 -7
  109. package/dist/storage/index.d.ts +4 -2
  110. package/dist/storage/index.js +530 -1
  111. package/dist/{tables-rZxRxNPW.js → tables-Dhha3BXM.js} +5 -5
  112. package/dist/temporal-BwshQk0W.js +27 -0
  113. package/dist/temporal-required-CMxeSVPo.js +19 -0
  114. package/dist/testing/index.d.ts +49 -16
  115. package/dist/testing/index.js +233 -66
  116. package/dist/{types-C2fslP1z.d.ts → types-DONv9SHN.d.ts} +68 -12
  117. package/dist/{version-Cqq1m3UY.js → version-BfpG2gks.js} +1 -1
  118. package/dist/view-3Po_PVlr.d.ts +79 -0
  119. package/dist/{view-B6F9QbG-.js → view-wZ-9qIUz.js} +12 -2
  120. package/dist/webhooks/index.d.ts +19 -17
  121. package/dist/webhooks/index.js +1 -1
  122. package/dist/{webhooks-DbMTJJX2.js → webhooks-CAIFQSTM.js} +12 -10
  123. package/package.json +30 -30
  124. package/schemas/config-v1.json +80 -5
  125. package/schemas/doctor-report-v1.json +18 -1
  126. package/schemas/snapshot-v2.json +39 -0
  127. package/skills/better-supabase/SKILL.md +23 -11
  128. package/skills/better-supabase/references/plugins.md +28 -16
  129. package/skills/better-supabase/references/troubleshooting.md +15 -15
  130. package/skills/better-supabase-api/SKILL.md +9 -9
  131. package/skills/better-supabase-api/references/adapters.md +30 -25
  132. package/skills/better-supabase-testing/SKILL.md +15 -11
  133. package/bin/better-supabase.js +0 -3
  134. package/dist/casing-da0uRTqt.js +0 -14
  135. package/dist/cli/bin.d.ts +0 -1
  136. package/dist/cli/bin.js +0 -15
  137. package/dist/cli/index.d.ts +0 -226
  138. package/dist/cli/index.js +0 -3
  139. package/dist/cli-CoByWYja.js +0 -6472
  140. package/dist/entitlements-BBg61DQZ.d.ts +0 -20
  141. package/dist/entitlements-BC_khwRy.js +0 -17
  142. package/dist/env-BEToab0I.js +0 -204
  143. package/dist/events-BuzTgn2D.js +0 -135
  144. package/dist/pool-BFeqzwQS.d.ts +0 -37
  145. package/dist/seed-CzecODcJ.js +0 -101
  146. package/dist/session-C3osL21k.d.ts +0 -21
  147. package/dist/session-CD-3orO_.js +0 -19
  148. package/dist/storage-BWXVUXGk.js +0 -461
  149. 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-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";
9
- //#region src/mcp/index.ts
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
- const SUPPORTED_VERSIONS = /* @__PURE__ */ new Set([MCP_PROTOCOL_VERSION, "2025-03-26"]);
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 page = {
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
- function rpcError(id, code, message, status = 200) {
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 typeof value === "object" && value !== null && value["jsonrpc"] === "2.0" && typeof value["method"] === "string";
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 = (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
- });
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)) 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
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
- if (!result.ok) return failure(result.error);
208
- return textResult(operation === "delete" ? { deleted: true } : result.data);
209
- });
281
+ }
210
282
  }
211
283
  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;
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 call = async (name, args, ctx) => {
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 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);
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" && 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
- },
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({ tools: listTools() });
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
- 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
- })));
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 rpcError(message.id, METHOD_NOT_FOUND, `Method not found: ${message.method}`);
456
+ default: return notFound();
298
457
  }
299
458
  };
300
- const metadata = (request) => resourceMetadataResponse(request, {
459
+ const metadata = (request) => Response.json({
301
460
  resource: resourceOf(request),
302
- authorizationServers: [...authorizationServers()]
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 = decodePayload(session.access_token)?.["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 checkClaims({
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 checkClaims(state, options);
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" || !options.claims) return state;
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 !== void 0 ? { audience: options.audience } : {},
305
- ...options.issuer !== void 0 ? { issuer: 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 checkClaims(state, options), cookies);
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 === "claims" || state.error.kind === "network") return resolution(state, cookies);
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 !== void 0 ? { role: auth.user.role } : {},
457
- ...auth.user.email !== void 0 ? { email: 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, impersonatorOf as g, actClaim 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 };
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 };
@@ -1,4 +1,4 @@
1
- import { p as DbError } from "./result-CTkCZ6JA.js";
1
+ import { p as DbError } from "./result-c2OYgCV2.js";
2
2
  //#region src/auth/impersonation.d.ts
3
3
  /** The admin acting as the user, from the RFC 8693 `act` claim. */
4
4
  interface Impersonator {