@lovable.dev/mcp-js 0.8.0 → 0.9.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 +3 -1
- package/dist/authorize-HTd0GKmB.d.ts +18 -0
- package/dist/{chunk-W7F6JRDB.js → chunk-3GQXQBYQ.js} +1 -1
- package/dist/{chunk-E335BBVM.js → chunk-BG3HOZXZ.js} +1 -1
- package/dist/{chunk-3XPKR4G6.js → chunk-CJMHLE7F.js} +1 -1
- package/dist/{chunk-HRMLGCXV.js → chunk-FQLM4JDU.js} +36 -12
- package/dist/{chunk-4Y72OCPS.js → chunk-SBKIKJNB.js} +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/protocols/mcp/index.cjs +36 -12
- package/dist/protocols/mcp/index.d.cts +2 -2
- package/dist/protocols/mcp/index.d.ts +2 -2
- package/dist/protocols/mcp/index.js +2 -2
- package/dist/protocols/oauth-metadata.cjs +10 -3
- package/dist/protocols/oauth-metadata.d.cts +2 -2
- package/dist/protocols/oauth-metadata.d.ts +2 -2
- package/dist/protocols/oauth-metadata.js +2 -2
- package/dist/protocols/rest/index.cjs +36 -12
- package/dist/protocols/rest/index.d.cts +2 -2
- package/dist/protocols/rest/index.d.ts +2 -2
- package/dist/protocols/rest/index.js +3 -3
- package/dist/stacks/tanstack/cli/extract-manifest.cjs +1 -1
- package/dist/stacks/tanstack/cli/extract-manifest.js +3 -3
- package/dist/stacks/tanstack/index.cjs +36 -12
- package/dist/stacks/tanstack/index.d.cts +2 -2
- package/dist/stacks/tanstack/index.d.ts +2 -2
- package/dist/stacks/tanstack/index.js +5 -5
- package/dist/stacks/tanstack/vite.cjs +43 -8
- package/dist/stacks/tanstack/vite.d.cts +17 -1
- package/dist/stacks/tanstack/vite.d.ts +17 -1
- package/dist/stacks/tanstack/vite.js +42 -8
- package/dist/{types-zMlr1mOK.d.ts → types-C5SSORxe.d.ts} +4 -0
- package/package.json +1 -1
- package/dist/authorize-DpTEIFvs.d.ts +0 -9
package/README.md
CHANGED
|
@@ -53,6 +53,8 @@ MCP (`POST /mcp`) is the public wire format clients speak directly. REST (`/.mcp
|
|
|
53
53
|
|
|
54
54
|
The OAuth metadata route is emitted by default and returns `404` until OAuth auth is configured. Disable it with `mcpPlugin({ protectedResourceMetadataRoute: false })` only if the app owns `/.well-known/oauth-protected-resource` itself.
|
|
55
55
|
|
|
56
|
+
To serve the metadata from a different path — e.g. a workspace-private project where only `/api/public/*` is anonymously reachable and `/.well-known/...` is gated — pass `mcpPlugin({ metadataPath: "/api/public/.well-known/oauth-protected-resource" })`. The plugin emits the route there and injects the same path into every handler, so the 401 `WWW-Authenticate` challenge advertises exactly what it serves. `metadataPath` is mutually exclusive with `auth.protectedResourceMetadataUrl` (and with `protectedResourceMetadataRoute: false`).
|
|
57
|
+
|
|
56
58
|
### `.lovable/mcp/manifest.json`
|
|
57
59
|
|
|
58
60
|
`.lovable/mcp/manifest.json` is a snapshot the Lovable platform reads to register the MCP server. Envelope fields: `version` (manifest schema version), `sdk_version` (the `@lovable.dev/mcp-js` release that wrote the snapshot), `path`, and `auth`. `auth` is the server's auth configuration, lifted into the envelope (not into `mcp`): `{ "type": "none" }`, or `{ "type": "oauth", ... }` mirroring the `defineMcp({ auth })` config (snake_case — `issuer`, `accepted_audiences`, `required_scopes`, `resource`, …). The `mcp` field is **exactly the `GET /.mcp/list-tools` body** — `server` plus the tool catalog (`name`/`title`/`description`/`annotations` and JSON-Schema `inputSchema`/`outputSchema`) — so the committed snapshot can't drift from what the live route serves. It's produced by loading the entry and reading the catalog off the `defineMcp` result, so the manifest reflects exactly what the server exposes, including tools built programmatically (spreads, computed names, tools from npm). **The `lovable-mcp-extract-manifest` CLI writes it** (loading the entry through Vite's SSR module loader, so it works under Node/Bun), and removes it when the entry is deleted. The Lovable platform runs the CLI in its commit pipeline; the Vite plugin only generates routes. Commit it: the platform reads the committed file.
|
|
@@ -182,7 +184,7 @@ Use an OAuth access token from the configured authorization server for MCP calls
|
|
|
182
184
|
| `algorithms` | Accepted JWT signing algorithms. Defaults to asymmetric algorithms (`RS*`, `ES*`, `EdDSA`); set explicitly only for a narrower policy. |
|
|
183
185
|
| `clockToleranceSeconds` | Allowed JWT clock skew for `exp`/`nbf` checks, in seconds. Defaults to `30`; max `300`. |
|
|
184
186
|
| `resourceName`, `resourceDocumentation` | Optional metadata fields for MCP clients and humans. `resourceName` defaults to the MCP server `title`. |
|
|
185
|
-
| `protectedResourceMetadataUrl` | Absolute HTTPS URL of an externally hosted RFC 9728 protected-resource metadata document. When set, the SDK advertises this URL in the 401 `WWW-Authenticate: resource_metadata` challenge and stops generating its own metadata — the `/.well-known/oauth-protected-resource` handler returns 404. Use it when the resource server already publishes its own PRM; you'll typically also pass `protectedResourceMetadataRoute: false` to the Vite plugin so the now-unused route isn't emitted. |
|
|
187
|
+
| `protectedResourceMetadataUrl` | Absolute HTTPS URL of an externally hosted RFC 9728 protected-resource metadata document. When set, the SDK advertises this URL in the 401 `WWW-Authenticate: resource_metadata` challenge and stops generating its own metadata — the `/.well-known/oauth-protected-resource` handler returns 404. Use it when the resource server already publishes its own PRM; you'll typically also pass `protectedResourceMetadataRoute: false` to the Vite plugin so the now-unused route isn't emitted. Mutually exclusive with the plugin's `metadataPath` (which has the SDK serve its own document at a custom path); configuring both throws at handler construction. |
|
|
186
188
|
|
|
187
189
|
Protected-resource metadata is derived from the same config. When `resource` is omitted, the published resource is the incoming request origin plus the MCP route path the stack adapter supplies as `resourcePath` (for example, `https://my-app.lovable.app/mcp`); `authorization_servers` is the configured issuer, and `resource_name` is `defineMcp({ title })` unless `resourceName` overrides it. Because the metadata endpoint is served from `/.well-known/...`, it can't infer the resource from its own path — the handler throws at construction when neither `resource` nor `resourcePath` is set (the generated TanStack routes always pass `resourcePath`). For Supabase auth, this metadata still names the MCP resource even though Supabase JWTs use the project audience `"authenticated"`.
|
|
188
190
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface McpRuntimeOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Public MCP protocol path. REST companion handlers pass this so OAuth
|
|
4
|
+
* resource/audience checks bind to `/mcp`, not internal `/.mcp/*` RPC URLs.
|
|
5
|
+
*/
|
|
6
|
+
resourcePath?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Host-relative path of the SDK-served protected-resource metadata document.
|
|
9
|
+
* The Vite plugin injects it into every emitted handler when it generates the
|
|
10
|
+
* PRM route, so the 401 challenge advertises the same path the route serves.
|
|
11
|
+
* Its presence is the signal that the SDK owns the PRM document, so it is
|
|
12
|
+
* mutually exclusive with `auth.protectedResourceMetadataUrl` (which points at
|
|
13
|
+
* an externally hosted document). Defaults to the well-known path when absent.
|
|
14
|
+
*/
|
|
15
|
+
metadataPath?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type { McpRuntimeOptions as M };
|
|
@@ -30,10 +30,10 @@ function resolveProtectedResource(auth, request, options) {
|
|
|
30
30
|
resourceURL.hash = "";
|
|
31
31
|
return trimTrailingSlash(resourceURL.toString());
|
|
32
32
|
}
|
|
33
|
-
function assertResourcePathShape(resourcePath) {
|
|
33
|
+
function assertResourcePathShape(resourcePath, label = "resourcePath") {
|
|
34
34
|
if (!resourcePath.startsWith("/") || resourcePath.startsWith("//") || resourcePath.includes("\\") || /(^|\/)\.\.(\/|$)/.test(resourcePath)) {
|
|
35
35
|
throw new Error(
|
|
36
|
-
`@lovable.dev/mcp-js:
|
|
36
|
+
`@lovable.dev/mcp-js: ${label} must be an absolute path beginning with "/" without ".." segments or backslashes (got ${JSON.stringify(resourcePath)})`
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -299,16 +299,16 @@ function quoteAuthenticateParam(value) {
|
|
|
299
299
|
const sanitized = value.replace(/[\u0000-\u001F\u007F]/g, "");
|
|
300
300
|
return `"${sanitized.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
301
301
|
}
|
|
302
|
-
function resolveProtectedResourceMetadataUrl(auth, request) {
|
|
302
|
+
function resolveProtectedResourceMetadataUrl(auth, request, options) {
|
|
303
303
|
if (auth.protectedResourceMetadataUrl !== void 0)
|
|
304
304
|
return auth.protectedResourceMetadataUrl;
|
|
305
305
|
const base = auth.resource ?? request.url;
|
|
306
|
-
return new URL(OAUTH_PROTECTED_RESOURCE_METADATA_PATH, base).toString();
|
|
306
|
+
return new URL(options.metadataPath ?? OAUTH_PROTECTED_RESOURCE_METADATA_PATH, base).toString();
|
|
307
307
|
}
|
|
308
|
-
function wwwAuthenticateHeader(auth, request, params = {}) {
|
|
308
|
+
function wwwAuthenticateHeader(auth, request, options, params = {}) {
|
|
309
309
|
const values = [
|
|
310
310
|
`realm=${quoteAuthenticateParam("mcp")}`,
|
|
311
|
-
`resource_metadata=${quoteAuthenticateParam(resolveProtectedResourceMetadataUrl(auth, request))}`
|
|
311
|
+
`resource_metadata=${quoteAuthenticateParam(resolveProtectedResourceMetadataUrl(auth, request, options))}`
|
|
312
312
|
];
|
|
313
313
|
if (auth.requiredScopes && auth.requiredScopes.length > 0) {
|
|
314
314
|
values.push(`scope=${quoteAuthenticateParam(auth.requiredScopes.join(" "))}`);
|
|
@@ -319,9 +319,12 @@ function wwwAuthenticateHeader(auth, request, params = {}) {
|
|
|
319
319
|
values.push(`error_description=${quoteAuthenticateParam(params.errorDescription)}`);
|
|
320
320
|
return `Bearer ${values.join(", ")}`;
|
|
321
321
|
}
|
|
322
|
-
function challengeResponse(auth, request, status, oauthError, errorDescription) {
|
|
322
|
+
function challengeResponse(auth, request, options, status, oauthError, errorDescription) {
|
|
323
323
|
const headers = new Headers(JSON_HEADERS);
|
|
324
|
-
headers.set(
|
|
324
|
+
headers.set(
|
|
325
|
+
"WWW-Authenticate",
|
|
326
|
+
wwwAuthenticateHeader(auth, request, options, { error: oauthError, errorDescription })
|
|
327
|
+
);
|
|
325
328
|
headers.set("Cache-Control", "no-store");
|
|
326
329
|
return new Response(JSON.stringify({ error: status === 403 ? "forbidden" : "unauthorized" }), {
|
|
327
330
|
status,
|
|
@@ -349,10 +352,17 @@ function parseBearerToken(request) {
|
|
|
349
352
|
function getOAuthRuntime(mcp, options = {}) {
|
|
350
353
|
if (options.resourcePath !== void 0)
|
|
351
354
|
assertResourcePathShape(options.resourcePath);
|
|
352
|
-
|
|
355
|
+
if (options.metadataPath !== void 0)
|
|
356
|
+
assertResourcePathShape(options.metadataPath, "metadataPath");
|
|
357
|
+
const stableOptions = { resourcePath: options.resourcePath, metadataPath: options.metadataPath };
|
|
353
358
|
const auth = mcp.auth?.type === "oauth" ? mcp.auth : void 0;
|
|
354
359
|
if (!auth)
|
|
355
360
|
return { kind: "unconfigured", options: stableOptions };
|
|
361
|
+
if (options.metadataPath !== void 0 && auth.protectedResourceMetadataUrl !== void 0) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
`@lovable.dev/mcp-js: the Vite plugin generates protected-resource metadata (metadataPath set), so auth.protectedResourceMetadataUrl must not also be set. Drop protectedResourceMetadataUrl, or set protectedResourceMetadataRoute: false in mcpPlugin(...) to host the document yourself.`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
356
366
|
const discovery = createOAuthDiscoveryResolver(auth);
|
|
357
367
|
return {
|
|
358
368
|
kind: "configured",
|
|
@@ -391,7 +401,7 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
391
401
|
const token = parseBearerToken(request);
|
|
392
402
|
if (!token) {
|
|
393
403
|
log.info("auth.no_bearer_token", { outcome: "401" });
|
|
394
|
-
return { ok: false, response: challengeResponse(runtime.auth, request, 401) };
|
|
404
|
+
return { ok: false, response: challengeResponse(runtime.auth, request, runtime.options, 401) };
|
|
395
405
|
}
|
|
396
406
|
try {
|
|
397
407
|
const auth = await runtime.verify(token, request, runtime.options);
|
|
@@ -406,13 +416,27 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
406
416
|
log.info("auth.token_rejected", { status: err.status, oauthError: err.oauthError });
|
|
407
417
|
return {
|
|
408
418
|
ok: false,
|
|
409
|
-
response: challengeResponse(
|
|
419
|
+
response: challengeResponse(
|
|
420
|
+
runtime.auth,
|
|
421
|
+
request,
|
|
422
|
+
runtime.options,
|
|
423
|
+
err.status,
|
|
424
|
+
err.oauthError,
|
|
425
|
+
err.message
|
|
426
|
+
)
|
|
410
427
|
};
|
|
411
428
|
}
|
|
412
429
|
log.error("auth.unexpected_error", { ...describeError(err), outcome: "401" });
|
|
413
430
|
return {
|
|
414
431
|
ok: false,
|
|
415
|
-
response: challengeResponse(
|
|
432
|
+
response: challengeResponse(
|
|
433
|
+
runtime.auth,
|
|
434
|
+
request,
|
|
435
|
+
runtime.options,
|
|
436
|
+
401,
|
|
437
|
+
"invalid_token",
|
|
438
|
+
"Invalid access token"
|
|
439
|
+
)
|
|
416
440
|
};
|
|
417
441
|
}
|
|
418
442
|
}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
createRequestAuthorizer,
|
|
9
9
|
methodNotAllowed,
|
|
10
10
|
withCors
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-FQLM4JDU.js";
|
|
12
12
|
|
|
13
13
|
// src/protocols/rest/invoke-tool.ts
|
|
14
14
|
import { getParseErrorMessage, objectFromShape, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
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-C5SSORxe.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-C5SSORxe.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
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-C5SSORxe.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-C5SSORxe.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -246,10 +246,10 @@ function resolveProtectedResource(auth, request, options) {
|
|
|
246
246
|
resourceURL.hash = "";
|
|
247
247
|
return trimTrailingSlash(resourceURL.toString());
|
|
248
248
|
}
|
|
249
|
-
function assertResourcePathShape(resourcePath) {
|
|
249
|
+
function assertResourcePathShape(resourcePath, label = "resourcePath") {
|
|
250
250
|
if (!resourcePath.startsWith("/") || resourcePath.startsWith("//") || resourcePath.includes("\\") || /(^|\/)\.\.(\/|$)/.test(resourcePath)) {
|
|
251
251
|
throw new Error(
|
|
252
|
-
`@lovable.dev/mcp-js:
|
|
252
|
+
`@lovable.dev/mcp-js: ${label} must be an absolute path beginning with "/" without ".." segments or backslashes (got ${JSON.stringify(resourcePath)})`
|
|
253
253
|
);
|
|
254
254
|
}
|
|
255
255
|
}
|
|
@@ -390,16 +390,16 @@ function quoteAuthenticateParam(value) {
|
|
|
390
390
|
const sanitized = value.replace(/[\u0000-\u001F\u007F]/g, "");
|
|
391
391
|
return `"${sanitized.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
392
392
|
}
|
|
393
|
-
function resolveProtectedResourceMetadataUrl(auth, request) {
|
|
393
|
+
function resolveProtectedResourceMetadataUrl(auth, request, options) {
|
|
394
394
|
if (auth.protectedResourceMetadataUrl !== void 0)
|
|
395
395
|
return auth.protectedResourceMetadataUrl;
|
|
396
396
|
const base = auth.resource ?? request.url;
|
|
397
|
-
return new URL(OAUTH_PROTECTED_RESOURCE_METADATA_PATH, base).toString();
|
|
397
|
+
return new URL(options.metadataPath ?? OAUTH_PROTECTED_RESOURCE_METADATA_PATH, base).toString();
|
|
398
398
|
}
|
|
399
|
-
function wwwAuthenticateHeader(auth, request, params = {}) {
|
|
399
|
+
function wwwAuthenticateHeader(auth, request, options, params = {}) {
|
|
400
400
|
const values = [
|
|
401
401
|
`realm=${quoteAuthenticateParam("mcp")}`,
|
|
402
|
-
`resource_metadata=${quoteAuthenticateParam(resolveProtectedResourceMetadataUrl(auth, request))}`
|
|
402
|
+
`resource_metadata=${quoteAuthenticateParam(resolveProtectedResourceMetadataUrl(auth, request, options))}`
|
|
403
403
|
];
|
|
404
404
|
if (auth.requiredScopes && auth.requiredScopes.length > 0) {
|
|
405
405
|
values.push(`scope=${quoteAuthenticateParam(auth.requiredScopes.join(" "))}`);
|
|
@@ -410,9 +410,12 @@ function wwwAuthenticateHeader(auth, request, params = {}) {
|
|
|
410
410
|
values.push(`error_description=${quoteAuthenticateParam(params.errorDescription)}`);
|
|
411
411
|
return `Bearer ${values.join(", ")}`;
|
|
412
412
|
}
|
|
413
|
-
function challengeResponse(auth, request, status, oauthError, errorDescription) {
|
|
413
|
+
function challengeResponse(auth, request, options, status, oauthError, errorDescription) {
|
|
414
414
|
const headers = new Headers(JSON_HEADERS);
|
|
415
|
-
headers.set(
|
|
415
|
+
headers.set(
|
|
416
|
+
"WWW-Authenticate",
|
|
417
|
+
wwwAuthenticateHeader(auth, request, options, { error: oauthError, errorDescription })
|
|
418
|
+
);
|
|
416
419
|
headers.set("Cache-Control", "no-store");
|
|
417
420
|
return new Response(JSON.stringify({ error: status === 403 ? "forbidden" : "unauthorized" }), {
|
|
418
421
|
status,
|
|
@@ -440,10 +443,17 @@ function parseBearerToken(request) {
|
|
|
440
443
|
function getOAuthRuntime(mcp, options = {}) {
|
|
441
444
|
if (options.resourcePath !== void 0)
|
|
442
445
|
assertResourcePathShape(options.resourcePath);
|
|
443
|
-
|
|
446
|
+
if (options.metadataPath !== void 0)
|
|
447
|
+
assertResourcePathShape(options.metadataPath, "metadataPath");
|
|
448
|
+
const stableOptions = { resourcePath: options.resourcePath, metadataPath: options.metadataPath };
|
|
444
449
|
const auth = mcp.auth?.type === "oauth" ? mcp.auth : void 0;
|
|
445
450
|
if (!auth)
|
|
446
451
|
return { kind: "unconfigured", options: stableOptions };
|
|
452
|
+
if (options.metadataPath !== void 0 && auth.protectedResourceMetadataUrl !== void 0) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
`@lovable.dev/mcp-js: the Vite plugin generates protected-resource metadata (metadataPath set), so auth.protectedResourceMetadataUrl must not also be set. Drop protectedResourceMetadataUrl, or set protectedResourceMetadataRoute: false in mcpPlugin(...) to host the document yourself.`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
447
457
|
const discovery = createOAuthDiscoveryResolver(auth);
|
|
448
458
|
return {
|
|
449
459
|
kind: "configured",
|
|
@@ -474,7 +484,7 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
474
484
|
const token = parseBearerToken(request);
|
|
475
485
|
if (!token) {
|
|
476
486
|
log.info("auth.no_bearer_token", { outcome: "401" });
|
|
477
|
-
return { ok: false, response: challengeResponse(runtime.auth, request, 401) };
|
|
487
|
+
return { ok: false, response: challengeResponse(runtime.auth, request, runtime.options, 401) };
|
|
478
488
|
}
|
|
479
489
|
try {
|
|
480
490
|
const auth = await runtime.verify(token, request, runtime.options);
|
|
@@ -489,13 +499,27 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
489
499
|
log.info("auth.token_rejected", { status: err.status, oauthError: err.oauthError });
|
|
490
500
|
return {
|
|
491
501
|
ok: false,
|
|
492
|
-
response: challengeResponse(
|
|
502
|
+
response: challengeResponse(
|
|
503
|
+
runtime.auth,
|
|
504
|
+
request,
|
|
505
|
+
runtime.options,
|
|
506
|
+
err.status,
|
|
507
|
+
err.oauthError,
|
|
508
|
+
err.message
|
|
509
|
+
)
|
|
493
510
|
};
|
|
494
511
|
}
|
|
495
512
|
log.error("auth.unexpected_error", { ...describeError(err), outcome: "401" });
|
|
496
513
|
return {
|
|
497
514
|
ok: false,
|
|
498
|
-
response: challengeResponse(
|
|
515
|
+
response: challengeResponse(
|
|
516
|
+
runtime.auth,
|
|
517
|
+
request,
|
|
518
|
+
runtime.options,
|
|
519
|
+
401,
|
|
520
|
+
"invalid_token",
|
|
521
|
+
"Invalid access token"
|
|
522
|
+
)
|
|
499
523
|
};
|
|
500
524
|
}
|
|
501
525
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as McpRuntimeOptions } from '../../authorize-
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
1
|
+
import { M as McpRuntimeOptions } from '../../authorize-HTd0GKmB.js';
|
|
2
|
+
import { d as McpDefinition } from '../../types-C5SSORxe.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type McpProtocolHandler = (request: Request) => Promise<Response>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as McpRuntimeOptions } from '../../authorize-
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
1
|
+
import { M as McpRuntimeOptions } from '../../authorize-HTd0GKmB.js';
|
|
2
|
+
import { d as McpDefinition } from '../../types-C5SSORxe.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-CJMHLE7F.js";
|
|
4
4
|
import "../../chunk-MA5H6PSF.js";
|
|
5
|
-
import "../../chunk-
|
|
5
|
+
import "../../chunk-FQLM4JDU.js";
|
|
6
6
|
import "../../chunk-QC3DXQTH.js";
|
|
7
7
|
import "../../chunk-6DXGZZA4.js";
|
|
8
8
|
export {
|
|
@@ -248,10 +248,10 @@ function resolveProtectedResource(auth, request, options) {
|
|
|
248
248
|
resourceURL.hash = "";
|
|
249
249
|
return trimTrailingSlash(resourceURL.toString());
|
|
250
250
|
}
|
|
251
|
-
function assertResourcePathShape(resourcePath) {
|
|
251
|
+
function assertResourcePathShape(resourcePath, label = "resourcePath") {
|
|
252
252
|
if (!resourcePath.startsWith("/") || resourcePath.startsWith("//") || resourcePath.includes("\\") || /(^|\/)\.\.(\/|$)/.test(resourcePath)) {
|
|
253
253
|
throw new Error(
|
|
254
|
-
`@lovable.dev/mcp-js:
|
|
254
|
+
`@lovable.dev/mcp-js: ${label} must be an absolute path beginning with "/" without ".." segments or backslashes (got ${JSON.stringify(resourcePath)})`
|
|
255
255
|
);
|
|
256
256
|
}
|
|
257
257
|
}
|
|
@@ -397,10 +397,17 @@ function oauthConfigurationErrorResponse() {
|
|
|
397
397
|
function getOAuthRuntime(mcp, options = {}) {
|
|
398
398
|
if (options.resourcePath !== void 0)
|
|
399
399
|
assertResourcePathShape(options.resourcePath);
|
|
400
|
-
|
|
400
|
+
if (options.metadataPath !== void 0)
|
|
401
|
+
assertResourcePathShape(options.metadataPath, "metadataPath");
|
|
402
|
+
const stableOptions = { resourcePath: options.resourcePath, metadataPath: options.metadataPath };
|
|
401
403
|
const auth = mcp.auth?.type === "oauth" ? mcp.auth : void 0;
|
|
402
404
|
if (!auth)
|
|
403
405
|
return { kind: "unconfigured", options: stableOptions };
|
|
406
|
+
if (options.metadataPath !== void 0 && auth.protectedResourceMetadataUrl !== void 0) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
`@lovable.dev/mcp-js: the Vite plugin generates protected-resource metadata (metadataPath set), so auth.protectedResourceMetadataUrl must not also be set. Drop protectedResourceMetadataUrl, or set protectedResourceMetadataRoute: false in mcpPlugin(...) to host the document yourself.`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
404
411
|
const discovery = createOAuthDiscoveryResolver(auth);
|
|
405
412
|
return {
|
|
406
413
|
kind: "configured",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as McpRuntimeOptions } from '../authorize-
|
|
2
|
-
import { d as McpDefinition } from '../types-
|
|
1
|
+
import { M as McpRuntimeOptions } from '../authorize-HTd0GKmB.js';
|
|
2
|
+
import { d as McpDefinition } from '../types-C5SSORxe.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type OAuthProtectedResourceMetadataHandler = (request: Request) => Promise<Response>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as McpRuntimeOptions } from '../authorize-
|
|
2
|
-
import { d as McpDefinition } from '../types-
|
|
1
|
+
import { M as McpRuntimeOptions } from '../authorize-HTd0GKmB.js';
|
|
2
|
+
import { d as McpDefinition } from '../types-C5SSORxe.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type OAuthProtectedResourceMetadataHandler = (request: Request) => Promise<Response>;
|
|
@@ -256,10 +256,10 @@ function resolveProtectedResource(auth, request, options) {
|
|
|
256
256
|
resourceURL.hash = "";
|
|
257
257
|
return trimTrailingSlash(resourceURL.toString());
|
|
258
258
|
}
|
|
259
|
-
function assertResourcePathShape(resourcePath) {
|
|
259
|
+
function assertResourcePathShape(resourcePath, label = "resourcePath") {
|
|
260
260
|
if (!resourcePath.startsWith("/") || resourcePath.startsWith("//") || resourcePath.includes("\\") || /(^|\/)\.\.(\/|$)/.test(resourcePath)) {
|
|
261
261
|
throw new Error(
|
|
262
|
-
`@lovable.dev/mcp-js:
|
|
262
|
+
`@lovable.dev/mcp-js: ${label} must be an absolute path beginning with "/" without ".." segments or backslashes (got ${JSON.stringify(resourcePath)})`
|
|
263
263
|
);
|
|
264
264
|
}
|
|
265
265
|
}
|
|
@@ -400,16 +400,16 @@ function quoteAuthenticateParam(value) {
|
|
|
400
400
|
const sanitized = value.replace(/[\u0000-\u001F\u007F]/g, "");
|
|
401
401
|
return `"${sanitized.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
402
402
|
}
|
|
403
|
-
function resolveProtectedResourceMetadataUrl(auth, request) {
|
|
403
|
+
function resolveProtectedResourceMetadataUrl(auth, request, options) {
|
|
404
404
|
if (auth.protectedResourceMetadataUrl !== void 0)
|
|
405
405
|
return auth.protectedResourceMetadataUrl;
|
|
406
406
|
const base = auth.resource ?? request.url;
|
|
407
|
-
return new URL(OAUTH_PROTECTED_RESOURCE_METADATA_PATH, base).toString();
|
|
407
|
+
return new URL(options.metadataPath ?? OAUTH_PROTECTED_RESOURCE_METADATA_PATH, base).toString();
|
|
408
408
|
}
|
|
409
|
-
function wwwAuthenticateHeader(auth, request, params = {}) {
|
|
409
|
+
function wwwAuthenticateHeader(auth, request, options, params = {}) {
|
|
410
410
|
const values = [
|
|
411
411
|
`realm=${quoteAuthenticateParam("mcp")}`,
|
|
412
|
-
`resource_metadata=${quoteAuthenticateParam(resolveProtectedResourceMetadataUrl(auth, request))}`
|
|
412
|
+
`resource_metadata=${quoteAuthenticateParam(resolveProtectedResourceMetadataUrl(auth, request, options))}`
|
|
413
413
|
];
|
|
414
414
|
if (auth.requiredScopes && auth.requiredScopes.length > 0) {
|
|
415
415
|
values.push(`scope=${quoteAuthenticateParam(auth.requiredScopes.join(" "))}`);
|
|
@@ -420,9 +420,12 @@ function wwwAuthenticateHeader(auth, request, params = {}) {
|
|
|
420
420
|
values.push(`error_description=${quoteAuthenticateParam(params.errorDescription)}`);
|
|
421
421
|
return `Bearer ${values.join(", ")}`;
|
|
422
422
|
}
|
|
423
|
-
function challengeResponse(auth, request, status, oauthError, errorDescription) {
|
|
423
|
+
function challengeResponse(auth, request, options, status, oauthError, errorDescription) {
|
|
424
424
|
const headers = new Headers(JSON_HEADERS);
|
|
425
|
-
headers.set(
|
|
425
|
+
headers.set(
|
|
426
|
+
"WWW-Authenticate",
|
|
427
|
+
wwwAuthenticateHeader(auth, request, options, { error: oauthError, errorDescription })
|
|
428
|
+
);
|
|
426
429
|
headers.set("Cache-Control", "no-store");
|
|
427
430
|
return new Response(JSON.stringify({ error: status === 403 ? "forbidden" : "unauthorized" }), {
|
|
428
431
|
status,
|
|
@@ -450,10 +453,17 @@ function parseBearerToken(request) {
|
|
|
450
453
|
function getOAuthRuntime(mcp, options = {}) {
|
|
451
454
|
if (options.resourcePath !== void 0)
|
|
452
455
|
assertResourcePathShape(options.resourcePath);
|
|
453
|
-
|
|
456
|
+
if (options.metadataPath !== void 0)
|
|
457
|
+
assertResourcePathShape(options.metadataPath, "metadataPath");
|
|
458
|
+
const stableOptions = { resourcePath: options.resourcePath, metadataPath: options.metadataPath };
|
|
454
459
|
const auth = mcp.auth?.type === "oauth" ? mcp.auth : void 0;
|
|
455
460
|
if (!auth)
|
|
456
461
|
return { kind: "unconfigured", options: stableOptions };
|
|
462
|
+
if (options.metadataPath !== void 0 && auth.protectedResourceMetadataUrl !== void 0) {
|
|
463
|
+
throw new Error(
|
|
464
|
+
`@lovable.dev/mcp-js: the Vite plugin generates protected-resource metadata (metadataPath set), so auth.protectedResourceMetadataUrl must not also be set. Drop protectedResourceMetadataUrl, or set protectedResourceMetadataRoute: false in mcpPlugin(...) to host the document yourself.`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
457
467
|
const discovery = createOAuthDiscoveryResolver(auth);
|
|
458
468
|
return {
|
|
459
469
|
kind: "configured",
|
|
@@ -492,7 +502,7 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
492
502
|
const token = parseBearerToken(request);
|
|
493
503
|
if (!token) {
|
|
494
504
|
log.info("auth.no_bearer_token", { outcome: "401" });
|
|
495
|
-
return { ok: false, response: challengeResponse(runtime.auth, request, 401) };
|
|
505
|
+
return { ok: false, response: challengeResponse(runtime.auth, request, runtime.options, 401) };
|
|
496
506
|
}
|
|
497
507
|
try {
|
|
498
508
|
const auth = await runtime.verify(token, request, runtime.options);
|
|
@@ -507,13 +517,27 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
507
517
|
log.info("auth.token_rejected", { status: err.status, oauthError: err.oauthError });
|
|
508
518
|
return {
|
|
509
519
|
ok: false,
|
|
510
|
-
response: challengeResponse(
|
|
520
|
+
response: challengeResponse(
|
|
521
|
+
runtime.auth,
|
|
522
|
+
request,
|
|
523
|
+
runtime.options,
|
|
524
|
+
err.status,
|
|
525
|
+
err.oauthError,
|
|
526
|
+
err.message
|
|
527
|
+
)
|
|
511
528
|
};
|
|
512
529
|
}
|
|
513
530
|
log.error("auth.unexpected_error", { ...describeError(err), outcome: "401" });
|
|
514
531
|
return {
|
|
515
532
|
ok: false,
|
|
516
|
-
response: challengeResponse(
|
|
533
|
+
response: challengeResponse(
|
|
534
|
+
runtime.auth,
|
|
535
|
+
request,
|
|
536
|
+
runtime.options,
|
|
537
|
+
401,
|
|
538
|
+
"invalid_token",
|
|
539
|
+
"Invalid access token"
|
|
540
|
+
)
|
|
517
541
|
};
|
|
518
542
|
}
|
|
519
543
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as McpRuntimeOptions } from '../../authorize-
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
1
|
+
import { M as McpRuntimeOptions } from '../../authorize-HTd0GKmB.js';
|
|
2
|
+
import { d as McpDefinition } from '../../types-C5SSORxe.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type RestListToolsHandler = (request: Request) => Promise<Response>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as McpRuntimeOptions } from '../../authorize-
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
1
|
+
import { M as McpRuntimeOptions } from '../../authorize-HTd0GKmB.js';
|
|
2
|
+
import { d as McpDefinition } from '../../types-C5SSORxe.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-SBKIKJNB.js";
|
|
4
4
|
import "../../chunk-MA5H6PSF.js";
|
|
5
5
|
import {
|
|
6
6
|
createListToolsHandler
|
|
7
|
-
} from "../../chunk-
|
|
8
|
-
import "../../chunk-
|
|
7
|
+
} from "../../chunk-BG3HOZXZ.js";
|
|
8
|
+
import "../../chunk-FQLM4JDU.js";
|
|
9
9
|
import "../../chunk-QC3DXQTH.js";
|
|
10
10
|
import "../../chunk-6DXGZZA4.js";
|
|
11
11
|
export {
|
|
@@ -8,7 +8,7 @@ var import_node_path = require("path");
|
|
|
8
8
|
var import_vite2 = require("vite");
|
|
9
9
|
|
|
10
10
|
// package.json
|
|
11
|
-
var version = "0.
|
|
11
|
+
var version = "0.9.0";
|
|
12
12
|
|
|
13
13
|
// src/protocols/rest/list-tools.ts
|
|
14
14
|
var import_zod_compat = require("@modelcontextprotocol/sdk/server/zod-compat.js");
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
buildMcpListing
|
|
4
|
-
} from "../../../chunk-
|
|
5
|
-
import "../../../chunk-
|
|
4
|
+
} from "../../../chunk-BG3HOZXZ.js";
|
|
5
|
+
import "../../../chunk-FQLM4JDU.js";
|
|
6
6
|
import "../../../chunk-QC3DXQTH.js";
|
|
7
7
|
import {
|
|
8
8
|
isFileMissing
|
|
@@ -16,7 +16,7 @@ import { dirname, resolve } from "path";
|
|
|
16
16
|
import { resolveConfig } from "vite";
|
|
17
17
|
|
|
18
18
|
// package.json
|
|
19
|
-
var version = "0.
|
|
19
|
+
var version = "0.9.0";
|
|
20
20
|
|
|
21
21
|
// src/stacks/tanstack/extract-manifest.ts
|
|
22
22
|
var MANIFEST_VERSION = 1;
|
|
@@ -258,10 +258,10 @@ function resolveProtectedResource(auth, request, options) {
|
|
|
258
258
|
resourceURL.hash = "";
|
|
259
259
|
return trimTrailingSlash(resourceURL.toString());
|
|
260
260
|
}
|
|
261
|
-
function assertResourcePathShape(resourcePath) {
|
|
261
|
+
function assertResourcePathShape(resourcePath, label = "resourcePath") {
|
|
262
262
|
if (!resourcePath.startsWith("/") || resourcePath.startsWith("//") || resourcePath.includes("\\") || /(^|\/)\.\.(\/|$)/.test(resourcePath)) {
|
|
263
263
|
throw new Error(
|
|
264
|
-
`@lovable.dev/mcp-js:
|
|
264
|
+
`@lovable.dev/mcp-js: ${label} must be an absolute path beginning with "/" without ".." segments or backslashes (got ${JSON.stringify(resourcePath)})`
|
|
265
265
|
);
|
|
266
266
|
}
|
|
267
267
|
}
|
|
@@ -402,16 +402,16 @@ function quoteAuthenticateParam(value) {
|
|
|
402
402
|
const sanitized = value.replace(/[\u0000-\u001F\u007F]/g, "");
|
|
403
403
|
return `"${sanitized.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
404
404
|
}
|
|
405
|
-
function resolveProtectedResourceMetadataUrl(auth, request) {
|
|
405
|
+
function resolveProtectedResourceMetadataUrl(auth, request, options) {
|
|
406
406
|
if (auth.protectedResourceMetadataUrl !== void 0)
|
|
407
407
|
return auth.protectedResourceMetadataUrl;
|
|
408
408
|
const base = auth.resource ?? request.url;
|
|
409
|
-
return new URL(OAUTH_PROTECTED_RESOURCE_METADATA_PATH, base).toString();
|
|
409
|
+
return new URL(options.metadataPath ?? OAUTH_PROTECTED_RESOURCE_METADATA_PATH, base).toString();
|
|
410
410
|
}
|
|
411
|
-
function wwwAuthenticateHeader(auth, request, params = {}) {
|
|
411
|
+
function wwwAuthenticateHeader(auth, request, options, params = {}) {
|
|
412
412
|
const values = [
|
|
413
413
|
`realm=${quoteAuthenticateParam("mcp")}`,
|
|
414
|
-
`resource_metadata=${quoteAuthenticateParam(resolveProtectedResourceMetadataUrl(auth, request))}`
|
|
414
|
+
`resource_metadata=${quoteAuthenticateParam(resolveProtectedResourceMetadataUrl(auth, request, options))}`
|
|
415
415
|
];
|
|
416
416
|
if (auth.requiredScopes && auth.requiredScopes.length > 0) {
|
|
417
417
|
values.push(`scope=${quoteAuthenticateParam(auth.requiredScopes.join(" "))}`);
|
|
@@ -422,9 +422,12 @@ function wwwAuthenticateHeader(auth, request, params = {}) {
|
|
|
422
422
|
values.push(`error_description=${quoteAuthenticateParam(params.errorDescription)}`);
|
|
423
423
|
return `Bearer ${values.join(", ")}`;
|
|
424
424
|
}
|
|
425
|
-
function challengeResponse(auth, request, status, oauthError, errorDescription) {
|
|
425
|
+
function challengeResponse(auth, request, options, status, oauthError, errorDescription) {
|
|
426
426
|
const headers = new Headers(JSON_HEADERS);
|
|
427
|
-
headers.set(
|
|
427
|
+
headers.set(
|
|
428
|
+
"WWW-Authenticate",
|
|
429
|
+
wwwAuthenticateHeader(auth, request, options, { error: oauthError, errorDescription })
|
|
430
|
+
);
|
|
428
431
|
headers.set("Cache-Control", "no-store");
|
|
429
432
|
return new Response(JSON.stringify({ error: status === 403 ? "forbidden" : "unauthorized" }), {
|
|
430
433
|
status,
|
|
@@ -452,10 +455,17 @@ function parseBearerToken(request) {
|
|
|
452
455
|
function getOAuthRuntime(mcp, options = {}) {
|
|
453
456
|
if (options.resourcePath !== void 0)
|
|
454
457
|
assertResourcePathShape(options.resourcePath);
|
|
455
|
-
|
|
458
|
+
if (options.metadataPath !== void 0)
|
|
459
|
+
assertResourcePathShape(options.metadataPath, "metadataPath");
|
|
460
|
+
const stableOptions = { resourcePath: options.resourcePath, metadataPath: options.metadataPath };
|
|
456
461
|
const auth = mcp.auth?.type === "oauth" ? mcp.auth : void 0;
|
|
457
462
|
if (!auth)
|
|
458
463
|
return { kind: "unconfigured", options: stableOptions };
|
|
464
|
+
if (options.metadataPath !== void 0 && auth.protectedResourceMetadataUrl !== void 0) {
|
|
465
|
+
throw new Error(
|
|
466
|
+
`@lovable.dev/mcp-js: the Vite plugin generates protected-resource metadata (metadataPath set), so auth.protectedResourceMetadataUrl must not also be set. Drop protectedResourceMetadataUrl, or set protectedResourceMetadataRoute: false in mcpPlugin(...) to host the document yourself.`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
459
469
|
const discovery = createOAuthDiscoveryResolver(auth);
|
|
460
470
|
return {
|
|
461
471
|
kind: "configured",
|
|
@@ -494,7 +504,7 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
494
504
|
const token = parseBearerToken(request);
|
|
495
505
|
if (!token) {
|
|
496
506
|
log.info("auth.no_bearer_token", { outcome: "401" });
|
|
497
|
-
return { ok: false, response: challengeResponse(runtime.auth, request, 401) };
|
|
507
|
+
return { ok: false, response: challengeResponse(runtime.auth, request, runtime.options, 401) };
|
|
498
508
|
}
|
|
499
509
|
try {
|
|
500
510
|
const auth = await runtime.verify(token, request, runtime.options);
|
|
@@ -509,13 +519,27 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
509
519
|
log.info("auth.token_rejected", { status: err.status, oauthError: err.oauthError });
|
|
510
520
|
return {
|
|
511
521
|
ok: false,
|
|
512
|
-
response: challengeResponse(
|
|
522
|
+
response: challengeResponse(
|
|
523
|
+
runtime.auth,
|
|
524
|
+
request,
|
|
525
|
+
runtime.options,
|
|
526
|
+
err.status,
|
|
527
|
+
err.oauthError,
|
|
528
|
+
err.message
|
|
529
|
+
)
|
|
513
530
|
};
|
|
514
531
|
}
|
|
515
532
|
log.error("auth.unexpected_error", { ...describeError(err), outcome: "401" });
|
|
516
533
|
return {
|
|
517
534
|
ok: false,
|
|
518
|
-
response: challengeResponse(
|
|
535
|
+
response: challengeResponse(
|
|
536
|
+
runtime.auth,
|
|
537
|
+
request,
|
|
538
|
+
runtime.options,
|
|
539
|
+
401,
|
|
540
|
+
"invalid_token",
|
|
541
|
+
"Invalid access token"
|
|
542
|
+
)
|
|
519
543
|
};
|
|
520
544
|
}
|
|
521
545
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as McpRuntimeOptions } from '../../authorize-
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
1
|
+
import { M as McpRuntimeOptions } from '../../authorize-HTd0GKmB.js';
|
|
2
|
+
import { d as McpDefinition } from '../../types-C5SSORxe.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
interface TanStackRouteCtx {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as McpRuntimeOptions } from '../../authorize-
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
1
|
+
import { M as McpRuntimeOptions } from '../../authorize-HTd0GKmB.js';
|
|
2
|
+
import { d as McpDefinition } from '../../types-C5SSORxe.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
interface TanStackRouteCtx {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMcpProtocolHandler
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-CJMHLE7F.js";
|
|
4
4
|
import {
|
|
5
5
|
createOAuthProtectedResourceMetadataHandler
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-3GQXQBYQ.js";
|
|
7
7
|
import {
|
|
8
8
|
createInvokeToolHandler
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-SBKIKJNB.js";
|
|
10
10
|
import "../../chunk-MA5H6PSF.js";
|
|
11
11
|
import {
|
|
12
12
|
createListToolsHandler
|
|
13
|
-
} from "../../chunk-
|
|
14
|
-
import "../../chunk-
|
|
13
|
+
} from "../../chunk-BG3HOZXZ.js";
|
|
14
|
+
import "../../chunk-FQLM4JDU.js";
|
|
15
15
|
import "../../chunk-QC3DXQTH.js";
|
|
16
16
|
import "../../chunk-6DXGZZA4.js";
|
|
17
17
|
|
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/stacks/tanstack/vite.ts
|
|
21
21
|
var vite_exports = {};
|
|
22
22
|
__export(vite_exports, {
|
|
23
|
+
assertMetadataPathShape: () => assertMetadataPathShape,
|
|
23
24
|
assertUrlPathShape: () => assertUrlPathShape,
|
|
24
25
|
default: () => vite_default,
|
|
25
26
|
deriveRouteFileName: () => deriveRouteFileName,
|
|
@@ -55,15 +56,17 @@ function wellKnownRouteFile(routesDir, urlPath) {
|
|
|
55
56
|
});
|
|
56
57
|
return (0, import_node_path.resolve)(routesDir, ...fileSegments);
|
|
57
58
|
}
|
|
58
|
-
function resolveAllRoutes(projectRoot, routesDirOption, routeFileName) {
|
|
59
|
+
function resolveAllRoutes(projectRoot, routesDirOption, routeFileName, metadataUrlPath) {
|
|
59
60
|
const routesDir = (0, import_node_path.resolve)(projectRoot, routesDirOption);
|
|
60
61
|
assertContains(projectRoot, routesDir, `routesDir "${routesDirOption}"`);
|
|
61
62
|
const mcpRouteFile = (0, import_node_path.resolve)(routesDir, routeFileName);
|
|
62
63
|
assertContains(routesDir, mcpRouteFile, `routeFileName "${routeFileName}"`);
|
|
64
|
+
const metadataRouteFile = wellKnownRouteFile(routesDir, metadataUrlPath);
|
|
65
|
+
assertContains(routesDir, metadataRouteFile, `metadataPath "${metadataUrlPath}"`);
|
|
63
66
|
return {
|
|
64
67
|
routesDir,
|
|
65
68
|
mcpRouteFile,
|
|
66
|
-
metadataRouteFile
|
|
69
|
+
metadataRouteFile,
|
|
67
70
|
listToolsRouteFile: (0, import_node_path.resolve)(routesDir, "[.mcp]", "list-tools.ts"),
|
|
68
71
|
invokeToolRouteFile: (0, import_node_path.resolve)(routesDir, "[.mcp]", "invoke-tool", "$tool.ts")
|
|
69
72
|
};
|
|
@@ -87,6 +90,24 @@ function assertUrlPathShape(urlPath) {
|
|
|
87
90
|
function deriveRouteFileName(urlPath) {
|
|
88
91
|
return `${assertUrlPathShape(urlPath)}.ts`;
|
|
89
92
|
}
|
|
93
|
+
function assertMetadataPathShape(metadataPath) {
|
|
94
|
+
if (!metadataPath.startsWith("/")) {
|
|
95
|
+
throw new Error(`@lovable.dev/mcp-js: metadataPath must start with "/", got "${metadataPath}"`);
|
|
96
|
+
}
|
|
97
|
+
if (/\/{2,}/.test(metadataPath)) {
|
|
98
|
+
throw new Error(`@lovable.dev/mcp-js: metadataPath "${metadataPath}" contains empty segments ("//")`);
|
|
99
|
+
}
|
|
100
|
+
if (/(^|\/)\.\.(\/|$)/.test(metadataPath)) {
|
|
101
|
+
throw new Error(`@lovable.dev/mcp-js: metadataPath "${metadataPath}" must not contain ".." segments`);
|
|
102
|
+
}
|
|
103
|
+
const trimmed = metadataPath.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
104
|
+
if (!trimmed) {
|
|
105
|
+
throw new Error(`@lovable.dev/mcp-js: metadataPath "${metadataPath}" has no route segment`);
|
|
106
|
+
}
|
|
107
|
+
if (!/^[a-zA-Z0-9_\-./]+$/.test(trimmed)) {
|
|
108
|
+
throw new Error(`@lovable.dev/mcp-js: metadataPath "${metadataPath}" contains unsupported characters`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
90
111
|
function canonicalUrlPath(urlPath) {
|
|
91
112
|
return `/${urlPath.replace(/^\/+/, "").replace(/\/+$/, "")}`;
|
|
92
113
|
}
|
|
@@ -95,10 +116,11 @@ function relativeImportSpecifier(routeFile, target) {
|
|
|
95
116
|
const withoutExt = rel.replace(/\.ts$/, "");
|
|
96
117
|
return withoutExt.startsWith(".") ? withoutExt : `./${withoutExt}`;
|
|
97
118
|
}
|
|
98
|
-
function buildRouteSource(route, resourcePath, mcpEntry, projectRoot) {
|
|
119
|
+
function buildRouteSource(route, resourcePath, metadataPath, mcpEntry, projectRoot) {
|
|
99
120
|
const mcpImport = relativeImportSpecifier(route.file, mcpEntry);
|
|
100
121
|
const routeRel = normalizePath((0, import_node_path.relative)(projectRoot, route.file));
|
|
101
122
|
const note = route.spaFallbackNote ? " // ANY: TanStack returns SPA HTML for methods not in `handlers`; the SDK 405s instead.\n" : "";
|
|
123
|
+
const runtimeOptions = metadataPath === void 0 ? `{ resourcePath: "${resourcePath}" }` : `{ resourcePath: "${resourcePath}", metadataPath: "${metadataPath}" }`;
|
|
102
124
|
return `${GENERATED_BANNER}
|
|
103
125
|
// route: ${route.routeLiteral}
|
|
104
126
|
// emitted to: ${routeRel}
|
|
@@ -112,7 +134,7 @@ import mcp from "${mcpImport}";
|
|
|
112
134
|
export const Route = createFileRoute("${route.routeLiteral}")({
|
|
113
135
|
server: {
|
|
114
136
|
handlers: {
|
|
115
|
-
${note} ANY: ${route.handlerFactory}(mcp,
|
|
137
|
+
${note} ANY: ${route.handlerFactory}(mcp, ${runtimeOptions}),
|
|
116
138
|
},
|
|
117
139
|
},
|
|
118
140
|
});
|
|
@@ -191,12 +213,23 @@ function mcpPlugin(options = {}) {
|
|
|
191
213
|
const urlPath = canonicalUrlPath(rawUrlPath);
|
|
192
214
|
const emitRestRoutes = options.restRoutes !== false;
|
|
193
215
|
const emitProtectedResourceMetadataRoute = options.protectedResourceMetadataRoute !== false;
|
|
216
|
+
if (options.metadataPath !== void 0) {
|
|
217
|
+
if (!emitProtectedResourceMetadataRoute) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`@lovable.dev/mcp-js: metadataPath sets where the protected-resource metadata route is served, so it can't be combined with protectedResourceMetadataRoute: false.`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
assertMetadataPathShape(options.metadataPath);
|
|
223
|
+
}
|
|
224
|
+
const metadataUrlPath = canonicalUrlPath(options.metadataPath ?? OAUTH_PROTECTED_RESOURCE_METADATA_PATH);
|
|
225
|
+
const injectedMetadataPath = emitProtectedResourceMetadataRoute ? metadataUrlPath : void 0;
|
|
194
226
|
let projectRoot = process.cwd();
|
|
195
227
|
let mcpEntry = (0, import_node_path.resolve)(projectRoot, mcpEntryOption);
|
|
196
228
|
let { routesDir, mcpRouteFile, metadataRouteFile, listToolsRouteFile, invokeToolRouteFile } = resolveAllRoutes(
|
|
197
229
|
projectRoot,
|
|
198
230
|
routesDirOption,
|
|
199
|
-
routeFileName
|
|
231
|
+
routeFileName,
|
|
232
|
+
metadataUrlPath
|
|
200
233
|
);
|
|
201
234
|
const regenerate = () => {
|
|
202
235
|
let mcpEntryExists = true;
|
|
@@ -215,7 +248,7 @@ function mcpPlugin(options = {}) {
|
|
|
215
248
|
if (emitProtectedResourceMetadataRoute) {
|
|
216
249
|
routes.push({
|
|
217
250
|
file: metadataRouteFile,
|
|
218
|
-
routeLiteral:
|
|
251
|
+
routeLiteral: metadataUrlPath,
|
|
219
252
|
handlerFactory: "createTanStackOAuthProtectedResourceMetadataHandler"
|
|
220
253
|
});
|
|
221
254
|
}
|
|
@@ -237,7 +270,7 @@ function mcpPlugin(options = {}) {
|
|
|
237
270
|
}
|
|
238
271
|
for (const route of routes) {
|
|
239
272
|
expected.add(route.file);
|
|
240
|
-
writeIfChanged(route.file, buildRouteSource(route, urlPath, mcpEntry, projectRoot));
|
|
273
|
+
writeIfChanged(route.file, buildRouteSource(route, urlPath, injectedMetadataPath, mcpEntry, projectRoot));
|
|
241
274
|
}
|
|
242
275
|
}
|
|
243
276
|
for (const file of findAdoptedFiles(routesDir)) {
|
|
@@ -271,7 +304,8 @@ function mcpPlugin(options = {}) {
|
|
|
271
304
|
({ routesDir, mcpRouteFile, metadataRouteFile, listToolsRouteFile, invokeToolRouteFile } = resolveAllRoutes(
|
|
272
305
|
projectRoot,
|
|
273
306
|
routesDirOption,
|
|
274
|
-
routeFileName
|
|
307
|
+
routeFileName,
|
|
308
|
+
metadataUrlPath
|
|
275
309
|
));
|
|
276
310
|
regenerate();
|
|
277
311
|
},
|
|
@@ -298,6 +332,7 @@ function mcpPlugin(options = {}) {
|
|
|
298
332
|
var vite_default = mcpPlugin;
|
|
299
333
|
// Annotate the CommonJS export names for ESM import in node:
|
|
300
334
|
0 && (module.exports = {
|
|
335
|
+
assertMetadataPathShape,
|
|
301
336
|
assertUrlPathShape,
|
|
302
337
|
deriveRouteFileName,
|
|
303
338
|
mcpPlugin
|
|
@@ -48,6 +48,21 @@ interface McpPluginOptions {
|
|
|
48
48
|
* @default true
|
|
49
49
|
*/
|
|
50
50
|
protectedResourceMetadataRoute?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Public URL path for the OAuth protected-resource metadata route (RFC 9728).
|
|
53
|
+
* Both the emitted route file and the runtime `WWW-Authenticate` challenge use
|
|
54
|
+
* this path, so they can't drift. Use it when a host gate exposes only a
|
|
55
|
+
* sub-prefix (e.g. workspace-private projects where only `/api/public/*` is
|
|
56
|
+
* anonymously reachable) and the default well-known path is blocked.
|
|
57
|
+
*
|
|
58
|
+
* Leading-dot segments are bracket-escaped for TanStack file routing
|
|
59
|
+
* (`.well-known` → `[.well-known]`); the final segment becomes the route file.
|
|
60
|
+
* Mutually exclusive with `auth.protectedResourceMetadataUrl` (that option
|
|
61
|
+
* advertises an externally hosted document instead of serving one), and
|
|
62
|
+
* cannot be combined with `protectedResourceMetadataRoute: false`.
|
|
63
|
+
* @default "/.well-known/oauth-protected-resource"
|
|
64
|
+
*/
|
|
65
|
+
metadataPath?: string;
|
|
51
66
|
}
|
|
52
67
|
interface McpPluginApi {
|
|
53
68
|
readonly mcpEntry: string;
|
|
@@ -55,6 +70,7 @@ interface McpPluginApi {
|
|
|
55
70
|
}
|
|
56
71
|
declare function assertUrlPathShape(urlPath: string): string;
|
|
57
72
|
declare function deriveRouteFileName(urlPath: string): string;
|
|
73
|
+
declare function assertMetadataPathShape(metadataPath: string): void;
|
|
58
74
|
/**
|
|
59
75
|
* Generate the TanStack route(s) that mount the MCP server.
|
|
60
76
|
*
|
|
@@ -69,4 +85,4 @@ declare function deriveRouteFileName(urlPath: string): string;
|
|
|
69
85
|
*/
|
|
70
86
|
declare function mcpPlugin(options?: McpPluginOptions): Plugin;
|
|
71
87
|
|
|
72
|
-
export { type McpPluginApi, type McpPluginOptions, assertUrlPathShape, mcpPlugin as default, deriveRouteFileName, mcpPlugin };
|
|
88
|
+
export { type McpPluginApi, type McpPluginOptions, assertMetadataPathShape, assertUrlPathShape, mcpPlugin as default, deriveRouteFileName, mcpPlugin };
|
|
@@ -48,6 +48,21 @@ interface McpPluginOptions {
|
|
|
48
48
|
* @default true
|
|
49
49
|
*/
|
|
50
50
|
protectedResourceMetadataRoute?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Public URL path for the OAuth protected-resource metadata route (RFC 9728).
|
|
53
|
+
* Both the emitted route file and the runtime `WWW-Authenticate` challenge use
|
|
54
|
+
* this path, so they can't drift. Use it when a host gate exposes only a
|
|
55
|
+
* sub-prefix (e.g. workspace-private projects where only `/api/public/*` is
|
|
56
|
+
* anonymously reachable) and the default well-known path is blocked.
|
|
57
|
+
*
|
|
58
|
+
* Leading-dot segments are bracket-escaped for TanStack file routing
|
|
59
|
+
* (`.well-known` → `[.well-known]`); the final segment becomes the route file.
|
|
60
|
+
* Mutually exclusive with `auth.protectedResourceMetadataUrl` (that option
|
|
61
|
+
* advertises an externally hosted document instead of serving one), and
|
|
62
|
+
* cannot be combined with `protectedResourceMetadataRoute: false`.
|
|
63
|
+
* @default "/.well-known/oauth-protected-resource"
|
|
64
|
+
*/
|
|
65
|
+
metadataPath?: string;
|
|
51
66
|
}
|
|
52
67
|
interface McpPluginApi {
|
|
53
68
|
readonly mcpEntry: string;
|
|
@@ -55,6 +70,7 @@ interface McpPluginApi {
|
|
|
55
70
|
}
|
|
56
71
|
declare function assertUrlPathShape(urlPath: string): string;
|
|
57
72
|
declare function deriveRouteFileName(urlPath: string): string;
|
|
73
|
+
declare function assertMetadataPathShape(metadataPath: string): void;
|
|
58
74
|
/**
|
|
59
75
|
* Generate the TanStack route(s) that mount the MCP server.
|
|
60
76
|
*
|
|
@@ -69,4 +85,4 @@ declare function deriveRouteFileName(urlPath: string): string;
|
|
|
69
85
|
*/
|
|
70
86
|
declare function mcpPlugin(options?: McpPluginOptions): Plugin;
|
|
71
87
|
|
|
72
|
-
export { type McpPluginApi, type McpPluginOptions, assertUrlPathShape, mcpPlugin as default, deriveRouteFileName, mcpPlugin };
|
|
88
|
+
export { type McpPluginApi, type McpPluginOptions, assertMetadataPathShape, assertUrlPathShape, mcpPlugin as default, deriveRouteFileName, mcpPlugin };
|
|
@@ -25,15 +25,17 @@ function wellKnownRouteFile(routesDir, urlPath) {
|
|
|
25
25
|
});
|
|
26
26
|
return resolve(routesDir, ...fileSegments);
|
|
27
27
|
}
|
|
28
|
-
function resolveAllRoutes(projectRoot, routesDirOption, routeFileName) {
|
|
28
|
+
function resolveAllRoutes(projectRoot, routesDirOption, routeFileName, metadataUrlPath) {
|
|
29
29
|
const routesDir = resolve(projectRoot, routesDirOption);
|
|
30
30
|
assertContains(projectRoot, routesDir, `routesDir "${routesDirOption}"`);
|
|
31
31
|
const mcpRouteFile = resolve(routesDir, routeFileName);
|
|
32
32
|
assertContains(routesDir, mcpRouteFile, `routeFileName "${routeFileName}"`);
|
|
33
|
+
const metadataRouteFile = wellKnownRouteFile(routesDir, metadataUrlPath);
|
|
34
|
+
assertContains(routesDir, metadataRouteFile, `metadataPath "${metadataUrlPath}"`);
|
|
33
35
|
return {
|
|
34
36
|
routesDir,
|
|
35
37
|
mcpRouteFile,
|
|
36
|
-
metadataRouteFile
|
|
38
|
+
metadataRouteFile,
|
|
37
39
|
listToolsRouteFile: resolve(routesDir, "[.mcp]", "list-tools.ts"),
|
|
38
40
|
invokeToolRouteFile: resolve(routesDir, "[.mcp]", "invoke-tool", "$tool.ts")
|
|
39
41
|
};
|
|
@@ -57,6 +59,24 @@ function assertUrlPathShape(urlPath) {
|
|
|
57
59
|
function deriveRouteFileName(urlPath) {
|
|
58
60
|
return `${assertUrlPathShape(urlPath)}.ts`;
|
|
59
61
|
}
|
|
62
|
+
function assertMetadataPathShape(metadataPath) {
|
|
63
|
+
if (!metadataPath.startsWith("/")) {
|
|
64
|
+
throw new Error(`@lovable.dev/mcp-js: metadataPath must start with "/", got "${metadataPath}"`);
|
|
65
|
+
}
|
|
66
|
+
if (/\/{2,}/.test(metadataPath)) {
|
|
67
|
+
throw new Error(`@lovable.dev/mcp-js: metadataPath "${metadataPath}" contains empty segments ("//")`);
|
|
68
|
+
}
|
|
69
|
+
if (/(^|\/)\.\.(\/|$)/.test(metadataPath)) {
|
|
70
|
+
throw new Error(`@lovable.dev/mcp-js: metadataPath "${metadataPath}" must not contain ".." segments`);
|
|
71
|
+
}
|
|
72
|
+
const trimmed = metadataPath.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
73
|
+
if (!trimmed) {
|
|
74
|
+
throw new Error(`@lovable.dev/mcp-js: metadataPath "${metadataPath}" has no route segment`);
|
|
75
|
+
}
|
|
76
|
+
if (!/^[a-zA-Z0-9_\-./]+$/.test(trimmed)) {
|
|
77
|
+
throw new Error(`@lovable.dev/mcp-js: metadataPath "${metadataPath}" contains unsupported characters`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
60
80
|
function canonicalUrlPath(urlPath) {
|
|
61
81
|
return `/${urlPath.replace(/^\/+/, "").replace(/\/+$/, "")}`;
|
|
62
82
|
}
|
|
@@ -65,10 +85,11 @@ function relativeImportSpecifier(routeFile, target) {
|
|
|
65
85
|
const withoutExt = rel.replace(/\.ts$/, "");
|
|
66
86
|
return withoutExt.startsWith(".") ? withoutExt : `./${withoutExt}`;
|
|
67
87
|
}
|
|
68
|
-
function buildRouteSource(route, resourcePath, mcpEntry, projectRoot) {
|
|
88
|
+
function buildRouteSource(route, resourcePath, metadataPath, mcpEntry, projectRoot) {
|
|
69
89
|
const mcpImport = relativeImportSpecifier(route.file, mcpEntry);
|
|
70
90
|
const routeRel = normalizePath(relative(projectRoot, route.file));
|
|
71
91
|
const note = route.spaFallbackNote ? " // ANY: TanStack returns SPA HTML for methods not in `handlers`; the SDK 405s instead.\n" : "";
|
|
92
|
+
const runtimeOptions = metadataPath === void 0 ? `{ resourcePath: "${resourcePath}" }` : `{ resourcePath: "${resourcePath}", metadataPath: "${metadataPath}" }`;
|
|
72
93
|
return `${GENERATED_BANNER}
|
|
73
94
|
// route: ${route.routeLiteral}
|
|
74
95
|
// emitted to: ${routeRel}
|
|
@@ -82,7 +103,7 @@ import mcp from "${mcpImport}";
|
|
|
82
103
|
export const Route = createFileRoute("${route.routeLiteral}")({
|
|
83
104
|
server: {
|
|
84
105
|
handlers: {
|
|
85
|
-
${note} ANY: ${route.handlerFactory}(mcp,
|
|
106
|
+
${note} ANY: ${route.handlerFactory}(mcp, ${runtimeOptions}),
|
|
86
107
|
},
|
|
87
108
|
},
|
|
88
109
|
});
|
|
@@ -161,12 +182,23 @@ function mcpPlugin(options = {}) {
|
|
|
161
182
|
const urlPath = canonicalUrlPath(rawUrlPath);
|
|
162
183
|
const emitRestRoutes = options.restRoutes !== false;
|
|
163
184
|
const emitProtectedResourceMetadataRoute = options.protectedResourceMetadataRoute !== false;
|
|
185
|
+
if (options.metadataPath !== void 0) {
|
|
186
|
+
if (!emitProtectedResourceMetadataRoute) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`@lovable.dev/mcp-js: metadataPath sets where the protected-resource metadata route is served, so it can't be combined with protectedResourceMetadataRoute: false.`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
assertMetadataPathShape(options.metadataPath);
|
|
192
|
+
}
|
|
193
|
+
const metadataUrlPath = canonicalUrlPath(options.metadataPath ?? OAUTH_PROTECTED_RESOURCE_METADATA_PATH);
|
|
194
|
+
const injectedMetadataPath = emitProtectedResourceMetadataRoute ? metadataUrlPath : void 0;
|
|
164
195
|
let projectRoot = process.cwd();
|
|
165
196
|
let mcpEntry = resolve(projectRoot, mcpEntryOption);
|
|
166
197
|
let { routesDir, mcpRouteFile, metadataRouteFile, listToolsRouteFile, invokeToolRouteFile } = resolveAllRoutes(
|
|
167
198
|
projectRoot,
|
|
168
199
|
routesDirOption,
|
|
169
|
-
routeFileName
|
|
200
|
+
routeFileName,
|
|
201
|
+
metadataUrlPath
|
|
170
202
|
);
|
|
171
203
|
const regenerate = () => {
|
|
172
204
|
let mcpEntryExists = true;
|
|
@@ -185,7 +217,7 @@ function mcpPlugin(options = {}) {
|
|
|
185
217
|
if (emitProtectedResourceMetadataRoute) {
|
|
186
218
|
routes.push({
|
|
187
219
|
file: metadataRouteFile,
|
|
188
|
-
routeLiteral:
|
|
220
|
+
routeLiteral: metadataUrlPath,
|
|
189
221
|
handlerFactory: "createTanStackOAuthProtectedResourceMetadataHandler"
|
|
190
222
|
});
|
|
191
223
|
}
|
|
@@ -207,7 +239,7 @@ function mcpPlugin(options = {}) {
|
|
|
207
239
|
}
|
|
208
240
|
for (const route of routes) {
|
|
209
241
|
expected.add(route.file);
|
|
210
|
-
writeIfChanged(route.file, buildRouteSource(route, urlPath, mcpEntry, projectRoot));
|
|
242
|
+
writeIfChanged(route.file, buildRouteSource(route, urlPath, injectedMetadataPath, mcpEntry, projectRoot));
|
|
211
243
|
}
|
|
212
244
|
}
|
|
213
245
|
for (const file of findAdoptedFiles(routesDir)) {
|
|
@@ -241,7 +273,8 @@ function mcpPlugin(options = {}) {
|
|
|
241
273
|
({ routesDir, mcpRouteFile, metadataRouteFile, listToolsRouteFile, invokeToolRouteFile } = resolveAllRoutes(
|
|
242
274
|
projectRoot,
|
|
243
275
|
routesDirOption,
|
|
244
|
-
routeFileName
|
|
276
|
+
routeFileName,
|
|
277
|
+
metadataUrlPath
|
|
245
278
|
));
|
|
246
279
|
regenerate();
|
|
247
280
|
},
|
|
@@ -267,6 +300,7 @@ function mcpPlugin(options = {}) {
|
|
|
267
300
|
}
|
|
268
301
|
var vite_default = mcpPlugin;
|
|
269
302
|
export {
|
|
303
|
+
assertMetadataPathShape,
|
|
270
304
|
assertUrlPathShape,
|
|
271
305
|
vite_default as default,
|
|
272
306
|
deriveRouteFileName,
|
|
@@ -52,6 +52,10 @@ interface McpAuthConfig {
|
|
|
52
52
|
* `WWW-Authenticate: resource_metadata` challenge and does not generate or
|
|
53
53
|
* serve its own metadata (the `/.well-known/oauth-protected-resource` handler
|
|
54
54
|
* 404s). Use it when the resource server already publishes its own PRM.
|
|
55
|
+
*
|
|
56
|
+
* Mutually exclusive with the Vite plugin's `metadataPath`: that option has
|
|
57
|
+
* the SDK serve the document itself at a custom path, so configuring both is
|
|
58
|
+
* rejected at handler construction.
|
|
55
59
|
*/
|
|
56
60
|
readonly protectedResourceMetadataUrl?: string;
|
|
57
61
|
readonly requireOAuthClientClaim?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovable.dev/mcp-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Author MCP servers for Lovable apps. Declare tools with defineTool, register them in defineMcp, and the framework adapter (TanStack today, Supabase Edge Functions next) emits the route(s) at build time.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
interface McpRuntimeOptions {
|
|
2
|
-
/**
|
|
3
|
-
* Public MCP protocol path. REST companion handlers pass this so OAuth
|
|
4
|
-
* resource/audience checks bind to `/mcp`, not internal `/.mcp/*` RPC URLs.
|
|
5
|
-
*/
|
|
6
|
-
resourcePath?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type { McpRuntimeOptions as M };
|