@lovable.dev/mcp-js 0.14.0 → 0.15.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.
Files changed (35) hide show
  1. package/README.md +28 -0
  2. package/dist/{chunk-NOFPRSSI.js → chunk-LWHVXXKQ.js} +1 -1
  3. package/dist/{chunk-MA5H6PSF.js → chunk-P3F324UC.js} +34 -0
  4. package/dist/{chunk-WLNT2FX7.js → chunk-RHDEW56Y.js} +18 -4
  5. package/dist/{chunk-YWLMMDET.js → chunk-T2RED2TG.js} +15 -2
  6. package/dist/chunk-XQSTN54Y.js +189 -0
  7. package/dist/cli/extract-manifest.cjs +1 -1
  8. package/dist/cli/extract-manifest.js +3 -3
  9. package/dist/index.cjs +33 -0
  10. package/dist/index.d.cts +2 -2
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.js +4 -2
  13. package/dist/protocols/mcp/index.cjs +221 -3
  14. package/dist/protocols/mcp/index.d.cts +3 -2
  15. package/dist/protocols/mcp/index.d.ts +3 -2
  16. package/dist/protocols/mcp/index.js +4 -2
  17. package/dist/protocols/oauth-metadata.d.cts +1 -1
  18. package/dist/protocols/oauth-metadata.d.ts +1 -1
  19. package/dist/protocols/rest/index.cjs +218 -1
  20. package/dist/protocols/rest/index.d.cts +3 -2
  21. package/dist/protocols/rest/index.d.ts +3 -2
  22. package/dist/protocols/rest/index.js +4 -2
  23. package/dist/recorder-B-eJU4Qu.d.ts +21 -0
  24. package/dist/stacks/supabase/index.cjs +250 -22
  25. package/dist/stacks/supabase/index.d.cts +1 -1
  26. package/dist/stacks/supabase/index.d.ts +1 -1
  27. package/dist/stacks/supabase/index.js +10 -5
  28. package/dist/stacks/supabase/vite.cjs +1 -1
  29. package/dist/stacks/supabase/vite.js +3 -3
  30. package/dist/stacks/tanstack/index.cjs +231 -4
  31. package/dist/stacks/tanstack/index.d.cts +1 -1
  32. package/dist/stacks/tanstack/index.d.ts +1 -1
  33. package/dist/stacks/tanstack/index.js +5 -3
  34. package/dist/{types-BanzncFh.d.ts → types-COY42xux.d.ts} +30 -1
  35. package/package.json +1 -1
@@ -95,22 +95,6 @@ function firstForwardedValue(request, header) {
95
95
  return value ? value : void 0;
96
96
  }
97
97
 
98
- // src/protocols/mcp/protocol.ts
99
- var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
100
- var import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js");
101
-
102
- // src/core/http.ts
103
- var JSON_HEADERS = { "Content-Type": "application/json" };
104
- function headResponse(response) {
105
- return new Response(null, { status: response.status, statusText: response.statusText, headers: response.headers });
106
- }
107
- function methodNotAllowed(allow) {
108
- return new Response(JSON.stringify({ error: "method not allowed" }), {
109
- status: 405,
110
- headers: { ...JSON_HEADERS, Allow: allow }
111
- });
112
- }
113
-
114
98
  // src/core/logger.ts
115
99
  var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
116
100
  function isLogLevel(value) {
@@ -152,6 +136,230 @@ function describeError(err) {
152
136
  return { value: String(err) };
153
137
  }
154
138
 
139
+ // src/metrics/config.ts
140
+ var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
141
+ var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
142
+ var METRICS_SAMPLE_RATE = 1;
143
+ var METRICS_FLUSH_INTERVAL_MS = 5e3;
144
+ var METRICS_MAX_BATCH_SIZE = 50;
145
+ function assertMetricsEndpoint(endpoint) {
146
+ let url;
147
+ try {
148
+ url = new URL(endpoint);
149
+ } catch {
150
+ throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
151
+ }
152
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
153
+ throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
154
+ }
155
+ }
156
+ function resolveMetricsConfig(config = true) {
157
+ const options = typeof config === "boolean" ? { enabled: config } : config;
158
+ const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
159
+ assertMetricsEndpoint(endpoint);
160
+ return Object.freeze({
161
+ enabled: options.enabled ?? true,
162
+ endpoint,
163
+ headers: options.headers ?? {},
164
+ apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
165
+ sampleRate: METRICS_SAMPLE_RATE,
166
+ flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
167
+ maxBatchSize: METRICS_MAX_BATCH_SIZE
168
+ });
169
+ }
170
+
171
+ // package.json
172
+ var version = "0.15.0";
173
+
174
+ // src/metrics/otlp.ts
175
+ var SCOPE_NAME = "@lovable.dev/mcp-js";
176
+ var EVENT_NAME = "mcp.tool.invocation";
177
+ var SEVERITY_INFO = 9;
178
+ function strAttr(key, value) {
179
+ return { key, value: { stringValue: value } };
180
+ }
181
+ function intAttr(key, value) {
182
+ return { key, value: { intValue: String(Math.round(value)) } };
183
+ }
184
+ function toLogRecord(rec) {
185
+ const attributes = [
186
+ strAttr("event.name", EVENT_NAME),
187
+ strAttr("mcp.method", rec.method),
188
+ strAttr("mcp.outcome", rec.outcome),
189
+ intAttr("mcp.duration_ms", rec.durationMs)
190
+ ];
191
+ if (rec.tool !== null)
192
+ attributes.push(strAttr("mcp.tool", rec.tool));
193
+ if (rec.reqBytes !== void 0)
194
+ attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
195
+ if (rec.resBytes !== void 0)
196
+ attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
197
+ return {
198
+ timeUnixNano: rec.timeUnixNano,
199
+ observedTimeUnixNano: rec.timeUnixNano,
200
+ severityNumber: SEVERITY_INFO,
201
+ severityText: "INFO",
202
+ body: { stringValue: EVENT_NAME },
203
+ attributes
204
+ };
205
+ }
206
+ function buildLogsPayload(records, server) {
207
+ return JSON.stringify({
208
+ resourceLogs: [
209
+ {
210
+ resource: {
211
+ attributes: [
212
+ strAttr("service.name", server.name),
213
+ strAttr("service.version", server.version),
214
+ strAttr("telemetry.sdk.name", SCOPE_NAME),
215
+ strAttr("telemetry.sdk.version", version),
216
+ strAttr("telemetry.sdk.language", "webjs")
217
+ ]
218
+ },
219
+ scopeLogs: [
220
+ {
221
+ scope: { name: SCOPE_NAME, version },
222
+ logRecords: records.map(toLogRecord)
223
+ }
224
+ ]
225
+ }
226
+ ]
227
+ });
228
+ }
229
+ function nowUnixNano() {
230
+ return `${Date.now()}000000`;
231
+ }
232
+
233
+ // src/metrics/recorder.ts
234
+ function nowMs() {
235
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
236
+ }
237
+ var NOOP_RECORDER = {
238
+ record() {
239
+ },
240
+ async flush() {
241
+ }
242
+ };
243
+ function createNoopRecorder() {
244
+ return NOOP_RECORDER;
245
+ }
246
+ function readRuntimeEnv(name) {
247
+ try {
248
+ const denoEnv = globalThis.Deno?.env;
249
+ const value = denoEnv?.get?.(name);
250
+ if (value)
251
+ return value;
252
+ } catch {
253
+ }
254
+ try {
255
+ if (typeof process !== "undefined") {
256
+ const value = process.env?.[name];
257
+ if (value)
258
+ return value;
259
+ }
260
+ } catch {
261
+ }
262
+ return void 0;
263
+ }
264
+ function probeWaitUntil() {
265
+ const fn = globalThis.EdgeRuntime?.waitUntil;
266
+ return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
267
+ }
268
+ function createMetricsRecorder(ctx, deps = {}) {
269
+ const { config, server } = ctx;
270
+ if (!config.enabled)
271
+ return NOOP_RECORDER;
272
+ 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 });
279
+ return NOOP_RECORDER;
280
+ }
281
+ const waitUntil = deps.waitUntil ?? probeWaitUntil();
282
+ const buffer = [];
283
+ let timer;
284
+ const schedule = (p) => {
285
+ if (waitUntil) {
286
+ try {
287
+ waitUntil(p);
288
+ return;
289
+ } catch {
290
+ }
291
+ }
292
+ void p.catch(() => {
293
+ });
294
+ };
295
+ const headers = {};
296
+ if (!usesLovableEndpoint) {
297
+ for (const [key, value] of Object.entries(config.headers)) {
298
+ if (key.toLowerCase() !== "content-type")
299
+ headers[key] = value;
300
+ }
301
+ }
302
+ headers["content-type"] = "application/json";
303
+ if (apiKey)
304
+ headers.authorization = `Bearer ${apiKey}`;
305
+ const flush = async () => {
306
+ if (buffer.length === 0)
307
+ return;
308
+ const records = buffer.splice(0, buffer.length);
309
+ try {
310
+ const res = await doFetch(config.endpoint, {
311
+ method: "POST",
312
+ headers,
313
+ body: buildLogsPayload(records, server),
314
+ keepalive: true
315
+ });
316
+ if (!res.ok)
317
+ log.debug("metrics.flush_rejected", { status: res.status });
318
+ } catch (err) {
319
+ log.debug("metrics.flush_failed", describeError(err));
320
+ }
321
+ };
322
+ const ensureTimer = () => {
323
+ if (timer !== void 0)
324
+ return;
325
+ timer = setInterval(() => schedule(flush()), config.flushIntervalMs);
326
+ timer.unref?.();
327
+ };
328
+ return {
329
+ record(ev) {
330
+ if (config.sampleRate < 1 && Math.random() >= config.sampleRate)
331
+ return;
332
+ buffer.push({ ...ev, timeUnixNano: nowUnixNano() });
333
+ ensureTimer();
334
+ if (buffer.length >= config.maxBatchSize)
335
+ schedule(flush());
336
+ },
337
+ flush
338
+ };
339
+ }
340
+ function createRecorderForRuntime(mcp) {
341
+ const config = resolveMetricsConfig(mcp.metrics);
342
+ if (!config.enabled)
343
+ return createNoopRecorder();
344
+ return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
345
+ }
346
+
347
+ // src/protocols/mcp/protocol.ts
348
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
349
+ var import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js");
350
+
351
+ // src/core/http.ts
352
+ var JSON_HEADERS = { "Content-Type": "application/json" };
353
+ function headResponse(response) {
354
+ return new Response(null, { status: response.status, statusText: response.statusText, headers: response.headers });
355
+ }
356
+ function methodNotAllowed(allow) {
357
+ return new Response(JSON.stringify({ error: "method not allowed" }), {
358
+ status: 405,
359
+ headers: { ...JSON_HEADERS, Allow: allow }
360
+ });
361
+ }
362
+
155
363
  // src/core/promise.ts
156
364
  function cachedPromise(load, label) {
157
365
  let settled = false;
@@ -647,22 +855,31 @@ function corsPreflightResponse(allowMethods) {
647
855
  }
648
856
 
649
857
  // src/protocols/mcp/protocol.ts
650
- function adaptToolToSdkCallback(tool, auth) {
858
+ function adaptToolToSdkCallback(tool, auth, recorder) {
651
859
  return async (first) => {
652
860
  const args = tool.inputSchema ? first ?? {} : {};
861
+ const start = nowMs();
653
862
  let result;
654
863
  try {
655
864
  result = await tool.handler(args, new ToolContext(auth));
656
865
  } catch {
866
+ recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
657
867
  return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
658
868
  }
659
869
  if (result == null) {
870
+ recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
660
871
  return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
661
872
  }
873
+ recorder.record({
874
+ tool: tool.name,
875
+ method: "tools/call",
876
+ outcome: result.isError ? "tool_error" : "ok",
877
+ durationMs: nowMs() - start
878
+ });
662
879
  return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
663
880
  };
664
881
  }
665
- function createMcpProtocolHandler(mcp, options = {}) {
882
+ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
666
883
  const authorizer = createRequestAuthorizer(mcp, options);
667
884
  const handle = async (request) => {
668
885
  const authResult = await authorizer.authorize(request);
@@ -683,7 +900,7 @@ function createMcpProtocolHandler(mcp, options = {}) {
683
900
  outputSchema: tool.outputSchema,
684
901
  annotations: tool.annotations
685
902
  },
686
- adaptToolToSdkCallback(tool, authResult.auth)
903
+ adaptToolToSdkCallback(tool, authResult.auth, recorder)
687
904
  );
688
905
  }
689
906
  const transport = new import_webStandardStreamableHttp.WebStandardStreamableHTTPServerTransport({
@@ -692,6 +909,7 @@ function createMcpProtocolHandler(mcp, options = {}) {
692
909
  await server.connect(transport);
693
910
  return await transport.handleRequest(request);
694
911
  } catch (err) {
912
+ recorder.record({ tool: null, method: "transport", outcome: "transport_error", durationMs: 0 });
695
913
  log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
696
914
  return Response.json(
697
915
  { jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
@@ -825,7 +1043,7 @@ function isEmptyArgs(value) {
825
1043
  return false;
826
1044
  return Object.keys(value).length === 0;
827
1045
  }
828
- function createInvokeToolHandler(mcp, options = {}) {
1046
+ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
829
1047
  assertRestResourceBinding(mcp, options);
830
1048
  const authorizer = createRequestAuthorizer(mcp, options);
831
1049
  const handle = async (request, toolName) => {
@@ -881,20 +1099,29 @@ function createInvokeToolHandler(mcp, options = {}) {
881
1099
  });
882
1100
  }
883
1101
  let result;
1102
+ const start = nowMs();
884
1103
  try {
885
1104
  result = await tool.handler(args, new ToolContext(authResult.auth));
886
1105
  } catch {
1106
+ recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
887
1107
  return new Response(JSON.stringify({ error: "handler threw", tool: toolName }), {
888
1108
  status: 500,
889
1109
  headers: JSON_HEADERS
890
1110
  });
891
1111
  }
892
1112
  if (result == null) {
1113
+ recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
893
1114
  return new Response(JSON.stringify({ error: `tool "${toolName}" returned no result` }), {
894
1115
  status: 500,
895
1116
  headers: JSON_HEADERS
896
1117
  });
897
1118
  }
1119
+ recorder.record({
1120
+ tool: tool.name,
1121
+ method: "tools/call",
1122
+ outcome: result.isError ? "tool_error" : "ok",
1123
+ durationMs: nowMs() - start
1124
+ });
898
1125
  return Response.json({
899
1126
  content: result.content ?? [],
900
1127
  structuredContent: result.structuredContent,
@@ -954,9 +1181,10 @@ function createSupabaseHandler(mcp, options = {}) {
954
1181
  const servesOwnPrm = !(mcp.auth?.type === "oauth" && mcp.auth.protectedResourceMetadataUrl !== void 0);
955
1182
  const metadataPath = resourcePath === void 0 || !servesOwnPrm ? void 0 : `${trimTrailingSlash(resourcePath)}${OAUTH_PROTECTED_RESOURCE_METADATA_PATH}`;
956
1183
  const runtimeOptions = resourcePath === void 0 ? {} : { resourcePath, ...metadataPath ? { metadataPath } : {} };
957
- const mcpHandler = createMcpProtocolHandler(mcp, runtimeOptions);
1184
+ const recorder = createRecorderForRuntime(mcp);
1185
+ const mcpHandler = createMcpProtocolHandler(mcp, runtimeOptions, recorder);
958
1186
  const listToolsHandler = createListToolsHandler(mcp, runtimeOptions);
959
- const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions);
1187
+ const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions, recorder);
960
1188
  const metadataHandler = createOAuthProtectedResourceMetadataHandler(mcp, runtimeOptions);
961
1189
  return async (request) => {
962
1190
  const req = applyForwardedOrigin(request, {
@@ -1,4 +1,4 @@
1
- import { d as McpDefinition } from '../../types-BanzncFh.js';
1
+ import { d as McpDefinition } from '../../types-COY42xux.js';
2
2
  import 'zod';
3
3
 
4
4
  type SupabaseHandler = (request: Request) => Promise<Response>;
@@ -1,4 +1,4 @@
1
- import { d as McpDefinition } from '../../types-BanzncFh.js';
1
+ import { d as McpDefinition } from '../../types-COY42xux.js';
2
2
  import 'zod';
3
3
 
4
4
  type SupabaseHandler = (request: Request) => Promise<Response>;
@@ -3,17 +3,20 @@ import {
3
3
  } from "../../chunk-UQK5UO6C.js";
4
4
  import {
5
5
  createMcpProtocolHandler
6
- } from "../../chunk-WLNT2FX7.js";
6
+ } from "../../chunk-RHDEW56Y.js";
7
7
  import {
8
8
  createOAuthProtectedResourceMetadataHandler
9
9
  } from "../../chunk-QDKOF4UF.js";
10
10
  import {
11
11
  createInvokeToolHandler
12
- } from "../../chunk-YWLMMDET.js";
12
+ } from "../../chunk-T2RED2TG.js";
13
13
  import {
14
14
  createListToolsHandler
15
15
  } from "../../chunk-VNRQPA4K.js";
16
- import "../../chunk-MA5H6PSF.js";
16
+ import {
17
+ createRecorderForRuntime
18
+ } from "../../chunk-XQSTN54Y.js";
19
+ import "../../chunk-P3F324UC.js";
17
20
  import {
18
21
  assertResourcePathShape
19
22
  } from "../../chunk-G4XA7IJM.js";
@@ -27,6 +30,7 @@ import {
27
30
  FUNCTIONS_MOUNT_PREFIX,
28
31
  assertFunctionName
29
32
  } from "../../chunk-XQWJN6DC.js";
33
+ import "../../chunk-LWHVXXKQ.js";
30
34
 
31
35
  // src/stacks/supabase/handler.ts
32
36
  function deriveResourcePath(options) {
@@ -64,9 +68,10 @@ function createSupabaseHandler(mcp, options = {}) {
64
68
  const servesOwnPrm = !(mcp.auth?.type === "oauth" && mcp.auth.protectedResourceMetadataUrl !== void 0);
65
69
  const metadataPath = resourcePath === void 0 || !servesOwnPrm ? void 0 : `${trimTrailingSlash(resourcePath)}${OAUTH_PROTECTED_RESOURCE_METADATA_PATH}`;
66
70
  const runtimeOptions = resourcePath === void 0 ? {} : { resourcePath, ...metadataPath ? { metadataPath } : {} };
67
- const mcpHandler = createMcpProtocolHandler(mcp, runtimeOptions);
71
+ const recorder = createRecorderForRuntime(mcp);
72
+ const mcpHandler = createMcpProtocolHandler(mcp, runtimeOptions, recorder);
68
73
  const listToolsHandler = createListToolsHandler(mcp, runtimeOptions);
69
- const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions);
74
+ const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions, recorder);
70
75
  const metadataHandler = createOAuthProtectedResourceMetadataHandler(mcp, runtimeOptions);
71
76
  return async (request) => {
72
77
  const req = applyForwardedOrigin(request, {
@@ -32,7 +32,7 @@ var import_node_fs = require("fs");
32
32
  var import_node_path = require("path");
33
33
 
34
34
  // package.json
35
- var version = "0.14.0";
35
+ var version = "0.15.0";
36
36
 
37
37
  // src/core/fs-errors.ts
38
38
  function isFileMissing(err) {
@@ -1,6 +1,3 @@
1
- import {
2
- version
3
- } from "../../chunk-NOFPRSSI.js";
4
1
  import {
5
2
  isFileMissing
6
3
  } from "../../chunk-Y3ZFPEQH.js";
@@ -8,6 +5,9 @@ import {
8
5
  FUNCTIONS_MOUNT_PREFIX,
9
6
  assertFunctionName
10
7
  } from "../../chunk-XQWJN6DC.js";
8
+ import {
9
+ version
10
+ } from "../../chunk-LWHVXXKQ.js";
11
11
 
12
12
  // src/stacks/supabase/vite.ts
13
13
  import { resolve as resolve2, sep as sep2 } from "path";