better-supabase 0.0.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.
- package/LICENSE +21 -0
- package/README.md +88 -1
- package/bin/better-supabase.js +4 -0
- package/dist/casing-da0uRTqt.js +14 -0
- package/dist/claims-CoTobNC-.js +25 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +15 -0
- package/dist/cli/index.d.ts +226 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli-DrSgDOQC.js +6646 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +112 -0
- package/dist/compiler-CZ9nIuqy.d.ts +14 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +3 -0
- package/dist/config-ClDXmVtY.js +277 -0
- package/dist/define-BXMYD9AY.js +1071 -0
- package/dist/define-e3zI4_4g.d.ts +679 -0
- package/dist/edge/index.d.ts +43 -0
- package/dist/edge/index.js +103 -0
- package/dist/entitlements-CTEBm7IB.js +20 -0
- package/dist/entitlements-Dng-ctuT.d.ts +42 -0
- package/dist/env/index.d.ts +2 -0
- package/dist/env/index.js +2 -0
- package/dist/env-BEToab0I.js +204 -0
- package/dist/errors-Bl3rkOzo.js +196 -0
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.js +2 -0
- package/dist/events-BuzTgn2D.js +135 -0
- package/dist/events-DOt3nSO-.d.ts +84 -0
- package/dist/executor-BYXQ9AGn.d.ts +25 -0
- package/dist/executor-CGdNQ27g.d.ts +195 -0
- package/dist/executor-DeppvirK.js +89 -0
- package/dist/guards-BnzDvFrz.js +10 -0
- package/dist/hono/index.d.ts +45 -0
- package/dist/hono/index.js +70 -0
- package/dist/index-Bw1bwtC0.d.ts +144 -0
- package/dist/index-CBDufH1M.d.ts +120 -0
- package/dist/index-CG69s15v.d.ts +38 -0
- package/dist/index-Ciu5Xlml.d.ts +74 -0
- package/dist/index-D4v7sncu.d.ts +62 -0
- package/dist/index-DOsjFvMf.d.ts +618 -0
- package/dist/index-DbxFr7XG.d.ts +76 -0
- package/dist/index-DuP14VHo.d.ts +216 -0
- package/dist/index-Dy6kuF0P.d.ts +71 -0
- package/dist/index-K-3WM2Hp.d.ts +117 -0
- package/dist/index.d.ts +110 -0
- package/dist/index.js +43 -0
- package/dist/invalidate-DbxuL2jL.js +18 -0
- package/dist/jobs/index.d.ts +173 -0
- package/dist/jobs/index.js +478 -0
- package/dist/json-schema-BpYqkCKf.js +182 -0
- package/dist/lint/index.d.ts +75 -0
- package/dist/lint/index.js +167 -0
- package/dist/list/index.d.ts +2 -0
- package/dist/list/index.js +410 -0
- package/dist/live-D8rV1Ldw.js +168 -0
- package/dist/live-DJyZMD6w.d.ts +191 -0
- package/dist/logger-glM28NVN.js +21 -0
- package/dist/mcp/index.d.ts +142 -0
- package/dist/mcp/index.js +463 -0
- package/dist/mfa-CpLfJSde.js +541 -0
- package/dist/mfa-TVia554c.d.ts +44 -0
- package/dist/next/image/index.d.ts +34 -0
- package/dist/next/image/index.js +40 -0
- package/dist/next/index.d.ts +210 -0
- package/dist/next/index.js +427 -0
- package/dist/openapi/index.d.ts +2 -0
- package/dist/openapi/index.js +332 -0
- package/dist/orpc/index.d.ts +46 -0
- package/dist/orpc/index.js +50 -0
- package/dist/otel/index.d.ts +40 -0
- package/dist/otel/index.js +168 -0
- package/dist/path-Cv__-OTj.d.ts +17 -0
- package/dist/permdock-sql-CghlHPuH.js +117 -0
- package/dist/plugin-BqR6wKMB.js +23 -0
- package/dist/plugin-C4TR9J0Z.d.ts +118 -0
- package/dist/plugins/actor/index.d.ts +13 -0
- package/dist/plugins/actor/index.js +43 -0
- package/dist/plugins/rules/index.d.ts +89 -0
- package/dist/plugins/rules/index.js +167 -0
- package/dist/plugins/soft-delete/index.d.ts +38 -0
- package/dist/plugins/soft-delete/index.js +69 -0
- package/dist/plugins/tenant/index.d.ts +49 -0
- package/dist/plugins/tenant/index.js +75 -0
- package/dist/plugins/timestamps/index.d.ts +9 -0
- package/dist/plugins/timestamps/index.js +37 -0
- package/dist/plugins/validation/index.d.ts +24 -0
- package/dist/plugins/validation/index.js +59 -0
- package/dist/pool-R2m_DkXi.d.ts +37 -0
- package/dist/postgres/index.d.ts +28 -0
- package/dist/postgres/index.js +79 -0
- package/dist/postgrest-cAEQ_IQN.d.ts +43 -0
- package/dist/problem-BTkTtRV9.js +97 -0
- package/dist/problem-DlgB0lYl.d.ts +47 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +3 -0
- package/dist/query-ChvDbsDE.js +186 -0
- package/dist/react/index.d.ts +7 -0
- package/dist/react/index.js +5 -0
- package/dist/react/server.d.ts +13 -0
- package/dist/react/server.js +25 -0
- package/dist/react/session.d.ts +2 -0
- package/dist/react/session.js +3 -0
- package/dist/react-C83BdBGt.js +242 -0
- package/dist/read-set-4lI3W_gZ.js +216 -0
- package/dist/realtime/index.d.ts +4 -0
- package/dist/realtime/index.js +268 -0
- package/dist/resolve-CjHILk2w.d.ts +170 -0
- package/dist/resource-BHEWmQJK.js +175 -0
- package/dist/resource-CgZa3YG_.d.ts +65 -0
- package/dist/respond-DMcbQwfN.js +383 -0
- package/dist/respond-Dne1ARSa.d.ts +169 -0
- package/dist/result-CTkCZ6JA.d.ts +198 -0
- package/dist/result-DWXatkd6.js +103 -0
- package/dist/scope-CDHcS7dE.js +90 -0
- package/dist/seed-CzecODcJ.js +101 -0
- package/dist/server/index.d.ts +42 -0
- package/dist/server/index.js +68 -0
- package/dist/session-C3LxnQ0e.d.ts +22 -0
- package/dist/session-CqEwzVup.js +20 -0
- package/dist/shared-_6-uPL7E.js +57 -0
- package/dist/simplify-bwJpLnaS.js +665 -0
- package/dist/spec-pins-C3oc-Tdp.js +20 -0
- package/dist/spec-pins-CqorWDnO.d.ts +20 -0
- package/dist/sql-DTjA1LPj.js +260 -0
- package/dist/ssr/index.d.ts +5 -0
- package/dist/ssr/index.js +4 -0
- package/dist/standard-CyhRzctb.d.ts +7 -0
- package/dist/standard-FUi3Z3-7.js +21 -0
- package/dist/stats-C5W-rBfR.js +566 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage-DuGn38fr.js +527 -0
- package/dist/tables-rZxRxNPW.js +59 -0
- package/dist/template-mbvlysOo.d.ts +6 -0
- package/dist/testing/index.d.ts +325 -0
- package/dist/testing/index.js +819 -0
- package/dist/types-6cYK9pbJ.js +29 -0
- package/dist/types-C83v5SQz.d.ts +248 -0
- package/dist/version-Cqq1m3UY.js +4 -0
- package/dist/view-D1s0wa0T.d.ts +49 -0
- package/dist/view-aOYizTeE.js +36 -0
- package/dist/webhooks/index.d.ts +152 -0
- package/dist/webhooks/index.js +2 -0
- package/dist/webhooks-DMipjhSP.js +139 -0
- package/package.json +250 -17
- package/schemas/config-v1.json +373 -0
- package/schemas/doctor-report-v1.json +106 -0
- package/schemas/snapshot-v2.json +1006 -0
- package/skills/better-supabase/SKILL.md +84 -0
- package/skills/better-supabase/references/plugins.md +65 -0
- package/skills/better-supabase/references/troubleshooting.md +39 -0
- package/skills/better-supabase-api/SKILL.md +67 -0
- package/skills/better-supabase-api/references/adapters.md +108 -0
- package/skills/better-supabase-testing/SKILL.md +66 -0
- package/index.js +0 -1
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { n as dbError } from "../errors-Bl3rkOzo.js";
|
|
2
|
+
import { t as validate } from "../standard-FUi3Z3-7.js";
|
|
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
|
+
//#region src/mcp/index.ts
|
|
10
|
+
const MCP_PROTOCOL_VERSION = SPEC_PINS.mcp;
|
|
11
|
+
const WELL_KNOWN = "/.well-known/oauth-protected-resource";
|
|
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];
|
|
19
|
+
/** A custom tool. Pass it in `createMcp({ tools: [...] })`. */
|
|
20
|
+
function defineTool(definition) {
|
|
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 "."`);
|
|
22
|
+
const standard = definition.input?.["~standard"];
|
|
23
|
+
const inputSchema = definition.inputSchema ?? standard?.jsonSchema?.input({ target: "draft-2020-12" }) ?? (definition.input ? void 0 : {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {}
|
|
26
|
+
});
|
|
27
|
+
if (!inputSchema) throw new TypeError(`Tool "${definition.name}": the input schema has no JSON Schema; pass inputSchema`);
|
|
28
|
+
const { $schema: _, ...schema } = inputSchema;
|
|
29
|
+
return {
|
|
30
|
+
info: {
|
|
31
|
+
name: definition.name,
|
|
32
|
+
...definition.title ? { title: definition.title } : {},
|
|
33
|
+
description: definition.description,
|
|
34
|
+
inputSchema: schema,
|
|
35
|
+
...definition.outputSchema ? { outputSchema: definition.outputSchema } : {},
|
|
36
|
+
...definition.annotations ? { annotations: definition.annotations } : {}
|
|
37
|
+
},
|
|
38
|
+
...definition.input ? { input: definition.input } : {},
|
|
39
|
+
...definition.meta === void 0 ? {} : { meta: definition.meta },
|
|
40
|
+
run: (args, ctx) => definition.run(args, ctx)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const ANNOTATIONS = {
|
|
44
|
+
list: {
|
|
45
|
+
readOnlyHint: true,
|
|
46
|
+
openWorldHint: false
|
|
47
|
+
},
|
|
48
|
+
get: {
|
|
49
|
+
readOnlyHint: true,
|
|
50
|
+
openWorldHint: false
|
|
51
|
+
},
|
|
52
|
+
create: {
|
|
53
|
+
readOnlyHint: false,
|
|
54
|
+
destructiveHint: false,
|
|
55
|
+
openWorldHint: false
|
|
56
|
+
},
|
|
57
|
+
update: {
|
|
58
|
+
readOnlyHint: false,
|
|
59
|
+
destructiveHint: true,
|
|
60
|
+
idempotentHint: true,
|
|
61
|
+
openWorldHint: false
|
|
62
|
+
},
|
|
63
|
+
delete: {
|
|
64
|
+
readOnlyHint: false,
|
|
65
|
+
destructiveHint: true,
|
|
66
|
+
idempotentHint: true,
|
|
67
|
+
openWorldHint: false
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function pageSchema(max) {
|
|
71
|
+
return {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
page: {
|
|
75
|
+
type: "integer",
|
|
76
|
+
minimum: 1,
|
|
77
|
+
default: 1
|
|
78
|
+
},
|
|
79
|
+
size: {
|
|
80
|
+
type: "integer",
|
|
81
|
+
minimum: 1,
|
|
82
|
+
maximum: max,
|
|
83
|
+
default: 50
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
additionalProperties: false
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function tableTools(resource, defs) {
|
|
90
|
+
const { table, keyParam } = resource;
|
|
91
|
+
const row = defs[`${table}Row`] ?? { type: "object" };
|
|
92
|
+
const key = keyParam ? row["properties"]?.[keyParam] ?? { type: "string" } : void 0;
|
|
93
|
+
const keyInput = (more = {}) => ({
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
[keyParam]: key,
|
|
97
|
+
...more
|
|
98
|
+
},
|
|
99
|
+
required: [keyParam, ...Object.keys(more)],
|
|
100
|
+
additionalProperties: false
|
|
101
|
+
});
|
|
102
|
+
const page = {
|
|
103
|
+
type: "object",
|
|
104
|
+
properties: {
|
|
105
|
+
items: {
|
|
106
|
+
type: "array",
|
|
107
|
+
items: row
|
|
108
|
+
},
|
|
109
|
+
page: { type: "object" }
|
|
110
|
+
},
|
|
111
|
+
required: ["items", "page"]
|
|
112
|
+
};
|
|
113
|
+
const tools = [];
|
|
114
|
+
const add = (operation, description, inputSchema, outputSchema) => {
|
|
115
|
+
tools.push({
|
|
116
|
+
operation,
|
|
117
|
+
info: {
|
|
118
|
+
name: `${table}_${operation}`,
|
|
119
|
+
title: `${operation[0].toUpperCase()}${operation.slice(1)} ${table}`,
|
|
120
|
+
description,
|
|
121
|
+
inputSchema,
|
|
122
|
+
...outputSchema ? { outputSchema } : {},
|
|
123
|
+
annotations: ANNOTATIONS[operation]
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
for (const operation of resource.operations) switch (operation) {
|
|
128
|
+
case "list": {
|
|
129
|
+
const { $schema: _, ...listSchema } = resource.list?.jsonSchema ?? pageSchema(resource.maxPageSize);
|
|
130
|
+
add("list", `List ${table} rows visible to the caller, one page at a time.`, listSchema, page);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case "get":
|
|
134
|
+
if (keyParam) add("get", `Get one ${table} row by ${keyParam}.`, keyInput(), row);
|
|
135
|
+
break;
|
|
136
|
+
case "create":
|
|
137
|
+
add("create", `Create a ${table} row. Returns the created row.`, defs[`${table}Insert`] ?? { type: "object" }, row);
|
|
138
|
+
break;
|
|
139
|
+
case "update":
|
|
140
|
+
if (keyParam) add("update", `Update the columns in "patch" on one ${table} row.`, keyInput({ patch: defs[`${table}Update`] ?? { type: "object" } }), row);
|
|
141
|
+
break;
|
|
142
|
+
case "delete":
|
|
143
|
+
if (keyParam) add("delete", `Delete one ${table} row by ${keyParam}.`, keyInput());
|
|
144
|
+
break;
|
|
145
|
+
default: throw new TypeError(`Unknown operation ${String(operation)}`);
|
|
146
|
+
}
|
|
147
|
+
return tools;
|
|
148
|
+
}
|
|
149
|
+
const PARSE_ERROR = -32700;
|
|
150
|
+
const INVALID_REQUEST = -32600;
|
|
151
|
+
const METHOD_NOT_FOUND = -32601;
|
|
152
|
+
const INVALID_PARAMS = -32602;
|
|
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) {
|
|
161
|
+
return Response.json({
|
|
162
|
+
jsonrpc: "2.0",
|
|
163
|
+
id: id ?? null,
|
|
164
|
+
error: {
|
|
165
|
+
code,
|
|
166
|
+
message,
|
|
167
|
+
...data ? { data } : {}
|
|
168
|
+
}
|
|
169
|
+
}, { status });
|
|
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("\"", "\\\"")}"`;
|
|
183
|
+
function isRpcRequest(value) {
|
|
184
|
+
return isObject(value) && value["jsonrpc"] === "2.0" && typeof value["method"] === "string" && (value["params"] === void 0 || isObject(value["params"]));
|
|
185
|
+
}
|
|
186
|
+
function textResult(value, isError = false) {
|
|
187
|
+
const structured = typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
188
|
+
return {
|
|
189
|
+
content: [{
|
|
190
|
+
type: "text",
|
|
191
|
+
text: typeof value === "string" ? value : JSON.stringify(value ?? null)
|
|
192
|
+
}],
|
|
193
|
+
...structured ? { structuredContent: structured } : {},
|
|
194
|
+
...isError ? { isError: true } : {}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function canonical(request) {
|
|
198
|
+
const url = new URL(request.url);
|
|
199
|
+
return `${url.origin}${url.pathname.replace(/\/$/, "")}`;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* An MCP server (Streamable HTTP, stateless) whose tools run as the caller:
|
|
203
|
+
* the Bearer token from the MCP client is verified and every query goes
|
|
204
|
+
* through RLS. Answers 401 with RFC 9728 metadata so clients can sign in with
|
|
205
|
+
* Supabase Auth's OAuth server.
|
|
206
|
+
*/
|
|
207
|
+
function createMcp(sb, options) {
|
|
208
|
+
const server = createServer(sb, options);
|
|
209
|
+
const expose = options.exposeErrors ?? defaultExpose();
|
|
210
|
+
const defs = buildJsonSchema({
|
|
211
|
+
meta: sb.meta,
|
|
212
|
+
config: { json: options.json ?? {} }
|
|
213
|
+
})["$defs"];
|
|
214
|
+
const registry = /* @__PURE__ */ new Map();
|
|
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);
|
|
218
|
+
};
|
|
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);
|
|
230
|
+
for (const [table, raw] of Object.entries(options.resources ?? {})) {
|
|
231
|
+
if (!raw) continue;
|
|
232
|
+
const resource = defineResource(sb, table, raw === true ? {} : raw);
|
|
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
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const addTool = (tool) => {
|
|
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);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
for (const tool of options.tools ?? []) addTool(tool);
|
|
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
|
+
};
|
|
284
|
+
const authorizationServers = () => options.authorizationServers ?? [fromSupabaseUrl(server.env.url)];
|
|
285
|
+
const resourceOf = (request) => {
|
|
286
|
+
if (typeof options.resource === "function") return options.resource(request);
|
|
287
|
+
if (options.resource) return options.resource;
|
|
288
|
+
const url = new URL(canonical(request));
|
|
289
|
+
return url.pathname.startsWith(WELL_KNOWN) ? `${url.origin}${url.pathname.slice(37)}` : url.href.replace(/\/$/, "");
|
|
290
|
+
};
|
|
291
|
+
const metadataUrl = (request) => {
|
|
292
|
+
const resource = new URL(resourceOf(request));
|
|
293
|
+
return `${resource.origin}${WELL_KNOWN}${resource.pathname.replace(/\/$/, "")}`;
|
|
294
|
+
};
|
|
295
|
+
const invoke = async (name, args, ctx, checkVisible = true) => {
|
|
296
|
+
const entry = registry.get(name);
|
|
297
|
+
try {
|
|
298
|
+
if (!entry || checkVisible && !await isVisible(entry, ctx)) return failure(dbError("not_found", `Unknown tool "${name}"`));
|
|
299
|
+
return await entry.call(args, ctx);
|
|
300
|
+
} catch (cause) {
|
|
301
|
+
return failure(dbError("unexpected", expose && cause instanceof Error ? cause.message : "The tool failed"));
|
|
302
|
+
}
|
|
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
|
+
};
|
|
352
|
+
const handler = async (request) => {
|
|
353
|
+
const origin = request.headers.get("origin");
|
|
354
|
+
if (origin && options.allowedOrigins && !options.allowedOrigins.includes(origin)) return rpcError(null, INVALID_REQUEST, "Origin not allowed", 403);
|
|
355
|
+
if (request.method !== "POST") return new Response(null, {
|
|
356
|
+
status: 405,
|
|
357
|
+
headers: { allow: "POST" }
|
|
358
|
+
});
|
|
359
|
+
const header = request.headers.get("mcp-protocol-version");
|
|
360
|
+
if (header && !SUPPORTED_VERSIONS.includes(header)) return unsupportedVersion(null, header);
|
|
361
|
+
const ctx = await server.context(request);
|
|
362
|
+
const denied = guard(ctx.auth, options.allow, options.aal);
|
|
363
|
+
if (denied) return denied.kind === "unauthorized" ? unauthorized(request) : forbidden(request, denied.message);
|
|
364
|
+
let message;
|
|
365
|
+
try {
|
|
366
|
+
message = await request.json();
|
|
367
|
+
} catch {
|
|
368
|
+
return rpcError(null, PARSE_ERROR, "Parse error", 400);
|
|
369
|
+
}
|
|
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
|
+
}
|
|
377
|
+
if (message.id === void 0) return new Response(null, { status: 202 });
|
|
378
|
+
const toolContext = () => withExtra(ctx, {
|
|
379
|
+
request,
|
|
380
|
+
signal: request.signal
|
|
381
|
+
});
|
|
382
|
+
const reply = (result) => Response.json({
|
|
383
|
+
jsonrpc: "2.0",
|
|
384
|
+
id: message.id,
|
|
385
|
+
result: modern ? {
|
|
386
|
+
...result,
|
|
387
|
+
resultType: "complete",
|
|
388
|
+
_meta: { [META_SERVER_INFO]: serverInfo }
|
|
389
|
+
} : result
|
|
390
|
+
});
|
|
391
|
+
const notFound = () => rpcError(message.id, METHOD_NOT_FOUND, `Method not found: ${message.method}`, modern ? 404 : 200);
|
|
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
|
+
});
|
|
400
|
+
case "initialize": {
|
|
401
|
+
if (modern) return notFound();
|
|
402
|
+
const requested = message.params?.["protocolVersion"];
|
|
403
|
+
return reply({
|
|
404
|
+
protocolVersion: typeof requested === "string" && LEGACY_VERSIONS.includes(requested) ? requested : LEGACY_VERSIONS[0],
|
|
405
|
+
capabilities,
|
|
406
|
+
serverInfo,
|
|
407
|
+
...options.instructions ? { instructions: options.instructions } : {}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
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
|
+
});
|
|
418
|
+
case "tools/call": {
|
|
419
|
+
const name = message.params?.["name"];
|
|
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)));
|
|
427
|
+
}
|
|
428
|
+
default: return notFound();
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
const metadata = (request) => Response.json({
|
|
432
|
+
resource: resourceOf(request),
|
|
433
|
+
authorization_servers: [...authorizationServers()],
|
|
434
|
+
bearer_methods_supported: ["header"],
|
|
435
|
+
...scopes.length > 0 ? { scopes_supported: scopes } : {}
|
|
436
|
+
}, { headers: { "access-control-allow-origin": "*" } });
|
|
437
|
+
const mcp = extendServer(server, {
|
|
438
|
+
get tools() {
|
|
439
|
+
return listTools();
|
|
440
|
+
},
|
|
441
|
+
tool(definition) {
|
|
442
|
+
addTool(defineTool(definition));
|
|
443
|
+
return mcp;
|
|
444
|
+
},
|
|
445
|
+
call,
|
|
446
|
+
handler,
|
|
447
|
+
metadata,
|
|
448
|
+
fetch(request) {
|
|
449
|
+
const { pathname } = new URL(request.url);
|
|
450
|
+
if (pathname.startsWith(WELL_KNOWN)) return Promise.resolve(request.method === "OPTIONS" ? new Response(null, {
|
|
451
|
+
status: 204,
|
|
452
|
+
headers: {
|
|
453
|
+
"access-control-allow-origin": "*",
|
|
454
|
+
"access-control-allow-methods": "GET"
|
|
455
|
+
}
|
|
456
|
+
}) : metadata(request));
|
|
457
|
+
return handler(request);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
return mcp;
|
|
461
|
+
}
|
|
462
|
+
//#endregion
|
|
463
|
+
export { MCP_PROTOCOL_VERSION, createMcp, defineTool };
|