@lovable.dev/mcp-js 0.15.1 → 0.17.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.
@@ -46,16 +46,29 @@ var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
46
46
  function isLogLevel(value) {
47
47
  return typeof value === "string" && value in LEVEL_RANK;
48
48
  }
49
+ var LOG_LEVEL_ENV_VAR = "LOVABLE_MCP_LOG_LEVEL";
49
50
  function readEnvLevel() {
50
51
  try {
51
- const raw = typeof process !== "undefined" ? process.env?.["LOVABLE_MCP_LOG_LEVEL"] : void 0;
52
+ const raw = typeof process !== "undefined" ? process.env?.[LOG_LEVEL_ENV_VAR] : void 0;
52
53
  const normalized = raw?.trim().toLowerCase();
53
54
  return isLogLevel(normalized) ? normalized : void 0;
54
55
  } catch {
55
56
  return void 0;
56
57
  }
57
58
  }
58
- var currentLevel = readEnvLevel() ?? "silent";
59
+ var currentLevel = readEnvLevel() ?? "debug";
60
+ function setLogLevel(level) {
61
+ currentLevel = level;
62
+ }
63
+ function parseLogLevel(raw) {
64
+ const normalized = raw?.trim().toLowerCase();
65
+ return isLogLevel(normalized) ? normalized : void 0;
66
+ }
67
+ function applyLogLevelFromEnv(raw) {
68
+ const level = parseLogLevel(raw);
69
+ if (level)
70
+ setLogLevel(level);
71
+ }
59
72
  function enabled(level) {
60
73
  return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
61
74
  }
@@ -115,7 +128,7 @@ function resolveMetricsConfig(config = true) {
115
128
  }
116
129
 
117
130
  // package.json
118
- var version = "0.15.1";
131
+ var version = "0.17.0";
119
132
 
120
133
  // src/metrics/otlp.ts
121
134
  var SCOPE_NAME = "@lovable.dev/mcp-js";
@@ -180,6 +193,10 @@ function nowUnixNano() {
180
193
  function nowMs() {
181
194
  return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
182
195
  }
196
+ function reportInvocation(recorder, ev) {
197
+ log.info("tool.invoked", { tool: ev.tool, method: ev.method, outcome: ev.outcome, durationMs: ev.durationMs });
198
+ recorder.record(ev);
199
+ }
183
200
  var NOOP_RECORDER = {
184
201
  record() {
185
202
  },
@@ -207,51 +224,92 @@ function readRuntimeEnv(name) {
207
224
  }
208
225
  return void 0;
209
226
  }
227
+ var cloudflareEnvPromise;
228
+ async function readCloudflareEnv(name) {
229
+ try {
230
+ const moduleSpecifier = "cloudflare:workers";
231
+ cloudflareEnvPromise ??= import(
232
+ /* @vite-ignore */
233
+ moduleSpecifier
234
+ ).then((m) => m.env).catch(() => void 0);
235
+ const env = await cloudflareEnvPromise;
236
+ const value = env?.[name];
237
+ return typeof value === "string" && value ? value : void 0;
238
+ } catch {
239
+ return void 0;
240
+ }
241
+ }
210
242
  function probeWaitUntil() {
211
243
  const fn = globalThis.EdgeRuntime?.waitUntil;
212
244
  return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
213
245
  }
214
246
  function createMetricsRecorder(ctx, deps = {}) {
215
247
  const { config, server } = ctx;
248
+ applyLogLevelFromEnv(readRuntimeEnv(LOG_LEVEL_ENV_VAR));
216
249
  if (!config.enabled)
217
250
  return NOOP_RECORDER;
218
251
  const doFetch = deps.fetch ?? globalThis.fetch;
219
- if (!doFetch)
220
- return NOOP_RECORDER;
221
- const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
222
- const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
223
- if (usesLovableEndpoint && !apiKey) {
224
- log.debug("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar });
252
+ if (!doFetch) {
253
+ log.warn("metrics.disabled_no_fetch", { endpoint: config.endpoint });
225
254
  return NOOP_RECORDER;
226
255
  }
256
+ const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
227
257
  const waitUntil = deps.waitUntil ?? probeWaitUntil();
228
258
  const buffer = [];
229
259
  let timer;
260
+ let lazyLogLevelApplied = false;
261
+ const applyLazyLogLevel = async () => {
262
+ if (lazyLogLevelApplied)
263
+ return;
264
+ lazyLogLevelApplied = true;
265
+ applyLogLevelFromEnv(await readCloudflareEnv(LOG_LEVEL_ENV_VAR));
266
+ };
267
+ let apiKey;
268
+ const resolveApiKey = async () => {
269
+ if (apiKey)
270
+ return apiKey;
271
+ apiKey = (deps.getApiKey ? deps.getApiKey() : readRuntimeEnv(config.apiKeyEnvVar)) ?? await readCloudflareEnv(config.apiKeyEnvVar);
272
+ return apiKey;
273
+ };
230
274
  const schedule = (p) => {
231
275
  if (waitUntil) {
232
276
  try {
233
277
  waitUntil(p);
234
278
  return;
235
- } catch {
279
+ } catch (err) {
280
+ log.warn("metrics.wait_until_failed", describeError(err));
236
281
  }
237
282
  }
238
283
  void p.catch(() => {
239
284
  });
240
285
  };
241
- const headers = {};
286
+ const baseHeaders = {};
242
287
  if (!usesLovableEndpoint) {
243
288
  for (const [key, value] of Object.entries(config.headers)) {
244
289
  if (key.toLowerCase() !== "content-type")
245
- headers[key] = value;
290
+ baseHeaders[key] = value;
246
291
  }
247
292
  }
248
- headers["content-type"] = "application/json";
249
- if (apiKey)
250
- headers.authorization = `Bearer ${apiKey}`;
293
+ baseHeaders["content-type"] = "application/json";
251
294
  const flush = async () => {
295
+ void applyLazyLogLevel();
252
296
  if (buffer.length === 0)
253
297
  return;
254
298
  const records = buffer.splice(0, buffer.length);
299
+ const dropped = records.length;
300
+ const headers = { ...baseHeaders };
301
+ if (usesLovableEndpoint) {
302
+ const key = await resolveApiKey();
303
+ if (!key) {
304
+ log.warn("metrics.disabled_no_api_key", {
305
+ envVar: config.apiKeyEnvVar,
306
+ endpoint: config.endpoint,
307
+ dropped
308
+ });
309
+ return;
310
+ }
311
+ headers.authorization = `Bearer ${key}`;
312
+ }
255
313
  try {
256
314
  const res = await doFetch(config.endpoint, {
257
315
  method: "POST",
@@ -259,10 +317,11 @@ function createMetricsRecorder(ctx, deps = {}) {
259
317
  body: buildLogsPayload(records, server),
260
318
  keepalive: true
261
319
  });
262
- if (!res.ok)
263
- log.debug("metrics.flush_rejected", { status: res.status });
320
+ if (!res.ok) {
321
+ log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
322
+ }
264
323
  } catch (err) {
265
- log.debug("metrics.flush_failed", describeError(err));
324
+ log.warn("metrics.flush_failed", { ...describeError(err), dropped, endpoint: config.endpoint });
266
325
  }
267
326
  };
268
327
  const ensureTimer = () => {
@@ -957,20 +1016,30 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
957
1016
  try {
958
1017
  result = await tool.handler(args, new ToolContext(authResult.auth));
959
1018
  } catch {
960
- recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
1019
+ reportInvocation(recorder, {
1020
+ tool: tool.name,
1021
+ method: "tools/call",
1022
+ outcome: "handler_error",
1023
+ durationMs: nowMs() - start
1024
+ });
961
1025
  return new Response(JSON.stringify({ error: "handler threw", tool: toolName }), {
962
1026
  status: 500,
963
1027
  headers: JSON_HEADERS
964
1028
  });
965
1029
  }
966
1030
  if (result == null) {
967
- recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
1031
+ reportInvocation(recorder, {
1032
+ tool: tool.name,
1033
+ method: "tools/call",
1034
+ outcome: "handler_error",
1035
+ durationMs: nowMs() - start
1036
+ });
968
1037
  return new Response(JSON.stringify({ error: `tool "${toolName}" returned no result` }), {
969
1038
  status: 500,
970
1039
  headers: JSON_HEADERS
971
1040
  });
972
1041
  }
973
- recorder.record({
1042
+ reportInvocation(recorder, {
974
1043
  tool: tool.name,
975
1044
  method: "tools/call",
976
1045
  outcome: result.isError ? "tool_error" : "ok",
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  createInvokeToolHandler
3
- } from "../../chunk-CEDD57G2.js";
3
+ } from "../../chunk-WYXIX7XF.js";
4
4
  import {
5
5
  createListToolsHandler
6
- } from "../../chunk-QT5L4CS3.js";
6
+ } from "../../chunk-FW6YL5C5.js";
7
7
  import "../../chunk-MA5H6PSF.js";
8
- import "../../chunk-YTOMGJV5.js";
9
- import "../../chunk-DCWYK4CA.js";
8
+ import "../../chunk-6TZNGFRM.js";
9
+ import "../../chunk-VQQZAFRA.js";
10
10
  import "../../chunk-6DXGZZA4.js";
11
- import "../../chunk-GIHCQT6L.js";
11
+ import "../../chunk-J5BTCP6T.js";
12
12
  export {
13
13
  createInvokeToolHandler,
14
14
  createListToolsHandler
@@ -100,16 +100,29 @@ var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
100
100
  function isLogLevel(value) {
101
101
  return typeof value === "string" && value in LEVEL_RANK;
102
102
  }
103
+ var LOG_LEVEL_ENV_VAR = "LOVABLE_MCP_LOG_LEVEL";
103
104
  function readEnvLevel() {
104
105
  try {
105
- const raw = typeof process !== "undefined" ? process.env?.["LOVABLE_MCP_LOG_LEVEL"] : void 0;
106
+ const raw = typeof process !== "undefined" ? process.env?.[LOG_LEVEL_ENV_VAR] : void 0;
106
107
  const normalized = raw?.trim().toLowerCase();
107
108
  return isLogLevel(normalized) ? normalized : void 0;
108
109
  } catch {
109
110
  return void 0;
110
111
  }
111
112
  }
112
- var currentLevel = readEnvLevel() ?? "silent";
113
+ var currentLevel = readEnvLevel() ?? "debug";
114
+ function setLogLevel(level) {
115
+ currentLevel = level;
116
+ }
117
+ function parseLogLevel(raw) {
118
+ const normalized = raw?.trim().toLowerCase();
119
+ return isLogLevel(normalized) ? normalized : void 0;
120
+ }
121
+ function applyLogLevelFromEnv(raw) {
122
+ const level = parseLogLevel(raw);
123
+ if (level)
124
+ setLogLevel(level);
125
+ }
113
126
  function enabled(level) {
114
127
  return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
115
128
  }
@@ -169,7 +182,7 @@ function resolveMetricsConfig(config = true) {
169
182
  }
170
183
 
171
184
  // package.json
172
- var version = "0.15.1";
185
+ var version = "0.17.0";
173
186
 
174
187
  // src/metrics/otlp.ts
175
188
  var SCOPE_NAME = "@lovable.dev/mcp-js";
@@ -234,6 +247,10 @@ function nowUnixNano() {
234
247
  function nowMs() {
235
248
  return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
236
249
  }
250
+ function reportInvocation(recorder, ev) {
251
+ log.info("tool.invoked", { tool: ev.tool, method: ev.method, outcome: ev.outcome, durationMs: ev.durationMs });
252
+ recorder.record(ev);
253
+ }
237
254
  var NOOP_RECORDER = {
238
255
  record() {
239
256
  },
@@ -261,51 +278,92 @@ function readRuntimeEnv(name) {
261
278
  }
262
279
  return void 0;
263
280
  }
281
+ var cloudflareEnvPromise;
282
+ async function readCloudflareEnv(name) {
283
+ try {
284
+ const moduleSpecifier = "cloudflare:workers";
285
+ cloudflareEnvPromise ??= import(
286
+ /* @vite-ignore */
287
+ moduleSpecifier
288
+ ).then((m) => m.env).catch(() => void 0);
289
+ const env = await cloudflareEnvPromise;
290
+ const value = env?.[name];
291
+ return typeof value === "string" && value ? value : void 0;
292
+ } catch {
293
+ return void 0;
294
+ }
295
+ }
264
296
  function probeWaitUntil() {
265
297
  const fn = globalThis.EdgeRuntime?.waitUntil;
266
298
  return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
267
299
  }
268
300
  function createMetricsRecorder(ctx, deps = {}) {
269
301
  const { config, server } = ctx;
302
+ applyLogLevelFromEnv(readRuntimeEnv(LOG_LEVEL_ENV_VAR));
270
303
  if (!config.enabled)
271
304
  return NOOP_RECORDER;
272
305
  const doFetch = deps.fetch ?? globalThis.fetch;
273
- if (!doFetch)
274
- return NOOP_RECORDER;
275
- const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
276
- const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
277
- if (usesLovableEndpoint && !apiKey) {
278
- log.debug("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar });
306
+ if (!doFetch) {
307
+ log.warn("metrics.disabled_no_fetch", { endpoint: config.endpoint });
279
308
  return NOOP_RECORDER;
280
309
  }
310
+ const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
281
311
  const waitUntil = deps.waitUntil ?? probeWaitUntil();
282
312
  const buffer = [];
283
313
  let timer;
314
+ let lazyLogLevelApplied = false;
315
+ const applyLazyLogLevel = async () => {
316
+ if (lazyLogLevelApplied)
317
+ return;
318
+ lazyLogLevelApplied = true;
319
+ applyLogLevelFromEnv(await readCloudflareEnv(LOG_LEVEL_ENV_VAR));
320
+ };
321
+ let apiKey;
322
+ const resolveApiKey = async () => {
323
+ if (apiKey)
324
+ return apiKey;
325
+ apiKey = (deps.getApiKey ? deps.getApiKey() : readRuntimeEnv(config.apiKeyEnvVar)) ?? await readCloudflareEnv(config.apiKeyEnvVar);
326
+ return apiKey;
327
+ };
284
328
  const schedule = (p) => {
285
329
  if (waitUntil) {
286
330
  try {
287
331
  waitUntil(p);
288
332
  return;
289
- } catch {
333
+ } catch (err) {
334
+ log.warn("metrics.wait_until_failed", describeError(err));
290
335
  }
291
336
  }
292
337
  void p.catch(() => {
293
338
  });
294
339
  };
295
- const headers = {};
340
+ const baseHeaders = {};
296
341
  if (!usesLovableEndpoint) {
297
342
  for (const [key, value] of Object.entries(config.headers)) {
298
343
  if (key.toLowerCase() !== "content-type")
299
- headers[key] = value;
344
+ baseHeaders[key] = value;
300
345
  }
301
346
  }
302
- headers["content-type"] = "application/json";
303
- if (apiKey)
304
- headers.authorization = `Bearer ${apiKey}`;
347
+ baseHeaders["content-type"] = "application/json";
305
348
  const flush = async () => {
349
+ void applyLazyLogLevel();
306
350
  if (buffer.length === 0)
307
351
  return;
308
352
  const records = buffer.splice(0, buffer.length);
353
+ const dropped = records.length;
354
+ const headers = { ...baseHeaders };
355
+ if (usesLovableEndpoint) {
356
+ const key = await resolveApiKey();
357
+ if (!key) {
358
+ log.warn("metrics.disabled_no_api_key", {
359
+ envVar: config.apiKeyEnvVar,
360
+ endpoint: config.endpoint,
361
+ dropped
362
+ });
363
+ return;
364
+ }
365
+ headers.authorization = `Bearer ${key}`;
366
+ }
309
367
  try {
310
368
  const res = await doFetch(config.endpoint, {
311
369
  method: "POST",
@@ -313,10 +371,11 @@ function createMetricsRecorder(ctx, deps = {}) {
313
371
  body: buildLogsPayload(records, server),
314
372
  keepalive: true
315
373
  });
316
- if (!res.ok)
317
- log.debug("metrics.flush_rejected", { status: res.status });
374
+ if (!res.ok) {
375
+ log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
376
+ }
318
377
  } catch (err) {
319
- log.debug("metrics.flush_failed", describeError(err));
378
+ log.warn("metrics.flush_failed", { ...describeError(err), dropped, endpoint: config.endpoint });
320
379
  }
321
380
  };
322
381
  const ensureTimer = () => {
@@ -870,14 +929,24 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
870
929
  try {
871
930
  result = await tool.handler(args, new ToolContext(auth));
872
931
  } catch {
873
- recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
932
+ reportInvocation(recorder, {
933
+ tool: tool.name,
934
+ method: "tools/call",
935
+ outcome: "handler_error",
936
+ durationMs: nowMs() - start
937
+ });
874
938
  return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
875
939
  }
876
940
  if (result == null) {
877
- recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
941
+ reportInvocation(recorder, {
942
+ tool: tool.name,
943
+ method: "tools/call",
944
+ outcome: "handler_error",
945
+ durationMs: nowMs() - start
946
+ });
878
947
  return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
879
948
  }
880
- recorder.record({
949
+ reportInvocation(recorder, {
881
950
  tool: tool.name,
882
951
  method: "tools/call",
883
952
  outcome: result.isError ? "tool_error" : "ok",
@@ -1110,20 +1179,30 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
1110
1179
  try {
1111
1180
  result = await tool.handler(args, new ToolContext(authResult.auth));
1112
1181
  } catch {
1113
- recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
1182
+ reportInvocation(recorder, {
1183
+ tool: tool.name,
1184
+ method: "tools/call",
1185
+ outcome: "handler_error",
1186
+ durationMs: nowMs() - start
1187
+ });
1114
1188
  return new Response(JSON.stringify({ error: "handler threw", tool: toolName }), {
1115
1189
  status: 500,
1116
1190
  headers: JSON_HEADERS
1117
1191
  });
1118
1192
  }
1119
1193
  if (result == null) {
1120
- recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
1194
+ reportInvocation(recorder, {
1195
+ tool: tool.name,
1196
+ method: "tools/call",
1197
+ outcome: "handler_error",
1198
+ durationMs: nowMs() - start
1199
+ });
1121
1200
  return new Response(JSON.stringify({ error: `tool "${toolName}" returned no result` }), {
1122
1201
  status: 500,
1123
1202
  headers: JSON_HEADERS
1124
1203
  });
1125
1204
  }
1126
- recorder.record({
1205
+ reportInvocation(recorder, {
1127
1206
  tool: tool.name,
1128
1207
  method: "tools/call",
1129
1208
  outcome: result.isError ? "tool_error" : "ok",
@@ -3,24 +3,24 @@ import {
3
3
  } from "../../chunk-UQK5UO6C.js";
4
4
  import {
5
5
  createMcpProtocolHandler
6
- } from "../../chunk-UCPYLSNN.js";
6
+ } from "../../chunk-EL6YNP2R.js";
7
7
  import {
8
8
  createOAuthProtectedResourceMetadataHandler
9
- } from "../../chunk-ILYTJXDR.js";
9
+ } from "../../chunk-H2HXKAMQ.js";
10
10
  import {
11
11
  createInvokeToolHandler
12
- } from "../../chunk-CEDD57G2.js";
12
+ } from "../../chunk-WYXIX7XF.js";
13
13
  import {
14
14
  createListToolsHandler
15
- } from "../../chunk-QT5L4CS3.js";
15
+ } from "../../chunk-FW6YL5C5.js";
16
16
  import "../../chunk-MA5H6PSF.js";
17
17
  import {
18
18
  assertResourcePathShape,
19
19
  createRecorderForRuntime
20
- } from "../../chunk-YTOMGJV5.js";
20
+ } from "../../chunk-6TZNGFRM.js";
21
21
  import {
22
22
  trimTrailingSlash
23
- } from "../../chunk-DCWYK4CA.js";
23
+ } from "../../chunk-VQQZAFRA.js";
24
24
  import {
25
25
  OAUTH_PROTECTED_RESOURCE_METADATA_PATH
26
26
  } from "../../chunk-6DXGZZA4.js";
@@ -28,7 +28,7 @@ import {
28
28
  FUNCTIONS_MOUNT_PREFIX,
29
29
  assertFunctionName
30
30
  } from "../../chunk-XQWJN6DC.js";
31
- import "../../chunk-GIHCQT6L.js";
31
+ import "../../chunk-J5BTCP6T.js";
32
32
 
33
33
  // src/stacks/supabase/handler.ts
34
34
  function deriveResourcePath(options) {
@@ -33,7 +33,7 @@ var import_node_fs = require("fs");
33
33
  var import_node_path = require("path");
34
34
 
35
35
  // package.json
36
- var version = "0.15.1";
36
+ var version = "0.17.0";
37
37
 
38
38
  // src/core/fs-errors.ts
39
39
  function isFileMissing(err) {
@@ -7,7 +7,7 @@ import {
7
7
  } from "../../chunk-XQWJN6DC.js";
8
8
  import {
9
9
  version
10
- } from "../../chunk-GIHCQT6L.js";
10
+ } from "../../chunk-J5BTCP6T.js";
11
11
 
12
12
  // src/stacks/supabase/vite.ts
13
13
  import { resolve as resolve2, sep as sep2 } from "path";