@lovable.dev/mcp-js 0.7.1 → 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.
Files changed (38) hide show
  1. package/README.internal.md +6 -2
  2. package/README.md +13 -1
  3. package/dist/authorize-HTd0GKmB.d.ts +18 -0
  4. package/dist/{chunk-W7F6JRDB.js → chunk-3GQXQBYQ.js} +1 -1
  5. package/dist/chunk-BG3HOZXZ.js +57 -0
  6. package/dist/{chunk-3XPKR4G6.js → chunk-CJMHLE7F.js} +1 -1
  7. package/dist/{chunk-HRMLGCXV.js → chunk-FQLM4JDU.js} +36 -12
  8. package/dist/chunk-QJ7XEKT3.js +8 -0
  9. package/dist/{chunk-LMT6RXUF.js → chunk-SBKIKJNB.js} +3 -47
  10. package/dist/index.d.cts +2 -2
  11. package/dist/index.d.ts +2 -2
  12. package/dist/protocols/mcp/index.cjs +36 -12
  13. package/dist/protocols/mcp/index.d.cts +2 -2
  14. package/dist/protocols/mcp/index.d.ts +2 -2
  15. package/dist/protocols/mcp/index.js +2 -2
  16. package/dist/protocols/oauth-metadata.cjs +10 -3
  17. package/dist/protocols/oauth-metadata.d.cts +2 -2
  18. package/dist/protocols/oauth-metadata.d.ts +2 -2
  19. package/dist/protocols/oauth-metadata.js +2 -2
  20. package/dist/protocols/rest/index.cjs +50 -24
  21. package/dist/protocols/rest/index.d.cts +2 -2
  22. package/dist/protocols/rest/index.d.ts +2 -2
  23. package/dist/protocols/rest/index.js +6 -4
  24. package/dist/stacks/tanstack/cli/extract-manifest.cjs +214 -0
  25. package/dist/stacks/tanstack/cli/extract-manifest.d.cts +1 -0
  26. package/dist/stacks/tanstack/cli/extract-manifest.d.ts +1 -0
  27. package/dist/stacks/tanstack/cli/extract-manifest.js +170 -0
  28. package/dist/stacks/tanstack/index.cjs +50 -24
  29. package/dist/stacks/tanstack/index.d.cts +2 -2
  30. package/dist/stacks/tanstack/index.d.ts +2 -2
  31. package/dist/stacks/tanstack/index.js +8 -6
  32. package/dist/stacks/tanstack/vite.cjs +67 -19
  33. package/dist/stacks/tanstack/vite.d.cts +26 -2
  34. package/dist/stacks/tanstack/vite.d.ts +26 -2
  35. package/dist/stacks/tanstack/vite.js +65 -20
  36. package/dist/{types-zMlr1mOK.d.ts → types-C5SSORxe.d.ts} +4 -0
  37. package/package.json +6 -3
  38. package/dist/authorize-DpTEIFvs.d.ts +0 -9
@@ -133,7 +133,11 @@ src/
133
133
  stacks/
134
134
  tanstack/
135
135
  handlers.ts # TanStack route-ctx adapters
136
- vite.ts # mcpPlugin (route emission + stale-file cleanup)
136
+ vite.ts # mcpPlugin (route emission + stale-file cleanup; no manifest)
137
+ ssr-loader.ts # produceViaSsr: load the entry via a throwaway Vite SSR server
138
+ extract-manifest.ts # project a defineMcp result to the manifest (types + manifestFromDefinition)
139
+ manifest-io.ts # runExtract/syncManifest: write/remove .lovable/mcp/manifest.json
140
+ cli/extract-manifest.ts # lovable-mcp-extract-manifest bin (runs runExtract on cwd)
137
141
  index.ts # barrel
138
142
  tests/
139
143
  core/{define,promise,url}.test.ts
@@ -143,7 +147,7 @@ tests/
143
147
  rest/{list-tools,invoke-tool}.test.ts
144
148
  parity.test.ts # REST↔MCP equivalence contract
145
149
  stacks/
146
- tanstack/{handlers,vite}.test.ts
150
+ tanstack/{handlers,vite,extract-manifest,manifest-io,ssr-loader}.test.ts
147
151
  integration/ # boots an example app, hits live HTTP
148
152
  global-setup.ts # spawns example/tanstack on :8080
149
153
  tools.test.ts # non-OAuth example
package/README.md CHANGED
@@ -53,6 +53,18 @@ 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
+
58
+ ### `.lovable/mcp/manifest.json`
59
+
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.
61
+
62
+ Three caveats:
63
+
64
+ - **The entry must import cleanly.** Because extraction imports the entry, it must not throw at module load — read env vars and do I/O *inside* tool handlers, not at module top level. A top-level throw also breaks Worker cold-start, so this is correct authoring regardless. An entry that can't be imported (or doesn't `export default defineMcp(...)`) **makes the extract CLI exit non-zero with a clear error** rather than emitting a partial manifest.
65
+ - **Tool `title`/`description` and the `auth` config are serialized verbatim.** They land in a committed file. The `auth` config is whatever the entry resolves at build time — an issuer read from env resolves to its build-time value (e.g. a fallback like `https://supabase.invalid/auth/v1` when the env var is unset), so don't put secrets in it.
66
+ - **The `version` field is a wire-format version, not the package version.** The platform reader must accept a manifest `version` before this package starts emitting it, so bumping it is a coordinated deploy with the Lovable backend — not something an app author changes.
67
+
56
68
  ## OAuth resource-server auth
57
69
 
58
70
  `@lovable.dev/mcp-js` can protect an app-hosted MCP server as an OAuth 2.1 resource server. The package does not implement `/authorize`, `/token`, client registration, refresh tokens, or consent UI; those stay with the authorization server (for today's Supabase-backed Lovable Cloud apps, Supabase Auth). The MCP runtime validates bearer JWTs, publishes RFC 9728 protected-resource metadata, returns `WWW-Authenticate: Bearer ... resource_metadata="..."` challenges, and passes verified claims to tools.
@@ -172,7 +184,7 @@ Use an OAuth access token from the configured authorization server for MCP calls
172
184
  | `algorithms` | Accepted JWT signing algorithms. Defaults to asymmetric algorithms (`RS*`, `ES*`, `EdDSA`); set explicitly only for a narrower policy. |
173
185
  | `clockToleranceSeconds` | Allowed JWT clock skew for `exp`/`nbf` checks, in seconds. Defaults to `30`; max `300`. |
174
186
  | `resourceName`, `resourceDocumentation` | Optional metadata fields for MCP clients and humans. `resourceName` defaults to the MCP server `title`. |
175
- | `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. |
176
188
 
177
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"`.
178
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 };
@@ -7,7 +7,7 @@ import {
7
7
  oauthConfigurationErrorResponse,
8
8
  resolveProtectedResource,
9
9
  withCors
10
- } from "./chunk-HRMLGCXV.js";
10
+ } from "./chunk-FQLM4JDU.js";
11
11
  import {
12
12
  describeError,
13
13
  log
@@ -0,0 +1,57 @@
1
+ import {
2
+ assertRestResourceBinding,
3
+ corsPreflightResponse,
4
+ createRequestAuthorizer,
5
+ headResponse,
6
+ methodNotAllowed,
7
+ withCors
8
+ } from "./chunk-FQLM4JDU.js";
9
+
10
+ // src/protocols/rest/list-tools.ts
11
+ import { objectFromShape } from "@modelcontextprotocol/sdk/server/zod-compat.js";
12
+ import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-schema-compat.js";
13
+ function shapeToJsonSchema(shape) {
14
+ if (!shape)
15
+ return null;
16
+ try {
17
+ return toJsonSchemaCompat(objectFromShape(shape));
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function buildMcpListing(mcp) {
23
+ return {
24
+ server: { name: mcp.name, version: mcp.version, title: mcp.title },
25
+ tools: mcp.tools.map((tool) => ({
26
+ name: tool.name,
27
+ title: tool.title,
28
+ description: tool.description,
29
+ annotations: tool.annotations,
30
+ inputSchema: shapeToJsonSchema(tool.inputSchema),
31
+ outputSchema: shapeToJsonSchema(tool.outputSchema)
32
+ }))
33
+ };
34
+ }
35
+ function createListToolsHandler(mcp, options = {}) {
36
+ assertRestResourceBinding(mcp, options);
37
+ const authorizer = createRequestAuthorizer(mcp, options);
38
+ const handle = async (request) => {
39
+ const authResult = await authorizer.authorize(request);
40
+ if (!authResult.ok)
41
+ return authResult.response;
42
+ if (request.method !== "GET" && request.method !== "HEAD")
43
+ return methodNotAllowed("GET, HEAD, OPTIONS");
44
+ const response = Response.json(buildMcpListing(mcp));
45
+ return request.method === "HEAD" ? headResponse(response) : response;
46
+ };
47
+ return async (request) => {
48
+ if (request.method === "OPTIONS")
49
+ return corsPreflightResponse("GET, HEAD, OPTIONS");
50
+ return withCors(await handle(request));
51
+ };
52
+ }
53
+
54
+ export {
55
+ buildMcpListing,
56
+ createListToolsHandler
57
+ };
@@ -5,7 +5,7 @@ import {
5
5
  corsPreflightResponse,
6
6
  createRequestAuthorizer,
7
7
  withCors
8
- } from "./chunk-HRMLGCXV.js";
8
+ } from "./chunk-FQLM4JDU.js";
9
9
  import {
10
10
  describeError,
11
11
  log
@@ -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: resourcePath must be an absolute path beginning with "/" without ".." segments or backslashes (got ${JSON.stringify(resourcePath)})`
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("WWW-Authenticate", wwwAuthenticateHeader(auth, request, { error: oauthError, errorDescription }));
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
- const stableOptions = { resourcePath: options.resourcePath };
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(runtime.auth, request, err.status, err.oauthError, err.message)
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(runtime.auth, request, 401, "invalid_token", "Invalid access token")
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
  }
@@ -0,0 +1,8 @@
1
+ // src/stacks/tanstack/fs-errors.ts
2
+ function isFileMissing(err) {
3
+ return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
4
+ }
5
+
6
+ export {
7
+ isFileMissing
8
+ };
@@ -6,55 +6,12 @@ import {
6
6
  assertRestResourceBinding,
7
7
  corsPreflightResponse,
8
8
  createRequestAuthorizer,
9
- headResponse,
10
9
  methodNotAllowed,
11
10
  withCors
12
- } from "./chunk-HRMLGCXV.js";
13
-
14
- // src/protocols/rest/list-tools.ts
15
- import { objectFromShape } from "@modelcontextprotocol/sdk/server/zod-compat.js";
16
- import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-schema-compat.js";
17
- function shapeToJsonSchema(shape) {
18
- if (!shape)
19
- return null;
20
- try {
21
- return toJsonSchemaCompat(objectFromShape(shape));
22
- } catch {
23
- return null;
24
- }
25
- }
26
- function createListToolsHandler(mcp, options = {}) {
27
- assertRestResourceBinding(mcp, options);
28
- const authorizer = createRequestAuthorizer(mcp, options);
29
- const handle = async (request) => {
30
- const authResult = await authorizer.authorize(request);
31
- if (!authResult.ok)
32
- return authResult.response;
33
- if (request.method !== "GET" && request.method !== "HEAD")
34
- return methodNotAllowed("GET, HEAD, OPTIONS");
35
- const body = {
36
- server: { name: mcp.name, version: mcp.version, title: mcp.title },
37
- tools: mcp.tools.map((tool) => ({
38
- name: tool.name,
39
- title: tool.title,
40
- description: tool.description,
41
- annotations: tool.annotations,
42
- inputSchema: shapeToJsonSchema(tool.inputSchema),
43
- outputSchema: shapeToJsonSchema(tool.outputSchema)
44
- }))
45
- };
46
- const response = Response.json(body);
47
- return request.method === "HEAD" ? headResponse(response) : response;
48
- };
49
- return async (request) => {
50
- if (request.method === "OPTIONS")
51
- return corsPreflightResponse("GET, HEAD, OPTIONS");
52
- return withCors(await handle(request));
53
- };
54
- }
11
+ } from "./chunk-FQLM4JDU.js";
55
12
 
56
13
  // src/protocols/rest/invoke-tool.ts
57
- import { getParseErrorMessage, objectFromShape as objectFromShape2, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
14
+ import { getParseErrorMessage, objectFromShape, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
58
15
  var MAX_REFLECTED_TOOL_NAME = 256;
59
16
  function safeReflectName(name) {
60
17
  const text = String(name);
@@ -98,7 +55,7 @@ function createInvokeToolHandler(mcp, options = {}) {
98
55
  let args = rawArgs;
99
56
  if (tool.inputSchema) {
100
57
  try {
101
- const schema = objectFromShape2(tool.inputSchema);
58
+ const schema = objectFromShape(tool.inputSchema);
102
59
  const parsed = await safeParseAsync(schema, rawArgs);
103
60
  if (!parsed.success) {
104
61
  return new Response(
@@ -151,6 +108,5 @@ function createInvokeToolHandler(mcp, options = {}) {
151
108
  }
152
109
 
153
110
  export {
154
- createListToolsHandler,
155
111
  createInvokeToolHandler
156
112
  };
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-zMlr1mOK.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-zMlr1mOK.js';
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-zMlr1mOK.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-zMlr1mOK.js';
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: resourcePath must be an absolute path beginning with "/" without ".." segments or backslashes (got ${JSON.stringify(resourcePath)})`
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("WWW-Authenticate", wwwAuthenticateHeader(auth, request, { error: oauthError, errorDescription }));
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
- const stableOptions = { resourcePath: options.resourcePath };
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(runtime.auth, request, err.status, err.oauthError, err.message)
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(runtime.auth, request, 401, "invalid_token", "Invalid access token")
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-DpTEIFvs.js';
2
- import { d as McpDefinition } from '../../types-zMlr1mOK.js';
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-DpTEIFvs.js';
2
- import { d as McpDefinition } from '../../types-zMlr1mOK.js';
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-3XPKR4G6.js";
3
+ } from "../../chunk-CJMHLE7F.js";
4
4
  import "../../chunk-MA5H6PSF.js";
5
- import "../../chunk-HRMLGCXV.js";
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: resourcePath must be an absolute path beginning with "/" without ".." segments or backslashes (got ${JSON.stringify(resourcePath)})`
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
- const stableOptions = { resourcePath: options.resourcePath };
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-DpTEIFvs.js';
2
- import { d as McpDefinition } from '../types-zMlr1mOK.js';
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-DpTEIFvs.js';
2
- import { d as McpDefinition } from '../types-zMlr1mOK.js';
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,7 +1,7 @@
1
1
  import {
2
2
  createOAuthProtectedResourceMetadataHandler
3
- } from "../chunk-W7F6JRDB.js";
4
- import "../chunk-HRMLGCXV.js";
3
+ } from "../chunk-3GQXQBYQ.js";
4
+ import "../chunk-FQLM4JDU.js";
5
5
  import "../chunk-QC3DXQTH.js";
6
6
  import "../chunk-6DXGZZA4.js";
7
7
  export {