@lovable.dev/mcp-js 0.6.0 → 0.7.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.md +21 -0
- package/dist/{chunk-SYI32IRK.js → chunk-53M2X7FU.js} +1 -1
- package/dist/{chunk-K2T4WTKX.js → chunk-722HKLIU.js} +7 -2
- package/dist/{chunk-NJ5WBRYI.js → chunk-G5GWSV5D.js} +7 -2
- package/dist/{chunk-ZKKLOL2C.js → chunk-NTXHOUK6.js} +74 -16
- package/dist/chunk-QC3DXQTH.js +87 -0
- package/dist/index.cjs +23 -2
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +5 -3
- package/dist/protocols/mcp/index.cjs +113 -15
- package/dist/protocols/mcp/index.js +3 -3
- package/dist/protocols/oauth-metadata.cjs +107 -14
- package/dist/protocols/oauth-metadata.js +3 -3
- package/dist/protocols/rest/index.cjs +111 -14
- package/dist/protocols/rest/index.js +3 -3
- package/dist/stacks/tanstack/index.cjs +115 -16
- package/dist/stacks/tanstack/index.js +5 -5
- package/package.json +1 -1
- package/dist/chunk-QA3FWDUV.js +0 -40
|
@@ -31,17 +31,71 @@ var import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server
|
|
|
31
31
|
// src/core/http.ts
|
|
32
32
|
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
33
33
|
|
|
34
|
+
// src/core/logger.ts
|
|
35
|
+
var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
36
|
+
function isLogLevel(value) {
|
|
37
|
+
return typeof value === "string" && value in LEVEL_RANK;
|
|
38
|
+
}
|
|
39
|
+
function readEnvLevel() {
|
|
40
|
+
try {
|
|
41
|
+
const raw = typeof process !== "undefined" ? process.env?.["LOVABLE_MCP_LOG_LEVEL"] : void 0;
|
|
42
|
+
const normalized = raw?.trim().toLowerCase();
|
|
43
|
+
return isLogLevel(normalized) ? normalized : void 0;
|
|
44
|
+
} catch {
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
var currentLevel = readEnvLevel() ?? "silent";
|
|
49
|
+
function enabled(level) {
|
|
50
|
+
return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
|
|
51
|
+
}
|
|
52
|
+
function emit(level, method, event, fields) {
|
|
53
|
+
if (!enabled(level))
|
|
54
|
+
return;
|
|
55
|
+
const message = `[mcp-js] ${event}`;
|
|
56
|
+
if (fields)
|
|
57
|
+
console[method](message, fields);
|
|
58
|
+
else
|
|
59
|
+
console[method](message);
|
|
60
|
+
}
|
|
61
|
+
var log = {
|
|
62
|
+
error: (event, fields) => emit("error", "error", event, fields),
|
|
63
|
+
warn: (event, fields) => emit("warn", "warn", event, fields),
|
|
64
|
+
info: (event, fields) => emit("info", "info", event, fields),
|
|
65
|
+
debug: (event, fields) => emit("debug", "debug", event, fields)
|
|
66
|
+
};
|
|
67
|
+
function describeError(err) {
|
|
68
|
+
if (err instanceof Error) {
|
|
69
|
+
const code = err.code;
|
|
70
|
+
return { name: err.name, message: err.message, ...typeof code === "string" ? { code } : {} };
|
|
71
|
+
}
|
|
72
|
+
return { value: String(err) };
|
|
73
|
+
}
|
|
74
|
+
|
|
34
75
|
// src/core/promise.ts
|
|
35
|
-
function cachedPromise(load) {
|
|
76
|
+
function cachedPromise(load, label) {
|
|
36
77
|
let settled = false;
|
|
37
78
|
let value;
|
|
38
79
|
return async () => {
|
|
39
|
-
if (settled)
|
|
80
|
+
if (settled) {
|
|
81
|
+
if (label)
|
|
82
|
+
log.debug(`${label}.cache_hit`);
|
|
40
83
|
return value;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
84
|
+
}
|
|
85
|
+
if (label)
|
|
86
|
+
log.debug(`${label}.load_start`);
|
|
87
|
+
try {
|
|
88
|
+
const loaded = await load();
|
|
89
|
+
settled = true;
|
|
90
|
+
value = loaded;
|
|
91
|
+
if (label)
|
|
92
|
+
log.debug(`${label}.settled`);
|
|
93
|
+
return loaded;
|
|
94
|
+
} catch (err) {
|
|
95
|
+
if (label)
|
|
96
|
+
log.debug(`${label}.load_failed`, describeError(err));
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
45
99
|
};
|
|
46
100
|
}
|
|
47
101
|
|
|
@@ -117,13 +171,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
|
|
|
117
171
|
try {
|
|
118
172
|
return await fetchOAuthServerMetadata(url, expectedIssuer);
|
|
119
173
|
} catch (err) {
|
|
174
|
+
log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
|
|
120
175
|
errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
121
176
|
}
|
|
122
177
|
}
|
|
178
|
+
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
|
|
123
179
|
throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
|
|
124
180
|
}
|
|
125
181
|
async function fetchOAuthServerMetadata(url, expectedIssuer) {
|
|
126
|
-
|
|
182
|
+
log.debug("oauth.discovery.fetch", { url });
|
|
183
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
127
184
|
if (!response.ok) {
|
|
128
185
|
throw new Error(String(response.status));
|
|
129
186
|
}
|
|
@@ -133,6 +190,7 @@ async function fetchOAuthServerMetadata(url, expectedIssuer) {
|
|
|
133
190
|
}
|
|
134
191
|
parseSafeUrl("discovered issuer", json.issuer);
|
|
135
192
|
if (trimTrailingSlash(json.issuer) !== expectedIssuer) {
|
|
193
|
+
log.warn("oauth.discovery.issuer_mismatch", { url, expectedIssuer, published: json.issuer });
|
|
136
194
|
throw new Error("issuer mismatch");
|
|
137
195
|
}
|
|
138
196
|
if (typeof json.jwks_uri !== "string") {
|
|
@@ -145,6 +203,11 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
|
|
|
145
203
|
try {
|
|
146
204
|
return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
|
|
147
205
|
} catch (err) {
|
|
206
|
+
log.error("oauth.discovery.config_error", {
|
|
207
|
+
issuer,
|
|
208
|
+
...describeError(err),
|
|
209
|
+
outcome: "500 oauth configuration error"
|
|
210
|
+
});
|
|
148
211
|
throw new OAuthConfigurationError(
|
|
149
212
|
`OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
150
213
|
);
|
|
@@ -152,10 +215,21 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
|
|
|
152
215
|
}
|
|
153
216
|
function createOAuthDiscoveryResolver(auth) {
|
|
154
217
|
const configuredIssuer = trimTrailingSlash(auth.issuer);
|
|
155
|
-
const oauthServerMetadata = cachedPromise(
|
|
218
|
+
const oauthServerMetadata = cachedPromise(
|
|
219
|
+
() => fetchIssuerOAuthServerMetadata(configuredIssuer),
|
|
220
|
+
"oauth.discovery.metadata"
|
|
221
|
+
);
|
|
156
222
|
return {
|
|
157
223
|
resolveIssuer: async () => configuredIssuer,
|
|
158
|
-
resolveJwksUri: async () =>
|
|
224
|
+
resolveJwksUri: async () => {
|
|
225
|
+
if (auth.jwksUri) {
|
|
226
|
+
log.debug("oauth.jwks.resolved", { jwksUri: auth.jwksUri, source: "configured" });
|
|
227
|
+
return auth.jwksUri;
|
|
228
|
+
}
|
|
229
|
+
const jwksUri = (await oauthServerMetadata()).jwks_uri;
|
|
230
|
+
log.debug("oauth.jwks.resolved", { jwksUri, source: "discovered" });
|
|
231
|
+
return jwksUri;
|
|
232
|
+
}
|
|
159
233
|
};
|
|
160
234
|
}
|
|
161
235
|
|
|
@@ -218,6 +292,14 @@ var OAuthTokenError = class extends Error {
|
|
|
218
292
|
function resolveAcceptedAudiences(auth, resource) {
|
|
219
293
|
return auth.acceptedAudiences ?? [resource];
|
|
220
294
|
}
|
|
295
|
+
function tokenHeaderFields(token) {
|
|
296
|
+
try {
|
|
297
|
+
const header = (0, import_jose.decodeProtectedHeader)(token);
|
|
298
|
+
return { jwtAlg: header.alg, jwtKid: header.kid, tokenLength: token.length };
|
|
299
|
+
} catch {
|
|
300
|
+
return { tokenLength: token.length };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
221
303
|
function isJwksFetchFailure(err) {
|
|
222
304
|
if (err instanceof import_jose.errors.JWKSTimeout || err instanceof import_jose.errors.JWKSInvalid)
|
|
223
305
|
return true;
|
|
@@ -239,19 +321,23 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
|
239
321
|
return payload;
|
|
240
322
|
} catch (err) {
|
|
241
323
|
if (isJwksFetchFailure(err)) {
|
|
324
|
+
log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
|
|
242
325
|
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
243
326
|
}
|
|
327
|
+
log.debug("oauth.verify.rejected", { ...describeError(err), outcome: "401 invalid_token" });
|
|
244
328
|
throw err;
|
|
245
329
|
}
|
|
246
330
|
}
|
|
247
331
|
function assertNonEmptySubject(claims) {
|
|
248
332
|
const sub = claims["sub"];
|
|
249
333
|
if (typeof sub !== "string" || sub.trim() === "") {
|
|
334
|
+
log.debug("oauth.verify.bad_subject", { subType: typeof sub });
|
|
250
335
|
throw new OAuthTokenError(401, "invalid_token", "token subject claim must be a non-empty string");
|
|
251
336
|
}
|
|
252
337
|
}
|
|
253
338
|
function assertOAuthClientClaim(auth, clientId) {
|
|
254
339
|
if (auth.requireOAuthClientClaim !== false && !clientId) {
|
|
340
|
+
log.debug("oauth.verify.missing_client_claim", { outcome: "401 invalid_token" });
|
|
255
341
|
throw new OAuthTokenError(401, "invalid_token", "OAuth client claim is required");
|
|
256
342
|
}
|
|
257
343
|
}
|
|
@@ -276,17 +362,23 @@ function buildMcpAuthContext(args) {
|
|
|
276
362
|
};
|
|
277
363
|
}
|
|
278
364
|
function createOAuthTokenVerifier(auth, discovery) {
|
|
279
|
-
const loadRemoteJwksKeySet = cachedPromise(
|
|
280
|
-
() => discovery.resolveJwksUri().then((jwksURI) => (0, import_jose.createRemoteJWKSet)(new URL(jwksURI)))
|
|
281
|
-
);
|
|
282
365
|
return async (token, request, options) => {
|
|
283
366
|
const resource = resolveProtectedResource(auth, request, options);
|
|
284
367
|
const issuer = await discovery.resolveIssuer();
|
|
285
368
|
const acceptedAudiences = resolveAcceptedAudiences(auth, resource);
|
|
286
|
-
|
|
369
|
+
log.debug("oauth.verify.start", { issuer, acceptedAudiences, resource, ...tokenHeaderFields(token) });
|
|
370
|
+
const jwksUri = await discovery.resolveJwksUri();
|
|
371
|
+
log.debug("oauth.jwks.keyset_created", { jwksUri });
|
|
372
|
+
const keySet = (0, import_jose.createRemoteJWKSet)(new URL(jwksUri));
|
|
373
|
+
const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
|
|
287
374
|
assertNonEmptySubject(claims);
|
|
288
375
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
289
376
|
assertOAuthClientClaim(auth, context.principal.clientId);
|
|
377
|
+
log.info("oauth.verify.ok", {
|
|
378
|
+
sub: context.principal.sub,
|
|
379
|
+
clientId: context.principal.clientId,
|
|
380
|
+
scopes: context.principal.scopes
|
|
381
|
+
});
|
|
290
382
|
return context;
|
|
291
383
|
};
|
|
292
384
|
}
|
|
@@ -378,22 +470,27 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
378
470
|
if (runtime.kind === "unconfigured")
|
|
379
471
|
return { ok: true };
|
|
380
472
|
const token = parseBearerToken(request);
|
|
381
|
-
if (!token)
|
|
473
|
+
if (!token) {
|
|
474
|
+
log.info("auth.no_bearer_token", { outcome: "401" });
|
|
382
475
|
return { ok: false, response: challengeResponse(runtime.auth, request, 401) };
|
|
476
|
+
}
|
|
383
477
|
try {
|
|
384
478
|
const auth = await runtime.verify(token, request, runtime.options);
|
|
385
479
|
assertRequiredScopes(runtime.auth, auth);
|
|
386
480
|
return { ok: true, auth };
|
|
387
481
|
} catch (err) {
|
|
388
482
|
if (err instanceof OAuthConfigurationError) {
|
|
483
|
+
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
389
484
|
return { ok: false, response: oauthConfigurationErrorResponse() };
|
|
390
485
|
}
|
|
391
486
|
if (err instanceof OAuthTokenError) {
|
|
487
|
+
log.info("auth.token_rejected", { status: err.status, oauthError: err.oauthError });
|
|
392
488
|
return {
|
|
393
489
|
ok: false,
|
|
394
490
|
response: challengeResponse(runtime.auth, request, err.status, err.oauthError, err.message)
|
|
395
491
|
};
|
|
396
492
|
}
|
|
493
|
+
log.error("auth.unexpected_error", { ...describeError(err), outcome: "401" });
|
|
397
494
|
return {
|
|
398
495
|
ok: false,
|
|
399
496
|
response: challengeResponse(runtime.auth, request, 401, "invalid_token", "Invalid access token")
|
|
@@ -511,7 +608,8 @@ function createMcpProtocolHandler(mcp, options = {}) {
|
|
|
511
608
|
});
|
|
512
609
|
await server.connect(transport);
|
|
513
610
|
return await transport.handleRequest(request);
|
|
514
|
-
} catch {
|
|
611
|
+
} catch (err) {
|
|
612
|
+
log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
|
|
515
613
|
return Response.json(
|
|
516
614
|
{ jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
|
|
517
615
|
{ status: 500 }
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMcpProtocolHandler
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-G5GWSV5D.js";
|
|
4
4
|
import "../../chunk-MA5H6PSF.js";
|
|
5
|
-
import "../../chunk-
|
|
6
|
-
import "../../chunk-
|
|
5
|
+
import "../../chunk-NTXHOUK6.js";
|
|
6
|
+
import "../../chunk-QC3DXQTH.js";
|
|
7
7
|
import "../../chunk-6DXGZZA4.js";
|
|
8
8
|
export {
|
|
9
9
|
createMcpProtocolHandler
|
|
@@ -36,17 +36,71 @@ function methodNotAllowed(allow) {
|
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// src/core/logger.ts
|
|
40
|
+
var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
41
|
+
function isLogLevel(value) {
|
|
42
|
+
return typeof value === "string" && value in LEVEL_RANK;
|
|
43
|
+
}
|
|
44
|
+
function readEnvLevel() {
|
|
45
|
+
try {
|
|
46
|
+
const raw = typeof process !== "undefined" ? process.env?.["LOVABLE_MCP_LOG_LEVEL"] : void 0;
|
|
47
|
+
const normalized = raw?.trim().toLowerCase();
|
|
48
|
+
return isLogLevel(normalized) ? normalized : void 0;
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
var currentLevel = readEnvLevel() ?? "silent";
|
|
54
|
+
function enabled(level) {
|
|
55
|
+
return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
|
|
56
|
+
}
|
|
57
|
+
function emit(level, method, event, fields) {
|
|
58
|
+
if (!enabled(level))
|
|
59
|
+
return;
|
|
60
|
+
const message = `[mcp-js] ${event}`;
|
|
61
|
+
if (fields)
|
|
62
|
+
console[method](message, fields);
|
|
63
|
+
else
|
|
64
|
+
console[method](message);
|
|
65
|
+
}
|
|
66
|
+
var log = {
|
|
67
|
+
error: (event, fields) => emit("error", "error", event, fields),
|
|
68
|
+
warn: (event, fields) => emit("warn", "warn", event, fields),
|
|
69
|
+
info: (event, fields) => emit("info", "info", event, fields),
|
|
70
|
+
debug: (event, fields) => emit("debug", "debug", event, fields)
|
|
71
|
+
};
|
|
72
|
+
function describeError(err) {
|
|
73
|
+
if (err instanceof Error) {
|
|
74
|
+
const code = err.code;
|
|
75
|
+
return { name: err.name, message: err.message, ...typeof code === "string" ? { code } : {} };
|
|
76
|
+
}
|
|
77
|
+
return { value: String(err) };
|
|
78
|
+
}
|
|
79
|
+
|
|
39
80
|
// src/core/promise.ts
|
|
40
|
-
function cachedPromise(load) {
|
|
81
|
+
function cachedPromise(load, label) {
|
|
41
82
|
let settled = false;
|
|
42
83
|
let value;
|
|
43
84
|
return async () => {
|
|
44
|
-
if (settled)
|
|
85
|
+
if (settled) {
|
|
86
|
+
if (label)
|
|
87
|
+
log.debug(`${label}.cache_hit`);
|
|
45
88
|
return value;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
89
|
+
}
|
|
90
|
+
if (label)
|
|
91
|
+
log.debug(`${label}.load_start`);
|
|
92
|
+
try {
|
|
93
|
+
const loaded = await load();
|
|
94
|
+
settled = true;
|
|
95
|
+
value = loaded;
|
|
96
|
+
if (label)
|
|
97
|
+
log.debug(`${label}.settled`);
|
|
98
|
+
return loaded;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (label)
|
|
101
|
+
log.debug(`${label}.load_failed`, describeError(err));
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
50
104
|
};
|
|
51
105
|
}
|
|
52
106
|
|
|
@@ -122,13 +176,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
|
|
|
122
176
|
try {
|
|
123
177
|
return await fetchOAuthServerMetadata(url, expectedIssuer);
|
|
124
178
|
} catch (err) {
|
|
179
|
+
log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
|
|
125
180
|
errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
126
181
|
}
|
|
127
182
|
}
|
|
183
|
+
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
|
|
128
184
|
throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
|
|
129
185
|
}
|
|
130
186
|
async function fetchOAuthServerMetadata(url, expectedIssuer) {
|
|
131
|
-
|
|
187
|
+
log.debug("oauth.discovery.fetch", { url });
|
|
188
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
132
189
|
if (!response.ok) {
|
|
133
190
|
throw new Error(String(response.status));
|
|
134
191
|
}
|
|
@@ -138,6 +195,7 @@ async function fetchOAuthServerMetadata(url, expectedIssuer) {
|
|
|
138
195
|
}
|
|
139
196
|
parseSafeUrl("discovered issuer", json.issuer);
|
|
140
197
|
if (trimTrailingSlash(json.issuer) !== expectedIssuer) {
|
|
198
|
+
log.warn("oauth.discovery.issuer_mismatch", { url, expectedIssuer, published: json.issuer });
|
|
141
199
|
throw new Error("issuer mismatch");
|
|
142
200
|
}
|
|
143
201
|
if (typeof json.jwks_uri !== "string") {
|
|
@@ -150,6 +208,11 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
|
|
|
150
208
|
try {
|
|
151
209
|
return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
|
|
152
210
|
} catch (err) {
|
|
211
|
+
log.error("oauth.discovery.config_error", {
|
|
212
|
+
issuer,
|
|
213
|
+
...describeError(err),
|
|
214
|
+
outcome: "500 oauth configuration error"
|
|
215
|
+
});
|
|
153
216
|
throw new OAuthConfigurationError(
|
|
154
217
|
`OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
155
218
|
);
|
|
@@ -157,10 +220,21 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
|
|
|
157
220
|
}
|
|
158
221
|
function createOAuthDiscoveryResolver(auth) {
|
|
159
222
|
const configuredIssuer = trimTrailingSlash(auth.issuer);
|
|
160
|
-
const oauthServerMetadata = cachedPromise(
|
|
223
|
+
const oauthServerMetadata = cachedPromise(
|
|
224
|
+
() => fetchIssuerOAuthServerMetadata(configuredIssuer),
|
|
225
|
+
"oauth.discovery.metadata"
|
|
226
|
+
);
|
|
161
227
|
return {
|
|
162
228
|
resolveIssuer: async () => configuredIssuer,
|
|
163
|
-
resolveJwksUri: async () =>
|
|
229
|
+
resolveJwksUri: async () => {
|
|
230
|
+
if (auth.jwksUri) {
|
|
231
|
+
log.debug("oauth.jwks.resolved", { jwksUri: auth.jwksUri, source: "configured" });
|
|
232
|
+
return auth.jwksUri;
|
|
233
|
+
}
|
|
234
|
+
const jwksUri = (await oauthServerMetadata()).jwks_uri;
|
|
235
|
+
log.debug("oauth.jwks.resolved", { jwksUri, source: "discovered" });
|
|
236
|
+
return jwksUri;
|
|
237
|
+
}
|
|
164
238
|
};
|
|
165
239
|
}
|
|
166
240
|
|
|
@@ -220,6 +294,14 @@ var OAuthTokenError = class extends Error {
|
|
|
220
294
|
function resolveAcceptedAudiences(auth, resource) {
|
|
221
295
|
return auth.acceptedAudiences ?? [resource];
|
|
222
296
|
}
|
|
297
|
+
function tokenHeaderFields(token) {
|
|
298
|
+
try {
|
|
299
|
+
const header = (0, import_jose.decodeProtectedHeader)(token);
|
|
300
|
+
return { jwtAlg: header.alg, jwtKid: header.kid, tokenLength: token.length };
|
|
301
|
+
} catch {
|
|
302
|
+
return { tokenLength: token.length };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
223
305
|
function isJwksFetchFailure(err) {
|
|
224
306
|
if (err instanceof import_jose.errors.JWKSTimeout || err instanceof import_jose.errors.JWKSInvalid)
|
|
225
307
|
return true;
|
|
@@ -241,19 +323,23 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
|
241
323
|
return payload;
|
|
242
324
|
} catch (err) {
|
|
243
325
|
if (isJwksFetchFailure(err)) {
|
|
326
|
+
log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
|
|
244
327
|
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
245
328
|
}
|
|
329
|
+
log.debug("oauth.verify.rejected", { ...describeError(err), outcome: "401 invalid_token" });
|
|
246
330
|
throw err;
|
|
247
331
|
}
|
|
248
332
|
}
|
|
249
333
|
function assertNonEmptySubject(claims) {
|
|
250
334
|
const sub = claims["sub"];
|
|
251
335
|
if (typeof sub !== "string" || sub.trim() === "") {
|
|
336
|
+
log.debug("oauth.verify.bad_subject", { subType: typeof sub });
|
|
252
337
|
throw new OAuthTokenError(401, "invalid_token", "token subject claim must be a non-empty string");
|
|
253
338
|
}
|
|
254
339
|
}
|
|
255
340
|
function assertOAuthClientClaim(auth, clientId) {
|
|
256
341
|
if (auth.requireOAuthClientClaim !== false && !clientId) {
|
|
342
|
+
log.debug("oauth.verify.missing_client_claim", { outcome: "401 invalid_token" });
|
|
257
343
|
throw new OAuthTokenError(401, "invalid_token", "OAuth client claim is required");
|
|
258
344
|
}
|
|
259
345
|
}
|
|
@@ -278,17 +364,23 @@ function buildMcpAuthContext(args) {
|
|
|
278
364
|
};
|
|
279
365
|
}
|
|
280
366
|
function createOAuthTokenVerifier(auth, discovery) {
|
|
281
|
-
const loadRemoteJwksKeySet = cachedPromise(
|
|
282
|
-
() => discovery.resolveJwksUri().then((jwksURI) => (0, import_jose.createRemoteJWKSet)(new URL(jwksURI)))
|
|
283
|
-
);
|
|
284
367
|
return async (token, request, options) => {
|
|
285
368
|
const resource = resolveProtectedResource(auth, request, options);
|
|
286
369
|
const issuer = await discovery.resolveIssuer();
|
|
287
370
|
const acceptedAudiences = resolveAcceptedAudiences(auth, resource);
|
|
288
|
-
|
|
371
|
+
log.debug("oauth.verify.start", { issuer, acceptedAudiences, resource, ...tokenHeaderFields(token) });
|
|
372
|
+
const jwksUri = await discovery.resolveJwksUri();
|
|
373
|
+
log.debug("oauth.jwks.keyset_created", { jwksUri });
|
|
374
|
+
const keySet = (0, import_jose.createRemoteJWKSet)(new URL(jwksUri));
|
|
375
|
+
const claims = await verifyJwtClaims(token, keySet, issuer, acceptedAudiences, auth);
|
|
289
376
|
assertNonEmptySubject(claims);
|
|
290
377
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
291
378
|
assertOAuthClientClaim(auth, context.principal.clientId);
|
|
379
|
+
log.info("oauth.verify.ok", {
|
|
380
|
+
sub: context.principal.sub,
|
|
381
|
+
clientId: context.principal.clientId,
|
|
382
|
+
scopes: context.principal.scopes
|
|
383
|
+
});
|
|
292
384
|
return context;
|
|
293
385
|
};
|
|
294
386
|
}
|
|
@@ -390,7 +482,8 @@ function createOAuthProtectedResourceMetadataHandler(mcp, options = {}) {
|
|
|
390
482
|
);
|
|
391
483
|
const response = withCors(Response.json(metadata, { headers }));
|
|
392
484
|
return request.method === "HEAD" ? headResponse(response) : response;
|
|
393
|
-
} catch {
|
|
485
|
+
} catch (err) {
|
|
486
|
+
log.error("oauth.metadata.config_error", { ...describeError(err), outcome: "500 oauth configuration error" });
|
|
394
487
|
const response = withCors(oauthConfigurationErrorResponse());
|
|
395
488
|
return request.method === "HEAD" ? headResponse(response) : response;
|
|
396
489
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createOAuthProtectedResourceMetadataHandler
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-722HKLIU.js";
|
|
4
|
+
import "../chunk-NTXHOUK6.js";
|
|
5
|
+
import "../chunk-QC3DXQTH.js";
|
|
6
6
|
import "../chunk-6DXGZZA4.js";
|
|
7
7
|
export {
|
|
8
8
|
createOAuthProtectedResourceMetadataHandler
|