@lovable.dev/mcp-js 0.15.0 → 0.16.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.
@@ -72,6 +72,223 @@ 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.16.0";
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
+ function reportInvocation(recorder, ev) {
174
+ log.info("tool.invoked", { tool: ev.tool, method: ev.method, outcome: ev.outcome, durationMs: ev.durationMs });
175
+ recorder.record(ev);
176
+ }
177
+ var NOOP_RECORDER = {
178
+ record() {
179
+ },
180
+ async flush() {
181
+ }
182
+ };
183
+ function createNoopRecorder() {
184
+ return NOOP_RECORDER;
185
+ }
186
+ function readRuntimeEnv(name) {
187
+ try {
188
+ const denoEnv = globalThis.Deno?.env;
189
+ const value = denoEnv?.get?.(name);
190
+ if (value)
191
+ return value;
192
+ } catch {
193
+ }
194
+ try {
195
+ if (typeof process !== "undefined") {
196
+ const value = process.env?.[name];
197
+ if (value)
198
+ return value;
199
+ }
200
+ } catch {
201
+ }
202
+ return void 0;
203
+ }
204
+ function probeWaitUntil() {
205
+ const fn = globalThis.EdgeRuntime?.waitUntil;
206
+ return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
207
+ }
208
+ function createMetricsRecorder(ctx, deps = {}) {
209
+ const { config, server } = ctx;
210
+ if (!config.enabled)
211
+ return NOOP_RECORDER;
212
+ const doFetch = deps.fetch ?? globalThis.fetch;
213
+ if (!doFetch) {
214
+ log.warn("metrics.disabled_no_fetch", { endpoint: config.endpoint });
215
+ return NOOP_RECORDER;
216
+ }
217
+ const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
218
+ const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
219
+ if (usesLovableEndpoint && !apiKey) {
220
+ log.warn("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar, endpoint: config.endpoint });
221
+ return NOOP_RECORDER;
222
+ }
223
+ const waitUntil = deps.waitUntil ?? probeWaitUntil();
224
+ const buffer = [];
225
+ let timer;
226
+ const schedule = (p) => {
227
+ if (waitUntil) {
228
+ try {
229
+ waitUntil(p);
230
+ return;
231
+ } catch (err) {
232
+ log.warn("metrics.wait_until_failed", describeError(err));
233
+ }
234
+ }
235
+ void p.catch(() => {
236
+ });
237
+ };
238
+ const headers = {};
239
+ if (!usesLovableEndpoint) {
240
+ for (const [key, value] of Object.entries(config.headers)) {
241
+ if (key.toLowerCase() !== "content-type")
242
+ headers[key] = value;
243
+ }
244
+ }
245
+ headers["content-type"] = "application/json";
246
+ if (apiKey)
247
+ headers.authorization = `Bearer ${apiKey}`;
248
+ const flush = async () => {
249
+ if (buffer.length === 0)
250
+ return;
251
+ const records = buffer.splice(0, buffer.length);
252
+ const dropped = records.length;
253
+ try {
254
+ const res = await doFetch(config.endpoint, {
255
+ method: "POST",
256
+ headers,
257
+ body: buildLogsPayload(records, server),
258
+ keepalive: true
259
+ });
260
+ if (!res.ok) {
261
+ log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
262
+ }
263
+ } catch (err) {
264
+ log.warn("metrics.flush_failed", { ...describeError(err), dropped, endpoint: config.endpoint });
265
+ }
266
+ };
267
+ const ensureTimer = () => {
268
+ if (timer !== void 0)
269
+ return;
270
+ timer = setInterval(() => schedule(flush()), config.flushIntervalMs);
271
+ timer.unref?.();
272
+ };
273
+ return {
274
+ record(ev) {
275
+ if (config.sampleRate < 1 && Math.random() >= config.sampleRate)
276
+ return;
277
+ buffer.push({ ...ev, timeUnixNano: nowUnixNano() });
278
+ ensureTimer();
279
+ if (buffer.length >= config.maxBatchSize)
280
+ schedule(flush());
281
+ },
282
+ flush
283
+ };
284
+ }
285
+ function createRecorderForRuntime(mcp) {
286
+ const config = resolveMetricsConfig(mcp.metrics);
287
+ if (!config.enabled)
288
+ return createNoopRecorder();
289
+ return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
290
+ }
291
+
75
292
  // src/core/promise.ts
76
293
  function cachedPromise(load, label) {
77
294
  let settled = false;
@@ -492,12 +709,13 @@ function assertRequiredScopes(auth, context) {
492
709
  throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
493
710
  }
494
711
  }
495
- function createRequestAuthorizer(mcp, options = {}) {
712
+ function createRequestAuthorizer(mcp, options = {}, recorder) {
496
713
  const runtime = getOAuthRuntime(mcp, options);
497
714
  return {
498
715
  async authorize(request) {
499
716
  if (runtime.kind === "unconfigured")
500
717
  return { ok: true };
718
+ const startedAt = nowMs();
501
719
  const token = parseBearerToken(request);
502
720
  if (!token) {
503
721
  log.info("auth.no_bearer_token", { outcome: "401" });
@@ -510,6 +728,12 @@ function createRequestAuthorizer(mcp, options = {}) {
510
728
  } catch (err) {
511
729
  if (err instanceof OAuthConfigurationError) {
512
730
  log.error("auth.config_error", { ...describeError(err), outcome: "500" });
731
+ recorder?.record({
732
+ tool: null,
733
+ method: "authorize",
734
+ outcome: "auth_config_error",
735
+ durationMs: nowMs() - startedAt
736
+ });
513
737
  return { ok: false, response: oauthConfigurationErrorResponse() };
514
738
  }
515
739
  if (err instanceof OAuthTokenError) {
@@ -606,214 +830,6 @@ function corsPreflightResponse(allowMethods) {
606
830
  });
607
831
  }
608
832
 
609
- // src/metrics/config.ts
610
- var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
611
- var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
612
- var METRICS_SAMPLE_RATE = 1;
613
- var METRICS_FLUSH_INTERVAL_MS = 5e3;
614
- var METRICS_MAX_BATCH_SIZE = 50;
615
- function assertMetricsEndpoint(endpoint) {
616
- let url;
617
- try {
618
- url = new URL(endpoint);
619
- } catch {
620
- throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
621
- }
622
- if (url.protocol !== "https:" && url.protocol !== "http:") {
623
- throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
624
- }
625
- }
626
- function resolveMetricsConfig(config = true) {
627
- const options = typeof config === "boolean" ? { enabled: config } : config;
628
- const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
629
- assertMetricsEndpoint(endpoint);
630
- return Object.freeze({
631
- enabled: options.enabled ?? true,
632
- endpoint,
633
- headers: options.headers ?? {},
634
- apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
635
- sampleRate: METRICS_SAMPLE_RATE,
636
- flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
637
- maxBatchSize: METRICS_MAX_BATCH_SIZE
638
- });
639
- }
640
-
641
- // package.json
642
- var version = "0.15.0";
643
-
644
- // src/metrics/otlp.ts
645
- var SCOPE_NAME = "@lovable.dev/mcp-js";
646
- var EVENT_NAME = "mcp.tool.invocation";
647
- var SEVERITY_INFO = 9;
648
- function strAttr(key, value) {
649
- return { key, value: { stringValue: value } };
650
- }
651
- function intAttr(key, value) {
652
- return { key, value: { intValue: String(Math.round(value)) } };
653
- }
654
- function toLogRecord(rec) {
655
- const attributes = [
656
- strAttr("event.name", EVENT_NAME),
657
- strAttr("mcp.method", rec.method),
658
- strAttr("mcp.outcome", rec.outcome),
659
- intAttr("mcp.duration_ms", rec.durationMs)
660
- ];
661
- if (rec.tool !== null)
662
- attributes.push(strAttr("mcp.tool", rec.tool));
663
- if (rec.reqBytes !== void 0)
664
- attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
665
- if (rec.resBytes !== void 0)
666
- attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
667
- return {
668
- timeUnixNano: rec.timeUnixNano,
669
- observedTimeUnixNano: rec.timeUnixNano,
670
- severityNumber: SEVERITY_INFO,
671
- severityText: "INFO",
672
- body: { stringValue: EVENT_NAME },
673
- attributes
674
- };
675
- }
676
- function buildLogsPayload(records, server) {
677
- return JSON.stringify({
678
- resourceLogs: [
679
- {
680
- resource: {
681
- attributes: [
682
- strAttr("service.name", server.name),
683
- strAttr("service.version", server.version),
684
- strAttr("telemetry.sdk.name", SCOPE_NAME),
685
- strAttr("telemetry.sdk.version", version),
686
- strAttr("telemetry.sdk.language", "webjs")
687
- ]
688
- },
689
- scopeLogs: [
690
- {
691
- scope: { name: SCOPE_NAME, version },
692
- logRecords: records.map(toLogRecord)
693
- }
694
- ]
695
- }
696
- ]
697
- });
698
- }
699
- function nowUnixNano() {
700
- return `${Date.now()}000000`;
701
- }
702
-
703
- // src/metrics/recorder.ts
704
- function nowMs() {
705
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
706
- }
707
- var NOOP_RECORDER = {
708
- record() {
709
- },
710
- async flush() {
711
- }
712
- };
713
- function createNoopRecorder() {
714
- return NOOP_RECORDER;
715
- }
716
- function readRuntimeEnv(name) {
717
- try {
718
- const denoEnv = globalThis.Deno?.env;
719
- const value = denoEnv?.get?.(name);
720
- if (value)
721
- return value;
722
- } catch {
723
- }
724
- try {
725
- if (typeof process !== "undefined") {
726
- const value = process.env?.[name];
727
- if (value)
728
- return value;
729
- }
730
- } catch {
731
- }
732
- return void 0;
733
- }
734
- function probeWaitUntil() {
735
- const fn = globalThis.EdgeRuntime?.waitUntil;
736
- return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
737
- }
738
- function createMetricsRecorder(ctx, deps = {}) {
739
- const { config, server } = ctx;
740
- if (!config.enabled)
741
- return NOOP_RECORDER;
742
- const doFetch = deps.fetch ?? globalThis.fetch;
743
- if (!doFetch)
744
- return NOOP_RECORDER;
745
- const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
746
- const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
747
- if (usesLovableEndpoint && !apiKey) {
748
- log.debug("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar });
749
- return NOOP_RECORDER;
750
- }
751
- const waitUntil = deps.waitUntil ?? probeWaitUntil();
752
- const buffer = [];
753
- let timer;
754
- const schedule = (p) => {
755
- if (waitUntil) {
756
- try {
757
- waitUntil(p);
758
- return;
759
- } catch {
760
- }
761
- }
762
- void p.catch(() => {
763
- });
764
- };
765
- const headers = {};
766
- if (!usesLovableEndpoint) {
767
- for (const [key, value] of Object.entries(config.headers)) {
768
- if (key.toLowerCase() !== "content-type")
769
- headers[key] = value;
770
- }
771
- }
772
- headers["content-type"] = "application/json";
773
- if (apiKey)
774
- headers.authorization = `Bearer ${apiKey}`;
775
- const flush = async () => {
776
- if (buffer.length === 0)
777
- return;
778
- const records = buffer.splice(0, buffer.length);
779
- try {
780
- const res = await doFetch(config.endpoint, {
781
- method: "POST",
782
- headers,
783
- body: buildLogsPayload(records, server),
784
- keepalive: true
785
- });
786
- if (!res.ok)
787
- log.debug("metrics.flush_rejected", { status: res.status });
788
- } catch (err) {
789
- log.debug("metrics.flush_failed", describeError(err));
790
- }
791
- };
792
- const ensureTimer = () => {
793
- if (timer !== void 0)
794
- return;
795
- timer = setInterval(() => schedule(flush()), config.flushIntervalMs);
796
- timer.unref?.();
797
- };
798
- return {
799
- record(ev) {
800
- if (config.sampleRate < 1 && Math.random() >= config.sampleRate)
801
- return;
802
- buffer.push({ ...ev, timeUnixNano: nowUnixNano() });
803
- ensureTimer();
804
- if (buffer.length >= config.maxBatchSize)
805
- schedule(flush());
806
- },
807
- flush
808
- };
809
- }
810
- function createRecorderForRuntime(mcp) {
811
- const config = resolveMetricsConfig(mcp.metrics);
812
- if (!config.enabled)
813
- return createNoopRecorder();
814
- return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
815
- }
816
-
817
833
  // src/protocols/mcp/protocol.ts
818
834
  function adaptToolToSdkCallback(tool, auth, recorder) {
819
835
  return async (first) => {
@@ -823,14 +839,24 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
823
839
  try {
824
840
  result = await tool.handler(args, new ToolContext(auth));
825
841
  } catch {
826
- recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
842
+ reportInvocation(recorder, {
843
+ tool: tool.name,
844
+ method: "tools/call",
845
+ outcome: "handler_error",
846
+ durationMs: nowMs() - start
847
+ });
827
848
  return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
828
849
  }
829
850
  if (result == null) {
830
- recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
851
+ reportInvocation(recorder, {
852
+ tool: tool.name,
853
+ method: "tools/call",
854
+ outcome: "handler_error",
855
+ durationMs: nowMs() - start
856
+ });
831
857
  return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
832
858
  }
833
- recorder.record({
859
+ reportInvocation(recorder, {
834
860
  tool: tool.name,
835
861
  method: "tools/call",
836
862
  outcome: result.isError ? "tool_error" : "ok",
@@ -840,7 +866,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
840
866
  };
841
867
  }
842
868
  function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
843
- const authorizer = createRequestAuthorizer(mcp, options);
869
+ const authorizer = createRequestAuthorizer(mcp, options, recorder);
844
870
  const handle = async (request) => {
845
871
  const authResult = await authorizer.authorize(request);
846
872
  if (!authResult.ok)
@@ -1,6 +1,6 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
2
  import { d as McpDefinition } from '../../types-COY42xux.js';
3
- import { M as MetricsRecorder } from '../../recorder-B-eJU4Qu.js';
3
+ import { M as MetricsRecorder } from '../../recorder-qlzUKFxG.js';
4
4
  import 'zod';
5
5
 
6
6
  type McpProtocolHandler = (request: Request) => Promise<Response>;
@@ -1,6 +1,6 @@
1
1
  import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
2
2
  import { d as McpDefinition } from '../../types-COY42xux.js';
3
- import { M as MetricsRecorder } from '../../recorder-B-eJU4Qu.js';
3
+ import { M as MetricsRecorder } from '../../recorder-qlzUKFxG.js';
4
4
  import 'zod';
5
5
 
6
6
  type McpProtocolHandler = (request: Request) => Promise<Response>;
@@ -1,12 +1,11 @@
1
1
  import {
2
2
  createMcpProtocolHandler
3
- } from "../../chunk-RHDEW56Y.js";
4
- import "../../chunk-XQSTN54Y.js";
5
- import "../../chunk-P3F324UC.js";
6
- import "../../chunk-G4XA7IJM.js";
7
- import "../../chunk-QC3DXQTH.js";
3
+ } from "../../chunk-EZQVUNXB.js";
4
+ import "../../chunk-MA5H6PSF.js";
5
+ import "../../chunk-H6BKTNJY.js";
6
+ import "../../chunk-DCWYK4CA.js";
8
7
  import "../../chunk-6DXGZZA4.js";
9
- import "../../chunk-LWHVXXKQ.js";
8
+ import "../../chunk-SKSKC747.js";
10
9
  export {
11
10
  createMcpProtocolHandler
12
11
  };
@@ -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-T24Z753F.js";
4
+ import "../chunk-H6BKTNJY.js";
5
+ import "../chunk-DCWYK4CA.js";
6
6
  import "../chunk-6DXGZZA4.js";
7
+ import "../chunk-SKSKC747.js";
7
8
  export {
8
9
  createOAuthProtectedResourceMetadataHandler
9
10
  };