@exposify/supabase 0.3.5 → 0.3.7
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/LICENSE +1 -1
- package/README.md +45 -0
- package/dist/cli.js +169 -24
- package/package.json +2 -11
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -110,6 +110,43 @@ note on introspection below) — without those, `init` can't reach anything to
|
|
|
110
110
|
introspect and falls back to the fixed scaffold-and-ask-to-deploy flow above,
|
|
111
111
|
same as before this wizard existed.
|
|
112
112
|
|
|
113
|
+
### Using the hosted broker
|
|
114
|
+
|
|
115
|
+
If you use the Exposify hosted broker (the dashboard's "Hosted broker" card),
|
|
116
|
+
`init` asks first:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
Use the Exposify hosted broker for auth (MCP clients get a long-lived extb_... token instead of a Supabase JWT)? [y/N]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Or skip the question with `--broker-url <url> --broker-project <id>`
|
|
123
|
+
(or `EXPOSIFY_BROKER_URL`/`EXPOSIFY_PROJECT_ID`), which also works
|
|
124
|
+
non-interactively. Saying yes does three things:
|
|
125
|
+
|
|
126
|
+
1. **Wires the resolver into the function.** Adds
|
|
127
|
+
`auth: createHostedAuthResolver({ brokerUrl, projectId, licenseKey })` and
|
|
128
|
+
`authRequired: true` to `aiReady()`. The license key is read from the
|
|
129
|
+
`EXPOSIFY_LICENSE_KEY` secret and is never written into the file. If
|
|
130
|
+
`index.ts` already exists, `init` prints the snippet for you to merge in
|
|
131
|
+
yourself.
|
|
132
|
+
2. **Sets `verify_jwt = false` for `mcp` in `supabase/config.toml`.** An
|
|
133
|
+
`extb_...` token isn't a JWT, so with the gateway's check on, every request
|
|
134
|
+
fails with `{"code":"UNAUTHORIZED_ASYMMETRIC_JWT","message":"Invalid JWT"}`
|
|
135
|
+
before the function runs. The setting goes in config.toml and not just
|
|
136
|
+
on the deploy command line, because a later plain
|
|
137
|
+
`supabase functions deploy mcp` would otherwise turn the check back on.
|
|
138
|
+
With the gateway check off, `authRequired: true` is what rejects requests
|
|
139
|
+
that have no valid broker token. Otherwise those requests would run as
|
|
140
|
+
`anon`.
|
|
141
|
+
3. **Deploys with `--no-verify-jwt`**, and adds
|
|
142
|
+
`supabase secrets set EXPOSIFY_LICENSE_KEY=...` to the printed steps.
|
|
143
|
+
`init` never sees the license key, so you set that secret yourself.
|
|
144
|
+
Until you do, every request gets a 401.
|
|
145
|
+
|
|
146
|
+
MCP clients then send `Authorization: Bearer extb_...`. Don't give the client
|
|
147
|
+
the JWT that `POST /resolve` returns. It's the short-lived Supabase token the
|
|
148
|
+
function swaps in on each request, and it stops working when it expires.
|
|
149
|
+
|
|
113
150
|
## 2. Decide what's exposed
|
|
114
151
|
|
|
115
152
|
**Every RLS-enabled table is automatically readable.** A table only shows up
|
|
@@ -253,6 +290,14 @@ Deno.serve(aiReady({ auth: broker.resolver, authRequired: true, writes: ["todos.
|
|
|
253
290
|
failed refresh. By default the request then runs as the `anon` role (RLS
|
|
254
291
|
decides). Set `authRequired: true` to answer `401` instead. Recommended.
|
|
255
292
|
- A resolver that throws yields a 503.
|
|
293
|
+
- **Turn off the gateway's JWT check for the function**
|
|
294
|
+
(`[functions.mcp] verify_jwt = false` in `supabase/config.toml`, or
|
|
295
|
+
`supabase functions deploy mcp --no-verify-jwt`). Otherwise Supabase
|
|
296
|
+
rejects the `extb_...` token as `Invalid JWT` before your code runs. With
|
|
297
|
+
the check off, `authRequired: true` stops being optional.
|
|
298
|
+
- For the platform-hosted broker, use `createHostedAuthResolver` in place of
|
|
299
|
+
`broker.resolver`. `init` can set this up for you; see "Using the hosted
|
|
300
|
+
broker" above.
|
|
256
301
|
|
|
257
302
|
**Deployment constraints** (not verified end to end, see HANDOVER):
|
|
258
303
|
|
package/dist/cli.js
CHANGED
|
@@ -198,6 +198,7 @@ function buildCommentStatements(entries) {
|
|
|
198
198
|
return lines.length > 0 ? `${lines.join("\n")}
|
|
199
199
|
` : "";
|
|
200
200
|
}
|
|
201
|
+
var LICENSE_KEY_SECRET = "EXPOSIFY_LICENSE_KEY";
|
|
201
202
|
var ANONYMOUS_SIGN_IN_WARNING = `// NOTE: this project has anonymous sign-in enabled. An anonymous Supabase
|
|
202
203
|
// Auth session still gets role: authenticated (with an is_anonymous claim),
|
|
203
204
|
// NOT role: anon -- so it passes any RLS policy scoped to "authenticated"
|
|
@@ -208,8 +209,20 @@ var ANONYMOUS_SIGN_IN_WARNING = `// NOTE: this project has anonymous sign-in ena
|
|
|
208
209
|
function buildFunctionTemplate(opts = {}) {
|
|
209
210
|
const lines = [];
|
|
210
211
|
if (opts.anonymousSignIn) lines.push(ANONYMOUS_SIGN_IN_WARNING);
|
|
211
|
-
lines.push(`import { aiReady } from "npm:@exposify/supabase"
|
|
212
|
+
lines.push(`import { aiReady } from "npm:@exposify/supabase";`);
|
|
213
|
+
if (opts.broker) lines.push(`import { createHostedAuthResolver } from "npm:@exposify/token-broker";`);
|
|
214
|
+
lines.push("");
|
|
212
215
|
lines.push("Deno.serve(aiReady({");
|
|
216
|
+
if (opts.broker) {
|
|
217
|
+
lines.push(
|
|
218
|
+
" auth: createHostedAuthResolver({",
|
|
219
|
+
` brokerUrl: ${JSON.stringify(opts.broker.brokerUrl)},`,
|
|
220
|
+
` projectId: ${JSON.stringify(opts.broker.projectId)},`,
|
|
221
|
+
` licenseKey: Deno.env.get(${JSON.stringify(LICENSE_KEY_SECRET)}) ?? "",`,
|
|
222
|
+
" }),",
|
|
223
|
+
" authRequired: true,"
|
|
224
|
+
);
|
|
225
|
+
}
|
|
213
226
|
lines.push(
|
|
214
227
|
opts.writes && opts.writes.length > 0 ? ` writes: ${JSON.stringify(opts.writes)},` : ` // writes: ["orders.insert", "orders.update"],`
|
|
215
228
|
);
|
|
@@ -225,6 +238,50 @@ function buildFunctionTemplate(opts = {}) {
|
|
|
225
238
|
);
|
|
226
239
|
return lines.join("\n");
|
|
227
240
|
}
|
|
241
|
+
function brokerUrlProblem(brokerUrl) {
|
|
242
|
+
let url;
|
|
243
|
+
try {
|
|
244
|
+
url = new URL(brokerUrl);
|
|
245
|
+
} catch {
|
|
246
|
+
return `"${brokerUrl}" is not a valid URL.`;
|
|
247
|
+
}
|
|
248
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
249
|
+
if (url.protocol === "https:" || url.protocol === "http:" && local) return null;
|
|
250
|
+
return `broker URL must be https (http is only allowed for localhost): ${brokerUrl}`;
|
|
251
|
+
}
|
|
252
|
+
function normalizeBrokerUrl(brokerUrl) {
|
|
253
|
+
return brokerUrl.trim().replace(/\/+$/, "");
|
|
254
|
+
}
|
|
255
|
+
function setFunctionVerifyJwtFalse(toml, name = "mcp") {
|
|
256
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
257
|
+
const header = new RegExp(`^\\s*\\[\\s*functions\\s*\\.\\s*("?)${escaped}\\1\\s*\\]\\s*(#.*)?$`);
|
|
258
|
+
const anyHeader = /^\s*\[/;
|
|
259
|
+
const verifyLine = /^(\s*)verify_jwt\s*=\s*([^#\s]*)(.*)$/;
|
|
260
|
+
const lines = toml.split("\n");
|
|
261
|
+
const start = lines.findIndex((l) => header.test(l));
|
|
262
|
+
if (start === -1) {
|
|
263
|
+
const sep = toml === "" || toml.endsWith("\n\n") ? "" : toml.endsWith("\n") ? "\n" : "\n\n";
|
|
264
|
+
return { toml: `${toml}${sep}[functions.${name}]
|
|
265
|
+
verify_jwt = false
|
|
266
|
+
`, changed: true };
|
|
267
|
+
}
|
|
268
|
+
let end = lines.length;
|
|
269
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
270
|
+
if (anyHeader.test(lines[i])) {
|
|
271
|
+
end = i;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
for (let i = start + 1; i < end; i++) {
|
|
276
|
+
const m = lines[i].match(verifyLine);
|
|
277
|
+
if (!m) continue;
|
|
278
|
+
if (m[2] === "false") return { toml, changed: false };
|
|
279
|
+
lines[i] = `${m[1]}verify_jwt = false${m[3]}`;
|
|
280
|
+
return { toml: lines.join("\n"), changed: true };
|
|
281
|
+
}
|
|
282
|
+
lines.splice(start + 1, 0, "verify_jwt = false");
|
|
283
|
+
return { toml: lines.join("\n"), changed: true };
|
|
284
|
+
}
|
|
228
285
|
function findExistingMigration(filenames, suffix) {
|
|
229
286
|
const matches = filenames.filter((f) => f.endsWith(suffix)).sort();
|
|
230
287
|
return matches[0] ?? null;
|
|
@@ -249,6 +306,7 @@ function partialConnectionWarning(url, key) {
|
|
|
249
306
|
var PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
250
307
|
var FUNCTION_DIR = "supabase/functions/mcp";
|
|
251
308
|
var MIGRATIONS_DIR = "supabase/migrations";
|
|
309
|
+
var CONFIG_TOML = "supabase/config.toml";
|
|
252
310
|
var FETCH_TIMEOUT_MS = 1e4;
|
|
253
311
|
function fetchWithTimeout(input, init) {
|
|
254
312
|
return fetch(input, { ...init, signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
@@ -289,22 +347,39 @@ async function promptYesNo(rl, question, defaultYes = false) {
|
|
|
289
347
|
async function promptText(rl, question) {
|
|
290
348
|
return (await rl.question(question)).trim();
|
|
291
349
|
}
|
|
350
|
+
function deployArgs(broker) {
|
|
351
|
+
return ["functions", "deploy", "mcp", ...broker ? ["--no-verify-jwt"] : []];
|
|
352
|
+
}
|
|
353
|
+
function clientCredential(broker) {
|
|
354
|
+
return broker ? "a broker token (extb_..., minted by the hosted broker)" : "a user JWT";
|
|
355
|
+
}
|
|
292
356
|
function printManualCommands(opts = {}) {
|
|
357
|
+
const broker = opts.broker ?? null;
|
|
293
358
|
console.log("\nRun these to finish setup:");
|
|
294
359
|
const steps = [];
|
|
295
360
|
if (!opts.alreadyPushed) steps.push("supabase db push");
|
|
296
|
-
if (
|
|
361
|
+
if (broker) steps.push(`supabase secrets set ${LICENSE_KEY_SECRET}=<your license key (expfy_...)>`);
|
|
362
|
+
if (!opts.alreadyDeployed) steps.push(`supabase ${deployArgs(broker).join(" ")}`);
|
|
297
363
|
steps.forEach((cmd, i) => console.log(` ${i + 1}. ${cmd}`));
|
|
298
|
-
console.log(` ${steps.length + 1}. Paste the function URL +
|
|
364
|
+
console.log(` ${steps.length + 1}. Paste the function URL + ${clientCredential(broker)} into an MCP client.`);
|
|
365
|
+
}
|
|
366
|
+
function printLicenseKeyReminder(broker) {
|
|
367
|
+
if (!broker) return;
|
|
368
|
+
console.log(
|
|
369
|
+
`
|
|
370
|
+
If you haven't already: supabase secrets set ${LICENSE_KEY_SECRET}=<your license key (expfy_...)> -- until it's set, every request is rejected with 401.`
|
|
371
|
+
);
|
|
299
372
|
}
|
|
300
|
-
function pushAndDeploy() {
|
|
373
|
+
function pushAndDeploy(broker) {
|
|
301
374
|
let pushed = false;
|
|
302
375
|
try {
|
|
303
376
|
console.log("\n$ supabase db push");
|
|
304
377
|
execFileSync("supabase", ["db", "push"], { stdio: "inherit" });
|
|
305
378
|
pushed = true;
|
|
306
|
-
|
|
307
|
-
|
|
379
|
+
const args = deployArgs(broker);
|
|
380
|
+
console.log(`
|
|
381
|
+
$ supabase ${args.join(" ")}`);
|
|
382
|
+
execFileSync("supabase", args, { stdio: "inherit" });
|
|
308
383
|
return { pushed: true, deployed: true };
|
|
309
384
|
} catch (err) {
|
|
310
385
|
return { pushed, deployed: false, error: err };
|
|
@@ -438,7 +513,53 @@ async function warnIfNotSupabaseProject() {
|
|
|
438
513
|
);
|
|
439
514
|
}
|
|
440
515
|
}
|
|
516
|
+
function brokerFromFlags() {
|
|
517
|
+
const flagUrl = process.env.EXPOSIFY_BROKER_URL ?? argValue("--broker-url");
|
|
518
|
+
const flagProject = process.env.EXPOSIFY_PROJECT_ID ?? argValue("--broker-project");
|
|
519
|
+
if (!flagUrl && !flagProject) return void 0;
|
|
520
|
+
if (!flagUrl || !flagProject) {
|
|
521
|
+
throw new Error("the hosted broker needs both --broker-url and --broker-project (or EXPOSIFY_BROKER_URL/EXPOSIFY_PROJECT_ID).");
|
|
522
|
+
}
|
|
523
|
+
const problem = brokerUrlProblem(flagUrl);
|
|
524
|
+
if (problem) throw new Error(problem);
|
|
525
|
+
return { brokerUrl: normalizeBrokerUrl(flagUrl), projectId: flagProject.trim() };
|
|
526
|
+
}
|
|
527
|
+
async function resolveBroker(rl, fromFlags) {
|
|
528
|
+
if (fromFlags) return fromFlags;
|
|
529
|
+
if (!rl) return null;
|
|
530
|
+
const use = await promptYesNo(
|
|
531
|
+
rl,
|
|
532
|
+
"\nUse the Exposify hosted broker for auth (MCP clients get a long-lived extb_... token instead of a Supabase JWT)?"
|
|
533
|
+
);
|
|
534
|
+
if (!use) return null;
|
|
535
|
+
let brokerUrl = "";
|
|
536
|
+
for (; ; ) {
|
|
537
|
+
brokerUrl = normalizeBrokerUrl(await promptText(rl, " Broker URL (from the dashboard's Hosted broker card): "));
|
|
538
|
+
const problem = brokerUrlProblem(brokerUrl);
|
|
539
|
+
if (!problem) break;
|
|
540
|
+
console.log(` ${problem}`);
|
|
541
|
+
}
|
|
542
|
+
let projectId = "";
|
|
543
|
+
while (!projectId) projectId = await promptText(rl, " Project ID: ");
|
|
544
|
+
return { brokerUrl, projectId };
|
|
545
|
+
}
|
|
546
|
+
async function disableJwtVerification() {
|
|
547
|
+
if (!await exists(CONFIG_TOML)) {
|
|
548
|
+
console.warn(
|
|
549
|
+
`warn: no ${CONFIG_TOML} to record verify_jwt = false in -- always deploy with \`supabase functions deploy mcp --no-verify-jwt\`, or broker tokens will be rejected with "Invalid JWT".`
|
|
550
|
+
);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const { toml, changed } = setFunctionVerifyJwtFalse(await readFile(CONFIG_TOML, "utf8"));
|
|
554
|
+
if (!changed) {
|
|
555
|
+
console.log(`Skipped ${CONFIG_TOML} (verify_jwt already false for mcp)`);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
await writeFile(CONFIG_TOML, toml);
|
|
559
|
+
console.log(`Updated ${CONFIG_TOML}: [functions.mcp] verify_jwt = false (broker tokens aren't Supabase JWTs)`);
|
|
560
|
+
}
|
|
441
561
|
async function cmdInit() {
|
|
562
|
+
const brokerFlags = brokerFromFlags();
|
|
442
563
|
await warnIfNotSupabaseProject();
|
|
443
564
|
const { path: migrationPath, wasNew } = await ensureMetaMigration();
|
|
444
565
|
console.log(wasNew ? `Wrote ${migrationPath}` : `Skipped ${migrationPath} (already scaffolded)`);
|
|
@@ -453,31 +574,41 @@ async function cmdInit() {
|
|
|
453
574
|
if (connWarning) console.warn(`warn: ${connWarning}`);
|
|
454
575
|
const rl = openInteractiveReadline();
|
|
455
576
|
if (!rl) {
|
|
577
|
+
const broker = await resolveBroker(null, brokerFlags);
|
|
578
|
+
if (broker) await disableJwtVerification();
|
|
456
579
|
if (!functionExists) {
|
|
457
|
-
await writeFile(functionPath, buildFunctionTemplate());
|
|
580
|
+
await writeFile(functionPath, buildFunctionTemplate({ broker: broker ?? void 0 }));
|
|
458
581
|
console.log(`Wrote ${functionPath}`);
|
|
459
582
|
} else {
|
|
460
583
|
console.log(`Skipped ${functionPath} (already exists)`);
|
|
584
|
+
if (broker) printBrokerMergeHint(functionPath, broker);
|
|
461
585
|
}
|
|
462
|
-
printManualCommands();
|
|
586
|
+
printManualCommands({ broker });
|
|
463
587
|
return;
|
|
464
588
|
}
|
|
465
589
|
try {
|
|
466
590
|
const conn = resolveConnectionInfo();
|
|
591
|
+
const broker = await resolveBroker(rl, brokerFlags);
|
|
592
|
+
if (broker) await disableJwtVerification();
|
|
467
593
|
const anonymousSignIn = await askAnonymousSignIn(rl, conn);
|
|
468
594
|
let introspection = conn ? await tryIntrospect(conn) : null;
|
|
469
595
|
let alreadyPushed = false;
|
|
470
596
|
let alreadyDeployed = false;
|
|
597
|
+
let brokerHintShown = false;
|
|
471
598
|
if (!introspection) {
|
|
472
599
|
if (!functionExists) {
|
|
473
|
-
await writeFile(functionPath, buildFunctionTemplate({ anonymousSignIn }));
|
|
600
|
+
await writeFile(functionPath, buildFunctionTemplate({ anonymousSignIn, broker: broker ?? void 0 }));
|
|
474
601
|
console.log(`Wrote ${functionPath}`);
|
|
475
602
|
} else {
|
|
476
603
|
console.log(`Skipped ${functionPath} (already exists)`);
|
|
604
|
+
if (broker) {
|
|
605
|
+
printBrokerMergeHint(functionPath, broker);
|
|
606
|
+
brokerHintShown = true;
|
|
607
|
+
}
|
|
477
608
|
}
|
|
478
609
|
if (!hasSupabaseCli()) {
|
|
479
610
|
console.log("\nSupabase CLI not found on PATH -- install it to run the next steps automatically next time.");
|
|
480
|
-
printManualCommands();
|
|
611
|
+
printManualCommands({ broker });
|
|
481
612
|
return;
|
|
482
613
|
}
|
|
483
614
|
const connect = await promptYesNo(
|
|
@@ -485,24 +616,26 @@ async function cmdInit() {
|
|
|
485
616
|
"\nApply the migration and deploy the function to whatever Supabase project is currently linked, now?"
|
|
486
617
|
);
|
|
487
618
|
if (!connect) {
|
|
488
|
-
printManualCommands();
|
|
619
|
+
printManualCommands({ broker });
|
|
489
620
|
return;
|
|
490
621
|
}
|
|
491
|
-
const result2 = pushAndDeploy();
|
|
622
|
+
const result2 = pushAndDeploy(broker);
|
|
492
623
|
alreadyPushed = result2.pushed;
|
|
493
624
|
alreadyDeployed = result2.deployed;
|
|
494
625
|
if (!result2.deployed) {
|
|
495
626
|
console.error(`
|
|
496
627
|
Supabase CLI command failed: ${result2.error instanceof Error ? result2.error.message : result2.error}`);
|
|
497
|
-
printManualCommands({ alreadyPushed });
|
|
628
|
+
printManualCommands({ alreadyPushed, broker });
|
|
498
629
|
return;
|
|
499
630
|
}
|
|
631
|
+
printLicenseKeyReminder(broker);
|
|
500
632
|
introspection = conn ? await tryIntrospect(conn) : null;
|
|
501
633
|
if (!introspection) {
|
|
502
634
|
console.log(
|
|
503
635
|
"\nDeployed, but couldn't reach the project to introspect tables (no SUPABASE_URL/SUPABASE_SERVICE_ROLE_KEY or --url/--key available). Re-run `exposify-supabase init` with those set to configure writes, RPCs, and documentation interactively."
|
|
504
636
|
);
|
|
505
|
-
console.log(
|
|
637
|
+
console.log(`
|
|
638
|
+
Done. Paste the function URL + ${clientCredential(broker)} into an MCP client to try it.`);
|
|
506
639
|
return;
|
|
507
640
|
}
|
|
508
641
|
}
|
|
@@ -513,14 +646,14 @@ Supabase CLI command failed: ${result2.error instanceof Error ? result2.error.me
|
|
|
513
646
|
const writes = flattenWriteGrants(grants);
|
|
514
647
|
let wroteFunctionFile = false;
|
|
515
648
|
if (!functionExists) {
|
|
516
|
-
await writeFile(functionPath, buildFunctionTemplate({ writes, rpc, anonymousSignIn }));
|
|
649
|
+
await writeFile(functionPath, buildFunctionTemplate({ writes, rpc, anonymousSignIn, broker: broker ?? void 0 }));
|
|
517
650
|
console.log(`
|
|
518
651
|
Wrote ${functionPath} with your selections.`);
|
|
519
652
|
wroteFunctionFile = true;
|
|
520
|
-
} else if (writes.length > 0 || rpc.length > 0) {
|
|
653
|
+
} else if (writes.length > 0 || rpc.length > 0 || broker && !brokerHintShown) {
|
|
521
654
|
console.log(`
|
|
522
655
|
${functionPath} already exists -- left untouched. Merge this into your aiReady() config:`);
|
|
523
|
-
console.log(buildFunctionTemplate({ writes, rpc, anonymousSignIn }));
|
|
656
|
+
console.log(buildFunctionTemplate({ writes, rpc, anonymousSignIn, broker: broker ?? void 0 }));
|
|
524
657
|
}
|
|
525
658
|
const commentsPath = await writeCommentsMigration(docs);
|
|
526
659
|
if (commentsPath) console.log(`Wrote ${commentsPath}`);
|
|
@@ -531,26 +664,33 @@ ${functionPath} already exists -- left untouched. Merge this into your aiReady()
|
|
|
531
664
|
}
|
|
532
665
|
if (!hasSupabaseCli()) {
|
|
533
666
|
console.log("\nSupabase CLI not found on PATH.");
|
|
534
|
-
printManualCommands({ alreadyPushed: commentsPath === null, alreadyDeployed: !wroteFunctionFile });
|
|
667
|
+
printManualCommands({ alreadyPushed: commentsPath === null, alreadyDeployed: !wroteFunctionFile, broker });
|
|
535
668
|
return;
|
|
536
669
|
}
|
|
537
670
|
const redeploy = await promptYesNo(rl, "\nApply/deploy these changes now?", true);
|
|
538
671
|
if (!redeploy) {
|
|
539
|
-
printManualCommands({ alreadyPushed: commentsPath === null, alreadyDeployed: !wroteFunctionFile });
|
|
672
|
+
printManualCommands({ alreadyPushed: commentsPath === null, alreadyDeployed: !wroteFunctionFile, broker });
|
|
540
673
|
return;
|
|
541
674
|
}
|
|
542
|
-
const result = pushAndDeploy();
|
|
675
|
+
const result = pushAndDeploy(broker);
|
|
543
676
|
if (!result.deployed) {
|
|
544
677
|
console.error(`
|
|
545
678
|
Supabase CLI command failed: ${result.error instanceof Error ? result.error.message : result.error}`);
|
|
546
|
-
printManualCommands({ alreadyPushed: result.pushed });
|
|
679
|
+
printManualCommands({ alreadyPushed: result.pushed, broker });
|
|
547
680
|
return;
|
|
548
681
|
}
|
|
549
|
-
|
|
682
|
+
printLicenseKeyReminder(broker);
|
|
683
|
+
console.log(`
|
|
684
|
+
Done. Paste the function URL + ${clientCredential(broker)} into an MCP client to try it.`);
|
|
550
685
|
} finally {
|
|
551
686
|
rl.close();
|
|
552
687
|
}
|
|
553
688
|
}
|
|
689
|
+
function printBrokerMergeHint(functionPath, broker) {
|
|
690
|
+
console.log(`
|
|
691
|
+
${functionPath} already exists -- left untouched. Merge the broker wiring into your aiReady() config:`);
|
|
692
|
+
console.log(buildFunctionTemplate({ broker }));
|
|
693
|
+
}
|
|
554
694
|
async function cmdGenerate() {
|
|
555
695
|
const conn = resolveConnectionInfo();
|
|
556
696
|
if (!conn) {
|
|
@@ -588,12 +728,17 @@ async function main() {
|
|
|
588
728
|
const [, , cmd] = process.argv;
|
|
589
729
|
if (cmd === "init") return cmdInit();
|
|
590
730
|
if (cmd === "generate") return cmdGenerate();
|
|
591
|
-
console.log(
|
|
731
|
+
console.log(
|
|
732
|
+
"Usage: exposify-supabase <init|generate> [--url <url>] [--key <service-role-key>] [--broker-url <url> --broker-project <id>]"
|
|
733
|
+
);
|
|
592
734
|
console.log(" init Scaffold the exposify_meta() migration + supabase/functions/mcp/index.ts.");
|
|
593
735
|
console.log(" Interactively offers to run `supabase db push` + `functions deploy`; if a");
|
|
594
736
|
console.log(" project is reachable (SUPABASE_URL/SUPABASE_SERVICE_ROLE_KEY env vars, or --url/--key),");
|
|
595
737
|
console.log(" also walks through anonymous sign-in, per-table writes, RPC exposure, and");
|
|
596
|
-
console.log(" documentation for tables/columns missing a comment.");
|
|
738
|
+
console.log(" documentation for tables/columns missing a comment. Offers to wire in the hosted");
|
|
739
|
+
console.log(" broker (or pass --broker-url/--broker-project): adds createHostedAuthResolver to the");
|
|
740
|
+
console.log(" function, sets verify_jwt = false for it in supabase/config.toml, and deploys with");
|
|
741
|
+
console.log(" --no-verify-jwt.");
|
|
597
742
|
console.log(" generate Build exposify.manifest.json from a running local/linked project");
|
|
598
743
|
process.exitCode = cmd ? 1 : 0;
|
|
599
744
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exposify/supabase",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"prepack": "yarn build"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@exposify/core": "0.3.
|
|
30
|
+
"@exposify/core": "0.3.11",
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
32
32
|
"zod": "^3.24.0"
|
|
33
33
|
},
|
|
@@ -40,15 +40,6 @@
|
|
|
40
40
|
"publishConfig": {
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
|
-
"repository": {
|
|
44
|
-
"type": "git",
|
|
45
|
-
"url": "git+https://github.com/ayadagain/agentready.git",
|
|
46
|
-
"directory": "packages/supabase"
|
|
47
|
-
},
|
|
48
|
-
"homepage": "https://github.com/ayadagain/agentready/tree/main/packages/supabase#readme",
|
|
49
|
-
"bugs": {
|
|
50
|
-
"url": "https://github.com/ayadagain/agentready/issues"
|
|
51
|
-
},
|
|
52
43
|
"description": "Expose a Supabase-backed app's database and edge functions to AI agents over MCP, generated from PostgREST + Row Level Security — RLS is the authorization layer, so agents can do exactly what the logged-in user can do.",
|
|
53
44
|
"author": "Exposify",
|
|
54
45
|
"keywords": [
|