@lovable.dev/mcp-js 0.16.0 → 0.19.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 (38) hide show
  1. package/dist/{recorder-qlzUKFxG.d.ts → base-C9rhAHZ0.d.ts} +3 -4
  2. package/dist/{chunk-W2EZWPJ5.js → chunk-57ZH2WW5.js} +11 -12
  3. package/dist/{chunk-H6BKTNJY.js → chunk-5FX6IIQ6.js} +129 -105
  4. package/dist/{chunk-SKSKC747.js → chunk-CDZ7MBXC.js} +1 -1
  5. package/dist/{chunk-EZQVUNXB.js → chunk-CLXKPZZE.js} +13 -14
  6. package/dist/{chunk-DCWYK4CA.js → chunk-H37EB22A.js} +13 -9
  7. package/dist/{chunk-I5CAQUQI.js → chunk-OMWXY6WY.js} +8 -8
  8. package/dist/{chunk-T24Z753F.js → chunk-UEOBGHXF.js} +2 -2
  9. package/dist/cli/extract-manifest.cjs +4 -3
  10. package/dist/cli/extract-manifest.js +4 -4
  11. package/dist/index.cjs +4 -9
  12. package/dist/index.d.cts +2 -2
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.js +1 -1
  15. package/dist/protocols/mcp/index.cjs +19 -225
  16. package/dist/protocols/mcp/index.d.cts +6 -5
  17. package/dist/protocols/mcp/index.d.ts +6 -5
  18. package/dist/protocols/mcp/index.js +4 -4
  19. package/dist/protocols/oauth-metadata.cjs +3 -2
  20. package/dist/protocols/oauth-metadata.d.cts +1 -1
  21. package/dist/protocols/oauth-metadata.d.ts +1 -1
  22. package/dist/protocols/oauth-metadata.js +4 -4
  23. package/dist/protocols/rest/index.cjs +24 -230
  24. package/dist/protocols/rest/index.d.cts +6 -6
  25. package/dist/protocols/rest/index.d.ts +6 -6
  26. package/dist/protocols/rest/index.js +5 -5
  27. package/dist/stacks/supabase/index.cjs +196 -169
  28. package/dist/stacks/supabase/index.d.cts +1 -1
  29. package/dist/stacks/supabase/index.d.ts +1 -1
  30. package/dist/stacks/supabase/index.js +14 -14
  31. package/dist/stacks/supabase/vite.cjs +1 -1
  32. package/dist/stacks/supabase/vite.js +1 -1
  33. package/dist/stacks/tanstack/index.cjs +205 -177
  34. package/dist/stacks/tanstack/index.d.cts +1 -1
  35. package/dist/stacks/tanstack/index.d.ts +1 -1
  36. package/dist/stacks/tanstack/index.js +13 -10
  37. package/dist/{types-COY42xux.d.ts → types-CPkhCRxc.d.ts} +3 -3
  38. package/package.json +1 -1
@@ -50,19 +50,29 @@ function firstForwardedValue(request, header) {
50
50
  return value ? value : void 0;
51
51
  }
52
52
 
53
- // src/protocols/mcp/protocol.ts
54
- var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
55
- var import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js");
56
-
57
- // src/core/http.ts
58
- var JSON_HEADERS = { "Content-Type": "application/json" };
59
- function headResponse(response) {
60
- return new Response(null, { status: response.status, statusText: response.statusText, headers: response.headers });
53
+ // src/metrics/config.ts
54
+ var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
55
+ var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
56
+ function assertMetricsEndpoint(endpoint) {
57
+ let url;
58
+ try {
59
+ url = new URL(endpoint);
60
+ } catch {
61
+ throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
62
+ }
63
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
64
+ throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
65
+ }
61
66
  }
62
- function methodNotAllowed(allow) {
63
- return new Response(JSON.stringify({ error: "method not allowed" }), {
64
- status: 405,
65
- headers: { ...JSON_HEADERS, Allow: allow }
67
+ function resolveMetricsConfig(config = true) {
68
+ const options = typeof config === "boolean" ? { enabled: config } : config;
69
+ const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
70
+ assertMetricsEndpoint(endpoint);
71
+ return Object.freeze({
72
+ enabled: options.enabled ?? true,
73
+ endpoint,
74
+ headers: options.headers ?? {},
75
+ apiKeyEnvVar: METRICS_API_KEY_ENV_VAR
66
76
  });
67
77
  }
68
78
 
@@ -71,16 +81,27 @@ var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
71
81
  function isLogLevel(value) {
72
82
  return typeof value === "string" && value in LEVEL_RANK;
73
83
  }
84
+ var LOG_LEVEL_ENV_VAR = "LOVABLE_MCP_LOG_LEVEL";
74
85
  function readEnvLevel() {
75
86
  try {
76
- const raw = typeof process !== "undefined" ? process.env?.["LOVABLE_MCP_LOG_LEVEL"] : void 0;
87
+ const raw = typeof process !== "undefined" ? process.env?.[LOG_LEVEL_ENV_VAR] : void 0;
77
88
  const normalized = raw?.trim().toLowerCase();
78
89
  return isLogLevel(normalized) ? normalized : void 0;
79
90
  } catch {
80
91
  return void 0;
81
92
  }
82
93
  }
83
- var currentLevel = readEnvLevel() ?? "silent";
94
+ var currentLevel = readEnvLevel() ?? "debug";
95
+ function setLogLevel(level) {
96
+ currentLevel = level;
97
+ }
98
+ function parseLogLevel(raw) {
99
+ const normalized = raw?.trim().toLowerCase();
100
+ return isLogLevel(normalized) ? normalized : void 0;
101
+ }
102
+ function applyLogLevelFromEnv(raw) {
103
+ setLogLevel(parseLogLevel(raw) ?? "debug");
104
+ }
84
105
  function enabled(level) {
85
106
  return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
86
107
  }
@@ -107,40 +128,8 @@ function describeError(err) {
107
128
  return { value: String(err) };
108
129
  }
109
130
 
110
- // src/metrics/config.ts
111
- var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
112
- var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
113
- var METRICS_SAMPLE_RATE = 1;
114
- var METRICS_FLUSH_INTERVAL_MS = 5e3;
115
- var METRICS_MAX_BATCH_SIZE = 50;
116
- function assertMetricsEndpoint(endpoint) {
117
- let url;
118
- try {
119
- url = new URL(endpoint);
120
- } catch {
121
- throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
122
- }
123
- if (url.protocol !== "https:" && url.protocol !== "http:") {
124
- throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
125
- }
126
- }
127
- function resolveMetricsConfig(config = true) {
128
- const options = typeof config === "boolean" ? { enabled: config } : config;
129
- const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
130
- assertMetricsEndpoint(endpoint);
131
- return Object.freeze({
132
- enabled: options.enabled ?? true,
133
- endpoint,
134
- headers: options.headers ?? {},
135
- apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
136
- sampleRate: METRICS_SAMPLE_RATE,
137
- flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
138
- maxBatchSize: METRICS_MAX_BATCH_SIZE
139
- });
140
- }
141
-
142
131
  // package.json
143
- var version = "0.16.0";
132
+ var version = "0.19.0";
144
133
 
145
134
  // src/metrics/otlp.ts
146
135
  var SCOPE_NAME = "@lovable.dev/mcp-js";
@@ -165,6 +154,7 @@ function toLogRecord(rec) {
165
154
  attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
166
155
  if (rec.resBytes !== void 0)
167
156
  attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
157
+ attributes.push(strAttr("mcp.stack", rec.stack));
168
158
  return {
169
159
  timeUnixNano: rec.timeUnixNano,
170
160
  observedTimeUnixNano: rec.timeUnixNano,
@@ -201,127 +191,164 @@ function nowUnixNano() {
201
191
  return `${Date.now()}000000`;
202
192
  }
203
193
 
204
- // src/metrics/recorder.ts
194
+ // src/metrics/impl/base.ts
205
195
  function nowMs() {
206
196
  return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
207
197
  }
208
- function reportInvocation(recorder, ev) {
209
- log.info("tool.invoked", { tool: ev.tool, method: ev.method, outcome: ev.outcome, durationMs: ev.durationMs });
210
- recorder.record(ev);
211
- }
212
- var NOOP_RECORDER = {
213
- record() {
214
- },
215
- async flush() {
216
- }
217
- };
198
+ var NOOP_RECORDER = { async emit() {
199
+ } };
218
200
  function createNoopRecorder() {
219
201
  return NOOP_RECORDER;
220
202
  }
221
- function readRuntimeEnv(name) {
222
- try {
223
- const denoEnv = globalThis.Deno?.env;
224
- const value = denoEnv?.get?.(name);
225
- if (value)
226
- return value;
227
- } catch {
228
- }
203
+ function readProcessEnv(name) {
229
204
  try {
230
- if (typeof process !== "undefined") {
231
- const value = process.env?.[name];
232
- if (value)
233
- return value;
234
- }
205
+ return typeof process !== "undefined" ? process.env?.[name] || void 0 : void 0;
235
206
  } catch {
207
+ return void 0;
236
208
  }
237
- return void 0;
238
209
  }
239
- function probeWaitUntil() {
240
- const fn = globalThis.EdgeRuntime?.waitUntil;
241
- return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
242
- }
243
- function createMetricsRecorder(ctx, deps = {}) {
244
- const { config, server } = ctx;
245
- if (!config.enabled)
246
- return NOOP_RECORDER;
247
- const doFetch = deps.fetch ?? globalThis.fetch;
248
- if (!doFetch) {
249
- log.warn("metrics.disabled_no_fetch", { endpoint: config.endpoint });
250
- return NOOP_RECORDER;
251
- }
252
- const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
253
- const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
254
- if (usesLovableEndpoint && !apiKey) {
255
- log.warn("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar, endpoint: config.endpoint });
256
- return NOOP_RECORDER;
257
- }
258
- const waitUntil = deps.waitUntil ?? probeWaitUntil();
259
- const buffer = [];
260
- let timer;
261
- const schedule = (p) => {
262
- if (waitUntil) {
263
- try {
264
- waitUntil(p);
265
- return;
266
- } catch (err) {
267
- log.warn("metrics.wait_until_failed", describeError(err));
210
+ var BaseMetricRecorder = class {
211
+ constructor(config, server, deps = {}) {
212
+ this.config = config;
213
+ this.server = server;
214
+ this.deps = deps;
215
+ this.doFetch = deps.fetch ?? globalThis.fetch;
216
+ this.usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
217
+ const headers = {};
218
+ if (!this.usesLovableEndpoint) {
219
+ for (const [key, value] of Object.entries(config.headers)) {
220
+ if (key.toLowerCase() !== "content-type")
221
+ headers[key] = value;
268
222
  }
269
223
  }
270
- void p.catch(() => {
271
- });
272
- };
273
- const headers = {};
274
- if (!usesLovableEndpoint) {
275
- for (const [key, value] of Object.entries(config.headers)) {
276
- if (key.toLowerCase() !== "content-type")
277
- headers[key] = value;
278
- }
224
+ headers["content-type"] = "application/json";
225
+ this.baseHeaders = headers;
279
226
  }
280
- headers["content-type"] = "application/json";
281
- if (apiKey)
282
- headers.authorization = `Bearer ${apiKey}`;
283
- const flush = async () => {
284
- if (buffer.length === 0)
227
+ doFetch;
228
+ usesLovableEndpoint;
229
+ baseHeaders;
230
+ apiKey;
231
+ lazyLogLevelApplied = false;
232
+ async emit(ev) {
233
+ await this.applyLazyLogLevel();
234
+ log.info("tool.invoked", {
235
+ tool: ev.tool,
236
+ method: ev.method,
237
+ outcome: ev.outcome,
238
+ durationMs: ev.durationMs,
239
+ stack: this.stack
240
+ });
241
+ if (!this.config.enabled)
285
242
  return;
286
- const records = buffer.splice(0, buffer.length);
287
- const dropped = records.length;
288
- try {
289
- const res = await doFetch(config.endpoint, {
290
- method: "POST",
291
- headers,
292
- body: buildLogsPayload(records, server),
293
- keepalive: true
294
- });
295
- if (!res.ok) {
296
- log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
243
+ if (!this.doFetch) {
244
+ log.warn("metrics.disabled_no_fetch", { endpoint: this.config.endpoint });
245
+ return;
246
+ }
247
+ const headers = { ...this.baseHeaders };
248
+ if (this.usesLovableEndpoint) {
249
+ const key = await this.resolveApiKey();
250
+ if (!key) {
251
+ log.warn("metrics.disabled_no_api_key", { envVar: this.config.apiKeyEnvVar, endpoint: this.config.endpoint });
252
+ return;
297
253
  }
254
+ headers.authorization = `Bearer ${key}`;
255
+ }
256
+ const body = buildLogsPayload([{ ...ev, timeUnixNano: nowUnixNano(), stack: this.stack }], this.server);
257
+ try {
258
+ const res = await this.doFetch(this.config.endpoint, { method: "POST", headers, body, keepalive: true });
259
+ if (!res.ok)
260
+ log.warn("metrics.rejected", { status: res.status, endpoint: this.config.endpoint });
298
261
  } catch (err) {
299
- log.warn("metrics.flush_failed", { ...describeError(err), dropped, endpoint: config.endpoint });
262
+ log.warn("metrics.failed", { ...describeError(err), endpoint: this.config.endpoint });
300
263
  }
301
- };
302
- const ensureTimer = () => {
303
- if (timer !== void 0)
264
+ }
265
+ // Cache only a successful resolution so a miss is retried on the next emit (the
266
+ // Cloudflare binding may not be readable on the very first attempt in an isolate).
267
+ async resolveApiKey() {
268
+ if (this.apiKey)
269
+ return this.apiKey;
270
+ this.apiKey = this.deps.getApiKey ? this.deps.getApiKey() : await this.readEnv(this.config.apiKeyEnvVar);
271
+ return this.apiKey;
272
+ }
273
+ async applyLazyLogLevel() {
274
+ if (this.lazyLogLevelApplied)
304
275
  return;
305
- timer = setInterval(() => schedule(flush()), config.flushIntervalMs);
306
- timer.unref?.();
307
- };
308
- return {
309
- record(ev) {
310
- if (config.sampleRate < 1 && Math.random() >= config.sampleRate)
311
- return;
312
- buffer.push({ ...ev, timeUnixNano: nowUnixNano() });
313
- ensureTimer();
314
- if (buffer.length >= config.maxBatchSize)
315
- schedule(flush());
316
- },
317
- flush
318
- };
276
+ this.lazyLogLevelApplied = true;
277
+ applyLogLevelFromEnv(await this.readEnv(LOG_LEVEL_ENV_VAR));
278
+ }
279
+ };
280
+
281
+ // src/metrics/impl/cloudflare.ts
282
+ var cloudflareEnvPromise;
283
+ async function readCloudflareEnv(name) {
284
+ try {
285
+ const moduleSpecifier = "cloudflare:workers";
286
+ cloudflareEnvPromise ??= import(
287
+ /* @vite-ignore */
288
+ moduleSpecifier
289
+ ).then((m) => m.env).catch((err) => {
290
+ log.debug("metrics.cloudflare_env_import_failed", describeError(err));
291
+ return void 0;
292
+ });
293
+ const env = await cloudflareEnvPromise;
294
+ const raw = env?.[name];
295
+ const value = typeof raw === "string" && raw ? raw : void 0;
296
+ log.debug("metrics.read_cloudflare_env", { name, hasEnvBinding: !!env, found: value !== void 0 });
297
+ return value;
298
+ } catch (err) {
299
+ log.debug("metrics.read_cloudflare_env_error", { name, ...describeError(err) });
300
+ return void 0;
301
+ }
302
+ }
303
+ var TanStackMetricRecorder = class extends BaseMetricRecorder {
304
+ stack = "tanstack";
305
+ async readEnv(name) {
306
+ return readProcessEnv(name) ?? await readCloudflareEnv(name);
307
+ }
308
+ };
309
+
310
+ // src/metrics/impl/supabase.ts
311
+ function readDenoEnv(name) {
312
+ try {
313
+ const denoEnv = globalThis.Deno?.env;
314
+ const value = denoEnv?.get?.(name) || void 0;
315
+ log.debug("metrics.read_deno_env", { name, hasDenoEnv: !!denoEnv, found: !!value });
316
+ return value;
317
+ } catch (err) {
318
+ log.debug("metrics.read_deno_env_error", { name, ...describeError(err) });
319
+ return void 0;
320
+ }
319
321
  }
320
- function createRecorderForRuntime(mcp) {
322
+ var SupabaseMetricRecorder = class extends BaseMetricRecorder {
323
+ stack = "supabase";
324
+ async readEnv(name) {
325
+ return readDenoEnv(name) ?? readProcessEnv(name);
326
+ }
327
+ };
328
+
329
+ // src/metrics/recorder.ts
330
+ function createRecorderForRuntime(mcp, opts) {
321
331
  const config = resolveMetricsConfig(mcp.metrics);
332
+ const server = { name: mcp.name, version: mcp.version };
322
333
  if (!config.enabled)
323
334
  return createNoopRecorder();
324
- return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
335
+ return opts.stack === "tanstack" ? new TanStackMetricRecorder(config, server) : new SupabaseMetricRecorder(config, server);
336
+ }
337
+
338
+ // src/protocols/mcp/protocol.ts
339
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
340
+ var import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js");
341
+
342
+ // src/core/http.ts
343
+ var JSON_HEADERS = { "Content-Type": "application/json" };
344
+ function headResponse(response) {
345
+ return new Response(null, { status: response.status, statusText: response.statusText, headers: response.headers });
346
+ }
347
+ function methodNotAllowed(allow) {
348
+ return new Response(JSON.stringify({ error: "method not allowed" }), {
349
+ status: 405,
350
+ headers: { ...JSON_HEADERS, Allow: allow }
351
+ });
325
352
  }
326
353
 
327
354
  // src/core/promise.ts
@@ -752,10 +779,10 @@ function assertRequiredScopes(auth, context) {
752
779
  throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
753
780
  }
754
781
  }
755
- function createRequestAuthorizer(mcp, options = {}, recorder) {
782
+ function createRequestAuthorizer(mcp, options = {}) {
756
783
  const runtime = getOAuthRuntime(mcp, options);
757
784
  return {
758
- async authorize(request) {
785
+ async authorize(request, recorder) {
759
786
  if (runtime.kind === "unconfigured")
760
787
  return { ok: true };
761
788
  const startedAt = nowMs();
@@ -771,7 +798,7 @@ function createRequestAuthorizer(mcp, options = {}, recorder) {
771
798
  } catch (err) {
772
799
  if (err instanceof OAuthConfigurationError) {
773
800
  log.error("auth.config_error", { ...describeError(err), outcome: "500" });
774
- recorder?.record({
801
+ await recorder?.emit({
775
802
  tool: null,
776
803
  method: "authorize",
777
804
  outcome: "auth_config_error",
@@ -882,7 +909,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
882
909
  try {
883
910
  result = await tool.handler(args, new ToolContext(auth));
884
911
  } catch {
885
- reportInvocation(recorder, {
912
+ await recorder.emit({
886
913
  tool: tool.name,
887
914
  method: "tools/call",
888
915
  outcome: "handler_error",
@@ -891,7 +918,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
891
918
  return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
892
919
  }
893
920
  if (result == null) {
894
- reportInvocation(recorder, {
921
+ await recorder.emit({
895
922
  tool: tool.name,
896
923
  method: "tools/call",
897
924
  outcome: "handler_error",
@@ -899,7 +926,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
899
926
  });
900
927
  return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
901
928
  }
902
- reportInvocation(recorder, {
929
+ await recorder.emit({
903
930
  tool: tool.name,
904
931
  method: "tools/call",
905
932
  outcome: result.isError ? "tool_error" : "ok",
@@ -908,10 +935,10 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
908
935
  return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
909
936
  };
910
937
  }
911
- function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
912
- const authorizer = createRequestAuthorizer(mcp, options, recorder);
913
- const handle = async (request) => {
914
- const authResult = await authorizer.authorize(request);
938
+ function createMcpProtocolHandler(mcp, options = {}) {
939
+ const authorizer = createRequestAuthorizer(mcp, options);
940
+ const handle = async (request, recorder) => {
941
+ const authResult = await authorizer.authorize(request, recorder);
915
942
  if (!authResult.ok)
916
943
  return authResult.response;
917
944
  try {
@@ -938,7 +965,7 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
938
965
  await server.connect(transport);
939
966
  return await transport.handleRequest(request);
940
967
  } catch (err) {
941
- recorder.record({ tool: null, method: "transport", outcome: "transport_error", durationMs: 0 });
968
+ await recorder.emit({ tool: null, method: "transport", outcome: "transport_error", durationMs: 0 });
942
969
  log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
943
970
  return Response.json(
944
971
  { jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
@@ -946,10 +973,10 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
946
973
  );
947
974
  }
948
975
  };
949
- return async (request) => {
976
+ return async (request, recorder = createNoopRecorder()) => {
950
977
  if (request.method === "OPTIONS")
951
978
  return corsPreflightResponse("GET, POST, DELETE, OPTIONS");
952
- return withCors(await handle(request));
979
+ return withCors(await handle(request, recorder));
953
980
  };
954
981
  }
955
982
 
@@ -1039,11 +1066,11 @@ function buildMcpListing(mcp) {
1039
1066
  }))
1040
1067
  };
1041
1068
  }
1042
- function createListToolsHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
1069
+ function createListToolsHandler(mcp, options = {}) {
1043
1070
  assertRestResourceBinding(mcp, options);
1044
- const authorizer = createRequestAuthorizer(mcp, options, recorder);
1045
- const handle = async (request) => {
1046
- const authResult = await authorizer.authorize(request);
1071
+ const authorizer = createRequestAuthorizer(mcp, options);
1072
+ const handle = async (request, recorder) => {
1073
+ const authResult = await authorizer.authorize(request, recorder);
1047
1074
  if (!authResult.ok)
1048
1075
  return authResult.response;
1049
1076
  if (request.method !== "GET" && request.method !== "HEAD")
@@ -1051,10 +1078,10 @@ function createListToolsHandler(mcp, options = {}, recorder = createRecorderForR
1051
1078
  const response = Response.json(buildMcpListing(mcp));
1052
1079
  return request.method === "HEAD" ? headResponse(response) : response;
1053
1080
  };
1054
- return async (request) => {
1081
+ return async (request, recorder = createNoopRecorder()) => {
1055
1082
  if (request.method === "OPTIONS")
1056
1083
  return corsPreflightResponse("GET, HEAD, OPTIONS");
1057
- return withCors(await handle(request));
1084
+ return withCors(await handle(request, recorder));
1058
1085
  };
1059
1086
  }
1060
1087
 
@@ -1072,11 +1099,11 @@ function isEmptyArgs(value) {
1072
1099
  return false;
1073
1100
  return Object.keys(value).length === 0;
1074
1101
  }
1075
- function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
1102
+ function createInvokeToolHandler(mcp, options = {}) {
1076
1103
  assertRestResourceBinding(mcp, options);
1077
- const authorizer = createRequestAuthorizer(mcp, options, recorder);
1078
- const handle = async (request, toolName) => {
1079
- const authResult = await authorizer.authorize(request);
1104
+ const authorizer = createRequestAuthorizer(mcp, options);
1105
+ const handle = async (request, toolName, recorder) => {
1106
+ const authResult = await authorizer.authorize(request, recorder);
1080
1107
  if (!authResult.ok)
1081
1108
  return authResult.response;
1082
1109
  if (request.method !== "POST")
@@ -1132,7 +1159,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
1132
1159
  try {
1133
1160
  result = await tool.handler(args, new ToolContext(authResult.auth));
1134
1161
  } catch {
1135
- reportInvocation(recorder, {
1162
+ await recorder.emit({
1136
1163
  tool: tool.name,
1137
1164
  method: "tools/call",
1138
1165
  outcome: "handler_error",
@@ -1144,7 +1171,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
1144
1171
  });
1145
1172
  }
1146
1173
  if (result == null) {
1147
- reportInvocation(recorder, {
1174
+ await recorder.emit({
1148
1175
  tool: tool.name,
1149
1176
  method: "tools/call",
1150
1177
  outcome: "handler_error",
@@ -1155,7 +1182,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
1155
1182
  headers: JSON_HEADERS
1156
1183
  });
1157
1184
  }
1158
- reportInvocation(recorder, {
1185
+ await recorder.emit({
1159
1186
  tool: tool.name,
1160
1187
  method: "tools/call",
1161
1188
  outcome: result.isError ? "tool_error" : "ok",
@@ -1167,28 +1194,29 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
1167
1194
  isError: result.isError
1168
1195
  });
1169
1196
  };
1170
- return async (request, toolName) => {
1197
+ return async (request, toolName, recorder = createNoopRecorder()) => {
1171
1198
  if (request.method === "OPTIONS")
1172
1199
  return corsPreflightResponse("POST, OPTIONS");
1173
- return withCors(await handle(request, toolName));
1200
+ return withCors(await handle(request, toolName, recorder));
1174
1201
  };
1175
1202
  }
1176
1203
 
1177
1204
  // src/stacks/tanstack/handlers.ts
1205
+ var STACK = "tanstack";
1178
1206
  function forwarded(request, options) {
1179
1207
  return applyForwardedOrigin(request, { trustForwardedHost: options.trustForwardedHost });
1180
1208
  }
1181
1209
  function createTanStackMcpHandler(mcp, options = {}) {
1182
1210
  const handler = createMcpProtocolHandler(mcp, options);
1183
- return ({ request }) => handler(forwarded(request, options));
1211
+ return ({ request }) => handler(forwarded(request, options), createRecorderForRuntime(mcp, { stack: STACK }));
1184
1212
  }
1185
1213
  function createTanStackListToolsHandler(mcp, options = {}) {
1186
1214
  const handler = createListToolsHandler(mcp, options);
1187
- return ({ request }) => handler(forwarded(request, options));
1215
+ return ({ request }) => handler(forwarded(request, options), createRecorderForRuntime(mcp, { stack: STACK }));
1188
1216
  }
1189
1217
  function createTanStackInvokeToolHandler(mcp, options = {}) {
1190
1218
  const handler = createInvokeToolHandler(mcp, options);
1191
- return ({ request, params }) => handler(forwarded(request, options), params.tool);
1219
+ return ({ request, params }) => handler(forwarded(request, options), params.tool, createRecorderForRuntime(mcp, { stack: STACK }));
1192
1220
  }
1193
1221
  function createTanStackOAuthProtectedResourceMetadataHandler(mcp, options = {}) {
1194
1222
  const handler = createOAuthProtectedResourceMetadataHandler(mcp, options);
@@ -1,5 +1,5 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
- import { d as McpDefinition } from '../../types-COY42xux.js';
2
+ import { d as McpDefinition } from '../../types-CPkhCRxc.js';
3
3
  import 'zod';
4
4
 
5
5
  interface TanStackRouteCtx {
@@ -1,5 +1,5 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
- import { d as McpDefinition } from '../../types-COY42xux.js';
2
+ import { d as McpDefinition } from '../../types-CPkhCRxc.js';
3
3
  import 'zod';
4
4
 
5
5
  interface TanStackRouteCtx {
@@ -3,37 +3,40 @@ import {
3
3
  } from "../../chunk-UQK5UO6C.js";
4
4
  import {
5
5
  createMcpProtocolHandler
6
- } from "../../chunk-EZQVUNXB.js";
6
+ } from "../../chunk-CLXKPZZE.js";
7
7
  import {
8
8
  createOAuthProtectedResourceMetadataHandler
9
- } from "../../chunk-T24Z753F.js";
9
+ } from "../../chunk-UEOBGHXF.js";
10
10
  import {
11
11
  createInvokeToolHandler
12
- } from "../../chunk-W2EZWPJ5.js";
12
+ } from "../../chunk-57ZH2WW5.js";
13
13
  import {
14
14
  createListToolsHandler
15
- } from "../../chunk-I5CAQUQI.js";
15
+ } from "../../chunk-OMWXY6WY.js";
16
16
  import "../../chunk-MA5H6PSF.js";
17
- import "../../chunk-H6BKTNJY.js";
18
- import "../../chunk-DCWYK4CA.js";
17
+ import {
18
+ createRecorderForRuntime
19
+ } from "../../chunk-5FX6IIQ6.js";
20
+ import "../../chunk-H37EB22A.js";
19
21
  import "../../chunk-6DXGZZA4.js";
20
- import "../../chunk-SKSKC747.js";
22
+ import "../../chunk-CDZ7MBXC.js";
21
23
 
22
24
  // src/stacks/tanstack/handlers.ts
25
+ var STACK = "tanstack";
23
26
  function forwarded(request, options) {
24
27
  return applyForwardedOrigin(request, { trustForwardedHost: options.trustForwardedHost });
25
28
  }
26
29
  function createTanStackMcpHandler(mcp, options = {}) {
27
30
  const handler = createMcpProtocolHandler(mcp, options);
28
- return ({ request }) => handler(forwarded(request, options));
31
+ return ({ request }) => handler(forwarded(request, options), createRecorderForRuntime(mcp, { stack: STACK }));
29
32
  }
30
33
  function createTanStackListToolsHandler(mcp, options = {}) {
31
34
  const handler = createListToolsHandler(mcp, options);
32
- return ({ request }) => handler(forwarded(request, options));
35
+ return ({ request }) => handler(forwarded(request, options), createRecorderForRuntime(mcp, { stack: STACK }));
33
36
  }
34
37
  function createTanStackInvokeToolHandler(mcp, options = {}) {
35
38
  const handler = createInvokeToolHandler(mcp, options);
36
- return ({ request, params }) => handler(forwarded(request, options), params.tool);
39
+ return ({ request, params }) => handler(forwarded(request, options), params.tool, createRecorderForRuntime(mcp, { stack: STACK }));
37
40
  }
38
41
  function createTanStackOAuthProtectedResourceMetadataHandler(mcp, options = {}) {
39
42
  const handler = createOAuthProtectedResourceMetadataHandler(mcp, options);
@@ -114,12 +114,12 @@ interface MetricsOptions {
114
114
  /** Master switch. Default `true`; metrics still self-disable at runtime when
115
115
  * the API key env var is absent. */
116
116
  enabled?: boolean;
117
- /** OTLP/HTTP (JSON) logs URL to POST batches to — the default Lovable route or
118
- * any OTLP collector. Must be an absolute http(s) URL.
117
+ /** OTLP/HTTP (JSON) logs URL to POST each invocation to — the default Lovable
118
+ * route or any OTLP collector. Must be an absolute http(s) URL.
119
119
  * @default "https://api.lovable.dev/v1/app-mcp-usage" */
120
120
  endpoint?: string;
121
121
  /** Extra request headers — e.g. the auth token for a private OTLP collector.
122
- * Sent verbatim with every batch (and baked into the build output, so source
122
+ * Sent verbatim with every request (and baked into the build output, so source
123
123
  * your own build-time env for secrets). Ignored for the default Lovable
124
124
  * endpoint, which authenticates with `LOVABLE_API_KEY` instead. `content-type`
125
125
  * cannot be overridden (OTLP/HTTP JSON requires `application/json`). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovable.dev/mcp-js",
3
- "version": "0.16.0",
3
+ "version": "0.19.0",
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": {