@lovable.dev/mcp-js 0.11.0 → 0.12.1

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.
@@ -145,7 +145,7 @@ src/
145
145
  index.ts # barrel
146
146
  supabase/
147
147
  handler.ts # createSupabaseHandler (URL-suffix dispatcher to protocols/*)
148
- emit.ts # syncSupabaseFunction (emits supabase/functions/<name>/{index.ts,deno.json})
148
+ emit.ts # syncSupabaseFunction (esbuild-bundles supabase/functions/<name>/index.ts)
149
149
  vite.ts # mcpPlugin (Vite-lifecycle trigger; exposes McpPluginApi with urlPath=/functions/v1/<functionName>)
150
150
  index.ts # barrel
151
151
  tests/
package/README.md CHANGED
@@ -217,7 +217,7 @@ A `500` on an OAuth-protected route is always one of two causes, logged at `erro
217
217
 
218
218
  ## Supabase Edge Functions
219
219
 
220
- Ship the same `defineMcp` server as a single Supabase Edge Function. Authoring is identical to the TanStack flow; only the build-time step differs — a different Vite plugin emits a single Deno function entry (plus a `deno.json` import map) instead of a tree of file-router routes.
220
+ Ship the same `defineMcp` server as a single Supabase Edge Function. Authoring is identical to the TanStack flow; only the build-time step differs — a different Vite plugin bundles your MCP entry and its local imports into one self-contained Deno function (with esbuild) instead of emitting a tree of file-router routes. Third-party dependencies are left as `npm:` specifiers that Deno resolves at runtime, so no import map / `deno.json` is needed.
221
221
 
222
222
  ```ts
223
223
  // src/lib/mcp/index.ts — same as the TanStack flow
@@ -249,7 +249,7 @@ import { mcpPlugin } from "@lovable.dev/mcp-js/stacks/supabase/vite";
249
249
  export default defineConfig({ plugins: [mcpPlugin()] });
250
250
  ```
251
251
 
252
- The plugin emits `supabase/functions/mcp/index.ts` and `supabase/functions/mcp/deno.json` on `configResolved`, re-emits on `buildStart`, and watches `src/lib/mcp/index.ts` for changes during `vite dev`. The emitted function calls `Deno.serve(createSupabaseHandler(mcp, { functionName: "mcp" }))`. The handler is one Web-Standard `Request → Response` that dispatches by URL suffix to the same protocol handlers the TanStack stack uses:
252
+ The plugin bundles `src/lib/mcp/index.ts` (and the tool files it imports) into `supabase/functions/mcp/index.ts` on `configResolved`, re-emits on `buildStart`, and watches the bundled source files for changes during `vite dev`. The emitted function calls `Deno.serve(createSupabaseHandler(mcp, { functionName: "mcp" }))`. The handler is one Web-Standard `Request → Response` that dispatches by URL suffix to the same protocol handlers the TanStack stack uses:
253
253
 
254
254
  | URL suffix | Target |
255
255
  | --- | --- |
@@ -260,7 +260,7 @@ The plugin emits `supabase/functions/mcp/index.ts` and `supabase/functions/mcp/d
260
260
 
261
261
  In production the function is mounted at `https://<project-ref>.supabase.co/functions/v1/mcp` and the dispatcher binds the OAuth `resource` to that URL via `functionName`. Locally, run `supabase functions serve mcp --no-verify-jwt` so Supabase's edge-runtime doesn't 401 before the SDK's OAuth verifier sees the bearer.
262
262
 
263
- Ownership: every emitted file carries a `// AUTO-GENERATED by @lovable.dev/mcp-js` banner. The plugin rewrites banner-tagged files in place and cleans up orphans; banner-less files (yours) are never touched. Delete the banner line to take ownership of a file. Deleting the `defineMcp` entry and re-running removes the SDK-owned function files.
263
+ Ownership: the emitted `index.ts` carries a `// AUTO-GENERATED by @lovable.dev/mcp-js` banner. The plugin rewrites the banner-tagged file in place and cleans up orphans (including a `deno.json` left by a pre-bundling SDK version); banner-less files (yours) are never touched. Delete the banner line to take ownership. Deleting the `defineMcp` entry and re-running removes the SDK-owned function file.
264
264
 
265
265
  ## Subpath exports
266
266
 
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "0.11.0";
2
+ var version = "0.12.1";
3
3
 
4
4
  export {
5
5
  version
@@ -13,7 +13,7 @@ function isFileMissing(err) {
13
13
  }
14
14
 
15
15
  // package.json
16
- var version = "0.11.0";
16
+ var version = "0.12.1";
17
17
 
18
18
  // src/protocols/rest/list-tools.ts
19
19
  var import_zod_compat = require("@modelcontextprotocol/sdk/server/zod-compat.js");
@@ -7,7 +7,7 @@ import "../chunk-QC3DXQTH.js";
7
7
  import "../chunk-6DXGZZA4.js";
8
8
  import {
9
9
  version
10
- } from "../chunk-ENWRS6AT.js";
10
+ } from "../chunk-Y2FTHVL3.js";
11
11
  import {
12
12
  isFileMissing
13
13
  } from "../chunk-Y3ZFPEQH.js";
@@ -27,11 +27,12 @@ module.exports = __toCommonJS(vite_exports);
27
27
  var import_node_path2 = require("path");
28
28
 
29
29
  // src/stacks/supabase/emit.ts
30
+ var import_esbuild = require("esbuild");
30
31
  var import_node_fs = require("fs");
31
32
  var import_node_path = require("path");
32
33
 
33
34
  // package.json
34
- var version = "0.11.0";
35
+ var version = "0.12.1";
35
36
 
36
37
  // src/core/fs-errors.ts
37
38
  function isFileMissing(err) {
@@ -54,10 +55,6 @@ var DEFAULT_FUNCTIONS_DIR = "supabase/functions";
54
55
  var DEFAULT_FUNCTION_NAME = "mcp";
55
56
  var GENERATED_BANNER = "// AUTO-GENERATED by @lovable.dev/mcp-js \u2014 do not edit. Regenerated by the Vite plugin.\n// To take ownership, delete this banner line; the plugin then leaves the file alone.";
56
57
  var GENERATED_JSON_MARKER_KEY = "_lovableMcpGenerated";
57
- var GENERATED_JSON_MARKER_VALUE = "AUTO-GENERATED by @lovable.dev/mcp-js \u2014 do not edit. Regenerated by the Vite plugin. To take ownership, remove this key; the plugin then leaves the file alone.";
58
- function normalizePath(p) {
59
- return p.split(import_node_path.sep).join("/");
60
- }
61
58
  function fileExists(path) {
62
59
  try {
63
60
  (0, import_node_fs.lstatSync)(path);
@@ -100,32 +97,63 @@ function writeIfChanged(file, content, ownershipCheck) {
100
97
  (0, import_node_fs.writeFileSync)(file, content, "utf8");
101
98
  return true;
102
99
  }
103
- function relativeTsSpecifier(fromFile, toFile) {
104
- const rel = normalizePath((0, import_node_path.relative)((0, import_node_path.dirname)(fromFile), toFile));
105
- return rel.startsWith(".") ? rel : `./${rel}`;
100
+ function readProjectDependencyVersions(projectRoot) {
101
+ try {
102
+ const pkg = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path.join)(projectRoot, "package.json"), "utf8"));
103
+ return { ...pkg.peerDependencies, ...pkg.devDependencies, ...pkg.dependencies };
104
+ } catch {
105
+ return {};
106
+ }
106
107
  }
107
- function buildFunctionEntry(mcpEntryAbs, functionEntryAbs, functionName) {
108
- const entryImport = relativeTsSpecifier(functionEntryAbs, mcpEntryAbs);
109
- return `${GENERATED_BANNER}
110
- // supabase function: ${functionName}
111
- // emitted to: supabase/functions/${functionName}/index.ts
112
-
113
- import { createSupabaseHandler } from "npm:@lovable.dev/mcp-js@${version}/stacks/supabase";
114
-
115
- import mcp from "${entryImport}";
116
-
117
- Deno.serve(createSupabaseHandler(mcp, { functionName: ${JSON.stringify(functionName)} }));
118
- `;
108
+ function npmSpecifierFor(bare, versions) {
109
+ const pkg = bare.startsWith("@") ? bare.split("/").slice(0, 2).join("/") : bare.split("/")[0];
110
+ const subpath = bare.slice(pkg.length);
111
+ if (pkg === "@lovable.dev/mcp-js")
112
+ return `npm:${pkg}@${version}${subpath}`;
113
+ const range = versions[pkg];
114
+ const version2 = range && /^[\dv^~>=]|^latest$/.test(range) ? range : void 0;
115
+ return version2 ? `npm:${pkg}@${version2}${subpath}` : `npm:${pkg}${subpath}`;
119
116
  }
120
- function buildDenoJson() {
121
- const config = {
122
- [GENERATED_JSON_MARKER_KEY]: GENERATED_JSON_MARKER_VALUE,
123
- imports: {
124
- "@lovable.dev/mcp-js": `npm:@lovable.dev/mcp-js@${version}`
117
+ function externalizeBareAsNpm(versions) {
118
+ return {
119
+ name: "lovable-mcp-externalize-bare-as-npm",
120
+ setup(pluginBuild) {
121
+ pluginBuild.onResolve({ filter: /.*/ }, (args) => {
122
+ const p = args.path;
123
+ if (p.startsWith(".") || p.startsWith("/"))
124
+ return null;
125
+ if (/^(npm|node|jsr|https?|data):/.test(p))
126
+ return { path: p, external: true };
127
+ return { path: npmSpecifierFor(p, versions), external: true };
128
+ });
125
129
  }
126
130
  };
127
- return `${JSON.stringify(config, null, 2)}
131
+ }
132
+ async function bundleFunctionEntry(projectRoot, mcpEntryAbs, functionName) {
133
+ const versions = readProjectDependencyVersions(projectRoot);
134
+ const wrapper = `import mcp from ${JSON.stringify(mcpEntryAbs)};
135
+ import { createSupabaseHandler } from "@lovable.dev/mcp-js/stacks/supabase";
136
+ Deno.serve(createSupabaseHandler(mcp, { functionName: ${JSON.stringify(functionName)} }));
128
137
  `;
138
+ const result = await (0, import_esbuild.build)({
139
+ stdin: { contents: wrapper, resolveDir: projectRoot, sourcefile: "lovable-mcp-supabase-entry.ts", loader: "ts" },
140
+ bundle: true,
141
+ write: false,
142
+ metafile: true,
143
+ format: "esm",
144
+ platform: "neutral",
145
+ target: "esnext",
146
+ absWorkingDir: projectRoot,
147
+ logLevel: "silent",
148
+ plugins: [externalizeBareAsNpm(versions)]
149
+ });
150
+ const out = result.outputFiles[0]?.text ?? "";
151
+ const inputs = Object.keys(result.metafile?.inputs ?? {}).filter((p) => !p.startsWith("<") && !p.startsWith("npm:")).map((p) => (0, import_node_path.resolve)(projectRoot, p));
152
+ const code = `${GENERATED_BANNER}
153
+ // supabase function: ${functionName}
154
+ // Bundled from ${(0, import_node_path.relative)(projectRoot, mcpEntryAbs).split(import_node_path.sep).join("/")} by @lovable.dev/mcp-js.
155
+ ` + out;
156
+ return { code, inputs };
129
157
  }
130
158
  function resolvePaths(options) {
131
159
  const projectRoot = (0, import_node_path.resolve)(options.projectRoot);
@@ -142,31 +170,25 @@ function resolvePaths(options) {
142
170
  return {
143
171
  functionDir,
144
172
  functionEntry: (0, import_node_path.join)(functionDir, "index.ts"),
145
- denoJson: (0, import_node_path.join)(functionDir, "deno.json"),
146
173
  mcpEntryAbs: (0, import_node_path.resolve)(projectRoot, mcpEntry)
147
174
  };
148
175
  }
149
- function syncSupabaseFunction(options) {
176
+ async function syncSupabaseFunction(options) {
177
+ const projectRoot = (0, import_node_path.resolve)(options.projectRoot);
150
178
  const paths = resolvePaths(options);
151
179
  const functionName = options.functionName ?? DEFAULT_FUNCTION_NAME;
152
180
  const written = [];
153
181
  const removed = [];
154
182
  if (!fileExists(paths.mcpEntryAbs)) {
155
183
  cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set(), removed);
156
- return { written, removed };
184
+ return { written, removed, inputs: [] };
157
185
  }
158
- if (writeIfChanged(
159
- paths.functionEntry,
160
- buildFunctionEntry(paths.mcpEntryAbs, paths.functionEntry, functionName),
161
- hasGeneratedBanner
162
- )) {
186
+ const { code, inputs } = await bundleFunctionEntry(projectRoot, paths.mcpEntryAbs, functionName);
187
+ if (writeIfChanged(paths.functionEntry, code, hasGeneratedBanner)) {
163
188
  written.push(paths.functionEntry);
164
189
  }
165
- if (writeIfChanged(paths.denoJson, buildDenoJson(), hasGeneratedJsonMarker)) {
166
- written.push(paths.denoJson);
167
- }
168
- cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set([paths.functionEntry, paths.denoJson]), removed);
169
- return { written, removed };
190
+ cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set([paths.functionEntry]), removed);
191
+ return { written, removed, inputs };
170
192
  }
171
193
  function cleanupOrphans(functionDir, expected, removed) {
172
194
  let entries;
@@ -202,7 +224,7 @@ function cleanupOrphans(functionDir, expected, removed) {
202
224
  }
203
225
 
204
226
  // src/stacks/supabase/vite.ts
205
- function normalizePath2(p) {
227
+ function normalizePath(p) {
206
228
  return p.split(import_node_path2.sep).join("/");
207
229
  }
208
230
  function mcpPlugin(options = {}) {
@@ -212,13 +234,15 @@ function mcpPlugin(options = {}) {
212
234
  const urlPath = `${FUNCTIONS_MOUNT_PREFIX}${functionName}`;
213
235
  let projectRoot = process.cwd();
214
236
  let mcpEntryAbs = (0, import_node_path2.resolve)(projectRoot, mcpEntryOption);
215
- const regenerate = () => {
216
- syncSupabaseFunction({
237
+ let watchedInputs = /* @__PURE__ */ new Set();
238
+ const regenerate = async () => {
239
+ const { inputs } = await syncSupabaseFunction({
217
240
  projectRoot,
218
241
  mcpEntry: mcpEntryOption,
219
242
  functionsDir: functionsDirOption,
220
243
  functionName
221
244
  });
245
+ watchedInputs = new Set(inputs.map(normalizePath));
222
246
  };
223
247
  return {
224
248
  name: "@lovable.dev/mcp-js/supabase",
@@ -231,16 +255,23 @@ function mcpPlugin(options = {}) {
231
255
  },
232
256
  urlPath
233
257
  },
234
- configResolved(config) {
258
+ async configResolved(config) {
235
259
  projectRoot = config.root;
236
260
  mcpEntryAbs = (0, import_node_path2.resolve)(projectRoot, mcpEntryOption);
237
- regenerate();
261
+ await regenerate();
238
262
  },
239
263
  configureServer(server) {
240
264
  const onChange = (file) => {
241
- if (normalizePath2(file) === normalizePath2(mcpEntryAbs)) {
242
- regenerate();
243
- }
265
+ const norm = normalizePath(file);
266
+ if (norm !== normalizePath(mcpEntryAbs) && !watchedInputs.has(norm))
267
+ return;
268
+ regenerate().catch((err) => {
269
+ const message = `[@lovable.dev/mcp-js] failed to regenerate the Supabase function: ${err instanceof Error ? err.message : String(err)}`;
270
+ if (server.config?.logger?.error)
271
+ server.config.logger.error(message);
272
+ else
273
+ console.error(message);
274
+ });
244
275
  };
245
276
  server.watcher.on("add", onChange);
246
277
  server.watcher.on("change", onChange);
@@ -251,8 +282,8 @@ function mcpPlugin(options = {}) {
251
282
  server.watcher.off("unlink", onChange);
252
283
  });
253
284
  },
254
- buildStart() {
255
- regenerate();
285
+ async buildStart() {
286
+ await regenerate();
256
287
  }
257
288
  };
258
289
  }
@@ -34,11 +34,14 @@ interface McpPluginOptions {
34
34
  * Lifecycle, mirroring `stacks/tanstack/vite`:
35
35
  * - `configResolved` finalizes the project root and runs the first emit.
36
36
  * - `buildStart` re-runs the emit so a `vite build` always picks up the
37
- * latest entry.
38
- * - `configureServer` re-emits when the entry file appears, changes, or
39
- * is removed during `vite dev`, so a developer editing tools sees the
40
- * function regenerate without restarting the server.
37
+ * latest source.
38
+ * - `configureServer` re-emits when the entry or any bundled source file
39
+ * (tools, local helpers) changes during `vite dev`, so editing a tool
40
+ * regenerates the function without restarting the server.
41
41
  *
42
+ * The entry and its local import graph are bundled by esbuild into one
43
+ * self-contained Deno module (deps left as `npm:` specifiers), so the deployed
44
+ * function carries no cross-project imports the Supabase deploy can't upload.
42
45
  * Idempotent: `syncSupabaseFunction` writes only on byte-level change and
43
46
  * cleans up SDK-owned files when the entry is deleted.
44
47
  */
@@ -34,11 +34,14 @@ interface McpPluginOptions {
34
34
  * Lifecycle, mirroring `stacks/tanstack/vite`:
35
35
  * - `configResolved` finalizes the project root and runs the first emit.
36
36
  * - `buildStart` re-runs the emit so a `vite build` always picks up the
37
- * latest entry.
38
- * - `configureServer` re-emits when the entry file appears, changes, or
39
- * is removed during `vite dev`, so a developer editing tools sees the
40
- * function regenerate without restarting the server.
37
+ * latest source.
38
+ * - `configureServer` re-emits when the entry or any bundled source file
39
+ * (tools, local helpers) changes during `vite dev`, so editing a tool
40
+ * regenerates the function without restarting the server.
41
41
  *
42
+ * The entry and its local import graph are bundled by esbuild into one
43
+ * self-contained Deno module (deps left as `npm:` specifiers), so the deployed
44
+ * function carries no cross-project imports the Supabase deploy can't upload.
42
45
  * Idempotent: `syncSupabaseFunction` writes only on byte-level change and
43
46
  * cleans up SDK-owned files when the entry is deleted.
44
47
  */
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  version
3
- } from "../../chunk-ENWRS6AT.js";
3
+ } from "../../chunk-Y2FTHVL3.js";
4
4
  import {
5
5
  isFileMissing
6
6
  } from "../../chunk-Y3ZFPEQH.js";
@@ -13,6 +13,7 @@ import {
13
13
  import { resolve as resolve2, sep as sep2 } from "path";
14
14
 
15
15
  // src/stacks/supabase/emit.ts
16
+ import { build } from "esbuild";
16
17
  import { lstatSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
17
18
  import { dirname, join, relative, resolve, sep } from "path";
18
19
  var DEFAULT_MCP_ENTRY = "src/lib/mcp/index.ts";
@@ -20,10 +21,6 @@ var DEFAULT_FUNCTIONS_DIR = "supabase/functions";
20
21
  var DEFAULT_FUNCTION_NAME = "mcp";
21
22
  var GENERATED_BANNER = "// AUTO-GENERATED by @lovable.dev/mcp-js \u2014 do not edit. Regenerated by the Vite plugin.\n// To take ownership, delete this banner line; the plugin then leaves the file alone.";
22
23
  var GENERATED_JSON_MARKER_KEY = "_lovableMcpGenerated";
23
- var GENERATED_JSON_MARKER_VALUE = "AUTO-GENERATED by @lovable.dev/mcp-js \u2014 do not edit. Regenerated by the Vite plugin. To take ownership, remove this key; the plugin then leaves the file alone.";
24
- function normalizePath(p) {
25
- return p.split(sep).join("/");
26
- }
27
24
  function fileExists(path) {
28
25
  try {
29
26
  lstatSync(path);
@@ -66,32 +63,63 @@ function writeIfChanged(file, content, ownershipCheck) {
66
63
  writeFileSync(file, content, "utf8");
67
64
  return true;
68
65
  }
69
- function relativeTsSpecifier(fromFile, toFile) {
70
- const rel = normalizePath(relative(dirname(fromFile), toFile));
71
- return rel.startsWith(".") ? rel : `./${rel}`;
66
+ function readProjectDependencyVersions(projectRoot) {
67
+ try {
68
+ const pkg = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf8"));
69
+ return { ...pkg.peerDependencies, ...pkg.devDependencies, ...pkg.dependencies };
70
+ } catch {
71
+ return {};
72
+ }
72
73
  }
73
- function buildFunctionEntry(mcpEntryAbs, functionEntryAbs, functionName) {
74
- const entryImport = relativeTsSpecifier(functionEntryAbs, mcpEntryAbs);
75
- return `${GENERATED_BANNER}
76
- // supabase function: ${functionName}
77
- // emitted to: supabase/functions/${functionName}/index.ts
78
-
79
- import { createSupabaseHandler } from "npm:@lovable.dev/mcp-js@${version}/stacks/supabase";
80
-
81
- import mcp from "${entryImport}";
82
-
83
- Deno.serve(createSupabaseHandler(mcp, { functionName: ${JSON.stringify(functionName)} }));
84
- `;
74
+ function npmSpecifierFor(bare, versions) {
75
+ const pkg = bare.startsWith("@") ? bare.split("/").slice(0, 2).join("/") : bare.split("/")[0];
76
+ const subpath = bare.slice(pkg.length);
77
+ if (pkg === "@lovable.dev/mcp-js")
78
+ return `npm:${pkg}@${version}${subpath}`;
79
+ const range = versions[pkg];
80
+ const version2 = range && /^[\dv^~>=]|^latest$/.test(range) ? range : void 0;
81
+ return version2 ? `npm:${pkg}@${version2}${subpath}` : `npm:${pkg}${subpath}`;
85
82
  }
86
- function buildDenoJson() {
87
- const config = {
88
- [GENERATED_JSON_MARKER_KEY]: GENERATED_JSON_MARKER_VALUE,
89
- imports: {
90
- "@lovable.dev/mcp-js": `npm:@lovable.dev/mcp-js@${version}`
83
+ function externalizeBareAsNpm(versions) {
84
+ return {
85
+ name: "lovable-mcp-externalize-bare-as-npm",
86
+ setup(pluginBuild) {
87
+ pluginBuild.onResolve({ filter: /.*/ }, (args) => {
88
+ const p = args.path;
89
+ if (p.startsWith(".") || p.startsWith("/"))
90
+ return null;
91
+ if (/^(npm|node|jsr|https?|data):/.test(p))
92
+ return { path: p, external: true };
93
+ return { path: npmSpecifierFor(p, versions), external: true };
94
+ });
91
95
  }
92
96
  };
93
- return `${JSON.stringify(config, null, 2)}
97
+ }
98
+ async function bundleFunctionEntry(projectRoot, mcpEntryAbs, functionName) {
99
+ const versions = readProjectDependencyVersions(projectRoot);
100
+ const wrapper = `import mcp from ${JSON.stringify(mcpEntryAbs)};
101
+ import { createSupabaseHandler } from "@lovable.dev/mcp-js/stacks/supabase";
102
+ Deno.serve(createSupabaseHandler(mcp, { functionName: ${JSON.stringify(functionName)} }));
94
103
  `;
104
+ const result = await build({
105
+ stdin: { contents: wrapper, resolveDir: projectRoot, sourcefile: "lovable-mcp-supabase-entry.ts", loader: "ts" },
106
+ bundle: true,
107
+ write: false,
108
+ metafile: true,
109
+ format: "esm",
110
+ platform: "neutral",
111
+ target: "esnext",
112
+ absWorkingDir: projectRoot,
113
+ logLevel: "silent",
114
+ plugins: [externalizeBareAsNpm(versions)]
115
+ });
116
+ const out = result.outputFiles[0]?.text ?? "";
117
+ const inputs = Object.keys(result.metafile?.inputs ?? {}).filter((p) => !p.startsWith("<") && !p.startsWith("npm:")).map((p) => resolve(projectRoot, p));
118
+ const code = `${GENERATED_BANNER}
119
+ // supabase function: ${functionName}
120
+ // Bundled from ${relative(projectRoot, mcpEntryAbs).split(sep).join("/")} by @lovable.dev/mcp-js.
121
+ ` + out;
122
+ return { code, inputs };
95
123
  }
96
124
  function resolvePaths(options) {
97
125
  const projectRoot = resolve(options.projectRoot);
@@ -108,31 +136,25 @@ function resolvePaths(options) {
108
136
  return {
109
137
  functionDir,
110
138
  functionEntry: join(functionDir, "index.ts"),
111
- denoJson: join(functionDir, "deno.json"),
112
139
  mcpEntryAbs: resolve(projectRoot, mcpEntry)
113
140
  };
114
141
  }
115
- function syncSupabaseFunction(options) {
142
+ async function syncSupabaseFunction(options) {
143
+ const projectRoot = resolve(options.projectRoot);
116
144
  const paths = resolvePaths(options);
117
145
  const functionName = options.functionName ?? DEFAULT_FUNCTION_NAME;
118
146
  const written = [];
119
147
  const removed = [];
120
148
  if (!fileExists(paths.mcpEntryAbs)) {
121
149
  cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set(), removed);
122
- return { written, removed };
150
+ return { written, removed, inputs: [] };
123
151
  }
124
- if (writeIfChanged(
125
- paths.functionEntry,
126
- buildFunctionEntry(paths.mcpEntryAbs, paths.functionEntry, functionName),
127
- hasGeneratedBanner
128
- )) {
152
+ const { code, inputs } = await bundleFunctionEntry(projectRoot, paths.mcpEntryAbs, functionName);
153
+ if (writeIfChanged(paths.functionEntry, code, hasGeneratedBanner)) {
129
154
  written.push(paths.functionEntry);
130
155
  }
131
- if (writeIfChanged(paths.denoJson, buildDenoJson(), hasGeneratedJsonMarker)) {
132
- written.push(paths.denoJson);
133
- }
134
- cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set([paths.functionEntry, paths.denoJson]), removed);
135
- return { written, removed };
156
+ cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set([paths.functionEntry]), removed);
157
+ return { written, removed, inputs };
136
158
  }
137
159
  function cleanupOrphans(functionDir, expected, removed) {
138
160
  let entries;
@@ -168,7 +190,7 @@ function cleanupOrphans(functionDir, expected, removed) {
168
190
  }
169
191
 
170
192
  // src/stacks/supabase/vite.ts
171
- function normalizePath2(p) {
193
+ function normalizePath(p) {
172
194
  return p.split(sep2).join("/");
173
195
  }
174
196
  function mcpPlugin(options = {}) {
@@ -178,13 +200,15 @@ function mcpPlugin(options = {}) {
178
200
  const urlPath = `${FUNCTIONS_MOUNT_PREFIX}${functionName}`;
179
201
  let projectRoot = process.cwd();
180
202
  let mcpEntryAbs = resolve2(projectRoot, mcpEntryOption);
181
- const regenerate = () => {
182
- syncSupabaseFunction({
203
+ let watchedInputs = /* @__PURE__ */ new Set();
204
+ const regenerate = async () => {
205
+ const { inputs } = await syncSupabaseFunction({
183
206
  projectRoot,
184
207
  mcpEntry: mcpEntryOption,
185
208
  functionsDir: functionsDirOption,
186
209
  functionName
187
210
  });
211
+ watchedInputs = new Set(inputs.map(normalizePath));
188
212
  };
189
213
  return {
190
214
  name: "@lovable.dev/mcp-js/supabase",
@@ -197,16 +221,23 @@ function mcpPlugin(options = {}) {
197
221
  },
198
222
  urlPath
199
223
  },
200
- configResolved(config) {
224
+ async configResolved(config) {
201
225
  projectRoot = config.root;
202
226
  mcpEntryAbs = resolve2(projectRoot, mcpEntryOption);
203
- regenerate();
227
+ await regenerate();
204
228
  },
205
229
  configureServer(server) {
206
230
  const onChange = (file) => {
207
- if (normalizePath2(file) === normalizePath2(mcpEntryAbs)) {
208
- regenerate();
209
- }
231
+ const norm = normalizePath(file);
232
+ if (norm !== normalizePath(mcpEntryAbs) && !watchedInputs.has(norm))
233
+ return;
234
+ regenerate().catch((err) => {
235
+ const message = `[@lovable.dev/mcp-js] failed to regenerate the Supabase function: ${err instanceof Error ? err.message : String(err)}`;
236
+ if (server.config?.logger?.error)
237
+ server.config.logger.error(message);
238
+ else
239
+ console.error(message);
240
+ });
210
241
  };
211
242
  server.watcher.on("add", onChange);
212
243
  server.watcher.on("change", onChange);
@@ -217,8 +248,8 @@ function mcpPlugin(options = {}) {
217
248
  server.watcher.off("unlink", onChange);
218
249
  });
219
250
  },
220
- buildStart() {
221
- regenerate();
251
+ async buildStart() {
252
+ await regenerate();
222
253
  }
223
254
  };
224
255
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovable.dev/mcp-js",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "Author MCP servers for Lovable apps. Declare tools with defineTool, register them in defineMcp, and a framework adapter (TanStack or Supabase Edge Functions) emits the route(s) at build time.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -76,6 +76,7 @@
76
76
  "license": "MIT",
77
77
  "dependencies": {
78
78
  "@modelcontextprotocol/sdk": "1.28.0",
79
+ "esbuild": "^0.27.0",
79
80
  "jose": "^6.2.2"
80
81
  },
81
82
  "peerDependencies": {