@lovable.dev/mcp-js 2.0.0 → 2.0.2-rc.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 CHANGED
@@ -228,9 +228,19 @@ The request-derived `resource` default trusts the incoming `Host` to name this s
228
228
 
229
229
  Use `auth.oauth.issuer(...)`, set `resource` or `acceptedAudiences` to anchor the accepted audience, and optionally set `jwksUri` and `requiredScopes`. For Supabase project auth, set `acceptedAudiences: "authenticated"`, keep app/business checks in app code, and forward `ctx.getToken()` to Supabase for RLS-backed data access.
230
230
 
231
+ ## Security posture
232
+
233
+ For anyone evaluating an app that exposes an MCP server with this package:
234
+
235
+ - **Verification runs in the app runtime.** When `defineMcp({ auth })` configures OAuth, the emitted TanStack route or Supabase Edge Function verifies the bearer before dispatching a tool. Clients call the app's published endpoint; the SDK does not relay MCP calls through a shared Lovable MCP protocol gateway, although a Lovable-hosted TanStack app still receives ordinary web traffic through Lovable's hosting proxy. The SDK may fetch issuer metadata and JWKS. When default usage telemetry is emitted, it sends the operational fields described below, including the verified token `sub`; it never sends the bearer, full claims, tool arguments, or tool results.
236
+ - **No protocol sessions.** Every HTTP request gets a fresh MCP server, auth context, and tool callback closure. The endpoint never mints a session ID or retains MCP protocol or auth session state across requests. Durable application state belongs in the app's own storage.
237
+ - **Audience validation is explicit.** A populated token `aud` must exactly match `resource` or `acceptedAudiences`. For issuer compatibility, the default also accepts a matching `resource` claim when `aud` is absent or an empty array; set `acceptResourceClaim: false` to require a matching `aud` (strict RFC 9068).
238
+ - **The deployment controls the SDK version.** TanStack resolves the app's declared dependency through its package manager and lockfile. The generated Supabase function exact-pins the SDK release that emitted it. Updating or regenerating the dependency and redeploying is how an app adopts a newer SDK.
239
+ - **Controls are divided between layers.** When OAuth is configured, the SDK owns bearer verification; it also owns origin policy (`allowedOrigins`) and redacts unexpected errors. `ToolError` and returned `isError` results are intentionally caller-visible. Per-tool authorization, data access (including RLS), rate limiting, and WAF belong to the app and its host.
240
+
231
241
  ## Debug logging
232
242
 
233
- The runtime logs operational events (tool invocations, auth outcomes, warnings, errors) at `info` by default. Raise the level to `debug` to trace OAuth discovery, JWKS resolution, and token verification when a deployed MCP server returns `401`/`500` and you need to see why. Logs go to `console` (so they land in your platform's function logs) and **never include the bearer token, full claims, or PII** only non-secret fields such as the JWT header `alg`/`kid`, `issuer`, `jwks_uri`, the token `scopes`, and `jose` error codes. End-user identifiers (the token `sub`/`client_id`) appear only at `debug`, as the `oauth.verify.principal` event — the default `info` level never collects them.
243
+ The runtime logs operational events (tool invocations, auth outcomes, warnings, errors) at `info` by default. Raise the level to `debug` to trace OAuth discovery, JWKS resolution, and token verification when a deployed MCP server returns `401`/`500` and you need to see why. Logs go to `console` (so they land in your platform's function logs) and **never include the bearer token or full claims**. Depending on the log level, operational fields include the JWT header `alg`/`kid`, `issuer`, `jwks_uri`, token scopes, and `jose` error codes. The default `info` level excludes end-user identifiers; the token `sub`/`client_id` appears only at `debug`, as the `oauth.verify.principal` event.
234
244
 
235
245
  Enable it either way:
236
246
 
@@ -251,7 +261,7 @@ A `500` on an OAuth-protected route is always one of two causes, logged at `erro
251
261
 
252
262
  ## Usage metrics
253
263
 
254
- Each MCP server records per-invocation telemetry — tool name, JSON-RPC method, outcome, and latency — as **OTLP/HTTP JSON logs**, one awaited POST per record, bounded by a 3-second timeout; a failed or slow POST is logged (`metrics.failed`) and swallowed, never failing the request. Outcomes cover tool results (`ok`, `tool_error`, `handler_error`, `transport_error`), the authorization gate — `auth_challenge` for an unauthenticated request answered with the `401` challenge (so probe traffic produces records), and the `auth_*` discovery/JWKS failure outcomes — and `origin_rejected` for a request refused `403` by the origin policy. Tool-call records also carry the protocol era (`mcp.protocol`: `2026-07-28` or `legacy`); records emitted before protocol classification (auth challenges, transport errors, origin rejections) have no protocol attribute. No tool arguments or response payloads are ever captured. Emission is **on by default** and self-disables at runtime when `LOVABLE_API_KEY` is absent: records are dropped and one `metrics.disabled_no_api_key` warning is logged per process, so a local `vite dev` stays quiet. With `metrics: false` the recorder never runs — on Deno and Cloudflare Workers that also means `LOVABLE_MCP_LOG_LEVEL` is never applied (use `setLogLevel()` instead).
264
+ Each MCP server records per-invocation telemetry — tool name, JSON-RPC method, outcome, latency, request and response sizes when available, and the verified token `sub` for authenticated calls — as **OTLP/HTTP JSON logs**, one awaited POST per record, bounded by a 3-second timeout; a failed or slow POST is logged (`metrics.failed`) and swallowed, never failing the request. Outcomes cover tool results (`ok`, `tool_error`, `handler_error`, `transport_error`), the authorization gate — `auth_challenge` for an unauthenticated request answered with the `401` challenge (so probe traffic produces records), and the `auth_*` discovery/JWKS failure outcomes — and `origin_rejected` for a request refused `403` by the origin policy. Tool-call records also carry the protocol era (`mcp.protocol`: `2026-07-28` or `legacy`); records emitted before protocol classification (auth challenges, transport errors, origin rejections) have no protocol attribute. The bearer token, full claims, tool arguments, and response payloads are never captured. Emission is **on by default** and self-disables at runtime when `LOVABLE_API_KEY` is absent: records are dropped and one `metrics.disabled_no_api_key` warning is logged per process, so a local `vite dev` stays quiet. With `metrics: false` the recorder never runs — on Deno and Cloudflare Workers that also means `LOVABLE_MCP_LOG_LEVEL` is never applied (use `setLogLevel()` instead).
255
265
 
256
266
  Configure it on the MCP definition:
257
267
 
@@ -1,155 +1,10 @@
1
1
  #!/usr/bin/env node
2
- const require_schema = require("../schema-D3vClxb7.cjs");
3
- const require_package = require("../package-RqZMyBY1.cjs");
4
- const require_fs_errors = require("../fs-errors-CWNzOV75.cjs");
5
- let node_fs = require("node:fs");
6
- let node_path = require("node:path");
7
- let vite = require("vite");
8
- let node_crypto = require("node:crypto");
9
- //#region src/manifest/listing.ts
10
- function schemaToJsonSchema(definition, pipeStrategy) {
11
- if (!definition) return null;
12
- return require_schema.jsonSchemaFromDefinition(definition, pipeStrategy);
13
- }
14
- function buildMcpListing(mcp) {
15
- return {
16
- server: {
17
- name: mcp.name,
18
- version: mcp.version,
19
- title: mcp.title
20
- },
21
- tools: mcp.tools.map((tool) => ({
22
- name: tool.name,
23
- title: tool.title,
24
- description: tool.description,
25
- annotations: tool.annotations,
26
- inputSchema: schemaToJsonSchema(tool.inputSchema, "input"),
27
- outputSchema: schemaToJsonSchema(tool.outputSchema, "output")
28
- }))
29
- };
30
- }
31
- const MANIFEST_RELATIVE_PATH = ".lovable/mcp/manifest.json";
32
- function manifestAuth(auth) {
33
- if (!auth) return { type: "none" };
34
- const oauth = {
35
- type: "oauth",
36
- issuer: auth.issuer
37
- };
38
- if (auth.resource !== void 0) oauth.resource = auth.resource;
39
- if (auth.resourceName !== void 0) oauth.resource_name = auth.resourceName;
40
- if (auth.resourceDocumentation !== void 0) oauth.resource_documentation = auth.resourceDocumentation;
41
- if (auth.protectedResourceMetadataUrl !== void 0) oauth.protected_resource_metadata_url = auth.protectedResourceMetadataUrl;
42
- if (auth.requireOAuthClientClaim !== void 0) oauth.require_oauth_client_claim = auth.requireOAuthClientClaim;
43
- if (auth.acceptResourceClaim !== void 0) oauth.accept_resource_claim = auth.acceptResourceClaim;
44
- if (auth.acceptedAudiences !== void 0) oauth.accepted_audiences = auth.acceptedAudiences;
45
- if (auth.requiredScopes !== void 0) oauth.required_scopes = auth.requiredScopes;
46
- if (auth.jwksUri !== void 0) oauth.jwks_uri = auth.jwksUri;
47
- if (auth.algorithms !== void 0) oauth.algorithms = auth.algorithms;
48
- if (auth.clockToleranceSeconds !== void 0) oauth.clock_tolerance_seconds = auth.clockToleranceSeconds;
49
- return oauth;
50
- }
51
- function isToolShape(tool) {
52
- if (typeof tool !== "object" || tool === null) return false;
53
- const t = tool;
54
- return typeof t.name === "string" && typeof t.title === "string" && typeof t.description === "string";
55
- }
56
- function isAuthShape(auth) {
57
- if (auth === void 0) return true;
58
- if (typeof auth !== "object" || auth === null) return false;
59
- const a = auth;
60
- return a.type === "oauth" && typeof a.issuer === "string" && a.issuer.length > 0;
61
- }
62
- function manifestFromDefinition(definition, urlPath, source = "the MCP entry") {
63
- const def = definition;
64
- if (!(!!def && typeof def === "object" && typeof def.name === "string" && typeof def.title === "string" && typeof def.version === "string" && Array.isArray(def.tools) && def.tools.every(isToolShape) && isAuthShape(def.auth))) throw new Error(`@lovable.dev/mcp-js: ${source} must \`export default defineMcp(...)\` (got ${definition == null ? "no default export" : typeof definition}).`);
65
- return {
66
- version: 1,
67
- sdk_version: require_package.version,
68
- path: urlPath,
69
- auth: manifestAuth(def.auth),
70
- mcp: buildMcpListing(def)
71
- };
72
- }
73
- //#endregion
74
- //#region src/manifest/ssr-loader.ts
75
- async function produceViaSsr(projectRoot, alias, entryAbs, urlPath) {
76
- const server = await (0, vite.createServer)({
77
- configFile: false,
78
- root: projectRoot,
79
- logLevel: "silent",
80
- server: {
81
- middlewareMode: true,
82
- hmr: false,
83
- watch: null
84
- },
85
- optimizeDeps: { noDiscovery: true },
86
- resolve: { alias }
87
- });
88
- try {
89
- return manifestFromDefinition((await server.ssrLoadModule(entryAbs)).default, urlPath, entryAbs);
90
- } finally {
91
- await server.close();
92
- }
93
- }
94
- //#endregion
95
- //#region src/manifest/io.ts
96
- const MCP_PLUGIN_NAME_PREFIX = "@lovable.dev/mcp-js";
97
- function writeManifestIfChanged(file, manifest) {
98
- const content = JSON.stringify(manifest, null, 2) + "\n";
99
- let existing;
100
- try {
101
- existing = (0, node_fs.readFileSync)(file, "utf8");
102
- } catch (err) {
103
- if (!require_fs_errors.isFileMissing(err)) throw err;
104
- }
105
- if (existing === content) return;
106
- (0, node_fs.mkdirSync)((0, node_path.dirname)(file), { recursive: true });
107
- const tmp = `${file}.${(0, node_crypto.randomUUID)()}.tmp`;
108
- try {
109
- (0, node_fs.writeFileSync)(tmp, content, "utf8");
110
- (0, node_fs.renameSync)(tmp, file);
111
- } catch (err) {
112
- try {
113
- (0, node_fs.unlinkSync)(tmp);
114
- } catch {}
115
- throw err;
116
- }
117
- }
118
- function removeManifest(file) {
119
- try {
120
- (0, node_fs.unlinkSync)(file);
121
- } catch (err) {
122
- if (!require_fs_errors.isFileMissing(err)) throw err;
123
- }
124
- }
125
- async function syncManifest(manifestFile, entryAbs, urlPath, produce) {
126
- let entryExists = true;
127
- try {
128
- (0, node_fs.lstatSync)(entryAbs);
129
- } catch (err) {
130
- if (!require_fs_errors.isFileMissing(err)) throw err;
131
- entryExists = false;
132
- }
133
- if (!entryExists) {
134
- removeManifest(manifestFile);
135
- return;
136
- }
137
- writeManifestIfChanged(manifestFile, await produce(entryAbs, urlPath));
138
- }
139
- async function runExtract(projectRoot) {
140
- const resolved = await (0, vite.resolveConfig)({
141
- root: projectRoot,
142
- logLevel: "silent"
143
- }, "serve");
144
- const alias = resolved.resolve?.alias ?? [];
145
- const pluginApi = resolved.plugins.find((p) => typeof p.name === "string" && p.name.startsWith(MCP_PLUGIN_NAME_PREFIX))?.api;
146
- const entryAbs = pluginApi?.mcpEntry ?? (0, node_path.resolve)(projectRoot, "src/lib/mcp/index.ts");
147
- const urlPath = pluginApi?.urlPath ?? "/mcp";
148
- await syncManifest((0, node_path.resolve)(projectRoot, MANIFEST_RELATIVE_PATH), entryAbs, urlPath, (entry, path) => produceViaSsr(projectRoot, alias, entry, path));
149
- }
150
- //#endregion
151
2
  //#region src/cli/extract-manifest.ts
152
- runExtract(process.argv[2] ?? process.cwd()).catch((err) => {
3
+ const projectRoot = process.argv[2] ?? process.cwd();
4
+ import("zod/v3").catch((err) => {
5
+ const message = err instanceof Error ? err.message : String(err);
6
+ throw new Error(`@lovable.dev/mcp-js requires zod ^3.25 or ^4.0 to extract MCP schemas. Verify the project's zod dependency and package resolution, then try again. Underlying error: ${message}`);
7
+ }).then(() => Promise.resolve().then(() => require("../io-CKGFa7_M.cjs"))).then(({ runExtract }) => runExtract(projectRoot)).catch((err) => {
153
8
  console.error(err instanceof Error ? err.message : String(err));
154
9
  process.exit(1);
155
10
  });
@@ -1,155 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { t as jsonSchemaFromDefinition } from "../schema-CW1jKvio.js";
3
- import { t as version } from "../package-CZERyrAn.js";
4
- import { t as isFileMissing } from "../fs-errors-PA2t1TIp.js";
5
- import { lstatSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
6
- import { dirname, resolve } from "node:path";
7
- import { createServer, resolveConfig } from "vite";
8
- import { randomUUID } from "node:crypto";
9
- //#region src/manifest/listing.ts
10
- function schemaToJsonSchema(definition, pipeStrategy) {
11
- if (!definition) return null;
12
- return jsonSchemaFromDefinition(definition, pipeStrategy);
13
- }
14
- function buildMcpListing(mcp) {
15
- return {
16
- server: {
17
- name: mcp.name,
18
- version: mcp.version,
19
- title: mcp.title
20
- },
21
- tools: mcp.tools.map((tool) => ({
22
- name: tool.name,
23
- title: tool.title,
24
- description: tool.description,
25
- annotations: tool.annotations,
26
- inputSchema: schemaToJsonSchema(tool.inputSchema, "input"),
27
- outputSchema: schemaToJsonSchema(tool.outputSchema, "output")
28
- }))
29
- };
30
- }
31
- const MANIFEST_RELATIVE_PATH = ".lovable/mcp/manifest.json";
32
- function manifestAuth(auth) {
33
- if (!auth) return { type: "none" };
34
- const oauth = {
35
- type: "oauth",
36
- issuer: auth.issuer
37
- };
38
- if (auth.resource !== void 0) oauth.resource = auth.resource;
39
- if (auth.resourceName !== void 0) oauth.resource_name = auth.resourceName;
40
- if (auth.resourceDocumentation !== void 0) oauth.resource_documentation = auth.resourceDocumentation;
41
- if (auth.protectedResourceMetadataUrl !== void 0) oauth.protected_resource_metadata_url = auth.protectedResourceMetadataUrl;
42
- if (auth.requireOAuthClientClaim !== void 0) oauth.require_oauth_client_claim = auth.requireOAuthClientClaim;
43
- if (auth.acceptResourceClaim !== void 0) oauth.accept_resource_claim = auth.acceptResourceClaim;
44
- if (auth.acceptedAudiences !== void 0) oauth.accepted_audiences = auth.acceptedAudiences;
45
- if (auth.requiredScopes !== void 0) oauth.required_scopes = auth.requiredScopes;
46
- if (auth.jwksUri !== void 0) oauth.jwks_uri = auth.jwksUri;
47
- if (auth.algorithms !== void 0) oauth.algorithms = auth.algorithms;
48
- if (auth.clockToleranceSeconds !== void 0) oauth.clock_tolerance_seconds = auth.clockToleranceSeconds;
49
- return oauth;
50
- }
51
- function isToolShape(tool) {
52
- if (typeof tool !== "object" || tool === null) return false;
53
- const t = tool;
54
- return typeof t.name === "string" && typeof t.title === "string" && typeof t.description === "string";
55
- }
56
- function isAuthShape(auth) {
57
- if (auth === void 0) return true;
58
- if (typeof auth !== "object" || auth === null) return false;
59
- const a = auth;
60
- return a.type === "oauth" && typeof a.issuer === "string" && a.issuer.length > 0;
61
- }
62
- function manifestFromDefinition(definition, urlPath, source = "the MCP entry") {
63
- const def = definition;
64
- if (!(!!def && typeof def === "object" && typeof def.name === "string" && typeof def.title === "string" && typeof def.version === "string" && Array.isArray(def.tools) && def.tools.every(isToolShape) && isAuthShape(def.auth))) throw new Error(`@lovable.dev/mcp-js: ${source} must \`export default defineMcp(...)\` (got ${definition == null ? "no default export" : typeof definition}).`);
65
- return {
66
- version: 1,
67
- sdk_version: version,
68
- path: urlPath,
69
- auth: manifestAuth(def.auth),
70
- mcp: buildMcpListing(def)
71
- };
72
- }
73
- //#endregion
74
- //#region src/manifest/ssr-loader.ts
75
- async function produceViaSsr(projectRoot, alias, entryAbs, urlPath) {
76
- const server = await createServer({
77
- configFile: false,
78
- root: projectRoot,
79
- logLevel: "silent",
80
- server: {
81
- middlewareMode: true,
82
- hmr: false,
83
- watch: null
84
- },
85
- optimizeDeps: { noDiscovery: true },
86
- resolve: { alias }
87
- });
88
- try {
89
- return manifestFromDefinition((await server.ssrLoadModule(entryAbs)).default, urlPath, entryAbs);
90
- } finally {
91
- await server.close();
92
- }
93
- }
94
- //#endregion
95
- //#region src/manifest/io.ts
96
- const MCP_PLUGIN_NAME_PREFIX = "@lovable.dev/mcp-js";
97
- function writeManifestIfChanged(file, manifest) {
98
- const content = JSON.stringify(manifest, null, 2) + "\n";
99
- let existing;
100
- try {
101
- existing = readFileSync(file, "utf8");
102
- } catch (err) {
103
- if (!isFileMissing(err)) throw err;
104
- }
105
- if (existing === content) return;
106
- mkdirSync(dirname(file), { recursive: true });
107
- const tmp = `${file}.${randomUUID()}.tmp`;
108
- try {
109
- writeFileSync(tmp, content, "utf8");
110
- renameSync(tmp, file);
111
- } catch (err) {
112
- try {
113
- unlinkSync(tmp);
114
- } catch {}
115
- throw err;
116
- }
117
- }
118
- function removeManifest(file) {
119
- try {
120
- unlinkSync(file);
121
- } catch (err) {
122
- if (!isFileMissing(err)) throw err;
123
- }
124
- }
125
- async function syncManifest(manifestFile, entryAbs, urlPath, produce) {
126
- let entryExists = true;
127
- try {
128
- lstatSync(entryAbs);
129
- } catch (err) {
130
- if (!isFileMissing(err)) throw err;
131
- entryExists = false;
132
- }
133
- if (!entryExists) {
134
- removeManifest(manifestFile);
135
- return;
136
- }
137
- writeManifestIfChanged(manifestFile, await produce(entryAbs, urlPath));
138
- }
139
- async function runExtract(projectRoot) {
140
- const resolved = await resolveConfig({
141
- root: projectRoot,
142
- logLevel: "silent"
143
- }, "serve");
144
- const alias = resolved.resolve?.alias ?? [];
145
- const pluginApi = resolved.plugins.find((p) => typeof p.name === "string" && p.name.startsWith(MCP_PLUGIN_NAME_PREFIX))?.api;
146
- const entryAbs = pluginApi?.mcpEntry ?? resolve(projectRoot, "src/lib/mcp/index.ts");
147
- const urlPath = pluginApi?.urlPath ?? "/mcp";
148
- await syncManifest(resolve(projectRoot, MANIFEST_RELATIVE_PATH), entryAbs, urlPath, (entry, path) => produceViaSsr(projectRoot, alias, entry, path));
149
- }
150
- //#endregion
151
2
  //#region src/cli/extract-manifest.ts
152
- runExtract(process.argv[2] ?? process.cwd()).catch((err) => {
3
+ const projectRoot = process.argv[2] ?? process.cwd();
4
+ import("zod/v3").catch((err) => {
5
+ const message = err instanceof Error ? err.message : String(err);
6
+ throw new Error(`@lovable.dev/mcp-js requires zod ^3.25 or ^4.0 to extract MCP schemas. Verify the project's zod dependency and package resolution, then try again. Underlying error: ${message}`);
7
+ }).then(() => import("../io-ufHBQyKg.js")).then(({ runExtract }) => runExtract(projectRoot)).catch((err) => {
153
8
  console.error(err instanceof Error ? err.message : String(err));
154
9
  process.exit(1);
155
10
  });
@@ -1,5 +1,5 @@
1
1
  import { i as log, n as applyLogLevelFromEnv, o as parseSafeUrl, r as describeError, s as trimTrailingSlash, t as LOG_LEVEL_ENV_VAR, u as resolveMetricsConfig } from "./logger-Ctv5xUSD.js";
2
- import { t as version } from "./package-CZERyrAn.js";
2
+ import { t as version } from "./package-Bu8wLlvG.js";
3
3
  import "./metadata-path-CAkNcfyY.js";
4
4
  import { createLocalJWKSet, decodeProtectedHeader, errors, jwtVerify } from "jose";
5
5
  //#region src/core/http.ts
@@ -1,5 +1,5 @@
1
1
  const require_logger = require("./logger-CN1q-KNn.cjs");
2
- const require_package = require("./package-RqZMyBY1.cjs");
2
+ const require_package = require("./package-BGn-LvT3.cjs");
3
3
  require("./metadata-path-zd7NSNoa.cjs");
4
4
  let jose = require("jose");
5
5
  //#region src/core/http.ts
@@ -0,0 +1,155 @@
1
+ const require_schema = require("./schema-D3vClxb7.cjs");
2
+ const require_package = require("./package-BGn-LvT3.cjs");
3
+ const require_fs_errors = require("./fs-errors-CWNzOV75.cjs");
4
+ let node_fs = require("node:fs");
5
+ let node_path = require("node:path");
6
+ let vite = require("vite");
7
+ let node_crypto = require("node:crypto");
8
+ //#region src/manifest/listing.ts
9
+ function schemaToJsonSchema(definition, pipeStrategy) {
10
+ if (!definition) return null;
11
+ return require_schema.jsonSchemaFromDefinition(definition, pipeStrategy);
12
+ }
13
+ function buildMcpListing(mcp) {
14
+ return {
15
+ server: {
16
+ name: mcp.name,
17
+ version: mcp.version,
18
+ title: mcp.title
19
+ },
20
+ tools: mcp.tools.map((tool) => ({
21
+ name: tool.name,
22
+ title: tool.title,
23
+ description: tool.description,
24
+ annotations: tool.annotations,
25
+ inputSchema: schemaToJsonSchema(tool.inputSchema, "input"),
26
+ outputSchema: schemaToJsonSchema(tool.outputSchema, "output")
27
+ }))
28
+ };
29
+ }
30
+ const MANIFEST_RELATIVE_PATH = ".lovable/mcp/manifest.json";
31
+ function manifestAuth(auth) {
32
+ if (!auth) return { type: "none" };
33
+ const oauth = {
34
+ type: "oauth",
35
+ issuer: auth.issuer
36
+ };
37
+ if (auth.resource !== void 0) oauth.resource = auth.resource;
38
+ if (auth.resourceName !== void 0) oauth.resource_name = auth.resourceName;
39
+ if (auth.resourceDocumentation !== void 0) oauth.resource_documentation = auth.resourceDocumentation;
40
+ if (auth.protectedResourceMetadataUrl !== void 0) oauth.protected_resource_metadata_url = auth.protectedResourceMetadataUrl;
41
+ if (auth.requireOAuthClientClaim !== void 0) oauth.require_oauth_client_claim = auth.requireOAuthClientClaim;
42
+ if (auth.acceptResourceClaim !== void 0) oauth.accept_resource_claim = auth.acceptResourceClaim;
43
+ if (auth.acceptedAudiences !== void 0) oauth.accepted_audiences = auth.acceptedAudiences;
44
+ if (auth.requiredScopes !== void 0) oauth.required_scopes = auth.requiredScopes;
45
+ if (auth.jwksUri !== void 0) oauth.jwks_uri = auth.jwksUri;
46
+ if (auth.algorithms !== void 0) oauth.algorithms = auth.algorithms;
47
+ if (auth.clockToleranceSeconds !== void 0) oauth.clock_tolerance_seconds = auth.clockToleranceSeconds;
48
+ return oauth;
49
+ }
50
+ function isToolShape(tool) {
51
+ if (typeof tool !== "object" || tool === null) return false;
52
+ const t = tool;
53
+ return typeof t.name === "string" && typeof t.title === "string" && typeof t.description === "string";
54
+ }
55
+ function isAuthShape(auth) {
56
+ if (auth === void 0) return true;
57
+ if (typeof auth !== "object" || auth === null) return false;
58
+ const a = auth;
59
+ return a.type === "oauth" && typeof a.issuer === "string" && a.issuer.length > 0;
60
+ }
61
+ function manifestFromDefinition(definition, urlPath, source = "the MCP entry") {
62
+ const def = definition;
63
+ if (!(!!def && typeof def === "object" && typeof def.name === "string" && typeof def.title === "string" && typeof def.version === "string" && Array.isArray(def.tools) && def.tools.every(isToolShape) && isAuthShape(def.auth))) throw new Error(`@lovable.dev/mcp-js: ${source} must \`export default defineMcp(...)\` (got ${definition == null ? "no default export" : typeof definition}).`);
64
+ return {
65
+ version: 1,
66
+ sdk_version: require_package.version,
67
+ path: urlPath,
68
+ auth: manifestAuth(def.auth),
69
+ mcp: buildMcpListing(def)
70
+ };
71
+ }
72
+ //#endregion
73
+ //#region src/manifest/ssr-loader.ts
74
+ async function produceViaSsr(projectRoot, alias, entryAbs, urlPath) {
75
+ const server = await (0, vite.createServer)({
76
+ configFile: false,
77
+ root: projectRoot,
78
+ logLevel: "silent",
79
+ server: {
80
+ middlewareMode: true,
81
+ hmr: false,
82
+ watch: null
83
+ },
84
+ optimizeDeps: { noDiscovery: true },
85
+ resolve: { alias }
86
+ });
87
+ try {
88
+ return manifestFromDefinition((await server.ssrLoadModule(entryAbs)).default, urlPath, entryAbs);
89
+ } finally {
90
+ await server.close();
91
+ }
92
+ }
93
+ //#endregion
94
+ //#region src/manifest/io.ts
95
+ const MCP_PLUGIN_NAME_PREFIX = "@lovable.dev/mcp-js";
96
+ const DEFAULT_MCP_ENTRY = "src/lib/mcp/index.ts";
97
+ const DEFAULT_MCP_PATH = "/mcp";
98
+ function writeManifestIfChanged(file, manifest) {
99
+ const content = JSON.stringify(manifest, null, 2) + "\n";
100
+ let existing;
101
+ try {
102
+ existing = (0, node_fs.readFileSync)(file, "utf8");
103
+ } catch (err) {
104
+ if (!require_fs_errors.isFileMissing(err)) throw err;
105
+ }
106
+ if (existing === content) return;
107
+ (0, node_fs.mkdirSync)((0, node_path.dirname)(file), { recursive: true });
108
+ const tmp = `${file}.${(0, node_crypto.randomUUID)()}.tmp`;
109
+ try {
110
+ (0, node_fs.writeFileSync)(tmp, content, "utf8");
111
+ (0, node_fs.renameSync)(tmp, file);
112
+ } catch (err) {
113
+ try {
114
+ (0, node_fs.unlinkSync)(tmp);
115
+ } catch {}
116
+ throw err;
117
+ }
118
+ }
119
+ function removeManifest(file) {
120
+ try {
121
+ (0, node_fs.unlinkSync)(file);
122
+ } catch (err) {
123
+ if (!require_fs_errors.isFileMissing(err)) throw err;
124
+ }
125
+ }
126
+ async function syncManifest(manifestFile, entryAbs, urlPath, produce) {
127
+ let entryExists = true;
128
+ try {
129
+ (0, node_fs.lstatSync)(entryAbs);
130
+ } catch (err) {
131
+ if (!require_fs_errors.isFileMissing(err)) throw err;
132
+ entryExists = false;
133
+ }
134
+ if (!entryExists) {
135
+ removeManifest(manifestFile);
136
+ return;
137
+ }
138
+ writeManifestIfChanged(manifestFile, await produce(entryAbs, urlPath));
139
+ }
140
+ async function runExtract(projectRoot) {
141
+ const resolved = await (0, vite.resolveConfig)({
142
+ root: projectRoot,
143
+ logLevel: "silent"
144
+ }, "serve");
145
+ const alias = resolved.resolve?.alias ?? [];
146
+ const pluginApi = resolved.plugins.find((p) => typeof p.name === "string" && p.name.startsWith(MCP_PLUGIN_NAME_PREFIX))?.api;
147
+ const entryAbs = pluginApi?.mcpEntry ?? (0, node_path.resolve)(projectRoot, "src/lib/mcp/index.ts");
148
+ const urlPath = pluginApi?.urlPath ?? "/mcp";
149
+ await syncManifest((0, node_path.resolve)(projectRoot, MANIFEST_RELATIVE_PATH), entryAbs, urlPath, (entry, path) => produceViaSsr(projectRoot, alias, entry, path));
150
+ }
151
+ //#endregion
152
+ exports.DEFAULT_MCP_ENTRY = DEFAULT_MCP_ENTRY;
153
+ exports.DEFAULT_MCP_PATH = DEFAULT_MCP_PATH;
154
+ exports.runExtract = runExtract;
155
+ exports.syncManifest = syncManifest;
@@ -0,0 +1,152 @@
1
+ import { t as jsonSchemaFromDefinition } from "./schema-CW1jKvio.js";
2
+ import { t as version } from "./package-Bu8wLlvG.js";
3
+ import { t as isFileMissing } from "./fs-errors-PA2t1TIp.js";
4
+ import { lstatSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
5
+ import { dirname, resolve } from "node:path";
6
+ import { createServer, resolveConfig } from "vite";
7
+ import { randomUUID } from "node:crypto";
8
+ //#region src/manifest/listing.ts
9
+ function schemaToJsonSchema(definition, pipeStrategy) {
10
+ if (!definition) return null;
11
+ return jsonSchemaFromDefinition(definition, pipeStrategy);
12
+ }
13
+ function buildMcpListing(mcp) {
14
+ return {
15
+ server: {
16
+ name: mcp.name,
17
+ version: mcp.version,
18
+ title: mcp.title
19
+ },
20
+ tools: mcp.tools.map((tool) => ({
21
+ name: tool.name,
22
+ title: tool.title,
23
+ description: tool.description,
24
+ annotations: tool.annotations,
25
+ inputSchema: schemaToJsonSchema(tool.inputSchema, "input"),
26
+ outputSchema: schemaToJsonSchema(tool.outputSchema, "output")
27
+ }))
28
+ };
29
+ }
30
+ const MANIFEST_RELATIVE_PATH = ".lovable/mcp/manifest.json";
31
+ function manifestAuth(auth) {
32
+ if (!auth) return { type: "none" };
33
+ const oauth = {
34
+ type: "oauth",
35
+ issuer: auth.issuer
36
+ };
37
+ if (auth.resource !== void 0) oauth.resource = auth.resource;
38
+ if (auth.resourceName !== void 0) oauth.resource_name = auth.resourceName;
39
+ if (auth.resourceDocumentation !== void 0) oauth.resource_documentation = auth.resourceDocumentation;
40
+ if (auth.protectedResourceMetadataUrl !== void 0) oauth.protected_resource_metadata_url = auth.protectedResourceMetadataUrl;
41
+ if (auth.requireOAuthClientClaim !== void 0) oauth.require_oauth_client_claim = auth.requireOAuthClientClaim;
42
+ if (auth.acceptResourceClaim !== void 0) oauth.accept_resource_claim = auth.acceptResourceClaim;
43
+ if (auth.acceptedAudiences !== void 0) oauth.accepted_audiences = auth.acceptedAudiences;
44
+ if (auth.requiredScopes !== void 0) oauth.required_scopes = auth.requiredScopes;
45
+ if (auth.jwksUri !== void 0) oauth.jwks_uri = auth.jwksUri;
46
+ if (auth.algorithms !== void 0) oauth.algorithms = auth.algorithms;
47
+ if (auth.clockToleranceSeconds !== void 0) oauth.clock_tolerance_seconds = auth.clockToleranceSeconds;
48
+ return oauth;
49
+ }
50
+ function isToolShape(tool) {
51
+ if (typeof tool !== "object" || tool === null) return false;
52
+ const t = tool;
53
+ return typeof t.name === "string" && typeof t.title === "string" && typeof t.description === "string";
54
+ }
55
+ function isAuthShape(auth) {
56
+ if (auth === void 0) return true;
57
+ if (typeof auth !== "object" || auth === null) return false;
58
+ const a = auth;
59
+ return a.type === "oauth" && typeof a.issuer === "string" && a.issuer.length > 0;
60
+ }
61
+ function manifestFromDefinition(definition, urlPath, source = "the MCP entry") {
62
+ const def = definition;
63
+ if (!(!!def && typeof def === "object" && typeof def.name === "string" && typeof def.title === "string" && typeof def.version === "string" && Array.isArray(def.tools) && def.tools.every(isToolShape) && isAuthShape(def.auth))) throw new Error(`@lovable.dev/mcp-js: ${source} must \`export default defineMcp(...)\` (got ${definition == null ? "no default export" : typeof definition}).`);
64
+ return {
65
+ version: 1,
66
+ sdk_version: version,
67
+ path: urlPath,
68
+ auth: manifestAuth(def.auth),
69
+ mcp: buildMcpListing(def)
70
+ };
71
+ }
72
+ //#endregion
73
+ //#region src/manifest/ssr-loader.ts
74
+ async function produceViaSsr(projectRoot, alias, entryAbs, urlPath) {
75
+ const server = await createServer({
76
+ configFile: false,
77
+ root: projectRoot,
78
+ logLevel: "silent",
79
+ server: {
80
+ middlewareMode: true,
81
+ hmr: false,
82
+ watch: null
83
+ },
84
+ optimizeDeps: { noDiscovery: true },
85
+ resolve: { alias }
86
+ });
87
+ try {
88
+ return manifestFromDefinition((await server.ssrLoadModule(entryAbs)).default, urlPath, entryAbs);
89
+ } finally {
90
+ await server.close();
91
+ }
92
+ }
93
+ //#endregion
94
+ //#region src/manifest/io.ts
95
+ const MCP_PLUGIN_NAME_PREFIX = "@lovable.dev/mcp-js";
96
+ const DEFAULT_MCP_ENTRY = "src/lib/mcp/index.ts";
97
+ const DEFAULT_MCP_PATH = "/mcp";
98
+ function writeManifestIfChanged(file, manifest) {
99
+ const content = JSON.stringify(manifest, null, 2) + "\n";
100
+ let existing;
101
+ try {
102
+ existing = readFileSync(file, "utf8");
103
+ } catch (err) {
104
+ if (!isFileMissing(err)) throw err;
105
+ }
106
+ if (existing === content) return;
107
+ mkdirSync(dirname(file), { recursive: true });
108
+ const tmp = `${file}.${randomUUID()}.tmp`;
109
+ try {
110
+ writeFileSync(tmp, content, "utf8");
111
+ renameSync(tmp, file);
112
+ } catch (err) {
113
+ try {
114
+ unlinkSync(tmp);
115
+ } catch {}
116
+ throw err;
117
+ }
118
+ }
119
+ function removeManifest(file) {
120
+ try {
121
+ unlinkSync(file);
122
+ } catch (err) {
123
+ if (!isFileMissing(err)) throw err;
124
+ }
125
+ }
126
+ async function syncManifest(manifestFile, entryAbs, urlPath, produce) {
127
+ let entryExists = true;
128
+ try {
129
+ lstatSync(entryAbs);
130
+ } catch (err) {
131
+ if (!isFileMissing(err)) throw err;
132
+ entryExists = false;
133
+ }
134
+ if (!entryExists) {
135
+ removeManifest(manifestFile);
136
+ return;
137
+ }
138
+ writeManifestIfChanged(manifestFile, await produce(entryAbs, urlPath));
139
+ }
140
+ async function runExtract(projectRoot) {
141
+ const resolved = await resolveConfig({
142
+ root: projectRoot,
143
+ logLevel: "silent"
144
+ }, "serve");
145
+ const alias = resolved.resolve?.alias ?? [];
146
+ const pluginApi = resolved.plugins.find((p) => typeof p.name === "string" && p.name.startsWith(MCP_PLUGIN_NAME_PREFIX))?.api;
147
+ const entryAbs = pluginApi?.mcpEntry ?? resolve(projectRoot, "src/lib/mcp/index.ts");
148
+ const urlPath = pluginApi?.urlPath ?? "/mcp";
149
+ await syncManifest(resolve(projectRoot, MANIFEST_RELATIVE_PATH), entryAbs, urlPath, (entry, path) => produceViaSsr(projectRoot, alias, entry, path));
150
+ }
151
+ //#endregion
152
+ export { DEFAULT_MCP_ENTRY, DEFAULT_MCP_PATH, runExtract, syncManifest };
@@ -1,7 +1,7 @@
1
1
  import { i as log, r as describeError } from "./logger-Ctv5xUSD.js";
2
2
  import { n as schemaFromDefinition, t as jsonSchemaFromDefinition } from "./schema-CW1jKvio.js";
3
3
  import { i as ToolContext, n as errorSummary, r as resolveToolErrorMessage } from "./errors-Bwd1L0Rf.js";
4
- import { f as finalizeWireResponse, i as normalizeHttpMethod, l as createNoopRecorder, n as createRequestAuthorizer, t as evaluateCors, u as nowMs } from "./cors-Bb8_NLEB.js";
4
+ import { f as finalizeWireResponse, i as normalizeHttpMethod, l as createNoopRecorder, n as createRequestAuthorizer, t as evaluateCors, u as nowMs } from "./cors-BLtFfCwv.js";
5
5
  import { getParseErrorMessage, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
6
6
  import { CLIENT_CAPABILITIES_META_KEY, CLIENT_INFO_META_KEY, McpServer, createMcpHandler } from "@modelcontextprotocol/server";
7
7
  //#region src/core/content.ts
@@ -1,7 +1,7 @@
1
1
  const require_logger = require("./logger-CN1q-KNn.cjs");
2
2
  const require_schema = require("./schema-D3vClxb7.cjs");
3
3
  const require_errors = require("./errors-Cr95rSkB.cjs");
4
- const require_cors = require("./cors-aOILwe5U.cjs");
4
+ const require_cors = require("./cors-DiBSnA0M.cjs");
5
5
  let _modelcontextprotocol_sdk_server_zod_compat_js = require("@modelcontextprotocol/sdk/server/zod-compat.js");
6
6
  let _modelcontextprotocol_server = require("@modelcontextprotocol/server");
7
7
  //#region src/core/content.ts
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "2.0.0";
2
+ var version = "2.0.2-rc.0";
3
3
  //#endregion
4
4
  Object.defineProperty(exports, "version", {
5
5
  enumerable: true,
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "2.0.0";
2
+ var version = "2.0.2-rc.0";
3
3
  //#endregion
4
4
  export { version as t };
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_mcp = require("../../mcp-Bbl8sdkl.cjs");
2
+ const require_mcp = require("../../mcp-LfeCqruq.cjs");
3
3
  exports.createMcpProtocolHandler = require_mcp.createMcpProtocolHandler;
@@ -1,2 +1,2 @@
1
- import { t as createMcpProtocolHandler } from "../../mcp-B1hj8ZPg.js";
1
+ import { t as createMcpProtocolHandler } from "../../mcp-BQeQCXB4.js";
2
2
  export { createMcpProtocolHandler };
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_logger = require("../logger-CN1q-KNn.cjs");
3
- const require_cors = require("../cors-aOILwe5U.cjs");
3
+ const require_cors = require("../cors-DiBSnA0M.cjs");
4
4
  //#region src/protocols/oauth-metadata.ts
5
5
  function notFound() {
6
6
  return new Response(JSON.stringify({ error: "not found" }), {
@@ -1,5 +1,5 @@
1
1
  import { i as log, r as describeError } from "../logger-Ctv5xUSD.js";
2
- import { a as oauthConfigurationErrorResponse, d as JSON_HEADERS, f as finalizeWireResponse, i as normalizeHttpMethod, l as createNoopRecorder, p as methodNotAllowed, r as getOAuthRuntime, s as resolveProtectedResource, t as evaluateCors } from "../cors-Bb8_NLEB.js";
2
+ import { a as oauthConfigurationErrorResponse, d as JSON_HEADERS, f as finalizeWireResponse, i as normalizeHttpMethod, l as createNoopRecorder, p as methodNotAllowed, r as getOAuthRuntime, s as resolveProtectedResource, t as evaluateCors } from "../cors-BLtFfCwv.js";
3
3
  //#region src/protocols/oauth-metadata.ts
4
4
  function notFound() {
5
5
  return new Response(JSON.stringify({ error: "not found" }), {
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_logger = require("../../logger-CN1q-KNn.cjs");
3
- const require_cors = require("../../cors-aOILwe5U.cjs");
3
+ const require_cors = require("../../cors-DiBSnA0M.cjs");
4
4
  const require_metadata_path = require("../../metadata-path-zd7NSNoa.cjs");
5
- const require_mcp = require("../../mcp-Bbl8sdkl.cjs");
5
+ const require_mcp = require("../../mcp-LfeCqruq.cjs");
6
6
  const require_protocols_oauth_metadata = require("../../protocols/oauth-metadata.cjs");
7
7
  const require_forwarded = require("../../forwarded-B37IdmMX.cjs");
8
8
  const require_paths = require("../../paths-OP318a6c.cjs");
@@ -1,7 +1,7 @@
1
1
  import { s as trimTrailingSlash } from "../../logger-Ctv5xUSD.js";
2
- import { c as createRecorderForRuntime, o as assertResourcePathShape } from "../../cors-Bb8_NLEB.js";
2
+ import { c as createRecorderForRuntime, o as assertResourcePathShape } from "../../cors-BLtFfCwv.js";
3
3
  import { t as OAUTH_PROTECTED_RESOURCE_METADATA_PATH } from "../../metadata-path-CAkNcfyY.js";
4
- import { t as createMcpProtocolHandler } from "../../mcp-B1hj8ZPg.js";
4
+ import { t as createMcpProtocolHandler } from "../../mcp-BQeQCXB4.js";
5
5
  import { createOAuthProtectedResourceMetadataHandler } from "../../protocols/oauth-metadata.js";
6
6
  import { t as applyForwardedOrigin } from "../../forwarded-BBXvFeh6.js";
7
7
  import { n as assertFunctionName, t as FUNCTIONS_MOUNT_PREFIX } from "../../paths-IS65L6TA.js";
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_package = require("../../package-RqZMyBY1.cjs");
5
+ const require_package = require("../../package-BGn-LvT3.cjs");
6
6
  const require_fs_errors = require("../../fs-errors-CWNzOV75.cjs");
7
7
  const require_supabase_config = require("../../supabase-config-Dz7dnUv0.cjs");
8
8
  const require_paths = require("../../paths-OP318a6c.cjs");
@@ -1,4 +1,4 @@
1
- import { t as version } from "../../package-CZERyrAn.js";
1
+ import { t as version } from "../../package-Bu8wLlvG.js";
2
2
  import { t as isFileMissing } from "../../fs-errors-PA2t1TIp.js";
3
3
  import { t as readSupabaseProjectRef } from "../../supabase-config-CakTU8YF.js";
4
4
  import { n as assertFunctionName, t as FUNCTIONS_MOUNT_PREFIX } from "../../paths-IS65L6TA.js";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_cors = require("../../cors-aOILwe5U.cjs");
3
- const require_mcp = require("../../mcp-Bbl8sdkl.cjs");
2
+ const require_cors = require("../../cors-DiBSnA0M.cjs");
3
+ const require_mcp = require("../../mcp-LfeCqruq.cjs");
4
4
  const require_protocols_oauth_metadata = require("../../protocols/oauth-metadata.cjs");
5
5
  const require_forwarded = require("../../forwarded-B37IdmMX.cjs");
6
6
  //#region src/stacks/tanstack/handlers.ts
@@ -1,5 +1,5 @@
1
- import { c as createRecorderForRuntime } from "../../cors-Bb8_NLEB.js";
2
- import { t as createMcpProtocolHandler } from "../../mcp-B1hj8ZPg.js";
1
+ import { c as createRecorderForRuntime } from "../../cors-BLtFfCwv.js";
2
+ import { t as createMcpProtocolHandler } from "../../mcp-BQeQCXB4.js";
3
3
  import { createOAuthProtectedResourceMetadataHandler } from "../../protocols/oauth-metadata.js";
4
4
  import { t as applyForwardedOrigin } from "../../forwarded-BBXvFeh6.js";
5
5
  //#region src/stacks/tanstack/handlers.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovable.dev/mcp-js",
3
- "version": "2.0.0",
3
+ "version": "2.0.2-rc.0",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },
@@ -108,14 +108,14 @@
108
108
  ],
109
109
  "license": "MIT",
110
110
  "dependencies": {
111
- "@modelcontextprotocol/server": "2.0.0",
112
111
  "@modelcontextprotocol/sdk": "1.28.0",
112
+ "@modelcontextprotocol/server": "2.0.0",
113
113
  "esbuild": "^0.27.0",
114
114
  "jose": "^6.2.2"
115
115
  },
116
116
  "peerDependencies": {
117
117
  "vite": ">=5.0.0 <9.0.0",
118
- "zod": ">=3.23.0"
118
+ "zod": "^3.25 || ^4.0"
119
119
  },
120
120
  "devDependencies": {
121
121
  "@types/node": "22.13.13",
@@ -135,7 +135,8 @@
135
135
  "format:check": "oxfmt --check src/ tests/",
136
136
  "lint": "oxfmt --check src/ tests/ && oxlint -c ../../oxlint.config.ts src/ tests/",
137
137
  "lint:fix": "oxfmt --write src/ tests/ && oxlint -c ../../oxlint.config.ts --fix src/ tests/",
138
- "test": "vitest run --project unit",
138
+ "test": "vitest run --project unit && npm run test:package:zod",
139
+ "test:package:zod": "npm run build && node tests/package/zod-compat.mjs",
139
140
  "test:watch": "vitest --project unit",
140
141
  "example:tanstack:install": "bun link && cd example/tanstack && bun install && bun link @lovable.dev/mcp-js",
141
142
  "example:tanstack:dev": "cd example/tanstack && bun --bun run dev",