@lovable.dev/mcp-js 0.14.0 → 0.15.1

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/README.md +28 -0
  2. package/dist/{chunk-YWLMMDET.js → chunk-CEDD57G2.js} +14 -3
  3. package/dist/{chunk-QC3DXQTH.js → chunk-DCWYK4CA.js} +34 -0
  4. package/dist/{chunk-NOFPRSSI.js → chunk-GIHCQT6L.js} +1 -1
  5. package/dist/{chunk-QDKOF4UF.js → chunk-ILYTJXDR.js} +2 -2
  6. package/dist/{chunk-VNRQPA4K.js → chunk-QT5L4CS3.js} +4 -3
  7. package/dist/{chunk-WLNT2FX7.js → chunk-UCPYLSNN.js} +18 -6
  8. package/dist/{chunk-G4XA7IJM.js → chunk-YTOMGJV5.js} +189 -2
  9. package/dist/cli/extract-manifest.cjs +1 -1
  10. package/dist/cli/extract-manifest.js +6 -6
  11. package/dist/index.cjs +33 -0
  12. package/dist/index.d.cts +2 -2
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.js +3 -1
  15. package/dist/protocols/mcp/index.cjs +230 -5
  16. package/dist/protocols/mcp/index.d.cts +3 -2
  17. package/dist/protocols/mcp/index.d.ts +3 -2
  18. package/dist/protocols/mcp/index.js +4 -3
  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 -3
  22. package/dist/protocols/rest/index.cjs +229 -5
  23. package/dist/protocols/rest/index.d.cts +4 -3
  24. package/dist/protocols/rest/index.d.ts +4 -3
  25. package/dist/protocols/rest/index.js +5 -4
  26. package/dist/recorder-qlzUKFxG.d.ts +21 -0
  27. package/dist/stacks/supabase/index.cjs +263 -28
  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 +13 -10
  31. package/dist/stacks/supabase/vite.cjs +28 -4
  32. package/dist/stacks/supabase/vite.js +30 -6
  33. package/dist/stacks/tanstack/index.cjs +243 -9
  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 +7 -6
  37. package/dist/{types-BanzncFh.d.ts → types-COY42xux.d.ts} +30 -1
  38. package/package.json +1 -1
@@ -72,6 +72,214 @@ function describeError(err) {
72
72
  return { value: String(err) };
73
73
  }
74
74
 
75
+ // src/metrics/config.ts
76
+ var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
77
+ var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
78
+ var METRICS_SAMPLE_RATE = 1;
79
+ var METRICS_FLUSH_INTERVAL_MS = 5e3;
80
+ var METRICS_MAX_BATCH_SIZE = 50;
81
+ function assertMetricsEndpoint(endpoint) {
82
+ let url;
83
+ try {
84
+ url = new URL(endpoint);
85
+ } catch {
86
+ throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
87
+ }
88
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
89
+ throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
90
+ }
91
+ }
92
+ function resolveMetricsConfig(config = true) {
93
+ const options = typeof config === "boolean" ? { enabled: config } : config;
94
+ const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
95
+ assertMetricsEndpoint(endpoint);
96
+ return Object.freeze({
97
+ enabled: options.enabled ?? true,
98
+ endpoint,
99
+ headers: options.headers ?? {},
100
+ apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
101
+ sampleRate: METRICS_SAMPLE_RATE,
102
+ flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
103
+ maxBatchSize: METRICS_MAX_BATCH_SIZE
104
+ });
105
+ }
106
+
107
+ // package.json
108
+ var version = "0.15.1";
109
+
110
+ // src/metrics/otlp.ts
111
+ var SCOPE_NAME = "@lovable.dev/mcp-js";
112
+ var EVENT_NAME = "mcp.tool.invocation";
113
+ var SEVERITY_INFO = 9;
114
+ function strAttr(key, value) {
115
+ return { key, value: { stringValue: value } };
116
+ }
117
+ function intAttr(key, value) {
118
+ return { key, value: { intValue: String(Math.round(value)) } };
119
+ }
120
+ function toLogRecord(rec) {
121
+ const attributes = [
122
+ strAttr("event.name", EVENT_NAME),
123
+ strAttr("mcp.method", rec.method),
124
+ strAttr("mcp.outcome", rec.outcome),
125
+ intAttr("mcp.duration_ms", rec.durationMs)
126
+ ];
127
+ if (rec.tool !== null)
128
+ attributes.push(strAttr("mcp.tool", rec.tool));
129
+ if (rec.reqBytes !== void 0)
130
+ attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
131
+ if (rec.resBytes !== void 0)
132
+ attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
133
+ return {
134
+ timeUnixNano: rec.timeUnixNano,
135
+ observedTimeUnixNano: rec.timeUnixNano,
136
+ severityNumber: SEVERITY_INFO,
137
+ severityText: "INFO",
138
+ body: { stringValue: EVENT_NAME },
139
+ attributes
140
+ };
141
+ }
142
+ function buildLogsPayload(records, server) {
143
+ return JSON.stringify({
144
+ resourceLogs: [
145
+ {
146
+ resource: {
147
+ attributes: [
148
+ strAttr("service.name", server.name),
149
+ strAttr("service.version", server.version),
150
+ strAttr("telemetry.sdk.name", SCOPE_NAME),
151
+ strAttr("telemetry.sdk.version", version),
152
+ strAttr("telemetry.sdk.language", "webjs")
153
+ ]
154
+ },
155
+ scopeLogs: [
156
+ {
157
+ scope: { name: SCOPE_NAME, version },
158
+ logRecords: records.map(toLogRecord)
159
+ }
160
+ ]
161
+ }
162
+ ]
163
+ });
164
+ }
165
+ function nowUnixNano() {
166
+ return `${Date.now()}000000`;
167
+ }
168
+
169
+ // src/metrics/recorder.ts
170
+ function nowMs() {
171
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
172
+ }
173
+ var NOOP_RECORDER = {
174
+ record() {
175
+ },
176
+ async flush() {
177
+ }
178
+ };
179
+ function createNoopRecorder() {
180
+ return NOOP_RECORDER;
181
+ }
182
+ function readRuntimeEnv(name) {
183
+ try {
184
+ const denoEnv = globalThis.Deno?.env;
185
+ const value = denoEnv?.get?.(name);
186
+ if (value)
187
+ return value;
188
+ } catch {
189
+ }
190
+ try {
191
+ if (typeof process !== "undefined") {
192
+ const value = process.env?.[name];
193
+ if (value)
194
+ return value;
195
+ }
196
+ } catch {
197
+ }
198
+ return void 0;
199
+ }
200
+ function probeWaitUntil() {
201
+ const fn = globalThis.EdgeRuntime?.waitUntil;
202
+ return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
203
+ }
204
+ function createMetricsRecorder(ctx, deps = {}) {
205
+ const { config, server } = ctx;
206
+ if (!config.enabled)
207
+ return NOOP_RECORDER;
208
+ const doFetch = deps.fetch ?? globalThis.fetch;
209
+ if (!doFetch)
210
+ return NOOP_RECORDER;
211
+ const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
212
+ const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
213
+ if (usesLovableEndpoint && !apiKey) {
214
+ log.debug("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar });
215
+ return NOOP_RECORDER;
216
+ }
217
+ const waitUntil = deps.waitUntil ?? probeWaitUntil();
218
+ const buffer = [];
219
+ let timer;
220
+ const schedule = (p) => {
221
+ if (waitUntil) {
222
+ try {
223
+ waitUntil(p);
224
+ return;
225
+ } catch {
226
+ }
227
+ }
228
+ void p.catch(() => {
229
+ });
230
+ };
231
+ const headers = {};
232
+ if (!usesLovableEndpoint) {
233
+ for (const [key, value] of Object.entries(config.headers)) {
234
+ if (key.toLowerCase() !== "content-type")
235
+ headers[key] = value;
236
+ }
237
+ }
238
+ headers["content-type"] = "application/json";
239
+ if (apiKey)
240
+ headers.authorization = `Bearer ${apiKey}`;
241
+ const flush = async () => {
242
+ if (buffer.length === 0)
243
+ return;
244
+ const records = buffer.splice(0, buffer.length);
245
+ try {
246
+ const res = await doFetch(config.endpoint, {
247
+ method: "POST",
248
+ headers,
249
+ body: buildLogsPayload(records, server),
250
+ keepalive: true
251
+ });
252
+ if (!res.ok)
253
+ log.debug("metrics.flush_rejected", { status: res.status });
254
+ } catch (err) {
255
+ log.debug("metrics.flush_failed", describeError(err));
256
+ }
257
+ };
258
+ const ensureTimer = () => {
259
+ if (timer !== void 0)
260
+ return;
261
+ timer = setInterval(() => schedule(flush()), config.flushIntervalMs);
262
+ timer.unref?.();
263
+ };
264
+ return {
265
+ record(ev) {
266
+ if (config.sampleRate < 1 && Math.random() >= config.sampleRate)
267
+ return;
268
+ buffer.push({ ...ev, timeUnixNano: nowUnixNano() });
269
+ ensureTimer();
270
+ if (buffer.length >= config.maxBatchSize)
271
+ schedule(flush());
272
+ },
273
+ flush
274
+ };
275
+ }
276
+ function createRecorderForRuntime(mcp) {
277
+ const config = resolveMetricsConfig(mcp.metrics);
278
+ if (!config.enabled)
279
+ return createNoopRecorder();
280
+ return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
281
+ }
282
+
75
283
  // src/core/promise.ts
76
284
  function cachedPromise(load, label) {
77
285
  let settled = false;
@@ -492,12 +700,13 @@ function assertRequiredScopes(auth, context) {
492
700
  throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
493
701
  }
494
702
  }
495
- function createRequestAuthorizer(mcp, options = {}) {
703
+ function createRequestAuthorizer(mcp, options = {}, recorder) {
496
704
  const runtime = getOAuthRuntime(mcp, options);
497
705
  return {
498
706
  async authorize(request) {
499
707
  if (runtime.kind === "unconfigured")
500
708
  return { ok: true };
709
+ const startedAt = nowMs();
501
710
  const token = parseBearerToken(request);
502
711
  if (!token) {
503
712
  log.info("auth.no_bearer_token", { outcome: "401" });
@@ -510,6 +719,12 @@ function createRequestAuthorizer(mcp, options = {}) {
510
719
  } catch (err) {
511
720
  if (err instanceof OAuthConfigurationError) {
512
721
  log.error("auth.config_error", { ...describeError(err), outcome: "500" });
722
+ recorder?.record({
723
+ tool: null,
724
+ method: "authorize",
725
+ outcome: "auth_config_error",
726
+ durationMs: nowMs() - startedAt
727
+ });
513
728
  return { ok: false, response: oauthConfigurationErrorResponse() };
514
729
  }
515
730
  if (err instanceof OAuthTokenError) {
@@ -607,23 +822,32 @@ function corsPreflightResponse(allowMethods) {
607
822
  }
608
823
 
609
824
  // src/protocols/mcp/protocol.ts
610
- function adaptToolToSdkCallback(tool, auth) {
825
+ function adaptToolToSdkCallback(tool, auth, recorder) {
611
826
  return async (first) => {
612
827
  const args = tool.inputSchema ? first ?? {} : {};
828
+ const start = nowMs();
613
829
  let result;
614
830
  try {
615
831
  result = await tool.handler(args, new ToolContext(auth));
616
832
  } catch {
833
+ recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
617
834
  return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
618
835
  }
619
836
  if (result == null) {
837
+ recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
620
838
  return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
621
839
  }
840
+ recorder.record({
841
+ tool: tool.name,
842
+ method: "tools/call",
843
+ outcome: result.isError ? "tool_error" : "ok",
844
+ durationMs: nowMs() - start
845
+ });
622
846
  return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
623
847
  };
624
848
  }
625
- function createMcpProtocolHandler(mcp, options = {}) {
626
- const authorizer = createRequestAuthorizer(mcp, options);
849
+ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
850
+ const authorizer = createRequestAuthorizer(mcp, options, recorder);
627
851
  const handle = async (request) => {
628
852
  const authResult = await authorizer.authorize(request);
629
853
  if (!authResult.ok)
@@ -643,7 +867,7 @@ function createMcpProtocolHandler(mcp, options = {}) {
643
867
  outputSchema: tool.outputSchema,
644
868
  annotations: tool.annotations
645
869
  },
646
- adaptToolToSdkCallback(tool, authResult.auth)
870
+ adaptToolToSdkCallback(tool, authResult.auth, recorder)
647
871
  );
648
872
  }
649
873
  const transport = new import_webStandardStreamableHttp.WebStandardStreamableHTTPServerTransport({
@@ -652,6 +876,7 @@ function createMcpProtocolHandler(mcp, options = {}) {
652
876
  await server.connect(transport);
653
877
  return await transport.handleRequest(request);
654
878
  } catch (err) {
879
+ recorder.record({ tool: null, method: "transport", outcome: "transport_error", durationMs: 0 });
655
880
  log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
656
881
  return Response.json(
657
882
  { jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
@@ -1,5 +1,6 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
- import { d as McpDefinition } from '../../types-BanzncFh.js';
2
+ import { d as McpDefinition } from '../../types-COY42xux.js';
3
+ import { M as MetricsRecorder } from '../../recorder-qlzUKFxG.js';
3
4
  import 'zod';
4
5
 
5
6
  type McpProtocolHandler = (request: Request) => Promise<Response>;
@@ -9,6 +10,6 @@ type McpProtocolHandler = (request: Request) => Promise<Response>;
9
10
  * request — the transport is stateless, so this is cheap and there is no
10
11
  * cross-request state to leak.
11
12
  */
12
- declare function createMcpProtocolHandler(mcp: McpDefinition, options?: McpRuntimeOptions): McpProtocolHandler;
13
+ declare function createMcpProtocolHandler(mcp: McpDefinition, options?: McpRuntimeOptions, recorder?: MetricsRecorder): McpProtocolHandler;
13
14
 
14
15
  export { type McpProtocolHandler, createMcpProtocolHandler };
@@ -1,5 +1,6 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
- import { d as McpDefinition } from '../../types-BanzncFh.js';
2
+ import { d as McpDefinition } from '../../types-COY42xux.js';
3
+ import { M as MetricsRecorder } from '../../recorder-qlzUKFxG.js';
3
4
  import 'zod';
4
5
 
5
6
  type McpProtocolHandler = (request: Request) => Promise<Response>;
@@ -9,6 +10,6 @@ type McpProtocolHandler = (request: Request) => Promise<Response>;
9
10
  * request — the transport is stateless, so this is cheap and there is no
10
11
  * cross-request state to leak.
11
12
  */
12
- declare function createMcpProtocolHandler(mcp: McpDefinition, options?: McpRuntimeOptions): McpProtocolHandler;
13
+ declare function createMcpProtocolHandler(mcp: McpDefinition, options?: McpRuntimeOptions, recorder?: MetricsRecorder): McpProtocolHandler;
13
14
 
14
15
  export { type McpProtocolHandler, createMcpProtocolHandler };
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  createMcpProtocolHandler
3
- } from "../../chunk-WLNT2FX7.js";
3
+ } from "../../chunk-UCPYLSNN.js";
4
4
  import "../../chunk-MA5H6PSF.js";
5
- import "../../chunk-G4XA7IJM.js";
6
- import "../../chunk-QC3DXQTH.js";
5
+ import "../../chunk-YTOMGJV5.js";
6
+ import "../../chunk-DCWYK4CA.js";
7
7
  import "../../chunk-6DXGZZA4.js";
8
+ import "../../chunk-GIHCQT6L.js";
8
9
  export {
9
10
  createMcpProtocolHandler
10
11
  };
@@ -1,5 +1,5 @@
1
1
  import { M as McpRuntimeOptions } from '../authorize-BMeXd_Sh.js';
2
- import { d as McpDefinition } from '../types-BanzncFh.js';
2
+ import { d as McpDefinition } from '../types-COY42xux.js';
3
3
  import 'zod';
4
4
 
5
5
  type OAuthProtectedResourceMetadataHandler = (request: Request) => Promise<Response>;
@@ -1,5 +1,5 @@
1
1
  import { M as McpRuntimeOptions } from '../authorize-BMeXd_Sh.js';
2
- import { d as McpDefinition } from '../types-BanzncFh.js';
2
+ import { d as McpDefinition } from '../types-COY42xux.js';
3
3
  import 'zod';
4
4
 
5
5
  type OAuthProtectedResourceMetadataHandler = (request: Request) => Promise<Response>;
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  createOAuthProtectedResourceMetadataHandler
3
- } from "../chunk-QDKOF4UF.js";
4
- import "../chunk-G4XA7IJM.js";
5
- import "../chunk-QC3DXQTH.js";
3
+ } from "../chunk-ILYTJXDR.js";
4
+ import "../chunk-YTOMGJV5.js";
5
+ import "../chunk-DCWYK4CA.js";
6
6
  import "../chunk-6DXGZZA4.js";
7
+ import "../chunk-GIHCQT6L.js";
7
8
  export {
8
9
  createOAuthProtectedResourceMetadataHandler
9
10
  };
@@ -82,6 +82,214 @@ function describeError(err) {
82
82
  return { value: String(err) };
83
83
  }
84
84
 
85
+ // src/metrics/config.ts
86
+ var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
87
+ var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
88
+ var METRICS_SAMPLE_RATE = 1;
89
+ var METRICS_FLUSH_INTERVAL_MS = 5e3;
90
+ var METRICS_MAX_BATCH_SIZE = 50;
91
+ function assertMetricsEndpoint(endpoint) {
92
+ let url;
93
+ try {
94
+ url = new URL(endpoint);
95
+ } catch {
96
+ throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
97
+ }
98
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
99
+ throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
100
+ }
101
+ }
102
+ function resolveMetricsConfig(config = true) {
103
+ const options = typeof config === "boolean" ? { enabled: config } : config;
104
+ const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
105
+ assertMetricsEndpoint(endpoint);
106
+ return Object.freeze({
107
+ enabled: options.enabled ?? true,
108
+ endpoint,
109
+ headers: options.headers ?? {},
110
+ apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
111
+ sampleRate: METRICS_SAMPLE_RATE,
112
+ flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
113
+ maxBatchSize: METRICS_MAX_BATCH_SIZE
114
+ });
115
+ }
116
+
117
+ // package.json
118
+ var version = "0.15.1";
119
+
120
+ // src/metrics/otlp.ts
121
+ var SCOPE_NAME = "@lovable.dev/mcp-js";
122
+ var EVENT_NAME = "mcp.tool.invocation";
123
+ var SEVERITY_INFO = 9;
124
+ function strAttr(key, value) {
125
+ return { key, value: { stringValue: value } };
126
+ }
127
+ function intAttr(key, value) {
128
+ return { key, value: { intValue: String(Math.round(value)) } };
129
+ }
130
+ function toLogRecord(rec) {
131
+ const attributes = [
132
+ strAttr("event.name", EVENT_NAME),
133
+ strAttr("mcp.method", rec.method),
134
+ strAttr("mcp.outcome", rec.outcome),
135
+ intAttr("mcp.duration_ms", rec.durationMs)
136
+ ];
137
+ if (rec.tool !== null)
138
+ attributes.push(strAttr("mcp.tool", rec.tool));
139
+ if (rec.reqBytes !== void 0)
140
+ attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
141
+ if (rec.resBytes !== void 0)
142
+ attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
143
+ return {
144
+ timeUnixNano: rec.timeUnixNano,
145
+ observedTimeUnixNano: rec.timeUnixNano,
146
+ severityNumber: SEVERITY_INFO,
147
+ severityText: "INFO",
148
+ body: { stringValue: EVENT_NAME },
149
+ attributes
150
+ };
151
+ }
152
+ function buildLogsPayload(records, server) {
153
+ return JSON.stringify({
154
+ resourceLogs: [
155
+ {
156
+ resource: {
157
+ attributes: [
158
+ strAttr("service.name", server.name),
159
+ strAttr("service.version", server.version),
160
+ strAttr("telemetry.sdk.name", SCOPE_NAME),
161
+ strAttr("telemetry.sdk.version", version),
162
+ strAttr("telemetry.sdk.language", "webjs")
163
+ ]
164
+ },
165
+ scopeLogs: [
166
+ {
167
+ scope: { name: SCOPE_NAME, version },
168
+ logRecords: records.map(toLogRecord)
169
+ }
170
+ ]
171
+ }
172
+ ]
173
+ });
174
+ }
175
+ function nowUnixNano() {
176
+ return `${Date.now()}000000`;
177
+ }
178
+
179
+ // src/metrics/recorder.ts
180
+ function nowMs() {
181
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
182
+ }
183
+ var NOOP_RECORDER = {
184
+ record() {
185
+ },
186
+ async flush() {
187
+ }
188
+ };
189
+ function createNoopRecorder() {
190
+ return NOOP_RECORDER;
191
+ }
192
+ function readRuntimeEnv(name) {
193
+ try {
194
+ const denoEnv = globalThis.Deno?.env;
195
+ const value = denoEnv?.get?.(name);
196
+ if (value)
197
+ return value;
198
+ } catch {
199
+ }
200
+ try {
201
+ if (typeof process !== "undefined") {
202
+ const value = process.env?.[name];
203
+ if (value)
204
+ return value;
205
+ }
206
+ } catch {
207
+ }
208
+ return void 0;
209
+ }
210
+ function probeWaitUntil() {
211
+ const fn = globalThis.EdgeRuntime?.waitUntil;
212
+ return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
213
+ }
214
+ function createMetricsRecorder(ctx, deps = {}) {
215
+ const { config, server } = ctx;
216
+ if (!config.enabled)
217
+ return NOOP_RECORDER;
218
+ 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 });
225
+ return NOOP_RECORDER;
226
+ }
227
+ const waitUntil = deps.waitUntil ?? probeWaitUntil();
228
+ const buffer = [];
229
+ let timer;
230
+ const schedule = (p) => {
231
+ if (waitUntil) {
232
+ try {
233
+ waitUntil(p);
234
+ return;
235
+ } catch {
236
+ }
237
+ }
238
+ void p.catch(() => {
239
+ });
240
+ };
241
+ const headers = {};
242
+ if (!usesLovableEndpoint) {
243
+ for (const [key, value] of Object.entries(config.headers)) {
244
+ if (key.toLowerCase() !== "content-type")
245
+ headers[key] = value;
246
+ }
247
+ }
248
+ headers["content-type"] = "application/json";
249
+ if (apiKey)
250
+ headers.authorization = `Bearer ${apiKey}`;
251
+ const flush = async () => {
252
+ if (buffer.length === 0)
253
+ return;
254
+ const records = buffer.splice(0, buffer.length);
255
+ try {
256
+ const res = await doFetch(config.endpoint, {
257
+ method: "POST",
258
+ headers,
259
+ body: buildLogsPayload(records, server),
260
+ keepalive: true
261
+ });
262
+ if (!res.ok)
263
+ log.debug("metrics.flush_rejected", { status: res.status });
264
+ } catch (err) {
265
+ log.debug("metrics.flush_failed", describeError(err));
266
+ }
267
+ };
268
+ const ensureTimer = () => {
269
+ if (timer !== void 0)
270
+ return;
271
+ timer = setInterval(() => schedule(flush()), config.flushIntervalMs);
272
+ timer.unref?.();
273
+ };
274
+ return {
275
+ record(ev) {
276
+ if (config.sampleRate < 1 && Math.random() >= config.sampleRate)
277
+ return;
278
+ buffer.push({ ...ev, timeUnixNano: nowUnixNano() });
279
+ ensureTimer();
280
+ if (buffer.length >= config.maxBatchSize)
281
+ schedule(flush());
282
+ },
283
+ flush
284
+ };
285
+ }
286
+ function createRecorderForRuntime(mcp) {
287
+ const config = resolveMetricsConfig(mcp.metrics);
288
+ if (!config.enabled)
289
+ return createNoopRecorder();
290
+ return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
291
+ }
292
+
85
293
  // src/core/promise.ts
86
294
  function cachedPromise(load, label) {
87
295
  let settled = false;
@@ -510,12 +718,13 @@ function assertRequiredScopes(auth, context) {
510
718
  throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
511
719
  }
512
720
  }
513
- function createRequestAuthorizer(mcp, options = {}) {
721
+ function createRequestAuthorizer(mcp, options = {}, recorder) {
514
722
  const runtime = getOAuthRuntime(mcp, options);
515
723
  return {
516
724
  async authorize(request) {
517
725
  if (runtime.kind === "unconfigured")
518
726
  return { ok: true };
727
+ const startedAt = nowMs();
519
728
  const token = parseBearerToken(request);
520
729
  if (!token) {
521
730
  log.info("auth.no_bearer_token", { outcome: "401" });
@@ -528,6 +737,12 @@ function createRequestAuthorizer(mcp, options = {}) {
528
737
  } catch (err) {
529
738
  if (err instanceof OAuthConfigurationError) {
530
739
  log.error("auth.config_error", { ...describeError(err), outcome: "500" });
740
+ recorder?.record({
741
+ tool: null,
742
+ method: "authorize",
743
+ outcome: "auth_config_error",
744
+ durationMs: nowMs() - startedAt
745
+ });
531
746
  return { ok: false, response: oauthConfigurationErrorResponse() };
532
747
  }
533
748
  if (err instanceof OAuthTokenError) {
@@ -604,9 +819,9 @@ function buildMcpListing(mcp) {
604
819
  }))
605
820
  };
606
821
  }
607
- function createListToolsHandler(mcp, options = {}) {
822
+ function createListToolsHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
608
823
  assertRestResourceBinding(mcp, options);
609
- const authorizer = createRequestAuthorizer(mcp, options);
824
+ const authorizer = createRequestAuthorizer(mcp, options, recorder);
610
825
  const handle = async (request) => {
611
826
  const authResult = await authorizer.authorize(request);
612
827
  if (!authResult.ok)
@@ -682,9 +897,9 @@ function isEmptyArgs(value) {
682
897
  return false;
683
898
  return Object.keys(value).length === 0;
684
899
  }
685
- function createInvokeToolHandler(mcp, options = {}) {
900
+ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
686
901
  assertRestResourceBinding(mcp, options);
687
- const authorizer = createRequestAuthorizer(mcp, options);
902
+ const authorizer = createRequestAuthorizer(mcp, options, recorder);
688
903
  const handle = async (request, toolName) => {
689
904
  const authResult = await authorizer.authorize(request);
690
905
  if (!authResult.ok)
@@ -738,20 +953,29 @@ function createInvokeToolHandler(mcp, options = {}) {
738
953
  });
739
954
  }
740
955
  let result;
956
+ const start = nowMs();
741
957
  try {
742
958
  result = await tool.handler(args, new ToolContext(authResult.auth));
743
959
  } catch {
960
+ recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
744
961
  return new Response(JSON.stringify({ error: "handler threw", tool: toolName }), {
745
962
  status: 500,
746
963
  headers: JSON_HEADERS
747
964
  });
748
965
  }
749
966
  if (result == null) {
967
+ recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
750
968
  return new Response(JSON.stringify({ error: `tool "${toolName}" returned no result` }), {
751
969
  status: 500,
752
970
  headers: JSON_HEADERS
753
971
  });
754
972
  }
973
+ recorder.record({
974
+ tool: tool.name,
975
+ method: "tools/call",
976
+ outcome: result.isError ? "tool_error" : "ok",
977
+ durationMs: nowMs() - start
978
+ });
755
979
  return Response.json({
756
980
  content: result.content ?? [],
757
981
  structuredContent: result.structuredContent,
@@ -1,11 +1,12 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
- import { d as McpDefinition } from '../../types-BanzncFh.js';
2
+ import { d as McpDefinition } from '../../types-COY42xux.js';
3
+ import { M as MetricsRecorder } from '../../recorder-qlzUKFxG.js';
3
4
  import 'zod';
4
5
 
5
6
  type RestListToolsHandler = (request: Request) => Promise<Response>;
6
- declare function createListToolsHandler(mcp: McpDefinition, options?: McpRuntimeOptions): RestListToolsHandler;
7
+ declare function createListToolsHandler(mcp: McpDefinition, options?: McpRuntimeOptions, recorder?: MetricsRecorder): RestListToolsHandler;
7
8
 
8
9
  type RestInvokeToolHandler = (request: Request, toolName: string) => Promise<Response>;
9
- declare function createInvokeToolHandler(mcp: McpDefinition, options?: McpRuntimeOptions): RestInvokeToolHandler;
10
+ declare function createInvokeToolHandler(mcp: McpDefinition, options?: McpRuntimeOptions, recorder?: MetricsRecorder): RestInvokeToolHandler;
10
11
 
11
12
  export { type RestInvokeToolHandler, type RestListToolsHandler, createInvokeToolHandler, createListToolsHandler };