@exposify/supabase 0.3.10 → 0.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/cli.js +43 -11
- package/dist/index.cjs +25 -9
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +26 -10
- package/package.json +2 -2
- package/sql/exposify_meta.sql +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ root.
|
|
|
15
15
|
|
|
16
16
|
- A Supabase project (local via the [Supabase CLI](https://supabase.com/docs/guides/cli), or hosted).
|
|
17
17
|
- The Supabase CLI (`supabase` on your `PATH`) for `db push` / `functions deploy`.
|
|
18
|
-
- Node or Yarn to run `npx exposify
|
|
18
|
+
- Node or Yarn to run `npx @exposify/supabase` (the CLI is a regular npm
|
|
19
19
|
package; the edge function itself runs on Deno and needs no local install).
|
|
20
20
|
|
|
21
21
|
## 1. Scaffold
|
|
@@ -23,7 +23,7 @@ root.
|
|
|
23
23
|
From your Supabase project's root (the directory containing `supabase/`):
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
npx exposify
|
|
26
|
+
npx @exposify/supabase init
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
This is safe to re-run; see "Re-running `init`" below. It writes:
|
|
@@ -216,7 +216,7 @@ or edit the managed block in `supabase/functions/mcp/index.ts` by hand,
|
|
|
216
216
|
keeping its format. Anything else goes below the block:
|
|
217
217
|
|
|
218
218
|
```ts
|
|
219
|
-
// exposify:begin -- generated by `npx exposify
|
|
219
|
+
// exposify:begin -- generated by `npx @exposify/supabase init`, ...
|
|
220
220
|
import { aiReady } from "npm:@exposify/supabase";
|
|
221
221
|
|
|
222
222
|
const generated = {
|
|
@@ -328,8 +328,8 @@ RLS enforcement, not anything in this SDK, is what makes that true.
|
|
|
328
328
|
## 5. Check it: `doctor`
|
|
329
329
|
|
|
330
330
|
```bash
|
|
331
|
-
npx exposify
|
|
332
|
-
npx exposify
|
|
331
|
+
npx @exposify/supabase doctor # finds the project the same way init does
|
|
332
|
+
npx @exposify/supabase doctor --token <user-jwt | extb_...>
|
|
333
333
|
```
|
|
334
334
|
|
|
335
335
|
A read-only health check of everything `init` sets up. It never writes a file
|
|
@@ -535,7 +535,7 @@ project (local `supabase start` or linked):
|
|
|
535
535
|
```bash
|
|
536
536
|
SUPABASE_URL=http://localhost:54321 \
|
|
537
537
|
SUPABASE_SERVICE_ROLE_KEY=<service-role-key> \
|
|
538
|
-
npx exposify
|
|
538
|
+
npx @exposify/supabase generate
|
|
539
539
|
```
|
|
540
540
|
|
|
541
541
|
Uses the **service role key**, deliberately the same key runtime
|
|
@@ -565,7 +565,7 @@ the function prints whether MCP is up. It shows in `supabase functions serve`
|
|
|
565
565
|
locally and in the function's logs in production:
|
|
566
566
|
|
|
567
567
|
```
|
|
568
|
-
[exposify
|
|
568
|
+
[@exposify/supabase] MCP ready at https://<ref>.supabase.co/functions/v1/mcp: 9 tools (5 query, 2 write, 1 rpc, search_docs), 3 docs indexed, auth: passthrough (caller's JWT)
|
|
569
569
|
```
|
|
570
570
|
|
|
571
571
|
- **Manifest fallback:** the same line, marked "from the bundled manifest",
|
|
@@ -582,7 +582,7 @@ overrides that.
|
|
|
582
582
|
|
|
583
583
|
## Troubleshooting
|
|
584
584
|
|
|
585
|
-
Start with `npx exposify
|
|
585
|
+
Start with `npx @exposify/supabase doctor`. It checks most of what's below
|
|
586
586
|
and names the fix.
|
|
587
587
|
|
|
588
588
|
**"exposify_meta() is not reachable" / "PGRST202" errors in function logs.**
|
|
@@ -617,7 +617,7 @@ above — pointing at a project where the migration is already applied). On a
|
|
|
617
617
|
brand-new project's first `init`, that's not true yet — say yes to the
|
|
618
618
|
deploy prompt (or run `supabase db push` yourself) and the wizard picks up
|
|
619
619
|
automatically once the migration is live, in the same run if you said yes,
|
|
620
|
-
or on your next
|
|
620
|
+
or on your next `@exposify/supabase init` otherwise.
|
|
621
621
|
|
|
622
622
|
**`PostgREST OpenAPI fetch failed: HTTP 401`, and the error body (if you
|
|
623
623
|
curl it directly) says `"Only the service_role API key can be used for this
|
package/dist/cli.js
CHANGED
|
@@ -40,7 +40,7 @@ async function fetchExposifyMeta(postgrestUrl, headers, fetchImpl) {
|
|
|
40
40
|
});
|
|
41
41
|
if (res.status === 404) {
|
|
42
42
|
throw new MissingExposifyMetaError(
|
|
43
|
-
"exposify_meta() is not reachable at /rpc/exposify_meta (404). Run `npx exposify
|
|
43
|
+
"exposify_meta() is not reachable at /rpc/exposify_meta (404). Run `npx @exposify/supabase init` (or apply sql/exposify_meta.sql directly) and `supabase db push`, then redeploy the function."
|
|
44
44
|
);
|
|
45
45
|
}
|
|
46
46
|
if (!res.ok) {
|
|
@@ -399,6 +399,9 @@ async function discoverConnection(deps) {
|
|
|
399
399
|
return { connection: local ? fromLocal() : null, notes };
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
+
// bin/doctor.ts
|
|
403
|
+
import { DEFAULT_BROKER_URL, parseExposifyKey, readSupabaseAuth } from "@exposify/core";
|
|
404
|
+
|
|
402
405
|
// bin/function-file.ts
|
|
403
406
|
var KEY_SECRET = "EXPOSIFY_KEY";
|
|
404
407
|
var EMPTY_CONFIG = { writes: [], rpc: [], anonymousSignIn: false, broker: null, oauth: false };
|
|
@@ -422,7 +425,7 @@ var ANONYMOUS_MARKER = "anonymous sign-in enabled";
|
|
|
422
425
|
function buildManagedBlock(config) {
|
|
423
426
|
if (config.broker && config.oauth) throw new Error("the hosted broker and Supabase Auth OAuth can't be combined");
|
|
424
427
|
const lines = [
|
|
425
|
-
`${BEGIN} -- generated by \`npx exposify
|
|
428
|
+
`${BEGIN} -- generated by \`npx @exposify/supabase init\`, which rewrites`,
|
|
426
429
|
`// everything down to exposify:end when you re-run it. Add your own code below.`
|
|
427
430
|
];
|
|
428
431
|
if (config.anonymousSignIn) lines.push(...ANONYMOUS_SIGN_IN_WARNING);
|
|
@@ -669,12 +672,12 @@ async function runDoctor(deps) {
|
|
|
669
672
|
const files = [];
|
|
670
673
|
const migration = findExistingMigration(await deps.listDir(MIGRATIONS_DIR), "_exposify_meta.sql");
|
|
671
674
|
files.push(
|
|
672
|
-
migration ? { status: "pass", message: `migration ${MIGRATIONS_DIR}/${migration}` } : { status: "fail", message: "no *_exposify_meta.sql migration", fix: "npx exposify
|
|
675
|
+
migration ? { status: "pass", message: `migration ${MIGRATIONS_DIR}/${migration}` } : { status: "fail", message: "no *_exposify_meta.sql migration", fix: "npx @exposify/supabase init" }
|
|
673
676
|
);
|
|
674
677
|
const functionSource = await deps.readText(FUNCTION_PATH);
|
|
675
678
|
const authMode = functionSource === null ? null : detectAuthMode(functionSource);
|
|
676
679
|
files.push(
|
|
677
|
-
functionSource !== null ? { status: "pass", message: `function ${FUNCTION_PATH} (auth: ${AUTH_LABEL[authMode]})` } : { status: "fail", message: `no ${FUNCTION_PATH}`, fix: "npx exposify
|
|
680
|
+
functionSource !== null ? { status: "pass", message: `function ${FUNCTION_PATH} (auth: ${AUTH_LABEL[authMode]})` } : { status: "fail", message: `no ${FUNCTION_PATH}`, fix: "npx @exposify/supabase init" }
|
|
678
681
|
);
|
|
679
682
|
if (bypassesGatewayJwt2(authMode)) {
|
|
680
683
|
const verifyJwt = toml === null ? null : readFunctionVerifyJwt(toml);
|
|
@@ -686,7 +689,7 @@ async function runDoctor(deps) {
|
|
|
686
689
|
} : {
|
|
687
690
|
status: "fail",
|
|
688
691
|
message: `verify_jwt is ${verifyJwt === null ? "unset (defaults to on)" : "on"} for ${FUNCTION_NAME}; ` + (oauth ? "MCP clients will never get the OAuth login challenge" : "the gateway will reject every extb_... token"),
|
|
689
|
-
fix: "re-run `npx exposify
|
|
692
|
+
fix: "re-run `npx @exposify/supabase init` (it sets it), or add `[functions.mcp] verify_jwt = false` to config.toml and redeploy"
|
|
690
693
|
}
|
|
691
694
|
);
|
|
692
695
|
}
|
|
@@ -747,7 +750,7 @@ async function runDoctor(deps) {
|
|
|
747
750
|
db.push({
|
|
748
751
|
status: "warn",
|
|
749
752
|
message: `${undocumented.length} table(s) missing comments, so tools have thin descriptions: ${undocumented.map((t) => t.name).join(", ")}`,
|
|
750
|
-
fix: "re-run `npx exposify
|
|
753
|
+
fix: "re-run `npx @exposify/supabase init` and write descriptions, or add COMMENT ON TABLE/COLUMN"
|
|
751
754
|
});
|
|
752
755
|
}
|
|
753
756
|
}
|
|
@@ -763,8 +766,35 @@ async function runDoctor(deps) {
|
|
|
763
766
|
}
|
|
764
767
|
if (authMode === "broker") fn.push(licenseSecretCheck(deps));
|
|
765
768
|
sections.push({ title: "Deployed function", checks: fn });
|
|
769
|
+
if (authMode === "broker" && conn) sections.push({ title: "Connect (agents sign in to your Supabase Auth)", checks: await connectChecks(deps, conn) });
|
|
766
770
|
return sections;
|
|
767
771
|
}
|
|
772
|
+
async function connectChecks(deps, conn) {
|
|
773
|
+
const auth = conn.anonKey ? await readSupabaseAuth(conn.url, conn.anonKey, deps.fetch) : void 0;
|
|
774
|
+
const checks = [];
|
|
775
|
+
if (!auth) {
|
|
776
|
+
checks.push({ status: "warn", message: "couldn't read the project's Auth settings (/auth/v1/settings)", fix: "pass the anon key with --key, or check Auth is enabled" });
|
|
777
|
+
return checks;
|
|
778
|
+
}
|
|
779
|
+
checks.push(
|
|
780
|
+
auth.providers.length ? { status: "pass", message: `social sign-in offered to agents: ${auth.providers.join(", ")}` } : {
|
|
781
|
+
status: "warn",
|
|
782
|
+
message: "no social provider is switched on, so agents can only sign in with an email code",
|
|
783
|
+
fix: "switch one on in Auth \u2192 Sign In / Providers, or turn on email codes in the agemno dashboard"
|
|
784
|
+
}
|
|
785
|
+
);
|
|
786
|
+
if (!auth.signupsDisabled) {
|
|
787
|
+
checks.push({ status: "warn", message: "sign-ups are allowed: a new user who signs in with a social provider through Connect gets an account" });
|
|
788
|
+
}
|
|
789
|
+
const projectId = deps.exposifyKey ? parseExposifyKey(deps.exposifyKey)?.projectId : void 0;
|
|
790
|
+
const callback = projectId ? `${(deps.brokerUrl ?? DEFAULT_BROKER_URL).replace(/\/+$/, "")}/projects/${projectId}/supabase-callback` : null;
|
|
791
|
+
checks.push({
|
|
792
|
+
status: "warn",
|
|
793
|
+
message: callback ? `Redirect URLs must include ${callback} (can't be checked from here)` : "Redirect URLs must include the broker's callback (can't be checked from here)",
|
|
794
|
+
fix: callback ? "add it in Auth \u2192 URL Configuration \u2192 Redirect URLs" : "the agemno dashboard's sign-in page shows it; or run doctor with EXPOSIFY_KEY set to print it"
|
|
795
|
+
});
|
|
796
|
+
return checks;
|
|
797
|
+
}
|
|
768
798
|
function probeChecks(url, outcome, authMode, local) {
|
|
769
799
|
switch (outcome.kind) {
|
|
770
800
|
case "ok": {
|
|
@@ -1470,14 +1500,14 @@ No files were changed. Merge the block above into ${FUNCTION_PATH2}, then re-run
|
|
|
1470
1500
|
}
|
|
1471
1501
|
if (!c) {
|
|
1472
1502
|
console.log(
|
|
1473
|
-
"\nNothing changed locally. Couldn't check the project itself (no --url/--key, env vars, or local/linked project) -- run `npx exposify
|
|
1503
|
+
"\nNothing changed locally. Couldn't check the project itself (no --url/--key, env vars, or local/linked project) -- run `npx @exposify/supabase doctor` once it's reachable."
|
|
1474
1504
|
);
|
|
1475
1505
|
return;
|
|
1476
1506
|
}
|
|
1477
1507
|
console.log("\nNothing changed locally, but the project is behind:");
|
|
1478
1508
|
for (const n of verdict.notes) console.log(` - ${n}`);
|
|
1479
1509
|
if (!verdict.push && !verdict.deploy) {
|
|
1480
|
-
console.log("Run `npx exposify
|
|
1510
|
+
console.log("Run `npx @exposify/supabase doctor` for details.");
|
|
1481
1511
|
return;
|
|
1482
1512
|
}
|
|
1483
1513
|
if (c.source === "local") {
|
|
@@ -1493,7 +1523,7 @@ That's the local stack: run ${todo.join(" and ")}.`);
|
|
|
1493
1523
|
const again = await tryIntrospect(await getConn());
|
|
1494
1524
|
if (!again.introspection) {
|
|
1495
1525
|
console.log(
|
|
1496
|
-
"\nDeployed, but couldn't reach the project to introspect tables (no --url/--key, no SUPABASE_URL/SUPABASE_SERVICE_ROLE_KEY, and no local stack or linked project the Supabase CLI could supply keys for). Re-run `exposify
|
|
1526
|
+
"\nDeployed, but couldn't reach the project to introspect tables (no --url/--key, no SUPABASE_URL/SUPABASE_SERVICE_ROLE_KEY, and no local stack or linked project the Supabase CLI could supply keys for). Re-run `npx @exposify/supabase init` with those set to configure writes, RPCs, and documentation interactively."
|
|
1497
1527
|
);
|
|
1498
1528
|
console.log(`
|
|
1499
1529
|
Done. ${clientStep(effectiveAuth)}`);
|
|
@@ -1567,7 +1597,9 @@ async function cmdDoctor() {
|
|
|
1567
1597
|
connectionNotes: notes,
|
|
1568
1598
|
projectRef,
|
|
1569
1599
|
fetch: fetchWithTimeout,
|
|
1570
|
-
token: argValue("--token")
|
|
1600
|
+
token: argValue("--token"),
|
|
1601
|
+
...process.env.EXPOSIFY_KEY ? { exposifyKey: process.env.EXPOSIFY_KEY } : {},
|
|
1602
|
+
...process.env.EXPOSIFY_BROKER_URL ? { brokerUrl: process.env.EXPOSIFY_BROKER_URL } : {}
|
|
1571
1603
|
});
|
|
1572
1604
|
console.log(formatReport(sections));
|
|
1573
1605
|
if (hasFailures(sections)) process.exitCode = 1;
|
|
@@ -1587,7 +1619,7 @@ async function main() {
|
|
|
1587
1619
|
if (cmd === "generate") return cmdGenerate();
|
|
1588
1620
|
if (cmd === "doctor") return cmdDoctor();
|
|
1589
1621
|
console.log(
|
|
1590
|
-
"Usage: exposify
|
|
1622
|
+
"Usage: @exposify/supabase <init|generate|doctor> [--url <url>] [--key <service-role-key>] [--local|--linked] [--broker [--broker-url <url>] | --oauth] [--token <token>] [--check]"
|
|
1591
1623
|
);
|
|
1592
1624
|
console.log(" init Scaffold the exposify_meta() migration + supabase/functions/mcp/index.ts, then (when the");
|
|
1593
1625
|
console.log(" project is reachable) walk through anonymous sign-in, per-table writes, RPC exposure, and");
|
package/dist/index.cjs
CHANGED
|
@@ -107,7 +107,7 @@ function warnOnce(message) {
|
|
|
107
107
|
if (warned.has(message)) return;
|
|
108
108
|
warned.add(message);
|
|
109
109
|
try {
|
|
110
|
-
console.warn(`[exposify
|
|
110
|
+
console.warn(`[@exposify/supabase] ${message}`);
|
|
111
111
|
} catch {
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -206,7 +206,7 @@ async function fetchExposifyMeta(postgrestUrl, headers, fetchImpl) {
|
|
|
206
206
|
});
|
|
207
207
|
if (res.status === 404) {
|
|
208
208
|
throw new MissingExposifyMetaError(
|
|
209
|
-
"exposify_meta() is not reachable at /rpc/exposify_meta (404). Run `npx exposify
|
|
209
|
+
"exposify_meta() is not reachable at /rpc/exposify_meta (404). Run `npx @exposify/supabase init` (or apply sql/exposify_meta.sql directly) and `supabase db push`, then redeploy the function."
|
|
210
210
|
);
|
|
211
211
|
}
|
|
212
212
|
if (!res.ok) {
|
|
@@ -624,7 +624,7 @@ function registerDiagnosticsResource(server, warnings) {
|
|
|
624
624
|
server.registerResource(
|
|
625
625
|
"exposify-diagnostics",
|
|
626
626
|
"exposify://diagnostics",
|
|
627
|
-
{ title: "exposify
|
|
627
|
+
{ title: "@exposify/supabase diagnostics", mimeType: "application/json" },
|
|
628
628
|
async (uri) => ({ contents: [{ uri: uri.href, text: JSON.stringify(warnings, null, 2) }] })
|
|
629
629
|
);
|
|
630
630
|
}
|
|
@@ -698,8 +698,8 @@ function createSupabaseOAuthGate(oauth, supabaseUrl, fetchImpl) {
|
|
|
698
698
|
|
|
699
699
|
// src/startup.ts
|
|
700
700
|
var import_core4 = require("@exposify/core");
|
|
701
|
-
var LOG_PREFIX = "[exposify
|
|
702
|
-
var INTROSPECTION_FIX = "check that SUPABASE_SERVICE_ROLE_KEY is available to the function and the exposify_meta migration is applied (`supabase db push`); the reason is logged above. To survive this, bundle a manifest with `exposify
|
|
701
|
+
var LOG_PREFIX = "[@exposify/supabase]";
|
|
702
|
+
var INTROSPECTION_FIX = "check that SUPABASE_SERVICE_ROLE_KEY is available to the function and the exposify_meta migration is applied (`supabase db push`); the reason is logged above. To survive this, bundle a manifest with `npx @exposify/supabase generate`";
|
|
703
703
|
function supabaseToolsPhrase(ladder, cfg, hasDocs) {
|
|
704
704
|
const query = ladder.tables.length;
|
|
705
705
|
let write = 0;
|
|
@@ -797,7 +797,7 @@ function aiReady(options = {}) {
|
|
|
797
797
|
manifest: options.manifest
|
|
798
798
|
}).then((ladder) => {
|
|
799
799
|
for (const w of ladder.warnings) {
|
|
800
|
-
console.error(`[exposify
|
|
800
|
+
console.error(`[@exposify/supabase] degradation ladder: ${w.rung}: ${w.reason}`);
|
|
801
801
|
}
|
|
802
802
|
return ladder;
|
|
803
803
|
});
|
|
@@ -834,7 +834,7 @@ function aiReady(options = {}) {
|
|
|
834
834
|
return { status: res.status, bodyText: await res.text() };
|
|
835
835
|
};
|
|
836
836
|
return buildSupabaseMcpServer({
|
|
837
|
-
appName: "exposify
|
|
837
|
+
appName: "@exposify/supabase",
|
|
838
838
|
appVersion: "0.0.0",
|
|
839
839
|
tables: ladder.tables,
|
|
840
840
|
cfg,
|
|
@@ -861,6 +861,16 @@ function aiReady(options = {}) {
|
|
|
861
861
|
const authRequired = options.authRequired ?? (envAuth !== void 0 || (0, import_core5.isHostedResolver)(auth));
|
|
862
862
|
if (!auth) return core;
|
|
863
863
|
const brokerGate = authRequired ? (0, import_core5.createBrokerGate)(auth) : null;
|
|
864
|
+
const authKey = anonKey || env?.get("SUPABASE_PUBLISHABLE_KEY") || "";
|
|
865
|
+
let supabaseAuth2;
|
|
866
|
+
const supabaseAuthOnce = () => {
|
|
867
|
+
if (!supabaseUrl || !authKey) return Promise.resolve(void 0);
|
|
868
|
+
supabaseAuth2 ??= (0, import_core5.readSupabaseAuth)(supabaseUrl, authKey, fetchImpl).then((found) => {
|
|
869
|
+
if (!found) supabaseAuth2 = void 0;
|
|
870
|
+
return found;
|
|
871
|
+
});
|
|
872
|
+
return supabaseAuth2;
|
|
873
|
+
};
|
|
864
874
|
const functionUrl = supabaseUrl && functionName ? `${supabaseUrl.replace(/\/+$/, "")}/functions/v1/${functionName}` : null;
|
|
865
875
|
return async (req) => {
|
|
866
876
|
let open = false;
|
|
@@ -868,7 +878,13 @@ function aiReady(options = {}) {
|
|
|
868
878
|
const own = (0, import_core5.publicRequestUrl)(req.url, req.headers);
|
|
869
879
|
const isPrm = own.endsWith(PRM_SUFFIX);
|
|
870
880
|
const resource = brokerGate.resourceFor(functionUrl ?? (isPrm ? own.slice(0, -PRM_SUFFIX.length) : own));
|
|
871
|
-
|
|
881
|
+
const supabase = await supabaseAuthOnce();
|
|
882
|
+
open = await (0, import_core5.reportAppLogin)(auth, resource, null, {
|
|
883
|
+
stacks: ["@supabase/supabase-js"],
|
|
884
|
+
why: supabase ? "Your Supabase Auth is turned off for agents in the dashboard." : "Couldn't read your project's Supabase Auth settings (/auth/v1/settings with the anon key), so Connect can't offer it yet.",
|
|
885
|
+
log: logReady,
|
|
886
|
+
...supabase ? { supabase } : {}
|
|
887
|
+
}) === "open";
|
|
872
888
|
if (open) {
|
|
873
889
|
if (isPrm) return new Response("not found", { status: 404 });
|
|
874
890
|
} else {
|
|
@@ -881,7 +897,7 @@ function aiReady(options = {}) {
|
|
|
881
897
|
try {
|
|
882
898
|
resolved = await auth(req.headers, {});
|
|
883
899
|
} catch (err) {
|
|
884
|
-
console.error("exposify
|
|
900
|
+
console.error("@exposify/supabase: auth resolver failed", err);
|
|
885
901
|
return Response.json({ error: "internal MCP server error" }, { status: 503 });
|
|
886
902
|
}
|
|
887
903
|
if (authRequired && !open && !resolved.get("authorization")) {
|
package/dist/index.d.cts
CHANGED
|
@@ -126,7 +126,7 @@ interface AgentifySupabaseOptions {
|
|
|
126
126
|
authRequired?: boolean;
|
|
127
127
|
/** Injectable for tests; defaults to global `fetch`. */
|
|
128
128
|
fetchImpl?: typeof fetch;
|
|
129
|
-
/** Bundled fallback manifest (from
|
|
129
|
+
/** Bundled fallback manifest (from `@exposify/supabase generate`), used when runtime introspection fails. */
|
|
130
130
|
manifest?: unknown;
|
|
131
131
|
rateLimit?: number;
|
|
132
132
|
rateWindowMs?: number;
|
|
@@ -196,7 +196,7 @@ interface ExposifyManifest {
|
|
|
196
196
|
generatedAt: string;
|
|
197
197
|
tables: TableSchema[];
|
|
198
198
|
}
|
|
199
|
-
/** Built by `npx exposify
|
|
199
|
+
/** Built by `npx @exposify/supabase generate` against a local/linked project. */
|
|
200
200
|
declare function buildManifest(openapi: PostgrestOpenApiDoc, metaRows: MetaRow[], generatedAt: string): ExposifyManifest;
|
|
201
201
|
/**
|
|
202
202
|
* Defensive load: a manifest is bundled JSON shipped alongside the function
|
package/dist/index.d.ts
CHANGED
|
@@ -126,7 +126,7 @@ interface AgentifySupabaseOptions {
|
|
|
126
126
|
authRequired?: boolean;
|
|
127
127
|
/** Injectable for tests; defaults to global `fetch`. */
|
|
128
128
|
fetchImpl?: typeof fetch;
|
|
129
|
-
/** Bundled fallback manifest (from
|
|
129
|
+
/** Bundled fallback manifest (from `@exposify/supabase generate`), used when runtime introspection fails. */
|
|
130
130
|
manifest?: unknown;
|
|
131
131
|
rateLimit?: number;
|
|
132
132
|
rateWindowMs?: number;
|
|
@@ -196,7 +196,7 @@ interface ExposifyManifest {
|
|
|
196
196
|
generatedAt: string;
|
|
197
197
|
tables: TableSchema[];
|
|
198
198
|
}
|
|
199
|
-
/** Built by `npx exposify
|
|
199
|
+
/** Built by `npx @exposify/supabase generate` against a local/linked project. */
|
|
200
200
|
declare function buildManifest(openapi: PostgrestOpenApiDoc, metaRows: MetaRow[], generatedAt: string): ExposifyManifest;
|
|
201
201
|
/**
|
|
202
202
|
* Defensive load: a manifest is bundled JSON shipped alongside the function
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { createBrokerGate, createWebMcpHandler, formatFailed as formatFailed2, publicRequestUrl, formatWarning as formatWarning2, hostedResolverFromEnv, isHostedResolver, readEnv, reportAppLogin, startupLogEnabled } from "@exposify/core";
|
|
2
|
+
import { createBrokerGate, createWebMcpHandler, formatFailed as formatFailed2, publicRequestUrl, formatWarning as formatWarning2, hostedResolverFromEnv, isHostedResolver, readEnv, readSupabaseAuth, reportAppLogin, startupLogEnabled } from "@exposify/core";
|
|
3
3
|
|
|
4
4
|
// src/kb.ts
|
|
5
5
|
import { KnowledgeBase } from "@exposify/core";
|
|
@@ -70,7 +70,7 @@ function warnOnce(message) {
|
|
|
70
70
|
if (warned.has(message)) return;
|
|
71
71
|
warned.add(message);
|
|
72
72
|
try {
|
|
73
|
-
console.warn(`[exposify
|
|
73
|
+
console.warn(`[@exposify/supabase] ${message}`);
|
|
74
74
|
} catch {
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -169,7 +169,7 @@ async function fetchExposifyMeta(postgrestUrl, headers, fetchImpl) {
|
|
|
169
169
|
});
|
|
170
170
|
if (res.status === 404) {
|
|
171
171
|
throw new MissingExposifyMetaError(
|
|
172
|
-
"exposify_meta() is not reachable at /rpc/exposify_meta (404). Run `npx exposify
|
|
172
|
+
"exposify_meta() is not reachable at /rpc/exposify_meta (404). Run `npx @exposify/supabase init` (or apply sql/exposify_meta.sql directly) and `supabase db push`, then redeploy the function."
|
|
173
173
|
);
|
|
174
174
|
}
|
|
175
175
|
if (!res.ok) {
|
|
@@ -590,7 +590,7 @@ function registerDiagnosticsResource(server, warnings) {
|
|
|
590
590
|
server.registerResource(
|
|
591
591
|
"exposify-diagnostics",
|
|
592
592
|
"exposify://diagnostics",
|
|
593
|
-
{ title: "exposify
|
|
593
|
+
{ title: "@exposify/supabase diagnostics", mimeType: "application/json" },
|
|
594
594
|
async (uri) => ({ contents: [{ uri: uri.href, text: JSON.stringify(warnings, null, 2) }] })
|
|
595
595
|
);
|
|
596
596
|
}
|
|
@@ -664,8 +664,8 @@ function createSupabaseOAuthGate(oauth, supabaseUrl, fetchImpl) {
|
|
|
664
664
|
|
|
665
665
|
// src/startup.ts
|
|
666
666
|
import { formatFailed, formatReady, formatWarning } from "@exposify/core";
|
|
667
|
-
var LOG_PREFIX = "[exposify
|
|
668
|
-
var INTROSPECTION_FIX = "check that SUPABASE_SERVICE_ROLE_KEY is available to the function and the exposify_meta migration is applied (`supabase db push`); the reason is logged above. To survive this, bundle a manifest with `exposify
|
|
667
|
+
var LOG_PREFIX = "[@exposify/supabase]";
|
|
668
|
+
var INTROSPECTION_FIX = "check that SUPABASE_SERVICE_ROLE_KEY is available to the function and the exposify_meta migration is applied (`supabase db push`); the reason is logged above. To survive this, bundle a manifest with `npx @exposify/supabase generate`";
|
|
669
669
|
function supabaseToolsPhrase(ladder, cfg, hasDocs) {
|
|
670
670
|
const query = ladder.tables.length;
|
|
671
671
|
let write = 0;
|
|
@@ -763,7 +763,7 @@ function aiReady(options = {}) {
|
|
|
763
763
|
manifest: options.manifest
|
|
764
764
|
}).then((ladder) => {
|
|
765
765
|
for (const w of ladder.warnings) {
|
|
766
|
-
console.error(`[exposify
|
|
766
|
+
console.error(`[@exposify/supabase] degradation ladder: ${w.rung}: ${w.reason}`);
|
|
767
767
|
}
|
|
768
768
|
return ladder;
|
|
769
769
|
});
|
|
@@ -800,7 +800,7 @@ function aiReady(options = {}) {
|
|
|
800
800
|
return { status: res.status, bodyText: await res.text() };
|
|
801
801
|
};
|
|
802
802
|
return buildSupabaseMcpServer({
|
|
803
|
-
appName: "exposify
|
|
803
|
+
appName: "@exposify/supabase",
|
|
804
804
|
appVersion: "0.0.0",
|
|
805
805
|
tables: ladder.tables,
|
|
806
806
|
cfg,
|
|
@@ -827,6 +827,16 @@ function aiReady(options = {}) {
|
|
|
827
827
|
const authRequired = options.authRequired ?? (envAuth !== void 0 || isHostedResolver(auth));
|
|
828
828
|
if (!auth) return core;
|
|
829
829
|
const brokerGate = authRequired ? createBrokerGate(auth) : null;
|
|
830
|
+
const authKey = anonKey || env?.get("SUPABASE_PUBLISHABLE_KEY") || "";
|
|
831
|
+
let supabaseAuth2;
|
|
832
|
+
const supabaseAuthOnce = () => {
|
|
833
|
+
if (!supabaseUrl || !authKey) return Promise.resolve(void 0);
|
|
834
|
+
supabaseAuth2 ??= readSupabaseAuth(supabaseUrl, authKey, fetchImpl).then((found) => {
|
|
835
|
+
if (!found) supabaseAuth2 = void 0;
|
|
836
|
+
return found;
|
|
837
|
+
});
|
|
838
|
+
return supabaseAuth2;
|
|
839
|
+
};
|
|
830
840
|
const functionUrl = supabaseUrl && functionName ? `${supabaseUrl.replace(/\/+$/, "")}/functions/v1/${functionName}` : null;
|
|
831
841
|
return async (req) => {
|
|
832
842
|
let open = false;
|
|
@@ -834,7 +844,13 @@ function aiReady(options = {}) {
|
|
|
834
844
|
const own = publicRequestUrl(req.url, req.headers);
|
|
835
845
|
const isPrm = own.endsWith(PRM_SUFFIX);
|
|
836
846
|
const resource = brokerGate.resourceFor(functionUrl ?? (isPrm ? own.slice(0, -PRM_SUFFIX.length) : own));
|
|
837
|
-
|
|
847
|
+
const supabase = await supabaseAuthOnce();
|
|
848
|
+
open = await reportAppLogin(auth, resource, null, {
|
|
849
|
+
stacks: ["@supabase/supabase-js"],
|
|
850
|
+
why: supabase ? "Your Supabase Auth is turned off for agents in the dashboard." : "Couldn't read your project's Supabase Auth settings (/auth/v1/settings with the anon key), so Connect can't offer it yet.",
|
|
851
|
+
log: logReady,
|
|
852
|
+
...supabase ? { supabase } : {}
|
|
853
|
+
}) === "open";
|
|
838
854
|
if (open) {
|
|
839
855
|
if (isPrm) return new Response("not found", { status: 404 });
|
|
840
856
|
} else {
|
|
@@ -847,7 +863,7 @@ function aiReady(options = {}) {
|
|
|
847
863
|
try {
|
|
848
864
|
resolved = await auth(req.headers, {});
|
|
849
865
|
} catch (err) {
|
|
850
|
-
console.error("exposify
|
|
866
|
+
console.error("@exposify/supabase: auth resolver failed", err);
|
|
851
867
|
return Response.json({ error: "internal MCP server error" }, { status: 503 });
|
|
852
868
|
}
|
|
853
869
|
if (authRequired && !open && !resolved.get("authorization")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exposify/supabase",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.11",
|
|
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.16",
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
32
32
|
"zod": "^3.24.0"
|
|
33
33
|
},
|
package/sql/exposify_meta.sql
CHANGED