@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
|
@@ -82,6 +82,223 @@ 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.16.0";
|
|
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
|
+
function reportInvocation(recorder, ev) {
|
|
184
|
+
log.info("tool.invoked", { tool: ev.tool, method: ev.method, outcome: ev.outcome, durationMs: ev.durationMs });
|
|
185
|
+
recorder.record(ev);
|
|
186
|
+
}
|
|
187
|
+
var NOOP_RECORDER = {
|
|
188
|
+
record() {
|
|
189
|
+
},
|
|
190
|
+
async flush() {
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
function createNoopRecorder() {
|
|
194
|
+
return NOOP_RECORDER;
|
|
195
|
+
}
|
|
196
|
+
function readRuntimeEnv(name) {
|
|
197
|
+
try {
|
|
198
|
+
const denoEnv = globalThis.Deno?.env;
|
|
199
|
+
const value = denoEnv?.get?.(name);
|
|
200
|
+
if (value)
|
|
201
|
+
return value;
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
if (typeof process !== "undefined") {
|
|
206
|
+
const value = process.env?.[name];
|
|
207
|
+
if (value)
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
return void 0;
|
|
213
|
+
}
|
|
214
|
+
function probeWaitUntil() {
|
|
215
|
+
const fn = globalThis.EdgeRuntime?.waitUntil;
|
|
216
|
+
return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
|
|
217
|
+
}
|
|
218
|
+
function createMetricsRecorder(ctx, deps = {}) {
|
|
219
|
+
const { config, server } = ctx;
|
|
220
|
+
if (!config.enabled)
|
|
221
|
+
return NOOP_RECORDER;
|
|
222
|
+
const doFetch = deps.fetch ?? globalThis.fetch;
|
|
223
|
+
if (!doFetch) {
|
|
224
|
+
log.warn("metrics.disabled_no_fetch", { endpoint: config.endpoint });
|
|
225
|
+
return NOOP_RECORDER;
|
|
226
|
+
}
|
|
227
|
+
const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
|
|
228
|
+
const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
|
|
229
|
+
if (usesLovableEndpoint && !apiKey) {
|
|
230
|
+
log.warn("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar, endpoint: config.endpoint });
|
|
231
|
+
return NOOP_RECORDER;
|
|
232
|
+
}
|
|
233
|
+
const waitUntil = deps.waitUntil ?? probeWaitUntil();
|
|
234
|
+
const buffer = [];
|
|
235
|
+
let timer;
|
|
236
|
+
const schedule = (p) => {
|
|
237
|
+
if (waitUntil) {
|
|
238
|
+
try {
|
|
239
|
+
waitUntil(p);
|
|
240
|
+
return;
|
|
241
|
+
} catch (err) {
|
|
242
|
+
log.warn("metrics.wait_until_failed", describeError(err));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
void p.catch(() => {
|
|
246
|
+
});
|
|
247
|
+
};
|
|
248
|
+
const headers = {};
|
|
249
|
+
if (!usesLovableEndpoint) {
|
|
250
|
+
for (const [key, value] of Object.entries(config.headers)) {
|
|
251
|
+
if (key.toLowerCase() !== "content-type")
|
|
252
|
+
headers[key] = value;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
headers["content-type"] = "application/json";
|
|
256
|
+
if (apiKey)
|
|
257
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
258
|
+
const flush = async () => {
|
|
259
|
+
if (buffer.length === 0)
|
|
260
|
+
return;
|
|
261
|
+
const records = buffer.splice(0, buffer.length);
|
|
262
|
+
const dropped = records.length;
|
|
263
|
+
try {
|
|
264
|
+
const res = await doFetch(config.endpoint, {
|
|
265
|
+
method: "POST",
|
|
266
|
+
headers,
|
|
267
|
+
body: buildLogsPayload(records, server),
|
|
268
|
+
keepalive: true
|
|
269
|
+
});
|
|
270
|
+
if (!res.ok) {
|
|
271
|
+
log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
|
|
272
|
+
}
|
|
273
|
+
} catch (err) {
|
|
274
|
+
log.warn("metrics.flush_failed", { ...describeError(err), dropped, endpoint: config.endpoint });
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
const ensureTimer = () => {
|
|
278
|
+
if (timer !== void 0)
|
|
279
|
+
return;
|
|
280
|
+
timer = setInterval(() => schedule(flush()), config.flushIntervalMs);
|
|
281
|
+
timer.unref?.();
|
|
282
|
+
};
|
|
283
|
+
return {
|
|
284
|
+
record(ev) {
|
|
285
|
+
if (config.sampleRate < 1 && Math.random() >= config.sampleRate)
|
|
286
|
+
return;
|
|
287
|
+
buffer.push({ ...ev, timeUnixNano: nowUnixNano() });
|
|
288
|
+
ensureTimer();
|
|
289
|
+
if (buffer.length >= config.maxBatchSize)
|
|
290
|
+
schedule(flush());
|
|
291
|
+
},
|
|
292
|
+
flush
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function createRecorderForRuntime(mcp) {
|
|
296
|
+
const config = resolveMetricsConfig(mcp.metrics);
|
|
297
|
+
if (!config.enabled)
|
|
298
|
+
return createNoopRecorder();
|
|
299
|
+
return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
|
|
300
|
+
}
|
|
301
|
+
|
|
85
302
|
// src/core/promise.ts
|
|
86
303
|
function cachedPromise(load, label) {
|
|
87
304
|
let settled = false;
|
|
@@ -510,12 +727,13 @@ function assertRequiredScopes(auth, context) {
|
|
|
510
727
|
throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
|
|
511
728
|
}
|
|
512
729
|
}
|
|
513
|
-
function createRequestAuthorizer(mcp, options = {}) {
|
|
730
|
+
function createRequestAuthorizer(mcp, options = {}, recorder) {
|
|
514
731
|
const runtime = getOAuthRuntime(mcp, options);
|
|
515
732
|
return {
|
|
516
733
|
async authorize(request) {
|
|
517
734
|
if (runtime.kind === "unconfigured")
|
|
518
735
|
return { ok: true };
|
|
736
|
+
const startedAt = nowMs();
|
|
519
737
|
const token = parseBearerToken(request);
|
|
520
738
|
if (!token) {
|
|
521
739
|
log.info("auth.no_bearer_token", { outcome: "401" });
|
|
@@ -528,6 +746,12 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
528
746
|
} catch (err) {
|
|
529
747
|
if (err instanceof OAuthConfigurationError) {
|
|
530
748
|
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
749
|
+
recorder?.record({
|
|
750
|
+
tool: null,
|
|
751
|
+
method: "authorize",
|
|
752
|
+
outcome: "auth_config_error",
|
|
753
|
+
durationMs: nowMs() - startedAt
|
|
754
|
+
});
|
|
531
755
|
return { ok: false, response: oauthConfigurationErrorResponse() };
|
|
532
756
|
}
|
|
533
757
|
if (err instanceof OAuthTokenError) {
|
|
@@ -604,9 +828,9 @@ function buildMcpListing(mcp) {
|
|
|
604
828
|
}))
|
|
605
829
|
};
|
|
606
830
|
}
|
|
607
|
-
function createListToolsHandler(mcp, options = {}) {
|
|
831
|
+
function createListToolsHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
|
|
608
832
|
assertRestResourceBinding(mcp, options);
|
|
609
|
-
const authorizer = createRequestAuthorizer(mcp, options);
|
|
833
|
+
const authorizer = createRequestAuthorizer(mcp, options, recorder);
|
|
610
834
|
const handle = async (request) => {
|
|
611
835
|
const authResult = await authorizer.authorize(request);
|
|
612
836
|
if (!authResult.ok)
|
|
@@ -669,214 +893,6 @@ var ToolContext = class {
|
|
|
669
893
|
}
|
|
670
894
|
};
|
|
671
895
|
|
|
672
|
-
// src/metrics/config.ts
|
|
673
|
-
var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
|
|
674
|
-
var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
|
|
675
|
-
var METRICS_SAMPLE_RATE = 1;
|
|
676
|
-
var METRICS_FLUSH_INTERVAL_MS = 5e3;
|
|
677
|
-
var METRICS_MAX_BATCH_SIZE = 50;
|
|
678
|
-
function assertMetricsEndpoint(endpoint) {
|
|
679
|
-
let url;
|
|
680
|
-
try {
|
|
681
|
-
url = new URL(endpoint);
|
|
682
|
-
} catch {
|
|
683
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
|
|
684
|
-
}
|
|
685
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
686
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
function resolveMetricsConfig(config = true) {
|
|
690
|
-
const options = typeof config === "boolean" ? { enabled: config } : config;
|
|
691
|
-
const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
|
|
692
|
-
assertMetricsEndpoint(endpoint);
|
|
693
|
-
return Object.freeze({
|
|
694
|
-
enabled: options.enabled ?? true,
|
|
695
|
-
endpoint,
|
|
696
|
-
headers: options.headers ?? {},
|
|
697
|
-
apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
|
|
698
|
-
sampleRate: METRICS_SAMPLE_RATE,
|
|
699
|
-
flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
|
|
700
|
-
maxBatchSize: METRICS_MAX_BATCH_SIZE
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// package.json
|
|
705
|
-
var version = "0.15.0";
|
|
706
|
-
|
|
707
|
-
// src/metrics/otlp.ts
|
|
708
|
-
var SCOPE_NAME = "@lovable.dev/mcp-js";
|
|
709
|
-
var EVENT_NAME = "mcp.tool.invocation";
|
|
710
|
-
var SEVERITY_INFO = 9;
|
|
711
|
-
function strAttr(key, value) {
|
|
712
|
-
return { key, value: { stringValue: value } };
|
|
713
|
-
}
|
|
714
|
-
function intAttr(key, value) {
|
|
715
|
-
return { key, value: { intValue: String(Math.round(value)) } };
|
|
716
|
-
}
|
|
717
|
-
function toLogRecord(rec) {
|
|
718
|
-
const attributes = [
|
|
719
|
-
strAttr("event.name", EVENT_NAME),
|
|
720
|
-
strAttr("mcp.method", rec.method),
|
|
721
|
-
strAttr("mcp.outcome", rec.outcome),
|
|
722
|
-
intAttr("mcp.duration_ms", rec.durationMs)
|
|
723
|
-
];
|
|
724
|
-
if (rec.tool !== null)
|
|
725
|
-
attributes.push(strAttr("mcp.tool", rec.tool));
|
|
726
|
-
if (rec.reqBytes !== void 0)
|
|
727
|
-
attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
|
|
728
|
-
if (rec.resBytes !== void 0)
|
|
729
|
-
attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
|
|
730
|
-
return {
|
|
731
|
-
timeUnixNano: rec.timeUnixNano,
|
|
732
|
-
observedTimeUnixNano: rec.timeUnixNano,
|
|
733
|
-
severityNumber: SEVERITY_INFO,
|
|
734
|
-
severityText: "INFO",
|
|
735
|
-
body: { stringValue: EVENT_NAME },
|
|
736
|
-
attributes
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
function buildLogsPayload(records, server) {
|
|
740
|
-
return JSON.stringify({
|
|
741
|
-
resourceLogs: [
|
|
742
|
-
{
|
|
743
|
-
resource: {
|
|
744
|
-
attributes: [
|
|
745
|
-
strAttr("service.name", server.name),
|
|
746
|
-
strAttr("service.version", server.version),
|
|
747
|
-
strAttr("telemetry.sdk.name", SCOPE_NAME),
|
|
748
|
-
strAttr("telemetry.sdk.version", version),
|
|
749
|
-
strAttr("telemetry.sdk.language", "webjs")
|
|
750
|
-
]
|
|
751
|
-
},
|
|
752
|
-
scopeLogs: [
|
|
753
|
-
{
|
|
754
|
-
scope: { name: SCOPE_NAME, version },
|
|
755
|
-
logRecords: records.map(toLogRecord)
|
|
756
|
-
}
|
|
757
|
-
]
|
|
758
|
-
}
|
|
759
|
-
]
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
function nowUnixNano() {
|
|
763
|
-
return `${Date.now()}000000`;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// src/metrics/recorder.ts
|
|
767
|
-
function nowMs() {
|
|
768
|
-
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
769
|
-
}
|
|
770
|
-
var NOOP_RECORDER = {
|
|
771
|
-
record() {
|
|
772
|
-
},
|
|
773
|
-
async flush() {
|
|
774
|
-
}
|
|
775
|
-
};
|
|
776
|
-
function createNoopRecorder() {
|
|
777
|
-
return NOOP_RECORDER;
|
|
778
|
-
}
|
|
779
|
-
function readRuntimeEnv(name) {
|
|
780
|
-
try {
|
|
781
|
-
const denoEnv = globalThis.Deno?.env;
|
|
782
|
-
const value = denoEnv?.get?.(name);
|
|
783
|
-
if (value)
|
|
784
|
-
return value;
|
|
785
|
-
} catch {
|
|
786
|
-
}
|
|
787
|
-
try {
|
|
788
|
-
if (typeof process !== "undefined") {
|
|
789
|
-
const value = process.env?.[name];
|
|
790
|
-
if (value)
|
|
791
|
-
return value;
|
|
792
|
-
}
|
|
793
|
-
} catch {
|
|
794
|
-
}
|
|
795
|
-
return void 0;
|
|
796
|
-
}
|
|
797
|
-
function probeWaitUntil() {
|
|
798
|
-
const fn = globalThis.EdgeRuntime?.waitUntil;
|
|
799
|
-
return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
|
|
800
|
-
}
|
|
801
|
-
function createMetricsRecorder(ctx, deps = {}) {
|
|
802
|
-
const { config, server } = ctx;
|
|
803
|
-
if (!config.enabled)
|
|
804
|
-
return NOOP_RECORDER;
|
|
805
|
-
const doFetch = deps.fetch ?? globalThis.fetch;
|
|
806
|
-
if (!doFetch)
|
|
807
|
-
return NOOP_RECORDER;
|
|
808
|
-
const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
|
|
809
|
-
const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
|
|
810
|
-
if (usesLovableEndpoint && !apiKey) {
|
|
811
|
-
log.debug("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar });
|
|
812
|
-
return NOOP_RECORDER;
|
|
813
|
-
}
|
|
814
|
-
const waitUntil = deps.waitUntil ?? probeWaitUntil();
|
|
815
|
-
const buffer = [];
|
|
816
|
-
let timer;
|
|
817
|
-
const schedule = (p) => {
|
|
818
|
-
if (waitUntil) {
|
|
819
|
-
try {
|
|
820
|
-
waitUntil(p);
|
|
821
|
-
return;
|
|
822
|
-
} catch {
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
void p.catch(() => {
|
|
826
|
-
});
|
|
827
|
-
};
|
|
828
|
-
const headers = {};
|
|
829
|
-
if (!usesLovableEndpoint) {
|
|
830
|
-
for (const [key, value] of Object.entries(config.headers)) {
|
|
831
|
-
if (key.toLowerCase() !== "content-type")
|
|
832
|
-
headers[key] = value;
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
headers["content-type"] = "application/json";
|
|
836
|
-
if (apiKey)
|
|
837
|
-
headers.authorization = `Bearer ${apiKey}`;
|
|
838
|
-
const flush = async () => {
|
|
839
|
-
if (buffer.length === 0)
|
|
840
|
-
return;
|
|
841
|
-
const records = buffer.splice(0, buffer.length);
|
|
842
|
-
try {
|
|
843
|
-
const res = await doFetch(config.endpoint, {
|
|
844
|
-
method: "POST",
|
|
845
|
-
headers,
|
|
846
|
-
body: buildLogsPayload(records, server),
|
|
847
|
-
keepalive: true
|
|
848
|
-
});
|
|
849
|
-
if (!res.ok)
|
|
850
|
-
log.debug("metrics.flush_rejected", { status: res.status });
|
|
851
|
-
} catch (err) {
|
|
852
|
-
log.debug("metrics.flush_failed", describeError(err));
|
|
853
|
-
}
|
|
854
|
-
};
|
|
855
|
-
const ensureTimer = () => {
|
|
856
|
-
if (timer !== void 0)
|
|
857
|
-
return;
|
|
858
|
-
timer = setInterval(() => schedule(flush()), config.flushIntervalMs);
|
|
859
|
-
timer.unref?.();
|
|
860
|
-
};
|
|
861
|
-
return {
|
|
862
|
-
record(ev) {
|
|
863
|
-
if (config.sampleRate < 1 && Math.random() >= config.sampleRate)
|
|
864
|
-
return;
|
|
865
|
-
buffer.push({ ...ev, timeUnixNano: nowUnixNano() });
|
|
866
|
-
ensureTimer();
|
|
867
|
-
if (buffer.length >= config.maxBatchSize)
|
|
868
|
-
schedule(flush());
|
|
869
|
-
},
|
|
870
|
-
flush
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
function createRecorderForRuntime(mcp) {
|
|
874
|
-
const config = resolveMetricsConfig(mcp.metrics);
|
|
875
|
-
if (!config.enabled)
|
|
876
|
-
return createNoopRecorder();
|
|
877
|
-
return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
|
|
878
|
-
}
|
|
879
|
-
|
|
880
896
|
// src/protocols/rest/invoke-tool.ts
|
|
881
897
|
var MAX_REFLECTED_TOOL_NAME = 256;
|
|
882
898
|
function safeReflectName(name) {
|
|
@@ -892,7 +908,7 @@ function isEmptyArgs(value) {
|
|
|
892
908
|
}
|
|
893
909
|
function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
|
|
894
910
|
assertRestResourceBinding(mcp, options);
|
|
895
|
-
const authorizer = createRequestAuthorizer(mcp, options);
|
|
911
|
+
const authorizer = createRequestAuthorizer(mcp, options, recorder);
|
|
896
912
|
const handle = async (request, toolName) => {
|
|
897
913
|
const authResult = await authorizer.authorize(request);
|
|
898
914
|
if (!authResult.ok)
|
|
@@ -950,20 +966,30 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
950
966
|
try {
|
|
951
967
|
result = await tool.handler(args, new ToolContext(authResult.auth));
|
|
952
968
|
} catch {
|
|
953
|
-
recorder
|
|
969
|
+
reportInvocation(recorder, {
|
|
970
|
+
tool: tool.name,
|
|
971
|
+
method: "tools/call",
|
|
972
|
+
outcome: "handler_error",
|
|
973
|
+
durationMs: nowMs() - start
|
|
974
|
+
});
|
|
954
975
|
return new Response(JSON.stringify({ error: "handler threw", tool: toolName }), {
|
|
955
976
|
status: 500,
|
|
956
977
|
headers: JSON_HEADERS
|
|
957
978
|
});
|
|
958
979
|
}
|
|
959
980
|
if (result == null) {
|
|
960
|
-
recorder
|
|
981
|
+
reportInvocation(recorder, {
|
|
982
|
+
tool: tool.name,
|
|
983
|
+
method: "tools/call",
|
|
984
|
+
outcome: "handler_error",
|
|
985
|
+
durationMs: nowMs() - start
|
|
986
|
+
});
|
|
961
987
|
return new Response(JSON.stringify({ error: `tool "${toolName}" returned no result` }), {
|
|
962
988
|
status: 500,
|
|
963
989
|
headers: JSON_HEADERS
|
|
964
990
|
});
|
|
965
991
|
}
|
|
966
|
-
recorder
|
|
992
|
+
reportInvocation(recorder, {
|
|
967
993
|
tool: tool.name,
|
|
968
994
|
method: "tools/call",
|
|
969
995
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -1,10 +1,10 @@
|
|
|
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-
|
|
3
|
+
import { M as MetricsRecorder } from '../../recorder-qlzUKFxG.js';
|
|
4
4
|
import 'zod';
|
|
5
5
|
|
|
6
6
|
type RestListToolsHandler = (request: Request) => Promise<Response>;
|
|
7
|
-
declare function createListToolsHandler(mcp: McpDefinition, options?: McpRuntimeOptions): RestListToolsHandler;
|
|
7
|
+
declare function createListToolsHandler(mcp: McpDefinition, options?: McpRuntimeOptions, recorder?: MetricsRecorder): RestListToolsHandler;
|
|
8
8
|
|
|
9
9
|
type RestInvokeToolHandler = (request: Request, toolName: string) => Promise<Response>;
|
|
10
10
|
declare function createInvokeToolHandler(mcp: McpDefinition, options?: McpRuntimeOptions, recorder?: MetricsRecorder): RestInvokeToolHandler;
|
|
@@ -1,10 +1,10 @@
|
|
|
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-
|
|
3
|
+
import { M as MetricsRecorder } from '../../recorder-qlzUKFxG.js';
|
|
4
4
|
import 'zod';
|
|
5
5
|
|
|
6
6
|
type RestListToolsHandler = (request: Request) => Promise<Response>;
|
|
7
|
-
declare function createListToolsHandler(mcp: McpDefinition, options?: McpRuntimeOptions): RestListToolsHandler;
|
|
7
|
+
declare function createListToolsHandler(mcp: McpDefinition, options?: McpRuntimeOptions, recorder?: MetricsRecorder): RestListToolsHandler;
|
|
8
8
|
|
|
9
9
|
type RestInvokeToolHandler = (request: Request, toolName: string) => Promise<Response>;
|
|
10
10
|
declare function createInvokeToolHandler(mcp: McpDefinition, options?: McpRuntimeOptions, recorder?: MetricsRecorder): RestInvokeToolHandler;
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createInvokeToolHandler
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-W2EZWPJ5.js";
|
|
4
4
|
import {
|
|
5
5
|
createListToolsHandler
|
|
6
|
-
} from "../../chunk-
|
|
7
|
-
import "../../chunk-
|
|
8
|
-
import "../../chunk-
|
|
9
|
-
import "../../chunk-
|
|
10
|
-
import "../../chunk-QC3DXQTH.js";
|
|
6
|
+
} from "../../chunk-I5CAQUQI.js";
|
|
7
|
+
import "../../chunk-MA5H6PSF.js";
|
|
8
|
+
import "../../chunk-H6BKTNJY.js";
|
|
9
|
+
import "../../chunk-DCWYK4CA.js";
|
|
11
10
|
import "../../chunk-6DXGZZA4.js";
|
|
12
|
-
import "../../chunk-
|
|
11
|
+
import "../../chunk-SKSKC747.js";
|
|
13
12
|
export {
|
|
14
13
|
createInvokeToolHandler,
|
|
15
14
|
createListToolsHandler
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
type InvocationOutcome = "ok" | "tool_error" | "handler_error" | "transport_error";
|
|
1
|
+
type InvocationOutcome = "ok" | "tool_error" | "handler_error" | "transport_error" | "auth_config_error";
|
|
2
2
|
/** One recorded MCP call. Carries no arguments or response payloads — only the
|
|
3
3
|
* tool name, the JSON-RPC method, the result class, timing, and byte sizes. */
|
|
4
4
|
interface InvocationRecord {
|
|
5
|
-
/** Tool name for `tools/call`; `null` for list/initialize and
|
|
5
|
+
/** Tool name for `tools/call`; `null` for list/initialize, transport faults, and auth-config failures. */
|
|
6
6
|
tool: string | null;
|
|
7
|
-
/** JSON-RPC method, e.g. `tools/call
|
|
7
|
+
/** JSON-RPC method, e.g. `tools/call`; `authorize` for auth-config failures. */
|
|
8
8
|
method: string;
|
|
9
9
|
outcome: InvocationOutcome;
|
|
10
10
|
durationMs: number;
|