@lovable.dev/mcp-js 0.10.1 → 0.12.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/README.internal.md +1 -1
- package/README.md +3 -3
- package/dist/{chunk-LAFJDJRY.js → chunk-G4XA7IJM.js} +17 -1
- package/dist/{chunk-X7V2CL7C.js → chunk-QDKOF4UF.js} +1 -1
- package/dist/{chunk-D6KB4Q7R.js → chunk-S3YHTZKJ.js} +1 -1
- package/dist/{chunk-PJXKJNSZ.js → chunk-VNRQPA4K.js} +1 -1
- package/dist/{chunk-DF3DD3KD.js → chunk-WLNT2FX7.js} +1 -1
- package/dist/{chunk-SZLSAGDF.js → chunk-YWLMMDET.js} +1 -1
- package/dist/cli/extract-manifest.cjs +1 -1
- package/dist/cli/extract-manifest.js +3 -3
- package/dist/index.cjs +18 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +18 -1
- package/dist/protocols/mcp/index.cjs +17 -1
- package/dist/protocols/mcp/index.d.cts +1 -1
- package/dist/protocols/mcp/index.d.ts +1 -1
- package/dist/protocols/mcp/index.js +2 -2
- package/dist/protocols/oauth-metadata.cjs +17 -1
- package/dist/protocols/oauth-metadata.d.cts +1 -1
- package/dist/protocols/oauth-metadata.d.ts +1 -1
- package/dist/protocols/oauth-metadata.js +2 -2
- package/dist/protocols/rest/index.cjs +17 -1
- package/dist/protocols/rest/index.d.cts +1 -1
- package/dist/protocols/rest/index.d.ts +1 -1
- package/dist/protocols/rest/index.js +3 -3
- package/dist/stacks/supabase/index.cjs +17 -1
- package/dist/stacks/supabase/index.d.cts +1 -1
- package/dist/stacks/supabase/index.d.ts +1 -1
- package/dist/stacks/supabase/index.js +5 -5
- package/dist/stacks/supabase/vite.cjs +80 -49
- package/dist/stacks/supabase/vite.d.cts +7 -4
- package/dist/stacks/supabase/vite.d.ts +7 -4
- package/dist/stacks/supabase/vite.js +80 -49
- package/dist/stacks/tanstack/index.cjs +17 -1
- package/dist/stacks/tanstack/index.d.cts +1 -1
- package/dist/stacks/tanstack/index.d.ts +1 -1
- package/dist/stacks/tanstack/index.js +5 -5
- package/dist/{types-C5SSORxe.d.ts → types-BanzncFh.d.ts} +8 -0
- package/package.json +2 -1
package/README.internal.md
CHANGED
|
@@ -145,7 +145,7 @@ src/
|
|
|
145
145
|
index.ts # barrel
|
|
146
146
|
supabase/
|
|
147
147
|
handler.ts # createSupabaseHandler (URL-suffix dispatcher to protocols/*)
|
|
148
|
-
emit.ts # syncSupabaseFunction (
|
|
148
|
+
emit.ts # syncSupabaseFunction (esbuild-bundles supabase/functions/<name>/index.ts)
|
|
149
149
|
vite.ts # mcpPlugin (Vite-lifecycle trigger; exposes McpPluginApi with urlPath=/functions/v1/<functionName>)
|
|
150
150
|
index.ts # barrel
|
|
151
151
|
tests/
|
package/README.md
CHANGED
|
@@ -217,7 +217,7 @@ A `500` on an OAuth-protected route is always one of two causes, logged at `erro
|
|
|
217
217
|
|
|
218
218
|
## Supabase Edge Functions
|
|
219
219
|
|
|
220
|
-
Ship the same `defineMcp` server as a single Supabase Edge Function. Authoring is identical to the TanStack flow; only the build-time step differs — a different Vite plugin
|
|
220
|
+
Ship the same `defineMcp` server as a single Supabase Edge Function. Authoring is identical to the TanStack flow; only the build-time step differs — a different Vite plugin bundles your MCP entry and its local imports into one self-contained Deno function (with esbuild) instead of emitting a tree of file-router routes. Third-party dependencies are left as `npm:` specifiers that Deno resolves at runtime, so no import map / `deno.json` is needed.
|
|
221
221
|
|
|
222
222
|
```ts
|
|
223
223
|
// src/lib/mcp/index.ts — same as the TanStack flow
|
|
@@ -249,7 +249,7 @@ import { mcpPlugin } from "@lovable.dev/mcp-js/stacks/supabase/vite";
|
|
|
249
249
|
export default defineConfig({ plugins: [mcpPlugin()] });
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
-
The plugin
|
|
252
|
+
The plugin bundles `src/lib/mcp/index.ts` (and the tool files it imports) into `supabase/functions/mcp/index.ts` on `configResolved`, re-emits on `buildStart`, and watches the bundled source files for changes during `vite dev`. The emitted function calls `Deno.serve(createSupabaseHandler(mcp, { functionName: "mcp" }))`. The handler is one Web-Standard `Request → Response` that dispatches by URL suffix to the same protocol handlers the TanStack stack uses:
|
|
253
253
|
|
|
254
254
|
| URL suffix | Target |
|
|
255
255
|
| --- | --- |
|
|
@@ -260,7 +260,7 @@ The plugin emits `supabase/functions/mcp/index.ts` and `supabase/functions/mcp/d
|
|
|
260
260
|
|
|
261
261
|
In production the function is mounted at `https://<project-ref>.supabase.co/functions/v1/mcp` and the dispatcher binds the OAuth `resource` to that URL via `functionName`. Locally, run `supabase functions serve mcp --no-verify-jwt` so Supabase's edge-runtime doesn't 401 before the SDK's OAuth verifier sees the bearer.
|
|
262
262
|
|
|
263
|
-
Ownership:
|
|
263
|
+
Ownership: the emitted `index.ts` carries a `// AUTO-GENERATED by @lovable.dev/mcp-js` banner. The plugin rewrites the banner-tagged file in place and cleans up orphans (including a `deno.json` left by a pre-bundling SDK version); banner-less files (yours) are never touched. Delete the banner line to take ownership. Deleting the `defineMcp` entry and re-running removes the SDK-owned function file.
|
|
264
264
|
|
|
265
265
|
## Subpath exports
|
|
266
266
|
|
|
@@ -190,6 +190,7 @@ function stringClaim(claims, name) {
|
|
|
190
190
|
// src/auth/verifier.ts
|
|
191
191
|
var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
192
192
|
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
193
|
+
var DEFAULT_ACCESS_TOKEN_TYPS = ["at+jwt", "JWT"];
|
|
193
194
|
var JWKS_FETCH_TIMEOUT_MS = 5e3;
|
|
194
195
|
var OAuthTokenError = class extends Error {
|
|
195
196
|
constructor(status, oauthError, message) {
|
|
@@ -222,10 +223,23 @@ async function fetchVerificationKeySet(jwksUri) {
|
|
|
222
223
|
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
223
224
|
}
|
|
224
225
|
}
|
|
226
|
+
function assertAccessTokenTyp(token, allowed) {
|
|
227
|
+
let header;
|
|
228
|
+
try {
|
|
229
|
+
header = decodeProtectedHeader(token);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
log.debug("oauth.verify.bad_header", { ...describeError(err), outcome: "401 invalid_token" });
|
|
232
|
+
throw new OAuthTokenError(401, "invalid_token", "Malformed JWT header");
|
|
233
|
+
}
|
|
234
|
+
const typ = header.typ;
|
|
235
|
+
if (typeof typ !== "string" || !allowed.includes(typ)) {
|
|
236
|
+
log.debug("oauth.verify.bad_typ", { jwtTyp: typ, allowed, outcome: "401 invalid_token" });
|
|
237
|
+
throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
225
240
|
async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
226
241
|
try {
|
|
227
242
|
const { payload } = await jwtVerify(token, keySet, {
|
|
228
|
-
typ: "at+jwt",
|
|
229
243
|
// `issuer` is trimmed of any trailing slash; accept both forms so a token whose
|
|
230
244
|
// `iss` carries the slash the AS publishes still verifies.
|
|
231
245
|
issuer: [issuer, `${issuer}/`],
|
|
@@ -274,6 +288,7 @@ function buildMcpAuthContext(args) {
|
|
|
274
288
|
};
|
|
275
289
|
}
|
|
276
290
|
function createOAuthTokenVerifier(auth, discovery) {
|
|
291
|
+
const allowedTyps = auth.accessTokenTyp ?? DEFAULT_ACCESS_TOKEN_TYPS;
|
|
277
292
|
return async (token, request, options) => {
|
|
278
293
|
const resource = resolveProtectedResource(auth, request, options);
|
|
279
294
|
const issuer = await discovery.resolveIssuer();
|
|
@@ -282,6 +297,7 @@ function createOAuthTokenVerifier(auth, discovery) {
|
|
|
282
297
|
const jwksUri = await discovery.resolveJwksUri();
|
|
283
298
|
log.debug("oauth.jwks.fetch", { jwksUri });
|
|
284
299
|
const keySet = await fetchVerificationKeySet(jwksUri);
|
|
300
|
+
assertAccessTokenTyp(token, allowedTyps);
|
|
285
301
|
const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
|
|
286
302
|
assertNonEmptySubject(claims);
|
|
287
303
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
createRequestAuthorizer,
|
|
9
9
|
methodNotAllowed,
|
|
10
10
|
withCors
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-G4XA7IJM.js";
|
|
12
12
|
|
|
13
13
|
// src/protocols/rest/invoke-tool.ts
|
|
14
14
|
import { getParseErrorMessage, objectFromShape, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
buildMcpListing
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-VNRQPA4K.js";
|
|
5
|
+
import "../chunk-G4XA7IJM.js";
|
|
6
6
|
import "../chunk-QC3DXQTH.js";
|
|
7
7
|
import "../chunk-6DXGZZA4.js";
|
|
8
8
|
import {
|
|
9
9
|
version
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-S3YHTZKJ.js";
|
|
11
11
|
import {
|
|
12
12
|
isFileMissing
|
|
13
13
|
} from "../chunk-Y3ZFPEQH.js";
|
package/dist/index.cjs
CHANGED
|
@@ -159,6 +159,20 @@ function assertOAuthConfig(auth2) {
|
|
|
159
159
|
for (const algorithm of auth2.algorithms)
|
|
160
160
|
assertJwtAlgorithm(algorithm);
|
|
161
161
|
}
|
|
162
|
+
if (auth2.accessTokenTyp !== void 0) {
|
|
163
|
+
if (!Array.isArray(auth2.accessTokenTyp)) {
|
|
164
|
+
throw new Error(`@lovable.dev/mcp-js: auth.accessTokenTyp must be an array`);
|
|
165
|
+
}
|
|
166
|
+
if (auth2.accessTokenTyp.length === 0) {
|
|
167
|
+
throw new Error(`@lovable.dev/mcp-js: auth.accessTokenTyp must not be empty`);
|
|
168
|
+
}
|
|
169
|
+
for (const typ of auth2.accessTokenTyp) {
|
|
170
|
+
if (typeof typ !== "string") {
|
|
171
|
+
throw new Error(`@lovable.dev/mcp-js: auth.accessTokenTyp must contain strings`);
|
|
172
|
+
}
|
|
173
|
+
assertNonEmptyString("auth.accessTokenTyp", typ);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
162
176
|
if (auth2.clockToleranceSeconds !== void 0)
|
|
163
177
|
assertClockToleranceSeconds(auth2.clockToleranceSeconds);
|
|
164
178
|
if (auth2.resource === void 0 && auth2.acceptedAudiences === void 0) {
|
|
@@ -174,6 +188,8 @@ function freezeAuth(auth2) {
|
|
|
174
188
|
Object.freeze(auth2.requiredScopes);
|
|
175
189
|
if (auth2.algorithms)
|
|
176
190
|
Object.freeze(auth2.algorithms);
|
|
191
|
+
if (auth2.accessTokenTyp)
|
|
192
|
+
Object.freeze(auth2.accessTokenTyp);
|
|
177
193
|
Object.freeze(auth2);
|
|
178
194
|
}
|
|
179
195
|
function defineTool(def) {
|
|
@@ -214,7 +230,8 @@ function issuer(options) {
|
|
|
214
230
|
...options,
|
|
215
231
|
acceptedAudiences: frozenAudiences(options.acceptedAudiences),
|
|
216
232
|
requiredScopes: frozenArray(options.requiredScopes),
|
|
217
|
-
algorithms: frozenArray(options.algorithms)
|
|
233
|
+
algorithms: frozenArray(options.algorithms),
|
|
234
|
+
accessTokenTyp: frozenArray(options.accessTokenTyp)
|
|
218
235
|
})
|
|
219
236
|
);
|
|
220
237
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { e as McpDefinitionInput, d as McpDefinition, Z as ZodRawShape, j as ToolDefinition, M as McpAuthConfig } from './types-
|
|
2
|
-
export { A as AudioContent, C as ContentAnnotations, a as ContentBlock, E as EmbeddedBlobResource, b as EmbeddedResource, c as EmbeddedTextResource, I as ImageContent, J as JwtClaims, R as ResourceLink, f as ResourceLinkIcon, T as TextContent, g as ToolAnnotations, h as ToolContent, i as ToolContext, k as ToolHandlerResult, l as ZodSchema } from './types-
|
|
1
|
+
import { e as McpDefinitionInput, d as McpDefinition, Z as ZodRawShape, j as ToolDefinition, M as McpAuthConfig } from './types-BanzncFh.js';
|
|
2
|
+
export { A as AudioContent, C as ContentAnnotations, a as ContentBlock, E as EmbeddedBlobResource, b as EmbeddedResource, c as EmbeddedTextResource, I as ImageContent, J as JwtClaims, R as ResourceLink, f as ResourceLinkIcon, T as TextContent, g as ToolAnnotations, h as ToolContent, i as ToolContext, k as ToolHandlerResult, l as ZodSchema } from './types-BanzncFh.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -29,6 +29,7 @@ interface IssuerOAuthOptionsBase {
|
|
|
29
29
|
readonly jwksUri?: string;
|
|
30
30
|
readonly algorithms?: readonly string[];
|
|
31
31
|
readonly clockToleranceSeconds?: number;
|
|
32
|
+
readonly accessTokenTyp?: readonly string[];
|
|
32
33
|
}
|
|
33
34
|
type IssuerOAuthOptions = IssuerOAuthOptionsBase & ({
|
|
34
35
|
readonly resource: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { e as McpDefinitionInput, d as McpDefinition, Z as ZodRawShape, j as ToolDefinition, M as McpAuthConfig } from './types-
|
|
2
|
-
export { A as AudioContent, C as ContentAnnotations, a as ContentBlock, E as EmbeddedBlobResource, b as EmbeddedResource, c as EmbeddedTextResource, I as ImageContent, J as JwtClaims, R as ResourceLink, f as ResourceLinkIcon, T as TextContent, g as ToolAnnotations, h as ToolContent, i as ToolContext, k as ToolHandlerResult, l as ZodSchema } from './types-
|
|
1
|
+
import { e as McpDefinitionInput, d as McpDefinition, Z as ZodRawShape, j as ToolDefinition, M as McpAuthConfig } from './types-BanzncFh.js';
|
|
2
|
+
export { A as AudioContent, C as ContentAnnotations, a as ContentBlock, E as EmbeddedBlobResource, b as EmbeddedResource, c as EmbeddedTextResource, I as ImageContent, J as JwtClaims, R as ResourceLink, f as ResourceLinkIcon, T as TextContent, g as ToolAnnotations, h as ToolContent, i as ToolContext, k as ToolHandlerResult, l as ZodSchema } from './types-BanzncFh.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -29,6 +29,7 @@ interface IssuerOAuthOptionsBase {
|
|
|
29
29
|
readonly jwksUri?: string;
|
|
30
30
|
readonly algorithms?: readonly string[];
|
|
31
31
|
readonly clockToleranceSeconds?: number;
|
|
32
|
+
readonly accessTokenTyp?: readonly string[];
|
|
32
33
|
}
|
|
33
34
|
type IssuerOAuthOptions = IssuerOAuthOptionsBase & ({
|
|
34
35
|
readonly resource: string;
|
package/dist/index.js
CHANGED
|
@@ -104,6 +104,20 @@ function assertOAuthConfig(auth2) {
|
|
|
104
104
|
for (const algorithm of auth2.algorithms)
|
|
105
105
|
assertJwtAlgorithm(algorithm);
|
|
106
106
|
}
|
|
107
|
+
if (auth2.accessTokenTyp !== void 0) {
|
|
108
|
+
if (!Array.isArray(auth2.accessTokenTyp)) {
|
|
109
|
+
throw new Error(`@lovable.dev/mcp-js: auth.accessTokenTyp must be an array`);
|
|
110
|
+
}
|
|
111
|
+
if (auth2.accessTokenTyp.length === 0) {
|
|
112
|
+
throw new Error(`@lovable.dev/mcp-js: auth.accessTokenTyp must not be empty`);
|
|
113
|
+
}
|
|
114
|
+
for (const typ of auth2.accessTokenTyp) {
|
|
115
|
+
if (typeof typ !== "string") {
|
|
116
|
+
throw new Error(`@lovable.dev/mcp-js: auth.accessTokenTyp must contain strings`);
|
|
117
|
+
}
|
|
118
|
+
assertNonEmptyString("auth.accessTokenTyp", typ);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
107
121
|
if (auth2.clockToleranceSeconds !== void 0)
|
|
108
122
|
assertClockToleranceSeconds(auth2.clockToleranceSeconds);
|
|
109
123
|
if (auth2.resource === void 0 && auth2.acceptedAudiences === void 0) {
|
|
@@ -119,6 +133,8 @@ function freezeAuth(auth2) {
|
|
|
119
133
|
Object.freeze(auth2.requiredScopes);
|
|
120
134
|
if (auth2.algorithms)
|
|
121
135
|
Object.freeze(auth2.algorithms);
|
|
136
|
+
if (auth2.accessTokenTyp)
|
|
137
|
+
Object.freeze(auth2.accessTokenTyp);
|
|
122
138
|
Object.freeze(auth2);
|
|
123
139
|
}
|
|
124
140
|
function defineTool(def) {
|
|
@@ -159,7 +175,8 @@ function issuer(options) {
|
|
|
159
175
|
...options,
|
|
160
176
|
acceptedAudiences: frozenAudiences(options.acceptedAudiences),
|
|
161
177
|
requiredScopes: frozenArray(options.requiredScopes),
|
|
162
|
-
algorithms: frozenArray(options.algorithms)
|
|
178
|
+
algorithms: frozenArray(options.algorithms),
|
|
179
|
+
accessTokenTyp: frozenArray(options.accessTokenTyp)
|
|
163
180
|
})
|
|
164
181
|
);
|
|
165
182
|
}
|
|
@@ -281,6 +281,7 @@ function stringClaim(claims, name) {
|
|
|
281
281
|
// src/auth/verifier.ts
|
|
282
282
|
var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
283
283
|
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
284
|
+
var DEFAULT_ACCESS_TOKEN_TYPS = ["at+jwt", "JWT"];
|
|
284
285
|
var JWKS_FETCH_TIMEOUT_MS = 5e3;
|
|
285
286
|
var OAuthTokenError = class extends Error {
|
|
286
287
|
constructor(status, oauthError, message) {
|
|
@@ -313,10 +314,23 @@ async function fetchVerificationKeySet(jwksUri) {
|
|
|
313
314
|
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
314
315
|
}
|
|
315
316
|
}
|
|
317
|
+
function assertAccessTokenTyp(token, allowed) {
|
|
318
|
+
let header;
|
|
319
|
+
try {
|
|
320
|
+
header = (0, import_jose.decodeProtectedHeader)(token);
|
|
321
|
+
} catch (err) {
|
|
322
|
+
log.debug("oauth.verify.bad_header", { ...describeError(err), outcome: "401 invalid_token" });
|
|
323
|
+
throw new OAuthTokenError(401, "invalid_token", "Malformed JWT header");
|
|
324
|
+
}
|
|
325
|
+
const typ = header.typ;
|
|
326
|
+
if (typeof typ !== "string" || !allowed.includes(typ)) {
|
|
327
|
+
log.debug("oauth.verify.bad_typ", { jwtTyp: typ, allowed, outcome: "401 invalid_token" });
|
|
328
|
+
throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
316
331
|
async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
317
332
|
try {
|
|
318
333
|
const { payload } = await (0, import_jose.jwtVerify)(token, keySet, {
|
|
319
|
-
typ: "at+jwt",
|
|
320
334
|
// `issuer` is trimmed of any trailing slash; accept both forms so a token whose
|
|
321
335
|
// `iss` carries the slash the AS publishes still verifies.
|
|
322
336
|
issuer: [issuer, `${issuer}/`],
|
|
@@ -365,6 +379,7 @@ function buildMcpAuthContext(args) {
|
|
|
365
379
|
};
|
|
366
380
|
}
|
|
367
381
|
function createOAuthTokenVerifier(auth, discovery) {
|
|
382
|
+
const allowedTyps = auth.accessTokenTyp ?? DEFAULT_ACCESS_TOKEN_TYPS;
|
|
368
383
|
return async (token, request, options) => {
|
|
369
384
|
const resource = resolveProtectedResource(auth, request, options);
|
|
370
385
|
const issuer = await discovery.resolveIssuer();
|
|
@@ -373,6 +388,7 @@ function createOAuthTokenVerifier(auth, discovery) {
|
|
|
373
388
|
const jwksUri = await discovery.resolveJwksUri();
|
|
374
389
|
log.debug("oauth.jwks.fetch", { jwksUri });
|
|
375
390
|
const keySet = await fetchVerificationKeySet(jwksUri);
|
|
391
|
+
assertAccessTokenTyp(token, allowedTyps);
|
|
376
392
|
const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
|
|
377
393
|
assertNonEmptySubject(claims);
|
|
378
394
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as McpRuntimeOptions } from '../../authorize-HTd0GKmB.js';
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
2
|
+
import { d as McpDefinition } from '../../types-BanzncFh.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type McpProtocolHandler = (request: Request) => Promise<Response>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as McpRuntimeOptions } from '../../authorize-HTd0GKmB.js';
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
2
|
+
import { d as McpDefinition } from '../../types-BanzncFh.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type McpProtocolHandler = (request: Request) => Promise<Response>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMcpProtocolHandler
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-WLNT2FX7.js";
|
|
4
4
|
import "../../chunk-MA5H6PSF.js";
|
|
5
|
-
import "../../chunk-
|
|
5
|
+
import "../../chunk-G4XA7IJM.js";
|
|
6
6
|
import "../../chunk-QC3DXQTH.js";
|
|
7
7
|
import "../../chunk-6DXGZZA4.js";
|
|
8
8
|
export {
|
|
@@ -283,6 +283,7 @@ function stringClaim(claims, name) {
|
|
|
283
283
|
// src/auth/verifier.ts
|
|
284
284
|
var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
285
285
|
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
286
|
+
var DEFAULT_ACCESS_TOKEN_TYPS = ["at+jwt", "JWT"];
|
|
286
287
|
var JWKS_FETCH_TIMEOUT_MS = 5e3;
|
|
287
288
|
var OAuthTokenError = class extends Error {
|
|
288
289
|
constructor(status, oauthError, message) {
|
|
@@ -315,10 +316,23 @@ async function fetchVerificationKeySet(jwksUri) {
|
|
|
315
316
|
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
316
317
|
}
|
|
317
318
|
}
|
|
319
|
+
function assertAccessTokenTyp(token, allowed) {
|
|
320
|
+
let header;
|
|
321
|
+
try {
|
|
322
|
+
header = (0, import_jose.decodeProtectedHeader)(token);
|
|
323
|
+
} catch (err) {
|
|
324
|
+
log.debug("oauth.verify.bad_header", { ...describeError(err), outcome: "401 invalid_token" });
|
|
325
|
+
throw new OAuthTokenError(401, "invalid_token", "Malformed JWT header");
|
|
326
|
+
}
|
|
327
|
+
const typ = header.typ;
|
|
328
|
+
if (typeof typ !== "string" || !allowed.includes(typ)) {
|
|
329
|
+
log.debug("oauth.verify.bad_typ", { jwtTyp: typ, allowed, outcome: "401 invalid_token" });
|
|
330
|
+
throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
318
333
|
async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
319
334
|
try {
|
|
320
335
|
const { payload } = await (0, import_jose.jwtVerify)(token, keySet, {
|
|
321
|
-
typ: "at+jwt",
|
|
322
336
|
// `issuer` is trimmed of any trailing slash; accept both forms so a token whose
|
|
323
337
|
// `iss` carries the slash the AS publishes still verifies.
|
|
324
338
|
issuer: [issuer, `${issuer}/`],
|
|
@@ -367,6 +381,7 @@ function buildMcpAuthContext(args) {
|
|
|
367
381
|
};
|
|
368
382
|
}
|
|
369
383
|
function createOAuthTokenVerifier(auth, discovery) {
|
|
384
|
+
const allowedTyps = auth.accessTokenTyp ?? DEFAULT_ACCESS_TOKEN_TYPS;
|
|
370
385
|
return async (token, request, options) => {
|
|
371
386
|
const resource = resolveProtectedResource(auth, request, options);
|
|
372
387
|
const issuer = await discovery.resolveIssuer();
|
|
@@ -375,6 +390,7 @@ function createOAuthTokenVerifier(auth, discovery) {
|
|
|
375
390
|
const jwksUri = await discovery.resolveJwksUri();
|
|
376
391
|
log.debug("oauth.jwks.fetch", { jwksUri });
|
|
377
392
|
const keySet = await fetchVerificationKeySet(jwksUri);
|
|
393
|
+
assertAccessTokenTyp(token, allowedTyps);
|
|
378
394
|
const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
|
|
379
395
|
assertNonEmptySubject(claims);
|
|
380
396
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as McpRuntimeOptions } from '../authorize-HTd0GKmB.js';
|
|
2
|
-
import { d as McpDefinition } from '../types-
|
|
2
|
+
import { d as McpDefinition } from '../types-BanzncFh.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type OAuthProtectedResourceMetadataHandler = (request: Request) => Promise<Response>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as McpRuntimeOptions } from '../authorize-HTd0GKmB.js';
|
|
2
|
-
import { d as McpDefinition } from '../types-
|
|
2
|
+
import { d as McpDefinition } from '../types-BanzncFh.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type OAuthProtectedResourceMetadataHandler = (request: Request) => Promise<Response>;
|
|
@@ -291,6 +291,7 @@ function stringClaim(claims, name) {
|
|
|
291
291
|
// src/auth/verifier.ts
|
|
292
292
|
var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
293
293
|
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
294
|
+
var DEFAULT_ACCESS_TOKEN_TYPS = ["at+jwt", "JWT"];
|
|
294
295
|
var JWKS_FETCH_TIMEOUT_MS = 5e3;
|
|
295
296
|
var OAuthTokenError = class extends Error {
|
|
296
297
|
constructor(status, oauthError, message) {
|
|
@@ -323,10 +324,23 @@ async function fetchVerificationKeySet(jwksUri) {
|
|
|
323
324
|
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
324
325
|
}
|
|
325
326
|
}
|
|
327
|
+
function assertAccessTokenTyp(token, allowed) {
|
|
328
|
+
let header;
|
|
329
|
+
try {
|
|
330
|
+
header = (0, import_jose.decodeProtectedHeader)(token);
|
|
331
|
+
} catch (err) {
|
|
332
|
+
log.debug("oauth.verify.bad_header", { ...describeError(err), outcome: "401 invalid_token" });
|
|
333
|
+
throw new OAuthTokenError(401, "invalid_token", "Malformed JWT header");
|
|
334
|
+
}
|
|
335
|
+
const typ = header.typ;
|
|
336
|
+
if (typeof typ !== "string" || !allowed.includes(typ)) {
|
|
337
|
+
log.debug("oauth.verify.bad_typ", { jwtTyp: typ, allowed, outcome: "401 invalid_token" });
|
|
338
|
+
throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
326
341
|
async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
327
342
|
try {
|
|
328
343
|
const { payload } = await (0, import_jose.jwtVerify)(token, keySet, {
|
|
329
|
-
typ: "at+jwt",
|
|
330
344
|
// `issuer` is trimmed of any trailing slash; accept both forms so a token whose
|
|
331
345
|
// `iss` carries the slash the AS publishes still verifies.
|
|
332
346
|
issuer: [issuer, `${issuer}/`],
|
|
@@ -375,6 +389,7 @@ function buildMcpAuthContext(args) {
|
|
|
375
389
|
};
|
|
376
390
|
}
|
|
377
391
|
function createOAuthTokenVerifier(auth, discovery) {
|
|
392
|
+
const allowedTyps = auth.accessTokenTyp ?? DEFAULT_ACCESS_TOKEN_TYPS;
|
|
378
393
|
return async (token, request, options) => {
|
|
379
394
|
const resource = resolveProtectedResource(auth, request, options);
|
|
380
395
|
const issuer = await discovery.resolveIssuer();
|
|
@@ -383,6 +398,7 @@ function createOAuthTokenVerifier(auth, discovery) {
|
|
|
383
398
|
const jwksUri = await discovery.resolveJwksUri();
|
|
384
399
|
log.debug("oauth.jwks.fetch", { jwksUri });
|
|
385
400
|
const keySet = await fetchVerificationKeySet(jwksUri);
|
|
401
|
+
assertAccessTokenTyp(token, allowedTyps);
|
|
386
402
|
const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
|
|
387
403
|
assertNonEmptySubject(claims);
|
|
388
404
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as McpRuntimeOptions } from '../../authorize-HTd0GKmB.js';
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
2
|
+
import { d as McpDefinition } from '../../types-BanzncFh.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type RestListToolsHandler = (request: Request) => Promise<Response>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as McpRuntimeOptions } from '../../authorize-HTd0GKmB.js';
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
2
|
+
import { d as McpDefinition } from '../../types-BanzncFh.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type RestListToolsHandler = (request: Request) => Promise<Response>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createInvokeToolHandler
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-YWLMMDET.js";
|
|
4
4
|
import {
|
|
5
5
|
createListToolsHandler
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-VNRQPA4K.js";
|
|
7
7
|
import "../../chunk-MA5H6PSF.js";
|
|
8
|
-
import "../../chunk-
|
|
8
|
+
import "../../chunk-G4XA7IJM.js";
|
|
9
9
|
import "../../chunk-QC3DXQTH.js";
|
|
10
10
|
import "../../chunk-6DXGZZA4.js";
|
|
11
11
|
export {
|
|
@@ -290,6 +290,7 @@ function stringClaim(claims, name) {
|
|
|
290
290
|
// src/auth/verifier.ts
|
|
291
291
|
var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
292
292
|
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
293
|
+
var DEFAULT_ACCESS_TOKEN_TYPS = ["at+jwt", "JWT"];
|
|
293
294
|
var JWKS_FETCH_TIMEOUT_MS = 5e3;
|
|
294
295
|
var OAuthTokenError = class extends Error {
|
|
295
296
|
constructor(status, oauthError, message) {
|
|
@@ -322,10 +323,23 @@ async function fetchVerificationKeySet(jwksUri) {
|
|
|
322
323
|
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
323
324
|
}
|
|
324
325
|
}
|
|
326
|
+
function assertAccessTokenTyp(token, allowed) {
|
|
327
|
+
let header;
|
|
328
|
+
try {
|
|
329
|
+
header = (0, import_jose.decodeProtectedHeader)(token);
|
|
330
|
+
} catch (err) {
|
|
331
|
+
log.debug("oauth.verify.bad_header", { ...describeError(err), outcome: "401 invalid_token" });
|
|
332
|
+
throw new OAuthTokenError(401, "invalid_token", "Malformed JWT header");
|
|
333
|
+
}
|
|
334
|
+
const typ = header.typ;
|
|
335
|
+
if (typeof typ !== "string" || !allowed.includes(typ)) {
|
|
336
|
+
log.debug("oauth.verify.bad_typ", { jwtTyp: typ, allowed, outcome: "401 invalid_token" });
|
|
337
|
+
throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
325
340
|
async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
326
341
|
try {
|
|
327
342
|
const { payload } = await (0, import_jose.jwtVerify)(token, keySet, {
|
|
328
|
-
typ: "at+jwt",
|
|
329
343
|
// `issuer` is trimmed of any trailing slash; accept both forms so a token whose
|
|
330
344
|
// `iss` carries the slash the AS publishes still verifies.
|
|
331
345
|
issuer: [issuer, `${issuer}/`],
|
|
@@ -374,6 +388,7 @@ function buildMcpAuthContext(args) {
|
|
|
374
388
|
};
|
|
375
389
|
}
|
|
376
390
|
function createOAuthTokenVerifier(auth, discovery) {
|
|
391
|
+
const allowedTyps = auth.accessTokenTyp ?? DEFAULT_ACCESS_TOKEN_TYPS;
|
|
377
392
|
return async (token, request, options) => {
|
|
378
393
|
const resource = resolveProtectedResource(auth, request, options);
|
|
379
394
|
const issuer = await discovery.resolveIssuer();
|
|
@@ -382,6 +397,7 @@ function createOAuthTokenVerifier(auth, discovery) {
|
|
|
382
397
|
const jwksUri = await discovery.resolveJwksUri();
|
|
383
398
|
log.debug("oauth.jwks.fetch", { jwksUri });
|
|
384
399
|
const keySet = await fetchVerificationKeySet(jwksUri);
|
|
400
|
+
assertAccessTokenTyp(token, allowedTyps);
|
|
385
401
|
const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
|
|
386
402
|
assertNonEmptySubject(claims);
|
|
387
403
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMcpProtocolHandler
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-WLNT2FX7.js";
|
|
4
4
|
import {
|
|
5
5
|
createOAuthProtectedResourceMetadataHandler
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-QDKOF4UF.js";
|
|
7
7
|
import {
|
|
8
8
|
createInvokeToolHandler
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-YWLMMDET.js";
|
|
10
10
|
import {
|
|
11
11
|
createListToolsHandler
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-VNRQPA4K.js";
|
|
13
13
|
import "../../chunk-MA5H6PSF.js";
|
|
14
14
|
import {
|
|
15
15
|
assertResourcePathShape
|
|
16
|
-
} from "../../chunk-
|
|
16
|
+
} from "../../chunk-G4XA7IJM.js";
|
|
17
17
|
import {
|
|
18
18
|
trimTrailingSlash
|
|
19
19
|
} from "../../chunk-QC3DXQTH.js";
|
|
@@ -27,11 +27,12 @@ module.exports = __toCommonJS(vite_exports);
|
|
|
27
27
|
var import_node_path2 = require("path");
|
|
28
28
|
|
|
29
29
|
// src/stacks/supabase/emit.ts
|
|
30
|
+
var import_esbuild = require("esbuild");
|
|
30
31
|
var import_node_fs = require("fs");
|
|
31
32
|
var import_node_path = require("path");
|
|
32
33
|
|
|
33
34
|
// package.json
|
|
34
|
-
var version = "0.
|
|
35
|
+
var version = "0.12.0";
|
|
35
36
|
|
|
36
37
|
// src/core/fs-errors.ts
|
|
37
38
|
function isFileMissing(err) {
|
|
@@ -54,10 +55,6 @@ var DEFAULT_FUNCTIONS_DIR = "supabase/functions";
|
|
|
54
55
|
var DEFAULT_FUNCTION_NAME = "mcp";
|
|
55
56
|
var GENERATED_BANNER = "// AUTO-GENERATED by @lovable.dev/mcp-js \u2014 do not edit. Regenerated by the Vite plugin.\n// To take ownership, delete this banner line; the plugin then leaves the file alone.";
|
|
56
57
|
var GENERATED_JSON_MARKER_KEY = "_lovableMcpGenerated";
|
|
57
|
-
var GENERATED_JSON_MARKER_VALUE = "AUTO-GENERATED by @lovable.dev/mcp-js \u2014 do not edit. Regenerated by the Vite plugin. To take ownership, remove this key; the plugin then leaves the file alone.";
|
|
58
|
-
function normalizePath(p) {
|
|
59
|
-
return p.split(import_node_path.sep).join("/");
|
|
60
|
-
}
|
|
61
58
|
function fileExists(path) {
|
|
62
59
|
try {
|
|
63
60
|
(0, import_node_fs.lstatSync)(path);
|
|
@@ -100,32 +97,63 @@ function writeIfChanged(file, content, ownershipCheck) {
|
|
|
100
97
|
(0, import_node_fs.writeFileSync)(file, content, "utf8");
|
|
101
98
|
return true;
|
|
102
99
|
}
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
function readProjectDependencyVersions(projectRoot) {
|
|
101
|
+
try {
|
|
102
|
+
const pkg = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path.join)(projectRoot, "package.json"), "utf8"));
|
|
103
|
+
return { ...pkg.peerDependencies, ...pkg.devDependencies, ...pkg.dependencies };
|
|
104
|
+
} catch {
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
106
107
|
}
|
|
107
|
-
function
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
import mcp from "${entryImport}";
|
|
116
|
-
|
|
117
|
-
Deno.serve(createSupabaseHandler(mcp, { functionName: ${JSON.stringify(functionName)} }));
|
|
118
|
-
`;
|
|
108
|
+
function npmSpecifierFor(bare, versions) {
|
|
109
|
+
const pkg = bare.startsWith("@") ? bare.split("/").slice(0, 2).join("/") : bare.split("/")[0];
|
|
110
|
+
const subpath = bare.slice(pkg.length);
|
|
111
|
+
if (pkg === "@lovable.dev/mcp-js")
|
|
112
|
+
return `npm:${pkg}@${version}${subpath}`;
|
|
113
|
+
const range = versions[pkg];
|
|
114
|
+
const version2 = range && /^[\dv^~>=]|^latest$/.test(range) ? range : void 0;
|
|
115
|
+
return version2 ? `npm:${pkg}@${version2}${subpath}` : `npm:${pkg}${subpath}`;
|
|
119
116
|
}
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
function externalizeBareAsNpm(versions) {
|
|
118
|
+
return {
|
|
119
|
+
name: "lovable-mcp-externalize-bare-as-npm",
|
|
120
|
+
setup(pluginBuild) {
|
|
121
|
+
pluginBuild.onResolve({ filter: /.*/ }, (args) => {
|
|
122
|
+
const p = args.path;
|
|
123
|
+
if (p.startsWith(".") || p.startsWith("/"))
|
|
124
|
+
return null;
|
|
125
|
+
if (/^(npm|node|jsr|https?|data):/.test(p))
|
|
126
|
+
return { path: p, external: true };
|
|
127
|
+
return { path: npmSpecifierFor(p, versions), external: true };
|
|
128
|
+
});
|
|
125
129
|
}
|
|
126
130
|
};
|
|
127
|
-
|
|
131
|
+
}
|
|
132
|
+
async function bundleFunctionEntry(projectRoot, mcpEntryAbs, functionName) {
|
|
133
|
+
const versions = readProjectDependencyVersions(projectRoot);
|
|
134
|
+
const wrapper = `import mcp from ${JSON.stringify(mcpEntryAbs)};
|
|
135
|
+
import { createSupabaseHandler } from "@lovable.dev/mcp-js/stacks/supabase";
|
|
136
|
+
Deno.serve(createSupabaseHandler(mcp, { functionName: ${JSON.stringify(functionName)} }));
|
|
128
137
|
`;
|
|
138
|
+
const result = await (0, import_esbuild.build)({
|
|
139
|
+
stdin: { contents: wrapper, resolveDir: projectRoot, sourcefile: "lovable-mcp-supabase-entry.ts", loader: "ts" },
|
|
140
|
+
bundle: true,
|
|
141
|
+
write: false,
|
|
142
|
+
metafile: true,
|
|
143
|
+
format: "esm",
|
|
144
|
+
platform: "neutral",
|
|
145
|
+
target: "esnext",
|
|
146
|
+
absWorkingDir: projectRoot,
|
|
147
|
+
logLevel: "silent",
|
|
148
|
+
plugins: [externalizeBareAsNpm(versions)]
|
|
149
|
+
});
|
|
150
|
+
const out = result.outputFiles[0]?.text ?? "";
|
|
151
|
+
const inputs = Object.keys(result.metafile?.inputs ?? {}).filter((p) => !p.startsWith("<") && !p.startsWith("npm:")).map((p) => (0, import_node_path.resolve)(projectRoot, p));
|
|
152
|
+
const code = `${GENERATED_BANNER}
|
|
153
|
+
// supabase function: ${functionName}
|
|
154
|
+
// Bundled from ${(0, import_node_path.relative)(projectRoot, mcpEntryAbs).split(import_node_path.sep).join("/")} by @lovable.dev/mcp-js.
|
|
155
|
+
` + out;
|
|
156
|
+
return { code, inputs };
|
|
129
157
|
}
|
|
130
158
|
function resolvePaths(options) {
|
|
131
159
|
const projectRoot = (0, import_node_path.resolve)(options.projectRoot);
|
|
@@ -142,31 +170,25 @@ function resolvePaths(options) {
|
|
|
142
170
|
return {
|
|
143
171
|
functionDir,
|
|
144
172
|
functionEntry: (0, import_node_path.join)(functionDir, "index.ts"),
|
|
145
|
-
denoJson: (0, import_node_path.join)(functionDir, "deno.json"),
|
|
146
173
|
mcpEntryAbs: (0, import_node_path.resolve)(projectRoot, mcpEntry)
|
|
147
174
|
};
|
|
148
175
|
}
|
|
149
|
-
function syncSupabaseFunction(options) {
|
|
176
|
+
async function syncSupabaseFunction(options) {
|
|
177
|
+
const projectRoot = (0, import_node_path.resolve)(options.projectRoot);
|
|
150
178
|
const paths = resolvePaths(options);
|
|
151
179
|
const functionName = options.functionName ?? DEFAULT_FUNCTION_NAME;
|
|
152
180
|
const written = [];
|
|
153
181
|
const removed = [];
|
|
154
182
|
if (!fileExists(paths.mcpEntryAbs)) {
|
|
155
183
|
cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set(), removed);
|
|
156
|
-
return { written, removed };
|
|
184
|
+
return { written, removed, inputs: [] };
|
|
157
185
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
buildFunctionEntry(paths.mcpEntryAbs, paths.functionEntry, functionName),
|
|
161
|
-
hasGeneratedBanner
|
|
162
|
-
)) {
|
|
186
|
+
const { code, inputs } = await bundleFunctionEntry(projectRoot, paths.mcpEntryAbs, functionName);
|
|
187
|
+
if (writeIfChanged(paths.functionEntry, code, hasGeneratedBanner)) {
|
|
163
188
|
written.push(paths.functionEntry);
|
|
164
189
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set([paths.functionEntry, paths.denoJson]), removed);
|
|
169
|
-
return { written, removed };
|
|
190
|
+
cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set([paths.functionEntry]), removed);
|
|
191
|
+
return { written, removed, inputs };
|
|
170
192
|
}
|
|
171
193
|
function cleanupOrphans(functionDir, expected, removed) {
|
|
172
194
|
let entries;
|
|
@@ -202,7 +224,7 @@ function cleanupOrphans(functionDir, expected, removed) {
|
|
|
202
224
|
}
|
|
203
225
|
|
|
204
226
|
// src/stacks/supabase/vite.ts
|
|
205
|
-
function
|
|
227
|
+
function normalizePath(p) {
|
|
206
228
|
return p.split(import_node_path2.sep).join("/");
|
|
207
229
|
}
|
|
208
230
|
function mcpPlugin(options = {}) {
|
|
@@ -212,13 +234,15 @@ function mcpPlugin(options = {}) {
|
|
|
212
234
|
const urlPath = `${FUNCTIONS_MOUNT_PREFIX}${functionName}`;
|
|
213
235
|
let projectRoot = process.cwd();
|
|
214
236
|
let mcpEntryAbs = (0, import_node_path2.resolve)(projectRoot, mcpEntryOption);
|
|
215
|
-
|
|
216
|
-
|
|
237
|
+
let watchedInputs = /* @__PURE__ */ new Set();
|
|
238
|
+
const regenerate = async () => {
|
|
239
|
+
const { inputs } = await syncSupabaseFunction({
|
|
217
240
|
projectRoot,
|
|
218
241
|
mcpEntry: mcpEntryOption,
|
|
219
242
|
functionsDir: functionsDirOption,
|
|
220
243
|
functionName
|
|
221
244
|
});
|
|
245
|
+
watchedInputs = new Set(inputs.map(normalizePath));
|
|
222
246
|
};
|
|
223
247
|
return {
|
|
224
248
|
name: "@lovable.dev/mcp-js/supabase",
|
|
@@ -231,16 +255,23 @@ function mcpPlugin(options = {}) {
|
|
|
231
255
|
},
|
|
232
256
|
urlPath
|
|
233
257
|
},
|
|
234
|
-
configResolved(config) {
|
|
258
|
+
async configResolved(config) {
|
|
235
259
|
projectRoot = config.root;
|
|
236
260
|
mcpEntryAbs = (0, import_node_path2.resolve)(projectRoot, mcpEntryOption);
|
|
237
|
-
regenerate();
|
|
261
|
+
await regenerate();
|
|
238
262
|
},
|
|
239
263
|
configureServer(server) {
|
|
240
264
|
const onChange = (file) => {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
265
|
+
const norm = normalizePath(file);
|
|
266
|
+
if (norm !== normalizePath(mcpEntryAbs) && !watchedInputs.has(norm))
|
|
267
|
+
return;
|
|
268
|
+
regenerate().catch((err) => {
|
|
269
|
+
const message = `[@lovable.dev/mcp-js] failed to regenerate the Supabase function: ${err instanceof Error ? err.message : String(err)}`;
|
|
270
|
+
if (server.config?.logger?.error)
|
|
271
|
+
server.config.logger.error(message);
|
|
272
|
+
else
|
|
273
|
+
console.error(message);
|
|
274
|
+
});
|
|
244
275
|
};
|
|
245
276
|
server.watcher.on("add", onChange);
|
|
246
277
|
server.watcher.on("change", onChange);
|
|
@@ -251,8 +282,8 @@ function mcpPlugin(options = {}) {
|
|
|
251
282
|
server.watcher.off("unlink", onChange);
|
|
252
283
|
});
|
|
253
284
|
},
|
|
254
|
-
buildStart() {
|
|
255
|
-
regenerate();
|
|
285
|
+
async buildStart() {
|
|
286
|
+
await regenerate();
|
|
256
287
|
}
|
|
257
288
|
};
|
|
258
289
|
}
|
|
@@ -34,11 +34,14 @@ interface McpPluginOptions {
|
|
|
34
34
|
* Lifecycle, mirroring `stacks/tanstack/vite`:
|
|
35
35
|
* - `configResolved` finalizes the project root and runs the first emit.
|
|
36
36
|
* - `buildStart` re-runs the emit so a `vite build` always picks up the
|
|
37
|
-
* latest
|
|
38
|
-
* - `configureServer` re-emits when the entry
|
|
39
|
-
*
|
|
40
|
-
* function
|
|
37
|
+
* latest source.
|
|
38
|
+
* - `configureServer` re-emits when the entry or any bundled source file
|
|
39
|
+
* (tools, local helpers) changes during `vite dev`, so editing a tool
|
|
40
|
+
* regenerates the function without restarting the server.
|
|
41
41
|
*
|
|
42
|
+
* The entry and its local import graph are bundled by esbuild into one
|
|
43
|
+
* self-contained Deno module (deps left as `npm:` specifiers), so the deployed
|
|
44
|
+
* function carries no cross-project imports the Supabase deploy can't upload.
|
|
42
45
|
* Idempotent: `syncSupabaseFunction` writes only on byte-level change and
|
|
43
46
|
* cleans up SDK-owned files when the entry is deleted.
|
|
44
47
|
*/
|
|
@@ -34,11 +34,14 @@ interface McpPluginOptions {
|
|
|
34
34
|
* Lifecycle, mirroring `stacks/tanstack/vite`:
|
|
35
35
|
* - `configResolved` finalizes the project root and runs the first emit.
|
|
36
36
|
* - `buildStart` re-runs the emit so a `vite build` always picks up the
|
|
37
|
-
* latest
|
|
38
|
-
* - `configureServer` re-emits when the entry
|
|
39
|
-
*
|
|
40
|
-
* function
|
|
37
|
+
* latest source.
|
|
38
|
+
* - `configureServer` re-emits when the entry or any bundled source file
|
|
39
|
+
* (tools, local helpers) changes during `vite dev`, so editing a tool
|
|
40
|
+
* regenerates the function without restarting the server.
|
|
41
41
|
*
|
|
42
|
+
* The entry and its local import graph are bundled by esbuild into one
|
|
43
|
+
* self-contained Deno module (deps left as `npm:` specifiers), so the deployed
|
|
44
|
+
* function carries no cross-project imports the Supabase deploy can't upload.
|
|
42
45
|
* Idempotent: `syncSupabaseFunction` writes only on byte-level change and
|
|
43
46
|
* cleans up SDK-owned files when the entry is deleted.
|
|
44
47
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
version
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-S3YHTZKJ.js";
|
|
4
4
|
import {
|
|
5
5
|
isFileMissing
|
|
6
6
|
} from "../../chunk-Y3ZFPEQH.js";
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { resolve as resolve2, sep as sep2 } from "path";
|
|
14
14
|
|
|
15
15
|
// src/stacks/supabase/emit.ts
|
|
16
|
+
import { build } from "esbuild";
|
|
16
17
|
import { lstatSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
|
|
17
18
|
import { dirname, join, relative, resolve, sep } from "path";
|
|
18
19
|
var DEFAULT_MCP_ENTRY = "src/lib/mcp/index.ts";
|
|
@@ -20,10 +21,6 @@ var DEFAULT_FUNCTIONS_DIR = "supabase/functions";
|
|
|
20
21
|
var DEFAULT_FUNCTION_NAME = "mcp";
|
|
21
22
|
var GENERATED_BANNER = "// AUTO-GENERATED by @lovable.dev/mcp-js \u2014 do not edit. Regenerated by the Vite plugin.\n// To take ownership, delete this banner line; the plugin then leaves the file alone.";
|
|
22
23
|
var GENERATED_JSON_MARKER_KEY = "_lovableMcpGenerated";
|
|
23
|
-
var GENERATED_JSON_MARKER_VALUE = "AUTO-GENERATED by @lovable.dev/mcp-js \u2014 do not edit. Regenerated by the Vite plugin. To take ownership, remove this key; the plugin then leaves the file alone.";
|
|
24
|
-
function normalizePath(p) {
|
|
25
|
-
return p.split(sep).join("/");
|
|
26
|
-
}
|
|
27
24
|
function fileExists(path) {
|
|
28
25
|
try {
|
|
29
26
|
lstatSync(path);
|
|
@@ -66,32 +63,63 @@ function writeIfChanged(file, content, ownershipCheck) {
|
|
|
66
63
|
writeFileSync(file, content, "utf8");
|
|
67
64
|
return true;
|
|
68
65
|
}
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
function readProjectDependencyVersions(projectRoot) {
|
|
67
|
+
try {
|
|
68
|
+
const pkg = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf8"));
|
|
69
|
+
return { ...pkg.peerDependencies, ...pkg.devDependencies, ...pkg.dependencies };
|
|
70
|
+
} catch {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
72
73
|
}
|
|
73
|
-
function
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
import mcp from "${entryImport}";
|
|
82
|
-
|
|
83
|
-
Deno.serve(createSupabaseHandler(mcp, { functionName: ${JSON.stringify(functionName)} }));
|
|
84
|
-
`;
|
|
74
|
+
function npmSpecifierFor(bare, versions) {
|
|
75
|
+
const pkg = bare.startsWith("@") ? bare.split("/").slice(0, 2).join("/") : bare.split("/")[0];
|
|
76
|
+
const subpath = bare.slice(pkg.length);
|
|
77
|
+
if (pkg === "@lovable.dev/mcp-js")
|
|
78
|
+
return `npm:${pkg}@${version}${subpath}`;
|
|
79
|
+
const range = versions[pkg];
|
|
80
|
+
const version2 = range && /^[\dv^~>=]|^latest$/.test(range) ? range : void 0;
|
|
81
|
+
return version2 ? `npm:${pkg}@${version2}${subpath}` : `npm:${pkg}${subpath}`;
|
|
85
82
|
}
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
function externalizeBareAsNpm(versions) {
|
|
84
|
+
return {
|
|
85
|
+
name: "lovable-mcp-externalize-bare-as-npm",
|
|
86
|
+
setup(pluginBuild) {
|
|
87
|
+
pluginBuild.onResolve({ filter: /.*/ }, (args) => {
|
|
88
|
+
const p = args.path;
|
|
89
|
+
if (p.startsWith(".") || p.startsWith("/"))
|
|
90
|
+
return null;
|
|
91
|
+
if (/^(npm|node|jsr|https?|data):/.test(p))
|
|
92
|
+
return { path: p, external: true };
|
|
93
|
+
return { path: npmSpecifierFor(p, versions), external: true };
|
|
94
|
+
});
|
|
91
95
|
}
|
|
92
96
|
};
|
|
93
|
-
|
|
97
|
+
}
|
|
98
|
+
async function bundleFunctionEntry(projectRoot, mcpEntryAbs, functionName) {
|
|
99
|
+
const versions = readProjectDependencyVersions(projectRoot);
|
|
100
|
+
const wrapper = `import mcp from ${JSON.stringify(mcpEntryAbs)};
|
|
101
|
+
import { createSupabaseHandler } from "@lovable.dev/mcp-js/stacks/supabase";
|
|
102
|
+
Deno.serve(createSupabaseHandler(mcp, { functionName: ${JSON.stringify(functionName)} }));
|
|
94
103
|
`;
|
|
104
|
+
const result = await build({
|
|
105
|
+
stdin: { contents: wrapper, resolveDir: projectRoot, sourcefile: "lovable-mcp-supabase-entry.ts", loader: "ts" },
|
|
106
|
+
bundle: true,
|
|
107
|
+
write: false,
|
|
108
|
+
metafile: true,
|
|
109
|
+
format: "esm",
|
|
110
|
+
platform: "neutral",
|
|
111
|
+
target: "esnext",
|
|
112
|
+
absWorkingDir: projectRoot,
|
|
113
|
+
logLevel: "silent",
|
|
114
|
+
plugins: [externalizeBareAsNpm(versions)]
|
|
115
|
+
});
|
|
116
|
+
const out = result.outputFiles[0]?.text ?? "";
|
|
117
|
+
const inputs = Object.keys(result.metafile?.inputs ?? {}).filter((p) => !p.startsWith("<") && !p.startsWith("npm:")).map((p) => resolve(projectRoot, p));
|
|
118
|
+
const code = `${GENERATED_BANNER}
|
|
119
|
+
// supabase function: ${functionName}
|
|
120
|
+
// Bundled from ${relative(projectRoot, mcpEntryAbs).split(sep).join("/")} by @lovable.dev/mcp-js.
|
|
121
|
+
` + out;
|
|
122
|
+
return { code, inputs };
|
|
95
123
|
}
|
|
96
124
|
function resolvePaths(options) {
|
|
97
125
|
const projectRoot = resolve(options.projectRoot);
|
|
@@ -108,31 +136,25 @@ function resolvePaths(options) {
|
|
|
108
136
|
return {
|
|
109
137
|
functionDir,
|
|
110
138
|
functionEntry: join(functionDir, "index.ts"),
|
|
111
|
-
denoJson: join(functionDir, "deno.json"),
|
|
112
139
|
mcpEntryAbs: resolve(projectRoot, mcpEntry)
|
|
113
140
|
};
|
|
114
141
|
}
|
|
115
|
-
function syncSupabaseFunction(options) {
|
|
142
|
+
async function syncSupabaseFunction(options) {
|
|
143
|
+
const projectRoot = resolve(options.projectRoot);
|
|
116
144
|
const paths = resolvePaths(options);
|
|
117
145
|
const functionName = options.functionName ?? DEFAULT_FUNCTION_NAME;
|
|
118
146
|
const written = [];
|
|
119
147
|
const removed = [];
|
|
120
148
|
if (!fileExists(paths.mcpEntryAbs)) {
|
|
121
149
|
cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set(), removed);
|
|
122
|
-
return { written, removed };
|
|
150
|
+
return { written, removed, inputs: [] };
|
|
123
151
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
buildFunctionEntry(paths.mcpEntryAbs, paths.functionEntry, functionName),
|
|
127
|
-
hasGeneratedBanner
|
|
128
|
-
)) {
|
|
152
|
+
const { code, inputs } = await bundleFunctionEntry(projectRoot, paths.mcpEntryAbs, functionName);
|
|
153
|
+
if (writeIfChanged(paths.functionEntry, code, hasGeneratedBanner)) {
|
|
129
154
|
written.push(paths.functionEntry);
|
|
130
155
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set([paths.functionEntry, paths.denoJson]), removed);
|
|
135
|
-
return { written, removed };
|
|
156
|
+
cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set([paths.functionEntry]), removed);
|
|
157
|
+
return { written, removed, inputs };
|
|
136
158
|
}
|
|
137
159
|
function cleanupOrphans(functionDir, expected, removed) {
|
|
138
160
|
let entries;
|
|
@@ -168,7 +190,7 @@ function cleanupOrphans(functionDir, expected, removed) {
|
|
|
168
190
|
}
|
|
169
191
|
|
|
170
192
|
// src/stacks/supabase/vite.ts
|
|
171
|
-
function
|
|
193
|
+
function normalizePath(p) {
|
|
172
194
|
return p.split(sep2).join("/");
|
|
173
195
|
}
|
|
174
196
|
function mcpPlugin(options = {}) {
|
|
@@ -178,13 +200,15 @@ function mcpPlugin(options = {}) {
|
|
|
178
200
|
const urlPath = `${FUNCTIONS_MOUNT_PREFIX}${functionName}`;
|
|
179
201
|
let projectRoot = process.cwd();
|
|
180
202
|
let mcpEntryAbs = resolve2(projectRoot, mcpEntryOption);
|
|
181
|
-
|
|
182
|
-
|
|
203
|
+
let watchedInputs = /* @__PURE__ */ new Set();
|
|
204
|
+
const regenerate = async () => {
|
|
205
|
+
const { inputs } = await syncSupabaseFunction({
|
|
183
206
|
projectRoot,
|
|
184
207
|
mcpEntry: mcpEntryOption,
|
|
185
208
|
functionsDir: functionsDirOption,
|
|
186
209
|
functionName
|
|
187
210
|
});
|
|
211
|
+
watchedInputs = new Set(inputs.map(normalizePath));
|
|
188
212
|
};
|
|
189
213
|
return {
|
|
190
214
|
name: "@lovable.dev/mcp-js/supabase",
|
|
@@ -197,16 +221,23 @@ function mcpPlugin(options = {}) {
|
|
|
197
221
|
},
|
|
198
222
|
urlPath
|
|
199
223
|
},
|
|
200
|
-
configResolved(config) {
|
|
224
|
+
async configResolved(config) {
|
|
201
225
|
projectRoot = config.root;
|
|
202
226
|
mcpEntryAbs = resolve2(projectRoot, mcpEntryOption);
|
|
203
|
-
regenerate();
|
|
227
|
+
await regenerate();
|
|
204
228
|
},
|
|
205
229
|
configureServer(server) {
|
|
206
230
|
const onChange = (file) => {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
231
|
+
const norm = normalizePath(file);
|
|
232
|
+
if (norm !== normalizePath(mcpEntryAbs) && !watchedInputs.has(norm))
|
|
233
|
+
return;
|
|
234
|
+
regenerate().catch((err) => {
|
|
235
|
+
const message = `[@lovable.dev/mcp-js] failed to regenerate the Supabase function: ${err instanceof Error ? err.message : String(err)}`;
|
|
236
|
+
if (server.config?.logger?.error)
|
|
237
|
+
server.config.logger.error(message);
|
|
238
|
+
else
|
|
239
|
+
console.error(message);
|
|
240
|
+
});
|
|
210
241
|
};
|
|
211
242
|
server.watcher.on("add", onChange);
|
|
212
243
|
server.watcher.on("change", onChange);
|
|
@@ -217,8 +248,8 @@ function mcpPlugin(options = {}) {
|
|
|
217
248
|
server.watcher.off("unlink", onChange);
|
|
218
249
|
});
|
|
219
250
|
},
|
|
220
|
-
buildStart() {
|
|
221
|
-
regenerate();
|
|
251
|
+
async buildStart() {
|
|
252
|
+
await regenerate();
|
|
222
253
|
}
|
|
223
254
|
};
|
|
224
255
|
}
|
|
@@ -293,6 +293,7 @@ function stringClaim(claims, name) {
|
|
|
293
293
|
// src/auth/verifier.ts
|
|
294
294
|
var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
295
295
|
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
296
|
+
var DEFAULT_ACCESS_TOKEN_TYPS = ["at+jwt", "JWT"];
|
|
296
297
|
var JWKS_FETCH_TIMEOUT_MS = 5e3;
|
|
297
298
|
var OAuthTokenError = class extends Error {
|
|
298
299
|
constructor(status, oauthError, message) {
|
|
@@ -325,10 +326,23 @@ async function fetchVerificationKeySet(jwksUri) {
|
|
|
325
326
|
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
326
327
|
}
|
|
327
328
|
}
|
|
329
|
+
function assertAccessTokenTyp(token, allowed) {
|
|
330
|
+
let header;
|
|
331
|
+
try {
|
|
332
|
+
header = (0, import_jose.decodeProtectedHeader)(token);
|
|
333
|
+
} catch (err) {
|
|
334
|
+
log.debug("oauth.verify.bad_header", { ...describeError(err), outcome: "401 invalid_token" });
|
|
335
|
+
throw new OAuthTokenError(401, "invalid_token", "Malformed JWT header");
|
|
336
|
+
}
|
|
337
|
+
const typ = header.typ;
|
|
338
|
+
if (typeof typ !== "string" || !allowed.includes(typ)) {
|
|
339
|
+
log.debug("oauth.verify.bad_typ", { jwtTyp: typ, allowed, outcome: "401 invalid_token" });
|
|
340
|
+
throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
328
343
|
async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
329
344
|
try {
|
|
330
345
|
const { payload } = await (0, import_jose.jwtVerify)(token, keySet, {
|
|
331
|
-
typ: "at+jwt",
|
|
332
346
|
// `issuer` is trimmed of any trailing slash; accept both forms so a token whose
|
|
333
347
|
// `iss` carries the slash the AS publishes still verifies.
|
|
334
348
|
issuer: [issuer, `${issuer}/`],
|
|
@@ -377,6 +391,7 @@ function buildMcpAuthContext(args) {
|
|
|
377
391
|
};
|
|
378
392
|
}
|
|
379
393
|
function createOAuthTokenVerifier(auth, discovery) {
|
|
394
|
+
const allowedTyps = auth.accessTokenTyp ?? DEFAULT_ACCESS_TOKEN_TYPS;
|
|
380
395
|
return async (token, request, options) => {
|
|
381
396
|
const resource = resolveProtectedResource(auth, request, options);
|
|
382
397
|
const issuer = await discovery.resolveIssuer();
|
|
@@ -385,6 +400,7 @@ function createOAuthTokenVerifier(auth, discovery) {
|
|
|
385
400
|
const jwksUri = await discovery.resolveJwksUri();
|
|
386
401
|
log.debug("oauth.jwks.fetch", { jwksUri });
|
|
387
402
|
const keySet = await fetchVerificationKeySet(jwksUri);
|
|
403
|
+
assertAccessTokenTyp(token, allowedTyps);
|
|
388
404
|
const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
|
|
389
405
|
assertNonEmptySubject(claims);
|
|
390
406
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMcpProtocolHandler
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-WLNT2FX7.js";
|
|
4
4
|
import {
|
|
5
5
|
createOAuthProtectedResourceMetadataHandler
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-QDKOF4UF.js";
|
|
7
7
|
import {
|
|
8
8
|
createInvokeToolHandler
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-YWLMMDET.js";
|
|
10
10
|
import {
|
|
11
11
|
createListToolsHandler
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-VNRQPA4K.js";
|
|
13
13
|
import "../../chunk-MA5H6PSF.js";
|
|
14
|
-
import "../../chunk-
|
|
14
|
+
import "../../chunk-G4XA7IJM.js";
|
|
15
15
|
import "../../chunk-QC3DXQTH.js";
|
|
16
16
|
import "../../chunk-6DXGZZA4.js";
|
|
17
17
|
|
|
@@ -64,6 +64,14 @@ interface McpAuthConfig {
|
|
|
64
64
|
readonly jwksUri?: string;
|
|
65
65
|
readonly algorithms?: readonly string[];
|
|
66
66
|
readonly clockToleranceSeconds?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Allowed values for the access token's JOSE `typ` header. Defaults to
|
|
69
|
+
* `["at+jwt", "JWT"]` so both the RFC 9068 access-token shape and the plain
|
|
70
|
+
* user-JWT shape (notably Supabase GoTrue's OAuth tokens) are accepted out of
|
|
71
|
+
* the box. Pin to `["at+jwt"]` to enforce strict RFC 9068. The header `typ`
|
|
72
|
+
* must be present and string-typed; this option only controls which values pass.
|
|
73
|
+
*/
|
|
74
|
+
readonly accessTokenTyp?: readonly string[];
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovable.dev/mcp-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Author MCP servers for Lovable apps. Declare tools with defineTool, register them in defineMcp, and a framework adapter (TanStack or Supabase Edge Functions) emits the route(s) at build time.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"license": "MIT",
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@modelcontextprotocol/sdk": "1.28.0",
|
|
79
|
+
"esbuild": "^0.27.0",
|
|
79
80
|
"jose": "^6.2.2"
|
|
80
81
|
},
|
|
81
82
|
"peerDependencies": {
|