@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.
- package/dist/{chunk-QC3DXQTH.js → chunk-DCWYK4CA.js} +34 -0
- package/dist/{chunk-RHDEW56Y.js → chunk-EZQVUNXB.js} +20 -11
- package/dist/{chunk-G4XA7IJM.js → chunk-H6BKTNJY.js} +199 -2
- package/dist/{chunk-VNRQPA4K.js → chunk-I5CAQUQI.js} +4 -3
- package/dist/{chunk-P3F324UC.js → chunk-MA5H6PSF.js} +0 -34
- package/dist/{chunk-LWHVXXKQ.js → chunk-SKSKC747.js} +1 -1
- package/dist/{chunk-QDKOF4UF.js → chunk-T24Z753F.js} +2 -2
- package/dist/{chunk-T2RED2TG.js → chunk-W2EZWPJ5.js} +19 -10
- package/dist/cli/extract-manifest.cjs +1 -1
- package/dist/cli/extract-manifest.js +4 -4
- package/dist/index.js +4 -4
- package/dist/protocols/mcp/index.cjs +239 -213
- package/dist/protocols/mcp/index.d.cts +1 -1
- package/dist/protocols/mcp/index.d.ts +1 -1
- package/dist/protocols/mcp/index.js +5 -6
- package/dist/protocols/oauth-metadata.js +4 -3
- package/dist/protocols/rest/index.cjs +241 -215
- package/dist/protocols/rest/index.d.cts +2 -2
- package/dist/protocols/rest/index.d.ts +2 -2
- package/dist/protocols/rest/index.js +6 -7
- package/dist/{recorder-B-eJU4Qu.d.ts → recorder-qlzUKFxG.d.ts} +3 -3
- package/dist/stacks/supabase/index.cjs +55 -19
- package/dist/stacks/supabase/index.js +10 -12
- package/dist/stacks/supabase/vite.cjs +28 -4
- package/dist/stacks/supabase/vite.js +28 -4
- package/dist/stacks/tanstack/index.cjs +255 -219
- package/dist/stacks/tanstack/index.js +8 -9
- package/package.json +1 -1
- package/dist/chunk-XQSTN54Y.js +0 -189
|
@@ -107,6 +107,223 @@ function describeError(err) {
|
|
|
107
107
|
return { value: String(err) };
|
|
108
108
|
}
|
|
109
109
|
|
|
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
|
+
// package.json
|
|
143
|
+
var version = "0.16.0";
|
|
144
|
+
|
|
145
|
+
// src/metrics/otlp.ts
|
|
146
|
+
var SCOPE_NAME = "@lovable.dev/mcp-js";
|
|
147
|
+
var EVENT_NAME = "mcp.tool.invocation";
|
|
148
|
+
var SEVERITY_INFO = 9;
|
|
149
|
+
function strAttr(key, value) {
|
|
150
|
+
return { key, value: { stringValue: value } };
|
|
151
|
+
}
|
|
152
|
+
function intAttr(key, value) {
|
|
153
|
+
return { key, value: { intValue: String(Math.round(value)) } };
|
|
154
|
+
}
|
|
155
|
+
function toLogRecord(rec) {
|
|
156
|
+
const attributes = [
|
|
157
|
+
strAttr("event.name", EVENT_NAME),
|
|
158
|
+
strAttr("mcp.method", rec.method),
|
|
159
|
+
strAttr("mcp.outcome", rec.outcome),
|
|
160
|
+
intAttr("mcp.duration_ms", rec.durationMs)
|
|
161
|
+
];
|
|
162
|
+
if (rec.tool !== null)
|
|
163
|
+
attributes.push(strAttr("mcp.tool", rec.tool));
|
|
164
|
+
if (rec.reqBytes !== void 0)
|
|
165
|
+
attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
|
|
166
|
+
if (rec.resBytes !== void 0)
|
|
167
|
+
attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
|
|
168
|
+
return {
|
|
169
|
+
timeUnixNano: rec.timeUnixNano,
|
|
170
|
+
observedTimeUnixNano: rec.timeUnixNano,
|
|
171
|
+
severityNumber: SEVERITY_INFO,
|
|
172
|
+
severityText: "INFO",
|
|
173
|
+
body: { stringValue: EVENT_NAME },
|
|
174
|
+
attributes
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function buildLogsPayload(records, server) {
|
|
178
|
+
return JSON.stringify({
|
|
179
|
+
resourceLogs: [
|
|
180
|
+
{
|
|
181
|
+
resource: {
|
|
182
|
+
attributes: [
|
|
183
|
+
strAttr("service.name", server.name),
|
|
184
|
+
strAttr("service.version", server.version),
|
|
185
|
+
strAttr("telemetry.sdk.name", SCOPE_NAME),
|
|
186
|
+
strAttr("telemetry.sdk.version", version),
|
|
187
|
+
strAttr("telemetry.sdk.language", "webjs")
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
scopeLogs: [
|
|
191
|
+
{
|
|
192
|
+
scope: { name: SCOPE_NAME, version },
|
|
193
|
+
logRecords: records.map(toLogRecord)
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function nowUnixNano() {
|
|
201
|
+
return `${Date.now()}000000`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/metrics/recorder.ts
|
|
205
|
+
function nowMs() {
|
|
206
|
+
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
207
|
+
}
|
|
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
|
+
};
|
|
218
|
+
function createNoopRecorder() {
|
|
219
|
+
return NOOP_RECORDER;
|
|
220
|
+
}
|
|
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
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
if (typeof process !== "undefined") {
|
|
231
|
+
const value = process.env?.[name];
|
|
232
|
+
if (value)
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
237
|
+
return void 0;
|
|
238
|
+
}
|
|
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));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
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
|
+
}
|
|
279
|
+
}
|
|
280
|
+
headers["content-type"] = "application/json";
|
|
281
|
+
if (apiKey)
|
|
282
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
283
|
+
const flush = async () => {
|
|
284
|
+
if (buffer.length === 0)
|
|
285
|
+
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 });
|
|
297
|
+
}
|
|
298
|
+
} catch (err) {
|
|
299
|
+
log.warn("metrics.flush_failed", { ...describeError(err), dropped, endpoint: config.endpoint });
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
const ensureTimer = () => {
|
|
303
|
+
if (timer !== void 0)
|
|
304
|
+
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
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function createRecorderForRuntime(mcp) {
|
|
321
|
+
const config = resolveMetricsConfig(mcp.metrics);
|
|
322
|
+
if (!config.enabled)
|
|
323
|
+
return createNoopRecorder();
|
|
324
|
+
return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
|
|
325
|
+
}
|
|
326
|
+
|
|
110
327
|
// src/core/promise.ts
|
|
111
328
|
function cachedPromise(load, label) {
|
|
112
329
|
let settled = false;
|
|
@@ -535,12 +752,13 @@ function assertRequiredScopes(auth, context) {
|
|
|
535
752
|
throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
|
|
536
753
|
}
|
|
537
754
|
}
|
|
538
|
-
function createRequestAuthorizer(mcp, options = {}) {
|
|
755
|
+
function createRequestAuthorizer(mcp, options = {}, recorder) {
|
|
539
756
|
const runtime = getOAuthRuntime(mcp, options);
|
|
540
757
|
return {
|
|
541
758
|
async authorize(request) {
|
|
542
759
|
if (runtime.kind === "unconfigured")
|
|
543
760
|
return { ok: true };
|
|
761
|
+
const startedAt = nowMs();
|
|
544
762
|
const token = parseBearerToken(request);
|
|
545
763
|
if (!token) {
|
|
546
764
|
log.info("auth.no_bearer_token", { outcome: "401" });
|
|
@@ -553,6 +771,12 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
553
771
|
} catch (err) {
|
|
554
772
|
if (err instanceof OAuthConfigurationError) {
|
|
555
773
|
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
774
|
+
recorder?.record({
|
|
775
|
+
tool: null,
|
|
776
|
+
method: "authorize",
|
|
777
|
+
outcome: "auth_config_error",
|
|
778
|
+
durationMs: nowMs() - startedAt
|
|
779
|
+
});
|
|
556
780
|
return { ok: false, response: oauthConfigurationErrorResponse() };
|
|
557
781
|
}
|
|
558
782
|
if (err instanceof OAuthTokenError) {
|
|
@@ -649,214 +873,6 @@ function corsPreflightResponse(allowMethods) {
|
|
|
649
873
|
});
|
|
650
874
|
}
|
|
651
875
|
|
|
652
|
-
// src/metrics/config.ts
|
|
653
|
-
var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
|
|
654
|
-
var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
|
|
655
|
-
var METRICS_SAMPLE_RATE = 1;
|
|
656
|
-
var METRICS_FLUSH_INTERVAL_MS = 5e3;
|
|
657
|
-
var METRICS_MAX_BATCH_SIZE = 50;
|
|
658
|
-
function assertMetricsEndpoint(endpoint) {
|
|
659
|
-
let url;
|
|
660
|
-
try {
|
|
661
|
-
url = new URL(endpoint);
|
|
662
|
-
} catch {
|
|
663
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
|
|
664
|
-
}
|
|
665
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
666
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
function resolveMetricsConfig(config = true) {
|
|
670
|
-
const options = typeof config === "boolean" ? { enabled: config } : config;
|
|
671
|
-
const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
|
|
672
|
-
assertMetricsEndpoint(endpoint);
|
|
673
|
-
return Object.freeze({
|
|
674
|
-
enabled: options.enabled ?? true,
|
|
675
|
-
endpoint,
|
|
676
|
-
headers: options.headers ?? {},
|
|
677
|
-
apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
|
|
678
|
-
sampleRate: METRICS_SAMPLE_RATE,
|
|
679
|
-
flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
|
|
680
|
-
maxBatchSize: METRICS_MAX_BATCH_SIZE
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// package.json
|
|
685
|
-
var version = "0.15.0";
|
|
686
|
-
|
|
687
|
-
// src/metrics/otlp.ts
|
|
688
|
-
var SCOPE_NAME = "@lovable.dev/mcp-js";
|
|
689
|
-
var EVENT_NAME = "mcp.tool.invocation";
|
|
690
|
-
var SEVERITY_INFO = 9;
|
|
691
|
-
function strAttr(key, value) {
|
|
692
|
-
return { key, value: { stringValue: value } };
|
|
693
|
-
}
|
|
694
|
-
function intAttr(key, value) {
|
|
695
|
-
return { key, value: { intValue: String(Math.round(value)) } };
|
|
696
|
-
}
|
|
697
|
-
function toLogRecord(rec) {
|
|
698
|
-
const attributes = [
|
|
699
|
-
strAttr("event.name", EVENT_NAME),
|
|
700
|
-
strAttr("mcp.method", rec.method),
|
|
701
|
-
strAttr("mcp.outcome", rec.outcome),
|
|
702
|
-
intAttr("mcp.duration_ms", rec.durationMs)
|
|
703
|
-
];
|
|
704
|
-
if (rec.tool !== null)
|
|
705
|
-
attributes.push(strAttr("mcp.tool", rec.tool));
|
|
706
|
-
if (rec.reqBytes !== void 0)
|
|
707
|
-
attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
|
|
708
|
-
if (rec.resBytes !== void 0)
|
|
709
|
-
attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
|
|
710
|
-
return {
|
|
711
|
-
timeUnixNano: rec.timeUnixNano,
|
|
712
|
-
observedTimeUnixNano: rec.timeUnixNano,
|
|
713
|
-
severityNumber: SEVERITY_INFO,
|
|
714
|
-
severityText: "INFO",
|
|
715
|
-
body: { stringValue: EVENT_NAME },
|
|
716
|
-
attributes
|
|
717
|
-
};
|
|
718
|
-
}
|
|
719
|
-
function buildLogsPayload(records, server) {
|
|
720
|
-
return JSON.stringify({
|
|
721
|
-
resourceLogs: [
|
|
722
|
-
{
|
|
723
|
-
resource: {
|
|
724
|
-
attributes: [
|
|
725
|
-
strAttr("service.name", server.name),
|
|
726
|
-
strAttr("service.version", server.version),
|
|
727
|
-
strAttr("telemetry.sdk.name", SCOPE_NAME),
|
|
728
|
-
strAttr("telemetry.sdk.version", version),
|
|
729
|
-
strAttr("telemetry.sdk.language", "webjs")
|
|
730
|
-
]
|
|
731
|
-
},
|
|
732
|
-
scopeLogs: [
|
|
733
|
-
{
|
|
734
|
-
scope: { name: SCOPE_NAME, version },
|
|
735
|
-
logRecords: records.map(toLogRecord)
|
|
736
|
-
}
|
|
737
|
-
]
|
|
738
|
-
}
|
|
739
|
-
]
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
function nowUnixNano() {
|
|
743
|
-
return `${Date.now()}000000`;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// src/metrics/recorder.ts
|
|
747
|
-
function nowMs() {
|
|
748
|
-
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
749
|
-
}
|
|
750
|
-
var NOOP_RECORDER = {
|
|
751
|
-
record() {
|
|
752
|
-
},
|
|
753
|
-
async flush() {
|
|
754
|
-
}
|
|
755
|
-
};
|
|
756
|
-
function createNoopRecorder() {
|
|
757
|
-
return NOOP_RECORDER;
|
|
758
|
-
}
|
|
759
|
-
function readRuntimeEnv(name) {
|
|
760
|
-
try {
|
|
761
|
-
const denoEnv = globalThis.Deno?.env;
|
|
762
|
-
const value = denoEnv?.get?.(name);
|
|
763
|
-
if (value)
|
|
764
|
-
return value;
|
|
765
|
-
} catch {
|
|
766
|
-
}
|
|
767
|
-
try {
|
|
768
|
-
if (typeof process !== "undefined") {
|
|
769
|
-
const value = process.env?.[name];
|
|
770
|
-
if (value)
|
|
771
|
-
return value;
|
|
772
|
-
}
|
|
773
|
-
} catch {
|
|
774
|
-
}
|
|
775
|
-
return void 0;
|
|
776
|
-
}
|
|
777
|
-
function probeWaitUntil() {
|
|
778
|
-
const fn = globalThis.EdgeRuntime?.waitUntil;
|
|
779
|
-
return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
|
|
780
|
-
}
|
|
781
|
-
function createMetricsRecorder(ctx, deps = {}) {
|
|
782
|
-
const { config, server } = ctx;
|
|
783
|
-
if (!config.enabled)
|
|
784
|
-
return NOOP_RECORDER;
|
|
785
|
-
const doFetch = deps.fetch ?? globalThis.fetch;
|
|
786
|
-
if (!doFetch)
|
|
787
|
-
return NOOP_RECORDER;
|
|
788
|
-
const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
|
|
789
|
-
const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
|
|
790
|
-
if (usesLovableEndpoint && !apiKey) {
|
|
791
|
-
log.debug("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar });
|
|
792
|
-
return NOOP_RECORDER;
|
|
793
|
-
}
|
|
794
|
-
const waitUntil = deps.waitUntil ?? probeWaitUntil();
|
|
795
|
-
const buffer = [];
|
|
796
|
-
let timer;
|
|
797
|
-
const schedule = (p) => {
|
|
798
|
-
if (waitUntil) {
|
|
799
|
-
try {
|
|
800
|
-
waitUntil(p);
|
|
801
|
-
return;
|
|
802
|
-
} catch {
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
void p.catch(() => {
|
|
806
|
-
});
|
|
807
|
-
};
|
|
808
|
-
const headers = {};
|
|
809
|
-
if (!usesLovableEndpoint) {
|
|
810
|
-
for (const [key, value] of Object.entries(config.headers)) {
|
|
811
|
-
if (key.toLowerCase() !== "content-type")
|
|
812
|
-
headers[key] = value;
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
headers["content-type"] = "application/json";
|
|
816
|
-
if (apiKey)
|
|
817
|
-
headers.authorization = `Bearer ${apiKey}`;
|
|
818
|
-
const flush = async () => {
|
|
819
|
-
if (buffer.length === 0)
|
|
820
|
-
return;
|
|
821
|
-
const records = buffer.splice(0, buffer.length);
|
|
822
|
-
try {
|
|
823
|
-
const res = await doFetch(config.endpoint, {
|
|
824
|
-
method: "POST",
|
|
825
|
-
headers,
|
|
826
|
-
body: buildLogsPayload(records, server),
|
|
827
|
-
keepalive: true
|
|
828
|
-
});
|
|
829
|
-
if (!res.ok)
|
|
830
|
-
log.debug("metrics.flush_rejected", { status: res.status });
|
|
831
|
-
} catch (err) {
|
|
832
|
-
log.debug("metrics.flush_failed", describeError(err));
|
|
833
|
-
}
|
|
834
|
-
};
|
|
835
|
-
const ensureTimer = () => {
|
|
836
|
-
if (timer !== void 0)
|
|
837
|
-
return;
|
|
838
|
-
timer = setInterval(() => schedule(flush()), config.flushIntervalMs);
|
|
839
|
-
timer.unref?.();
|
|
840
|
-
};
|
|
841
|
-
return {
|
|
842
|
-
record(ev) {
|
|
843
|
-
if (config.sampleRate < 1 && Math.random() >= config.sampleRate)
|
|
844
|
-
return;
|
|
845
|
-
buffer.push({ ...ev, timeUnixNano: nowUnixNano() });
|
|
846
|
-
ensureTimer();
|
|
847
|
-
if (buffer.length >= config.maxBatchSize)
|
|
848
|
-
schedule(flush());
|
|
849
|
-
},
|
|
850
|
-
flush
|
|
851
|
-
};
|
|
852
|
-
}
|
|
853
|
-
function createRecorderForRuntime(mcp) {
|
|
854
|
-
const config = resolveMetricsConfig(mcp.metrics);
|
|
855
|
-
if (!config.enabled)
|
|
856
|
-
return createNoopRecorder();
|
|
857
|
-
return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
|
|
858
|
-
}
|
|
859
|
-
|
|
860
876
|
// src/protocols/mcp/protocol.ts
|
|
861
877
|
function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
862
878
|
return async (first) => {
|
|
@@ -866,14 +882,24 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
866
882
|
try {
|
|
867
883
|
result = await tool.handler(args, new ToolContext(auth));
|
|
868
884
|
} catch {
|
|
869
|
-
recorder
|
|
885
|
+
reportInvocation(recorder, {
|
|
886
|
+
tool: tool.name,
|
|
887
|
+
method: "tools/call",
|
|
888
|
+
outcome: "handler_error",
|
|
889
|
+
durationMs: nowMs() - start
|
|
890
|
+
});
|
|
870
891
|
return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
|
|
871
892
|
}
|
|
872
893
|
if (result == null) {
|
|
873
|
-
recorder
|
|
894
|
+
reportInvocation(recorder, {
|
|
895
|
+
tool: tool.name,
|
|
896
|
+
method: "tools/call",
|
|
897
|
+
outcome: "handler_error",
|
|
898
|
+
durationMs: nowMs() - start
|
|
899
|
+
});
|
|
874
900
|
return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
|
|
875
901
|
}
|
|
876
|
-
recorder
|
|
902
|
+
reportInvocation(recorder, {
|
|
877
903
|
tool: tool.name,
|
|
878
904
|
method: "tools/call",
|
|
879
905
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -883,7 +909,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
883
909
|
};
|
|
884
910
|
}
|
|
885
911
|
function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
|
|
886
|
-
const authorizer = createRequestAuthorizer(mcp, options);
|
|
912
|
+
const authorizer = createRequestAuthorizer(mcp, options, recorder);
|
|
887
913
|
const handle = async (request) => {
|
|
888
914
|
const authResult = await authorizer.authorize(request);
|
|
889
915
|
if (!authResult.ok)
|
|
@@ -1013,9 +1039,9 @@ function buildMcpListing(mcp) {
|
|
|
1013
1039
|
}))
|
|
1014
1040
|
};
|
|
1015
1041
|
}
|
|
1016
|
-
function createListToolsHandler(mcp, options = {}) {
|
|
1042
|
+
function createListToolsHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
|
|
1017
1043
|
assertRestResourceBinding(mcp, options);
|
|
1018
|
-
const authorizer = createRequestAuthorizer(mcp, options);
|
|
1044
|
+
const authorizer = createRequestAuthorizer(mcp, options, recorder);
|
|
1019
1045
|
const handle = async (request) => {
|
|
1020
1046
|
const authResult = await authorizer.authorize(request);
|
|
1021
1047
|
if (!authResult.ok)
|
|
@@ -1048,7 +1074,7 @@ function isEmptyArgs(value) {
|
|
|
1048
1074
|
}
|
|
1049
1075
|
function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
|
|
1050
1076
|
assertRestResourceBinding(mcp, options);
|
|
1051
|
-
const authorizer = createRequestAuthorizer(mcp, options);
|
|
1077
|
+
const authorizer = createRequestAuthorizer(mcp, options, recorder);
|
|
1052
1078
|
const handle = async (request, toolName) => {
|
|
1053
1079
|
const authResult = await authorizer.authorize(request);
|
|
1054
1080
|
if (!authResult.ok)
|
|
@@ -1106,20 +1132,30 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1106
1132
|
try {
|
|
1107
1133
|
result = await tool.handler(args, new ToolContext(authResult.auth));
|
|
1108
1134
|
} catch {
|
|
1109
|
-
recorder
|
|
1135
|
+
reportInvocation(recorder, {
|
|
1136
|
+
tool: tool.name,
|
|
1137
|
+
method: "tools/call",
|
|
1138
|
+
outcome: "handler_error",
|
|
1139
|
+
durationMs: nowMs() - start
|
|
1140
|
+
});
|
|
1110
1141
|
return new Response(JSON.stringify({ error: "handler threw", tool: toolName }), {
|
|
1111
1142
|
status: 500,
|
|
1112
1143
|
headers: JSON_HEADERS
|
|
1113
1144
|
});
|
|
1114
1145
|
}
|
|
1115
1146
|
if (result == null) {
|
|
1116
|
-
recorder
|
|
1147
|
+
reportInvocation(recorder, {
|
|
1148
|
+
tool: tool.name,
|
|
1149
|
+
method: "tools/call",
|
|
1150
|
+
outcome: "handler_error",
|
|
1151
|
+
durationMs: nowMs() - start
|
|
1152
|
+
});
|
|
1117
1153
|
return new Response(JSON.stringify({ error: `tool "${toolName}" returned no result` }), {
|
|
1118
1154
|
status: 500,
|
|
1119
1155
|
headers: JSON_HEADERS
|
|
1120
1156
|
});
|
|
1121
1157
|
}
|
|
1122
|
-
recorder
|
|
1158
|
+
reportInvocation(recorder, {
|
|
1123
1159
|
tool: tool.name,
|
|
1124
1160
|
method: "tools/call",
|
|
1125
1161
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -3,22 +3,21 @@ import {
|
|
|
3
3
|
} from "../../chunk-UQK5UO6C.js";
|
|
4
4
|
import {
|
|
5
5
|
createMcpProtocolHandler
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-EZQVUNXB.js";
|
|
7
7
|
import {
|
|
8
8
|
createOAuthProtectedResourceMetadataHandler
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-T24Z753F.js";
|
|
10
10
|
import {
|
|
11
11
|
createInvokeToolHandler
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-W2EZWPJ5.js";
|
|
13
13
|
import {
|
|
14
14
|
createListToolsHandler
|
|
15
|
-
} from "../../chunk-
|
|
16
|
-
import "../../chunk-
|
|
17
|
-
import "../../chunk-
|
|
18
|
-
import "../../chunk-
|
|
19
|
-
import "../../chunk-QC3DXQTH.js";
|
|
15
|
+
} from "../../chunk-I5CAQUQI.js";
|
|
16
|
+
import "../../chunk-MA5H6PSF.js";
|
|
17
|
+
import "../../chunk-H6BKTNJY.js";
|
|
18
|
+
import "../../chunk-DCWYK4CA.js";
|
|
20
19
|
import "../../chunk-6DXGZZA4.js";
|
|
21
|
-
import "../../chunk-
|
|
20
|
+
import "../../chunk-SKSKC747.js";
|
|
22
21
|
|
|
23
22
|
// src/stacks/tanstack/handlers.ts
|
|
24
23
|
function forwarded(request, options) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovable.dev/mcp-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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": {
|