@lovable.dev/mcp-js 0.15.0 → 0.16.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.
@@ -169,7 +169,7 @@ function resolveMetricsConfig(config = true) {
169
169
  }
170
170
 
171
171
  // package.json
172
- var version = "0.15.0";
172
+ var version = "0.16.0";
173
173
 
174
174
  // src/metrics/otlp.ts
175
175
  var SCOPE_NAME = "@lovable.dev/mcp-js";
@@ -234,6 +234,10 @@ function nowUnixNano() {
234
234
  function nowMs() {
235
235
  return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
236
236
  }
237
+ function reportInvocation(recorder, ev) {
238
+ log.info("tool.invoked", { tool: ev.tool, method: ev.method, outcome: ev.outcome, durationMs: ev.durationMs });
239
+ recorder.record(ev);
240
+ }
237
241
  var NOOP_RECORDER = {
238
242
  record() {
239
243
  },
@@ -270,12 +274,14 @@ function createMetricsRecorder(ctx, deps = {}) {
270
274
  if (!config.enabled)
271
275
  return NOOP_RECORDER;
272
276
  const doFetch = deps.fetch ?? globalThis.fetch;
273
- if (!doFetch)
277
+ if (!doFetch) {
278
+ log.warn("metrics.disabled_no_fetch", { endpoint: config.endpoint });
274
279
  return NOOP_RECORDER;
280
+ }
275
281
  const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
276
282
  const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
277
283
  if (usesLovableEndpoint && !apiKey) {
278
- log.debug("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar });
284
+ log.warn("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar, endpoint: config.endpoint });
279
285
  return NOOP_RECORDER;
280
286
  }
281
287
  const waitUntil = deps.waitUntil ?? probeWaitUntil();
@@ -286,7 +292,8 @@ function createMetricsRecorder(ctx, deps = {}) {
286
292
  try {
287
293
  waitUntil(p);
288
294
  return;
289
- } catch {
295
+ } catch (err) {
296
+ log.warn("metrics.wait_until_failed", describeError(err));
290
297
  }
291
298
  }
292
299
  void p.catch(() => {
@@ -306,6 +313,7 @@ function createMetricsRecorder(ctx, deps = {}) {
306
313
  if (buffer.length === 0)
307
314
  return;
308
315
  const records = buffer.splice(0, buffer.length);
316
+ const dropped = records.length;
309
317
  try {
310
318
  const res = await doFetch(config.endpoint, {
311
319
  method: "POST",
@@ -313,10 +321,11 @@ function createMetricsRecorder(ctx, deps = {}) {
313
321
  body: buildLogsPayload(records, server),
314
322
  keepalive: true
315
323
  });
316
- if (!res.ok)
317
- log.debug("metrics.flush_rejected", { status: res.status });
324
+ if (!res.ok) {
325
+ log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
326
+ }
318
327
  } catch (err) {
319
- log.debug("metrics.flush_failed", describeError(err));
328
+ log.warn("metrics.flush_failed", { ...describeError(err), dropped, endpoint: config.endpoint });
320
329
  }
321
330
  };
322
331
  const ensureTimer = () => {
@@ -740,12 +749,13 @@ function assertRequiredScopes(auth, context) {
740
749
  throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
741
750
  }
742
751
  }
743
- function createRequestAuthorizer(mcp, options = {}) {
752
+ function createRequestAuthorizer(mcp, options = {}, recorder) {
744
753
  const runtime = getOAuthRuntime(mcp, options);
745
754
  return {
746
755
  async authorize(request) {
747
756
  if (runtime.kind === "unconfigured")
748
757
  return { ok: true };
758
+ const startedAt = nowMs();
749
759
  const token = parseBearerToken(request);
750
760
  if (!token) {
751
761
  log.info("auth.no_bearer_token", { outcome: "401" });
@@ -758,6 +768,12 @@ function createRequestAuthorizer(mcp, options = {}) {
758
768
  } catch (err) {
759
769
  if (err instanceof OAuthConfigurationError) {
760
770
  log.error("auth.config_error", { ...describeError(err), outcome: "500" });
771
+ recorder?.record({
772
+ tool: null,
773
+ method: "authorize",
774
+ outcome: "auth_config_error",
775
+ durationMs: nowMs() - startedAt
776
+ });
761
777
  return { ok: false, response: oauthConfigurationErrorResponse() };
762
778
  }
763
779
  if (err instanceof OAuthTokenError) {
@@ -863,14 +879,24 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
863
879
  try {
864
880
  result = await tool.handler(args, new ToolContext(auth));
865
881
  } catch {
866
- recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
882
+ reportInvocation(recorder, {
883
+ tool: tool.name,
884
+ method: "tools/call",
885
+ outcome: "handler_error",
886
+ durationMs: nowMs() - start
887
+ });
867
888
  return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
868
889
  }
869
890
  if (result == null) {
870
- recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
891
+ reportInvocation(recorder, {
892
+ tool: tool.name,
893
+ method: "tools/call",
894
+ outcome: "handler_error",
895
+ durationMs: nowMs() - start
896
+ });
871
897
  return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
872
898
  }
873
- recorder.record({
899
+ reportInvocation(recorder, {
874
900
  tool: tool.name,
875
901
  method: "tools/call",
876
902
  outcome: result.isError ? "tool_error" : "ok",
@@ -880,7 +906,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
880
906
  };
881
907
  }
882
908
  function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
883
- const authorizer = createRequestAuthorizer(mcp, options);
909
+ const authorizer = createRequestAuthorizer(mcp, options, recorder);
884
910
  const handle = async (request) => {
885
911
  const authResult = await authorizer.authorize(request);
886
912
  if (!authResult.ok)
@@ -1010,9 +1036,9 @@ function buildMcpListing(mcp) {
1010
1036
  }))
1011
1037
  };
1012
1038
  }
1013
- function createListToolsHandler(mcp, options = {}) {
1039
+ function createListToolsHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
1014
1040
  assertRestResourceBinding(mcp, options);
1015
- const authorizer = createRequestAuthorizer(mcp, options);
1041
+ const authorizer = createRequestAuthorizer(mcp, options, recorder);
1016
1042
  const handle = async (request) => {
1017
1043
  const authResult = await authorizer.authorize(request);
1018
1044
  if (!authResult.ok)
@@ -1045,7 +1071,7 @@ function isEmptyArgs(value) {
1045
1071
  }
1046
1072
  function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
1047
1073
  assertRestResourceBinding(mcp, options);
1048
- const authorizer = createRequestAuthorizer(mcp, options);
1074
+ const authorizer = createRequestAuthorizer(mcp, options, recorder);
1049
1075
  const handle = async (request, toolName) => {
1050
1076
  const authResult = await authorizer.authorize(request);
1051
1077
  if (!authResult.ok)
@@ -1103,20 +1129,30 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
1103
1129
  try {
1104
1130
  result = await tool.handler(args, new ToolContext(authResult.auth));
1105
1131
  } catch {
1106
- recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
1132
+ reportInvocation(recorder, {
1133
+ tool: tool.name,
1134
+ method: "tools/call",
1135
+ outcome: "handler_error",
1136
+ durationMs: nowMs() - start
1137
+ });
1107
1138
  return new Response(JSON.stringify({ error: "handler threw", tool: toolName }), {
1108
1139
  status: 500,
1109
1140
  headers: JSON_HEADERS
1110
1141
  });
1111
1142
  }
1112
1143
  if (result == null) {
1113
- recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
1144
+ reportInvocation(recorder, {
1145
+ tool: tool.name,
1146
+ method: "tools/call",
1147
+ outcome: "handler_error",
1148
+ durationMs: nowMs() - start
1149
+ });
1114
1150
  return new Response(JSON.stringify({ error: `tool "${toolName}" returned no result` }), {
1115
1151
  status: 500,
1116
1152
  headers: JSON_HEADERS
1117
1153
  });
1118
1154
  }
1119
- recorder.record({
1155
+ reportInvocation(recorder, {
1120
1156
  tool: tool.name,
1121
1157
  method: "tools/call",
1122
1158
  outcome: result.isError ? "tool_error" : "ok",
@@ -1183,7 +1219,7 @@ function createSupabaseHandler(mcp, options = {}) {
1183
1219
  const runtimeOptions = resourcePath === void 0 ? {} : { resourcePath, ...metadataPath ? { metadataPath } : {} };
1184
1220
  const recorder = createRecorderForRuntime(mcp);
1185
1221
  const mcpHandler = createMcpProtocolHandler(mcp, runtimeOptions, recorder);
1186
- const listToolsHandler = createListToolsHandler(mcp, runtimeOptions);
1222
+ const listToolsHandler = createListToolsHandler(mcp, runtimeOptions, recorder);
1187
1223
  const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions, recorder);
1188
1224
  const metadataHandler = createOAuthProtectedResourceMetadataHandler(mcp, runtimeOptions);
1189
1225
  return async (request) => {
@@ -3,26 +3,24 @@ import {
3
3
  } from "../../chunk-UQK5UO6C.js";
4
4
  import {
5
5
  createMcpProtocolHandler
6
- } from "../../chunk-RHDEW56Y.js";
6
+ } from "../../chunk-EZQVUNXB.js";
7
7
  import {
8
8
  createOAuthProtectedResourceMetadataHandler
9
- } from "../../chunk-QDKOF4UF.js";
9
+ } from "../../chunk-T24Z753F.js";
10
10
  import {
11
11
  createInvokeToolHandler
12
- } from "../../chunk-T2RED2TG.js";
12
+ } from "../../chunk-W2EZWPJ5.js";
13
13
  import {
14
14
  createListToolsHandler
15
- } from "../../chunk-VNRQPA4K.js";
15
+ } from "../../chunk-I5CAQUQI.js";
16
+ import "../../chunk-MA5H6PSF.js";
16
17
  import {
18
+ assertResourcePathShape,
17
19
  createRecorderForRuntime
18
- } from "../../chunk-XQSTN54Y.js";
19
- import "../../chunk-P3F324UC.js";
20
- import {
21
- assertResourcePathShape
22
- } from "../../chunk-G4XA7IJM.js";
20
+ } from "../../chunk-H6BKTNJY.js";
23
21
  import {
24
22
  trimTrailingSlash
25
- } from "../../chunk-QC3DXQTH.js";
23
+ } from "../../chunk-DCWYK4CA.js";
26
24
  import {
27
25
  OAUTH_PROTECTED_RESOURCE_METADATA_PATH
28
26
  } from "../../chunk-6DXGZZA4.js";
@@ -30,7 +28,7 @@ import {
30
28
  FUNCTIONS_MOUNT_PREFIX,
31
29
  assertFunctionName
32
30
  } from "../../chunk-XQWJN6DC.js";
33
- import "../../chunk-LWHVXXKQ.js";
31
+ import "../../chunk-SKSKC747.js";
34
32
 
35
33
  // src/stacks/supabase/handler.ts
36
34
  function deriveResourcePath(options) {
@@ -70,7 +68,7 @@ function createSupabaseHandler(mcp, options = {}) {
70
68
  const runtimeOptions = resourcePath === void 0 ? {} : { resourcePath, ...metadataPath ? { metadataPath } : {} };
71
69
  const recorder = createRecorderForRuntime(mcp);
72
70
  const mcpHandler = createMcpProtocolHandler(mcp, runtimeOptions, recorder);
73
- const listToolsHandler = createListToolsHandler(mcp, runtimeOptions);
71
+ const listToolsHandler = createListToolsHandler(mcp, runtimeOptions, recorder);
74
72
  const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions, recorder);
75
73
  const metadataHandler = createOAuthProtectedResourceMetadataHandler(mcp, runtimeOptions);
76
74
  return async (request) => {
@@ -25,6 +25,7 @@ __export(vite_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(vite_exports);
27
27
  var import_node_path2 = require("path");
28
+ var import_vite = require("vite");
28
29
 
29
30
  // src/stacks/supabase/emit.ts
30
31
  var import_esbuild = require("esbuild");
@@ -32,7 +33,7 @@ var import_node_fs = require("fs");
32
33
  var import_node_path = require("path");
33
34
 
34
35
  // package.json
35
- var version = "0.15.0";
36
+ var version = "0.16.0";
36
37
 
37
38
  // src/core/fs-errors.ts
38
39
  function isFileMissing(err) {
@@ -129,7 +130,16 @@ function externalizeBareAsNpm(versions) {
129
130
  }
130
131
  };
131
132
  }
132
- async function bundleFunctionEntry(projectRoot, mcpEntryAbs, functionName) {
133
+ function importMetaEnvDefines(inlineEnv) {
134
+ const env = inlineEnv ?? {};
135
+ const defines = { "import.meta.env": JSON.stringify(env) };
136
+ for (const [key, value] of Object.entries(env)) {
137
+ if (/^[A-Za-z_$][\w$]*$/.test(key))
138
+ defines[`import.meta.env.${key}`] = JSON.stringify(value);
139
+ }
140
+ return defines;
141
+ }
142
+ async function bundleFunctionEntry(projectRoot, mcpEntryAbs, functionName, inlineEnv) {
133
143
  const versions = readProjectDependencyVersions(projectRoot);
134
144
  const wrapper = `import mcp from ${JSON.stringify(mcpEntryAbs)};
135
145
  import { createSupabaseHandler } from "@lovable.dev/mcp-js/stacks/supabase";
@@ -145,6 +155,7 @@ Deno.serve(createSupabaseHandler(mcp, { functionName: ${JSON.stringify(functionN
145
155
  target: "esnext",
146
156
  absWorkingDir: projectRoot,
147
157
  logLevel: "silent",
158
+ define: importMetaEnvDefines(inlineEnv),
148
159
  plugins: [externalizeBareAsNpm(versions)]
149
160
  });
150
161
  const out = result.outputFiles[0]?.text ?? "";
@@ -183,7 +194,7 @@ async function syncSupabaseFunction(options) {
183
194
  cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set(), removed);
184
195
  return { written, removed, inputs: [] };
185
196
  }
186
- const { code, inputs } = await bundleFunctionEntry(projectRoot, paths.mcpEntryAbs, functionName);
197
+ const { code, inputs } = await bundleFunctionEntry(projectRoot, paths.mcpEntryAbs, functionName, options.inlineEnv);
187
198
  if (writeIfChanged(paths.functionEntry, code, hasGeneratedBanner)) {
188
199
  written.push(paths.functionEntry);
189
200
  }
@@ -227,6 +238,16 @@ function cleanupOrphans(functionDir, expected, removed) {
227
238
  function normalizePath(p) {
228
239
  return p.split(import_node_path2.sep).join("/");
229
240
  }
241
+ function productionImportMetaEnv(config) {
242
+ return {
243
+ ...(0, import_vite.loadEnv)("production", config.envDir, config.envPrefix),
244
+ MODE: "production",
245
+ BASE_URL: config.base ?? "/",
246
+ DEV: false,
247
+ PROD: true,
248
+ SSR: false
249
+ };
250
+ }
230
251
  function mcpPlugin(options = {}) {
231
252
  const mcpEntryOption = options.mcpEntry ?? DEFAULT_MCP_ENTRY;
232
253
  const functionsDirOption = options.functionsDir ?? DEFAULT_FUNCTIONS_DIR;
@@ -234,13 +255,15 @@ function mcpPlugin(options = {}) {
234
255
  const urlPath = `${FUNCTIONS_MOUNT_PREFIX}${functionName}`;
235
256
  let projectRoot = process.cwd();
236
257
  let mcpEntryAbs = (0, import_node_path2.resolve)(projectRoot, mcpEntryOption);
258
+ let inlineEnv = {};
237
259
  let watchedInputs = /* @__PURE__ */ new Set();
238
260
  const regenerate = async () => {
239
261
  const { inputs } = await syncSupabaseFunction({
240
262
  projectRoot,
241
263
  mcpEntry: mcpEntryOption,
242
264
  functionsDir: functionsDirOption,
243
- functionName
265
+ functionName,
266
+ inlineEnv
244
267
  });
245
268
  watchedInputs = new Set(inputs.map(normalizePath));
246
269
  };
@@ -258,6 +281,7 @@ function mcpPlugin(options = {}) {
258
281
  async configResolved(config) {
259
282
  projectRoot = config.root;
260
283
  mcpEntryAbs = (0, import_node_path2.resolve)(projectRoot, mcpEntryOption);
284
+ inlineEnv = productionImportMetaEnv(config);
261
285
  await regenerate();
262
286
  },
263
287
  configureServer(server) {
@@ -7,10 +7,11 @@ import {
7
7
  } from "../../chunk-XQWJN6DC.js";
8
8
  import {
9
9
  version
10
- } from "../../chunk-LWHVXXKQ.js";
10
+ } from "../../chunk-SKSKC747.js";
11
11
 
12
12
  // src/stacks/supabase/vite.ts
13
13
  import { resolve as resolve2, sep as sep2 } from "path";
14
+ import { loadEnv } from "vite";
14
15
 
15
16
  // src/stacks/supabase/emit.ts
16
17
  import { build } from "esbuild";
@@ -95,7 +96,16 @@ function externalizeBareAsNpm(versions) {
95
96
  }
96
97
  };
97
98
  }
98
- async function bundleFunctionEntry(projectRoot, mcpEntryAbs, functionName) {
99
+ function importMetaEnvDefines(inlineEnv) {
100
+ const env = inlineEnv ?? {};
101
+ const defines = { "import.meta.env": JSON.stringify(env) };
102
+ for (const [key, value] of Object.entries(env)) {
103
+ if (/^[A-Za-z_$][\w$]*$/.test(key))
104
+ defines[`import.meta.env.${key}`] = JSON.stringify(value);
105
+ }
106
+ return defines;
107
+ }
108
+ async function bundleFunctionEntry(projectRoot, mcpEntryAbs, functionName, inlineEnv) {
99
109
  const versions = readProjectDependencyVersions(projectRoot);
100
110
  const wrapper = `import mcp from ${JSON.stringify(mcpEntryAbs)};
101
111
  import { createSupabaseHandler } from "@lovable.dev/mcp-js/stacks/supabase";
@@ -111,6 +121,7 @@ Deno.serve(createSupabaseHandler(mcp, { functionName: ${JSON.stringify(functionN
111
121
  target: "esnext",
112
122
  absWorkingDir: projectRoot,
113
123
  logLevel: "silent",
124
+ define: importMetaEnvDefines(inlineEnv),
114
125
  plugins: [externalizeBareAsNpm(versions)]
115
126
  });
116
127
  const out = result.outputFiles[0]?.text ?? "";
@@ -149,7 +160,7 @@ async function syncSupabaseFunction(options) {
149
160
  cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set(), removed);
150
161
  return { written, removed, inputs: [] };
151
162
  }
152
- const { code, inputs } = await bundleFunctionEntry(projectRoot, paths.mcpEntryAbs, functionName);
163
+ const { code, inputs } = await bundleFunctionEntry(projectRoot, paths.mcpEntryAbs, functionName, options.inlineEnv);
153
164
  if (writeIfChanged(paths.functionEntry, code, hasGeneratedBanner)) {
154
165
  written.push(paths.functionEntry);
155
166
  }
@@ -193,6 +204,16 @@ function cleanupOrphans(functionDir, expected, removed) {
193
204
  function normalizePath(p) {
194
205
  return p.split(sep2).join("/");
195
206
  }
207
+ function productionImportMetaEnv(config) {
208
+ return {
209
+ ...loadEnv("production", config.envDir, config.envPrefix),
210
+ MODE: "production",
211
+ BASE_URL: config.base ?? "/",
212
+ DEV: false,
213
+ PROD: true,
214
+ SSR: false
215
+ };
216
+ }
196
217
  function mcpPlugin(options = {}) {
197
218
  const mcpEntryOption = options.mcpEntry ?? DEFAULT_MCP_ENTRY;
198
219
  const functionsDirOption = options.functionsDir ?? DEFAULT_FUNCTIONS_DIR;
@@ -200,13 +221,15 @@ function mcpPlugin(options = {}) {
200
221
  const urlPath = `${FUNCTIONS_MOUNT_PREFIX}${functionName}`;
201
222
  let projectRoot = process.cwd();
202
223
  let mcpEntryAbs = resolve2(projectRoot, mcpEntryOption);
224
+ let inlineEnv = {};
203
225
  let watchedInputs = /* @__PURE__ */ new Set();
204
226
  const regenerate = async () => {
205
227
  const { inputs } = await syncSupabaseFunction({
206
228
  projectRoot,
207
229
  mcpEntry: mcpEntryOption,
208
230
  functionsDir: functionsDirOption,
209
- functionName
231
+ functionName,
232
+ inlineEnv
210
233
  });
211
234
  watchedInputs = new Set(inputs.map(normalizePath));
212
235
  };
@@ -224,6 +247,7 @@ function mcpPlugin(options = {}) {
224
247
  async configResolved(config) {
225
248
  projectRoot = config.root;
226
249
  mcpEntryAbs = resolve2(projectRoot, mcpEntryOption);
250
+ inlineEnv = productionImportMetaEnv(config);
227
251
  await regenerate();
228
252
  },
229
253
  configureServer(server) {