@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.
- package/README.internal.md +6 -2
- package/README.md +13 -1
- package/dist/authorize-HTd0GKmB.d.ts +18 -0
- package/dist/{chunk-W7F6JRDB.js → chunk-3GQXQBYQ.js} +1 -1
- package/dist/chunk-BG3HOZXZ.js +57 -0
- package/dist/{chunk-3XPKR4G6.js → chunk-CJMHLE7F.js} +1 -1
- package/dist/{chunk-HRMLGCXV.js → chunk-FQLM4JDU.js} +36 -12
- package/dist/chunk-QJ7XEKT3.js +8 -0
- package/dist/{chunk-LMT6RXUF.js → chunk-SBKIKJNB.js} +3 -47
- 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 +50 -24
- package/dist/protocols/rest/index.d.cts +2 -2
- package/dist/protocols/rest/index.d.ts +2 -2
- package/dist/protocols/rest/index.js +6 -4
- package/dist/stacks/tanstack/cli/extract-manifest.cjs +214 -0
- package/dist/stacks/tanstack/cli/extract-manifest.d.cts +1 -0
- package/dist/stacks/tanstack/cli/extract-manifest.d.ts +1 -0
- package/dist/stacks/tanstack/cli/extract-manifest.js +170 -0
- package/dist/stacks/tanstack/index.cjs +50 -24
- package/dist/stacks/tanstack/index.d.cts +2 -2
- package/dist/stacks/tanstack/index.d.ts +2 -2
- package/dist/stacks/tanstack/index.js +8 -6
- package/dist/stacks/tanstack/vite.cjs +67 -19
- package/dist/stacks/tanstack/vite.d.cts +26 -2
- package/dist/stacks/tanstack/vite.d.ts +26 -2
- package/dist/stacks/tanstack/vite.js +65 -20
- package/dist/{types-zMlr1mOK.d.ts → types-C5SSORxe.d.ts} +4 -0
- package/package.json +6 -3
- package/dist/authorize-DpTEIFvs.d.ts +0 -9
package/README.internal.md
CHANGED
|
@@ -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 };
|
|
@@ -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
|
+
};
|
|
@@ -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
|
}
|
|
@@ -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-
|
|
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
|
|
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 =
|
|
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-
|
|
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>;
|