@lovable.dev/mcp-js 0.17.0 → 0.20.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 (37) hide show
  1. package/dist/{recorder-qlzUKFxG.d.ts → base-C9rhAHZ0.d.ts} +3 -4
  2. package/dist/{chunk-FW6YL5C5.js → chunk-7JJEPFSO.js} +8 -8
  3. package/dist/{chunk-H2HXKAMQ.js → chunk-DUNDNXK5.js} +2 -2
  4. package/dist/{chunk-6TZNGFRM.js → chunk-EPEEEI3C.js} +121 -136
  5. package/dist/{chunk-J5BTCP6T.js → chunk-FTTVLHNN.js} +1 -1
  6. package/dist/{chunk-VQQZAFRA.js → chunk-H37EB22A.js} +2 -10
  7. package/dist/{chunk-WYXIX7XF.js → chunk-WKIXRW46.js} +11 -12
  8. package/dist/{chunk-EL6YNP2R.js → chunk-WW7NRRCJ.js} +13 -14
  9. package/dist/cli/extract-manifest.cjs +1 -1
  10. package/dist/cli/extract-manifest.js +4 -4
  11. package/dist/index.cjs +1 -7
  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 +16 -272
  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.d.cts +1 -1
  20. package/dist/protocols/oauth-metadata.d.ts +1 -1
  21. package/dist/protocols/oauth-metadata.js +4 -4
  22. package/dist/protocols/rest/index.cjs +21 -277
  23. package/dist/protocols/rest/index.d.cts +6 -6
  24. package/dist/protocols/rest/index.d.ts +6 -6
  25. package/dist/protocols/rest/index.js +5 -5
  26. package/dist/stacks/supabase/index.cjs +178 -201
  27. package/dist/stacks/supabase/index.d.cts +1 -1
  28. package/dist/stacks/supabase/index.d.ts +1 -1
  29. package/dist/stacks/supabase/index.js +14 -14
  30. package/dist/stacks/supabase/vite.cjs +1 -1
  31. package/dist/stacks/supabase/vite.js +1 -1
  32. package/dist/stacks/tanstack/index.cjs +187 -209
  33. package/dist/stacks/tanstack/index.d.cts +1 -1
  34. package/dist/stacks/tanstack/index.d.ts +1 -1
  35. package/dist/stacks/tanstack/index.js +13 -10
  36. package/dist/{types-COY42xux.d.ts → types-CPkhCRxc.d.ts} +3 -3
  37. package/package.json +1 -1
@@ -11,11 +11,10 @@ interface InvocationRecord {
11
11
  reqBytes?: number;
12
12
  resBytes?: number;
13
13
  }
14
+ /** A per-request telemetry recorder. `emit` logs the invocation and sends it as a
15
+ * single OTLP POST, awaited so it lands before the request ends. */
14
16
  interface MetricsRecorder {
15
- /** Buffer one invocation. Synchronous, sampled, and never throws. */
16
- record(ev: InvocationRecord): void;
17
- /** Best-effort POST of the current buffer. Resolves even on network failure. */
18
- flush(): Promise<void>;
17
+ emit(ev: InvocationRecord): Promise<void>;
19
18
  }
20
19
 
21
20
  export type { MetricsRecorder as M };
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  assertRestResourceBinding,
3
3
  corsPreflightResponse,
4
- createRecorderForRuntime,
4
+ createNoopRecorder,
5
5
  createRequestAuthorizer,
6
6
  headResponse,
7
7
  methodNotAllowed,
8
8
  withCors
9
- } from "./chunk-6TZNGFRM.js";
9
+ } from "./chunk-EPEEEI3C.js";
10
10
 
11
11
  // src/protocols/rest/list-tools.ts
12
12
  import { objectFromShape } from "@modelcontextprotocol/sdk/server/zod-compat.js";
@@ -33,11 +33,11 @@ function buildMcpListing(mcp) {
33
33
  }))
34
34
  };
35
35
  }
36
- function createListToolsHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
36
+ function createListToolsHandler(mcp, options = {}) {
37
37
  assertRestResourceBinding(mcp, options);
38
- const authorizer = createRequestAuthorizer(mcp, options, recorder);
39
- const handle = async (request) => {
40
- const authResult = await authorizer.authorize(request);
38
+ const authorizer = createRequestAuthorizer(mcp, options);
39
+ const handle = async (request, recorder) => {
40
+ const authResult = await authorizer.authorize(request, recorder);
41
41
  if (!authResult.ok)
42
42
  return authResult.response;
43
43
  if (request.method !== "GET" && request.method !== "HEAD")
@@ -45,10 +45,10 @@ function createListToolsHandler(mcp, options = {}, recorder = createRecorderForR
45
45
  const response = Response.json(buildMcpListing(mcp));
46
46
  return request.method === "HEAD" ? headResponse(response) : response;
47
47
  };
48
- return async (request) => {
48
+ return async (request, recorder = createNoopRecorder()) => {
49
49
  if (request.method === "OPTIONS")
50
50
  return corsPreflightResponse("GET, HEAD, OPTIONS");
51
- return withCors(await handle(request));
51
+ return withCors(await handle(request, recorder));
52
52
  };
53
53
  }
54
54
 
@@ -7,11 +7,11 @@ import {
7
7
  oauthConfigurationErrorResponse,
8
8
  resolveProtectedResource,
9
9
  withCors
10
- } from "./chunk-6TZNGFRM.js";
10
+ } from "./chunk-EPEEEI3C.js";
11
11
  import {
12
12
  describeError,
13
13
  log
14
- } from "./chunk-VQQZAFRA.js";
14
+ } from "./chunk-H37EB22A.js";
15
15
 
16
16
  // src/protocols/oauth-metadata.ts
17
17
  function notFound() {
@@ -7,13 +7,13 @@ import {
7
7
  parseSafeUrl,
8
8
  resolveMetricsConfig,
9
9
  trimTrailingSlash
10
- } from "./chunk-VQQZAFRA.js";
10
+ } from "./chunk-H37EB22A.js";
11
11
  import {
12
12
  OAUTH_PROTECTED_RESOURCE_METADATA_PATH
13
13
  } from "./chunk-6DXGZZA4.js";
14
14
  import {
15
15
  version
16
- } from "./chunk-J5BTCP6T.js";
16
+ } from "./chunk-FTTVLHNN.js";
17
17
 
18
18
  // src/core/http.ts
19
19
  var JSON_HEADERS = { "Content-Type": "application/json" };
@@ -74,6 +74,7 @@ function toLogRecord(rec) {
74
74
  attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
75
75
  if (rec.resBytes !== void 0)
76
76
  attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
77
+ attributes.push(strAttr("mcp.stack", rec.stack));
77
78
  return {
78
79
  timeUnixNano: rec.timeUnixNano,
79
80
  observedTimeUnixNano: rec.timeUnixNano,
@@ -110,164 +111,148 @@ function nowUnixNano() {
110
111
  return `${Date.now()}000000`;
111
112
  }
112
113
 
113
- // src/metrics/recorder.ts
114
+ // src/metrics/impl/base.ts
114
115
  function nowMs() {
115
116
  return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
116
117
  }
117
- function reportInvocation(recorder, ev) {
118
- log.info("tool.invoked", { tool: ev.tool, method: ev.method, outcome: ev.outcome, durationMs: ev.durationMs });
119
- recorder.record(ev);
120
- }
121
- var NOOP_RECORDER = {
122
- record() {
123
- },
124
- async flush() {
125
- }
126
- };
118
+ var NOOP_RECORDER = { async emit() {
119
+ } };
127
120
  function createNoopRecorder() {
128
121
  return NOOP_RECORDER;
129
122
  }
130
- function readRuntimeEnv(name) {
131
- try {
132
- const denoEnv = globalThis.Deno?.env;
133
- const value = denoEnv?.get?.(name);
134
- if (value)
135
- return value;
136
- } catch {
137
- }
123
+ function readProcessEnv(name) {
138
124
  try {
139
- if (typeof process !== "undefined") {
140
- const value = process.env?.[name];
141
- if (value)
142
- return value;
143
- }
144
- } catch {
145
- }
146
- return void 0;
147
- }
148
- var cloudflareEnvPromise;
149
- async function readCloudflareEnv(name) {
150
- try {
151
- const moduleSpecifier = "cloudflare:workers";
152
- cloudflareEnvPromise ??= import(
153
- /* @vite-ignore */
154
- moduleSpecifier
155
- ).then((m) => m.env).catch(() => void 0);
156
- const env = await cloudflareEnvPromise;
157
- const value = env?.[name];
158
- return typeof value === "string" && value ? value : void 0;
125
+ return typeof process !== "undefined" ? process.env?.[name] || void 0 : void 0;
159
126
  } catch {
160
127
  return void 0;
161
128
  }
162
129
  }
163
- function probeWaitUntil() {
164
- const fn = globalThis.EdgeRuntime?.waitUntil;
165
- return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
166
- }
167
- function createMetricsRecorder(ctx, deps = {}) {
168
- const { config, server } = ctx;
169
- applyLogLevelFromEnv(readRuntimeEnv(LOG_LEVEL_ENV_VAR));
170
- if (!config.enabled)
171
- return NOOP_RECORDER;
172
- const doFetch = deps.fetch ?? globalThis.fetch;
173
- if (!doFetch) {
174
- log.warn("metrics.disabled_no_fetch", { endpoint: config.endpoint });
175
- return NOOP_RECORDER;
176
- }
177
- const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
178
- const waitUntil = deps.waitUntil ?? probeWaitUntil();
179
- const buffer = [];
180
- let timer;
181
- let lazyLogLevelApplied = false;
182
- const applyLazyLogLevel = async () => {
183
- if (lazyLogLevelApplied)
184
- return;
185
- lazyLogLevelApplied = true;
186
- applyLogLevelFromEnv(await readCloudflareEnv(LOG_LEVEL_ENV_VAR));
187
- };
188
- let apiKey;
189
- const resolveApiKey = async () => {
190
- if (apiKey)
191
- return apiKey;
192
- apiKey = (deps.getApiKey ? deps.getApiKey() : readRuntimeEnv(config.apiKeyEnvVar)) ?? await readCloudflareEnv(config.apiKeyEnvVar);
193
- return apiKey;
194
- };
195
- const schedule = (p) => {
196
- if (waitUntil) {
197
- try {
198
- waitUntil(p);
199
- return;
200
- } catch (err) {
201
- log.warn("metrics.wait_until_failed", describeError(err));
130
+ var BaseMetricRecorder = class {
131
+ constructor(config, server, deps = {}) {
132
+ this.config = config;
133
+ this.server = server;
134
+ this.deps = deps;
135
+ this.doFetch = deps.fetch ?? (typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0);
136
+ this.usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
137
+ const headers = {};
138
+ if (!this.usesLovableEndpoint) {
139
+ for (const [key, value] of Object.entries(config.headers)) {
140
+ if (key.toLowerCase() !== "content-type")
141
+ headers[key] = value;
202
142
  }
203
143
  }
204
- void p.catch(() => {
205
- });
206
- };
207
- const baseHeaders = {};
208
- if (!usesLovableEndpoint) {
209
- for (const [key, value] of Object.entries(config.headers)) {
210
- if (key.toLowerCase() !== "content-type")
211
- baseHeaders[key] = value;
212
- }
144
+ headers["content-type"] = "application/json";
145
+ this.baseHeaders = headers;
213
146
  }
214
- baseHeaders["content-type"] = "application/json";
215
- const flush = async () => {
216
- void applyLazyLogLevel();
217
- if (buffer.length === 0)
147
+ doFetch;
148
+ usesLovableEndpoint;
149
+ baseHeaders;
150
+ apiKey;
151
+ lazyLogLevelApplied = false;
152
+ async emit(ev) {
153
+ await this.applyLazyLogLevel();
154
+ log.info("tool.invoked", {
155
+ tool: ev.tool,
156
+ method: ev.method,
157
+ outcome: ev.outcome,
158
+ durationMs: ev.durationMs,
159
+ stack: this.stack
160
+ });
161
+ if (!this.config.enabled)
162
+ return;
163
+ if (!this.doFetch) {
164
+ log.warn("metrics.disabled_no_fetch", { endpoint: this.config.endpoint });
218
165
  return;
219
- const records = buffer.splice(0, buffer.length);
220
- const dropped = records.length;
221
- const headers = { ...baseHeaders };
222
- if (usesLovableEndpoint) {
223
- const key = await resolveApiKey();
166
+ }
167
+ const headers = { ...this.baseHeaders };
168
+ if (this.usesLovableEndpoint) {
169
+ const key = await this.resolveApiKey();
224
170
  if (!key) {
225
- log.warn("metrics.disabled_no_api_key", {
226
- envVar: config.apiKeyEnvVar,
227
- endpoint: config.endpoint,
228
- dropped
229
- });
171
+ log.warn("metrics.disabled_no_api_key", { envVar: this.config.apiKeyEnvVar, endpoint: this.config.endpoint });
230
172
  return;
231
173
  }
232
174
  headers.authorization = `Bearer ${key}`;
233
175
  }
176
+ const body = buildLogsPayload([{ ...ev, timeUnixNano: nowUnixNano(), stack: this.stack }], this.server);
234
177
  try {
235
- const res = await doFetch(config.endpoint, {
236
- method: "POST",
237
- headers,
238
- body: buildLogsPayload(records, server),
239
- keepalive: true
240
- });
241
- if (!res.ok) {
242
- log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
243
- }
178
+ const res = await this.doFetch(this.config.endpoint, { method: "POST", headers, body, keepalive: true });
179
+ if (!res.ok)
180
+ log.warn("metrics.rejected", { status: res.status, endpoint: this.config.endpoint });
244
181
  } catch (err) {
245
- log.warn("metrics.flush_failed", { ...describeError(err), dropped, endpoint: config.endpoint });
182
+ log.warn("metrics.failed", { ...describeError(err), endpoint: this.config.endpoint });
246
183
  }
247
- };
248
- const ensureTimer = () => {
249
- if (timer !== void 0)
184
+ }
185
+ // Cache only a successful resolution so a miss is retried on the next emit (the
186
+ // Cloudflare binding may not be readable on the very first attempt in an isolate).
187
+ async resolveApiKey() {
188
+ if (this.apiKey)
189
+ return this.apiKey;
190
+ this.apiKey = this.deps.getApiKey ? this.deps.getApiKey() : await this.readEnv(this.config.apiKeyEnvVar);
191
+ return this.apiKey;
192
+ }
193
+ async applyLazyLogLevel() {
194
+ if (this.lazyLogLevelApplied)
250
195
  return;
251
- timer = setInterval(() => schedule(flush()), config.flushIntervalMs);
252
- timer.unref?.();
253
- };
254
- return {
255
- record(ev) {
256
- if (config.sampleRate < 1 && Math.random() >= config.sampleRate)
257
- return;
258
- buffer.push({ ...ev, timeUnixNano: nowUnixNano() });
259
- ensureTimer();
260
- if (buffer.length >= config.maxBatchSize)
261
- schedule(flush());
262
- },
263
- flush
264
- };
196
+ this.lazyLogLevelApplied = true;
197
+ applyLogLevelFromEnv(await this.readEnv(LOG_LEVEL_ENV_VAR));
198
+ }
199
+ };
200
+
201
+ // src/metrics/impl/cloudflare.ts
202
+ var cloudflareEnvPromise;
203
+ async function readCloudflareEnv(name) {
204
+ try {
205
+ const moduleSpecifier = "cloudflare:workers";
206
+ cloudflareEnvPromise ??= import(
207
+ /* @vite-ignore */
208
+ moduleSpecifier
209
+ ).then((m) => m.env).catch((err) => {
210
+ log.debug("metrics.cloudflare_env_import_failed", describeError(err));
211
+ return void 0;
212
+ });
213
+ const env = await cloudflareEnvPromise;
214
+ const raw = env?.[name];
215
+ const value = typeof raw === "string" && raw ? raw : void 0;
216
+ log.debug("metrics.read_cloudflare_env", { name, hasEnvBinding: !!env, found: value !== void 0 });
217
+ return value;
218
+ } catch (err) {
219
+ log.debug("metrics.read_cloudflare_env_error", { name, ...describeError(err) });
220
+ return void 0;
221
+ }
222
+ }
223
+ var TanStackMetricRecorder = class extends BaseMetricRecorder {
224
+ stack = "tanstack";
225
+ async readEnv(name) {
226
+ return readProcessEnv(name) ?? await readCloudflareEnv(name);
227
+ }
228
+ };
229
+
230
+ // src/metrics/impl/supabase.ts
231
+ function readDenoEnv(name) {
232
+ try {
233
+ const denoEnv = globalThis.Deno?.env;
234
+ const value = denoEnv?.get?.(name) || void 0;
235
+ log.debug("metrics.read_deno_env", { name, hasDenoEnv: !!denoEnv, found: !!value });
236
+ return value;
237
+ } catch (err) {
238
+ log.debug("metrics.read_deno_env_error", { name, ...describeError(err) });
239
+ return void 0;
240
+ }
265
241
  }
266
- function createRecorderForRuntime(mcp) {
242
+ var SupabaseMetricRecorder = class extends BaseMetricRecorder {
243
+ stack = "supabase";
244
+ async readEnv(name) {
245
+ return readDenoEnv(name) ?? readProcessEnv(name);
246
+ }
247
+ };
248
+
249
+ // src/metrics/recorder.ts
250
+ function createRecorderForRuntime(mcp, opts) {
267
251
  const config = resolveMetricsConfig(mcp.metrics);
252
+ const server = { name: mcp.name, version: mcp.version };
268
253
  if (!config.enabled)
269
254
  return createNoopRecorder();
270
- return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
255
+ return opts.stack === "tanstack" ? new TanStackMetricRecorder(config, server) : new SupabaseMetricRecorder(config, server);
271
256
  }
272
257
 
273
258
  // src/core/promise.ts
@@ -635,10 +620,10 @@ function assertRequiredScopes(auth, context) {
635
620
  throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
636
621
  }
637
622
  }
638
- function createRequestAuthorizer(mcp, options = {}, recorder) {
623
+ function createRequestAuthorizer(mcp, options = {}) {
639
624
  const runtime = getOAuthRuntime(mcp, options);
640
625
  return {
641
- async authorize(request) {
626
+ async authorize(request, recorder) {
642
627
  if (runtime.kind === "unconfigured")
643
628
  return { ok: true };
644
629
  const startedAt = nowMs();
@@ -654,7 +639,7 @@ function createRequestAuthorizer(mcp, options = {}, recorder) {
654
639
  } catch (err) {
655
640
  if (err instanceof OAuthConfigurationError) {
656
641
  log.error("auth.config_error", { ...describeError(err), outcome: "500" });
657
- recorder?.record({
642
+ await recorder?.emit({
658
643
  tool: null,
659
644
  method: "authorize",
660
645
  outcome: "auth_config_error",
@@ -718,7 +703,7 @@ export {
718
703
  headResponse,
719
704
  methodNotAllowed,
720
705
  nowMs,
721
- reportInvocation,
706
+ createNoopRecorder,
722
707
  createRecorderForRuntime,
723
708
  resolveProtectedResource,
724
709
  assertResourcePathShape,
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "0.17.0";
2
+ var version = "0.20.0";
3
3
 
4
4
  export {
5
5
  version
@@ -22,9 +22,7 @@ function parseLogLevel(raw) {
22
22
  return isLogLevel(normalized) ? normalized : void 0;
23
23
  }
24
24
  function applyLogLevelFromEnv(raw) {
25
- const level = parseLogLevel(raw);
26
- if (level)
27
- setLogLevel(level);
25
+ setLogLevel(parseLogLevel(raw) ?? "debug");
28
26
  }
29
27
  function enabled(level) {
30
28
  return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
@@ -55,9 +53,6 @@ function describeError(err) {
55
53
  // src/metrics/config.ts
56
54
  var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
57
55
  var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
58
- var METRICS_SAMPLE_RATE = 1;
59
- var METRICS_FLUSH_INTERVAL_MS = 5e3;
60
- var METRICS_MAX_BATCH_SIZE = 50;
61
56
  function assertMetricsEndpoint(endpoint) {
62
57
  let url;
63
58
  try {
@@ -77,10 +72,7 @@ function resolveMetricsConfig(config = true) {
77
72
  enabled: options.enabled ?? true,
78
73
  endpoint,
79
74
  headers: options.headers ?? {},
80
- apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
81
- sampleRate: METRICS_SAMPLE_RATE,
82
- flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
83
- maxBatchSize: METRICS_MAX_BATCH_SIZE
75
+ apiKeyEnvVar: METRICS_API_KEY_ENV_VAR
84
76
  });
85
77
  }
86
78
 
@@ -5,13 +5,12 @@ import {
5
5
  JSON_HEADERS,
6
6
  assertRestResourceBinding,
7
7
  corsPreflightResponse,
8
- createRecorderForRuntime,
8
+ createNoopRecorder,
9
9
  createRequestAuthorizer,
10
10
  methodNotAllowed,
11
11
  nowMs,
12
- reportInvocation,
13
12
  withCors
14
- } from "./chunk-6TZNGFRM.js";
13
+ } from "./chunk-EPEEEI3C.js";
15
14
 
16
15
  // src/protocols/rest/invoke-tool.ts
17
16
  import { getParseErrorMessage, objectFromShape, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
@@ -27,11 +26,11 @@ function isEmptyArgs(value) {
27
26
  return false;
28
27
  return Object.keys(value).length === 0;
29
28
  }
30
- function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
29
+ function createInvokeToolHandler(mcp, options = {}) {
31
30
  assertRestResourceBinding(mcp, options);
32
- const authorizer = createRequestAuthorizer(mcp, options, recorder);
33
- const handle = async (request, toolName) => {
34
- const authResult = await authorizer.authorize(request);
31
+ const authorizer = createRequestAuthorizer(mcp, options);
32
+ const handle = async (request, toolName, recorder) => {
33
+ const authResult = await authorizer.authorize(request, recorder);
35
34
  if (!authResult.ok)
36
35
  return authResult.response;
37
36
  if (request.method !== "POST")
@@ -87,7 +86,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
87
86
  try {
88
87
  result = await tool.handler(args, new ToolContext(authResult.auth));
89
88
  } catch {
90
- reportInvocation(recorder, {
89
+ await recorder.emit({
91
90
  tool: tool.name,
92
91
  method: "tools/call",
93
92
  outcome: "handler_error",
@@ -99,7 +98,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
99
98
  });
100
99
  }
101
100
  if (result == null) {
102
- reportInvocation(recorder, {
101
+ await recorder.emit({
103
102
  tool: tool.name,
104
103
  method: "tools/call",
105
104
  outcome: "handler_error",
@@ -110,7 +109,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
110
109
  headers: JSON_HEADERS
111
110
  });
112
111
  }
113
- reportInvocation(recorder, {
112
+ await recorder.emit({
114
113
  tool: tool.name,
115
114
  method: "tools/call",
116
115
  outcome: result.isError ? "tool_error" : "ok",
@@ -122,10 +121,10 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
122
121
  isError: result.isError
123
122
  });
124
123
  };
125
- return async (request, toolName) => {
124
+ return async (request, toolName, recorder = createNoopRecorder()) => {
126
125
  if (request.method === "OPTIONS")
127
126
  return corsPreflightResponse("POST, OPTIONS");
128
- return withCors(await handle(request, toolName));
127
+ return withCors(await handle(request, toolName, recorder));
129
128
  };
130
129
  }
131
130
 
@@ -3,16 +3,15 @@ import {
3
3
  } from "./chunk-MA5H6PSF.js";
4
4
  import {
5
5
  corsPreflightResponse,
6
- createRecorderForRuntime,
6
+ createNoopRecorder,
7
7
  createRequestAuthorizer,
8
8
  nowMs,
9
- reportInvocation,
10
9
  withCors
11
- } from "./chunk-6TZNGFRM.js";
10
+ } from "./chunk-EPEEEI3C.js";
12
11
  import {
13
12
  describeError,
14
13
  log
15
- } from "./chunk-VQQZAFRA.js";
14
+ } from "./chunk-H37EB22A.js";
16
15
 
17
16
  // src/protocols/mcp/protocol.ts
18
17
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -25,7 +24,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
25
24
  try {
26
25
  result = await tool.handler(args, new ToolContext(auth));
27
26
  } catch {
28
- reportInvocation(recorder, {
27
+ await recorder.emit({
29
28
  tool: tool.name,
30
29
  method: "tools/call",
31
30
  outcome: "handler_error",
@@ -34,7 +33,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
34
33
  return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
35
34
  }
36
35
  if (result == null) {
37
- reportInvocation(recorder, {
36
+ await recorder.emit({
38
37
  tool: tool.name,
39
38
  method: "tools/call",
40
39
  outcome: "handler_error",
@@ -42,7 +41,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
42
41
  });
43
42
  return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
44
43
  }
45
- reportInvocation(recorder, {
44
+ await recorder.emit({
46
45
  tool: tool.name,
47
46
  method: "tools/call",
48
47
  outcome: result.isError ? "tool_error" : "ok",
@@ -51,10 +50,10 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
51
50
  return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
52
51
  };
53
52
  }
54
- function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
55
- const authorizer = createRequestAuthorizer(mcp, options, recorder);
56
- const handle = async (request) => {
57
- const authResult = await authorizer.authorize(request);
53
+ function createMcpProtocolHandler(mcp, options = {}) {
54
+ const authorizer = createRequestAuthorizer(mcp, options);
55
+ const handle = async (request, recorder) => {
56
+ const authResult = await authorizer.authorize(request, recorder);
58
57
  if (!authResult.ok)
59
58
  return authResult.response;
60
59
  try {
@@ -81,7 +80,7 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
81
80
  await server.connect(transport);
82
81
  return await transport.handleRequest(request);
83
82
  } catch (err) {
84
- recorder.record({ tool: null, method: "transport", outcome: "transport_error", durationMs: 0 });
83
+ await recorder.emit({ tool: null, method: "transport", outcome: "transport_error", durationMs: 0 });
85
84
  log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
86
85
  return Response.json(
87
86
  { jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
@@ -89,10 +88,10 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
89
88
  );
90
89
  }
91
90
  };
92
- return async (request) => {
91
+ return async (request, recorder = createNoopRecorder()) => {
93
92
  if (request.method === "OPTIONS")
94
93
  return corsPreflightResponse("GET, POST, DELETE, OPTIONS");
95
- return withCors(await handle(request));
94
+ return withCors(await handle(request, recorder));
96
95
  };
97
96
  }
98
97
 
@@ -13,7 +13,7 @@ function isFileMissing(err) {
13
13
  }
14
14
 
15
15
  // package.json
16
- var version = "0.17.0";
16
+ var version = "0.20.0";
17
17
 
18
18
  // src/protocols/rest/list-tools.ts
19
19
  var import_zod_compat = require("@modelcontextprotocol/sdk/server/zod-compat.js");
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildMcpListing
4
- } from "../chunk-FW6YL5C5.js";
5
- import "../chunk-6TZNGFRM.js";
6
- import "../chunk-VQQZAFRA.js";
4
+ } from "../chunk-7JJEPFSO.js";
5
+ import "../chunk-EPEEEI3C.js";
6
+ import "../chunk-H37EB22A.js";
7
7
  import "../chunk-6DXGZZA4.js";
8
8
  import {
9
9
  isFileMissing
10
10
  } from "../chunk-Y3ZFPEQH.js";
11
11
  import {
12
12
  version
13
- } from "../chunk-J5BTCP6T.js";
13
+ } from "../chunk-FTTVLHNN.js";
14
14
 
15
15
  // src/manifest/io.ts
16
16
  import { randomUUID } from "crypto";
package/dist/index.cjs CHANGED
@@ -31,9 +31,6 @@ module.exports = __toCommonJS(src_exports);
31
31
  // src/metrics/config.ts
32
32
  var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
33
33
  var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
34
- var METRICS_SAMPLE_RATE = 1;
35
- var METRICS_FLUSH_INTERVAL_MS = 5e3;
36
- var METRICS_MAX_BATCH_SIZE = 50;
37
34
  function assertMetricsEndpoint(endpoint) {
38
35
  let url;
39
36
  try {
@@ -53,10 +50,7 @@ function resolveMetricsConfig(config = true) {
53
50
  enabled: options.enabled ?? true,
54
51
  endpoint,
55
52
  headers: options.headers ?? {},
56
- apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
57
- sampleRate: METRICS_SAMPLE_RATE,
58
- flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
59
- maxBatchSize: METRICS_MAX_BATCH_SIZE
53
+ apiKeyEnvVar: METRICS_API_KEY_ENV_VAR
60
54
  });
61
55
  }
62
56
 
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { e as McpDefinitionInput, d as McpDefinition, Z as ZodRawShape, l as ToolDefinition, M as McpAuthConfig } from './types-COY42xux.js';
2
- export { A as AudioContent, C as ContentAnnotations, a as ContentBlock, E as EmbeddedBlobResource, b as EmbeddedResource, c as EmbeddedTextResource, I as ImageContent, J as JwtClaims, f as MetricsConfig, g as MetricsOptions, R as ResourceLink, h as ResourceLinkIcon, T as TextContent, i as ToolAnnotations, j as ToolContent, k as ToolContext, m as ToolHandlerResult, n as ZodSchema } from './types-COY42xux.js';
1
+ import { e as McpDefinitionInput, d as McpDefinition, Z as ZodRawShape, l as ToolDefinition, M as McpAuthConfig } from './types-CPkhCRxc.js';
2
+ export { A as AudioContent, C as ContentAnnotations, a as ContentBlock, E as EmbeddedBlobResource, b as EmbeddedResource, c as EmbeddedTextResource, I as ImageContent, J as JwtClaims, f as MetricsConfig, g as MetricsOptions, R as ResourceLink, h as ResourceLinkIcon, T as TextContent, i as ToolAnnotations, j as ToolContent, k as ToolContext, m as ToolHandlerResult, n as ZodSchema } from './types-CPkhCRxc.js';
3
3
  import 'zod';
4
4
 
5
5
  /**