@lovable.dev/mcp-js 0.10.0 → 0.11.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/dist/{chunk-4XJ2R3DD.js → chunk-ENWRS6AT.js} +1 -1
- package/dist/{chunk-LAFJDJRY.js → chunk-G4XA7IJM.js} +17 -1
- package/dist/{chunk-X7V2CL7C.js → chunk-QDKOF4UF.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 +33 -6
- package/dist/stacks/supabase/index.d.cts +1 -1
- package/dist/stacks/supabase/index.d.ts +1 -1
- package/dist/stacks/supabase/index.js +21 -10
- package/dist/stacks/supabase/vite.cjs +1 -1
- package/dist/stacks/supabase/vite.js +1 -1
- 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 +1 -1
|
@@ -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-ENWRS6AT.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 });
|
|
@@ -908,6 +924,16 @@ function dispatchFor(pathname) {
|
|
|
908
924
|
}
|
|
909
925
|
return { kind: "mcp" };
|
|
910
926
|
}
|
|
927
|
+
function applyForwardedProto(request) {
|
|
928
|
+
const proto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
929
|
+
if (!proto)
|
|
930
|
+
return request;
|
|
931
|
+
const url = new URL(request.url);
|
|
932
|
+
if (`${proto}:` === url.protocol)
|
|
933
|
+
return request;
|
|
934
|
+
url.protocol = `${proto}:`;
|
|
935
|
+
return new Request(url.href, request);
|
|
936
|
+
}
|
|
911
937
|
function createSupabaseHandler(mcp, options = {}) {
|
|
912
938
|
const resourcePath = deriveResourcePath(options);
|
|
913
939
|
if (resourcePath !== void 0)
|
|
@@ -920,16 +946,17 @@ function createSupabaseHandler(mcp, options = {}) {
|
|
|
920
946
|
const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions);
|
|
921
947
|
const metadataHandler = createOAuthProtectedResourceMetadataHandler(mcp, runtimeOptions);
|
|
922
948
|
return async (request) => {
|
|
923
|
-
const
|
|
949
|
+
const req = applyForwardedProto(request);
|
|
950
|
+
const target = dispatchFor(new URL(req.url).pathname);
|
|
924
951
|
switch (target.kind) {
|
|
925
952
|
case "metadata":
|
|
926
|
-
return metadataHandler(
|
|
953
|
+
return metadataHandler(req);
|
|
927
954
|
case "list-tools":
|
|
928
|
-
return listToolsHandler(
|
|
955
|
+
return listToolsHandler(req);
|
|
929
956
|
case "invoke-tool":
|
|
930
|
-
return invokeToolHandler(
|
|
957
|
+
return invokeToolHandler(req, target.toolName);
|
|
931
958
|
case "mcp":
|
|
932
|
-
return mcpHandler(
|
|
959
|
+
return mcpHandler(req);
|
|
933
960
|
}
|
|
934
961
|
};
|
|
935
962
|
}
|
|
@@ -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";
|
|
@@ -54,6 +54,16 @@ function dispatchFor(pathname) {
|
|
|
54
54
|
}
|
|
55
55
|
return { kind: "mcp" };
|
|
56
56
|
}
|
|
57
|
+
function applyForwardedProto(request) {
|
|
58
|
+
const proto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
59
|
+
if (!proto)
|
|
60
|
+
return request;
|
|
61
|
+
const url = new URL(request.url);
|
|
62
|
+
if (`${proto}:` === url.protocol)
|
|
63
|
+
return request;
|
|
64
|
+
url.protocol = `${proto}:`;
|
|
65
|
+
return new Request(url.href, request);
|
|
66
|
+
}
|
|
57
67
|
function createSupabaseHandler(mcp, options = {}) {
|
|
58
68
|
const resourcePath = deriveResourcePath(options);
|
|
59
69
|
if (resourcePath !== void 0)
|
|
@@ -66,16 +76,17 @@ function createSupabaseHandler(mcp, options = {}) {
|
|
|
66
76
|
const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions);
|
|
67
77
|
const metadataHandler = createOAuthProtectedResourceMetadataHandler(mcp, runtimeOptions);
|
|
68
78
|
return async (request) => {
|
|
69
|
-
const
|
|
79
|
+
const req = applyForwardedProto(request);
|
|
80
|
+
const target = dispatchFor(new URL(req.url).pathname);
|
|
70
81
|
switch (target.kind) {
|
|
71
82
|
case "metadata":
|
|
72
|
-
return metadataHandler(
|
|
83
|
+
return metadataHandler(req);
|
|
73
84
|
case "list-tools":
|
|
74
|
-
return listToolsHandler(
|
|
85
|
+
return listToolsHandler(req);
|
|
75
86
|
case "invoke-tool":
|
|
76
|
-
return invokeToolHandler(
|
|
87
|
+
return invokeToolHandler(req, target.toolName);
|
|
77
88
|
case "mcp":
|
|
78
|
-
return mcpHandler(
|
|
89
|
+
return mcpHandler(req);
|
|
79
90
|
}
|
|
80
91
|
};
|
|
81
92
|
}
|
|
@@ -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.11.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": {
|