@lovable.dev/mcp-js 0.17.0 → 0.19.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/{recorder-qlzUKFxG.d.ts → base-C9rhAHZ0.d.ts} +3 -4
- package/dist/{chunk-WYXIX7XF.js → chunk-57ZH2WW5.js} +11 -12
- package/dist/{chunk-6TZNGFRM.js → chunk-5FX6IIQ6.js} +121 -136
- package/dist/{chunk-J5BTCP6T.js → chunk-CDZ7MBXC.js} +1 -1
- package/dist/{chunk-EL6YNP2R.js → chunk-CLXKPZZE.js} +13 -14
- package/dist/{chunk-VQQZAFRA.js → chunk-H37EB22A.js} +2 -10
- package/dist/{chunk-FW6YL5C5.js → chunk-OMWXY6WY.js} +8 -8
- package/dist/{chunk-H2HXKAMQ.js → chunk-UEOBGHXF.js} +2 -2
- package/dist/cli/extract-manifest.cjs +1 -1
- package/dist/cli/extract-manifest.js +4 -4
- package/dist/index.cjs +1 -7
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/protocols/mcp/index.cjs +16 -272
- package/dist/protocols/mcp/index.d.cts +6 -5
- package/dist/protocols/mcp/index.d.ts +6 -5
- package/dist/protocols/mcp/index.js +4 -4
- package/dist/protocols/oauth-metadata.d.cts +1 -1
- package/dist/protocols/oauth-metadata.d.ts +1 -1
- package/dist/protocols/oauth-metadata.js +4 -4
- package/dist/protocols/rest/index.cjs +21 -277
- package/dist/protocols/rest/index.d.cts +6 -6
- package/dist/protocols/rest/index.d.ts +6 -6
- package/dist/protocols/rest/index.js +5 -5
- package/dist/stacks/supabase/index.cjs +178 -201
- package/dist/stacks/supabase/index.d.cts +1 -1
- package/dist/stacks/supabase/index.d.ts +1 -1
- package/dist/stacks/supabase/index.js +14 -14
- package/dist/stacks/supabase/vite.cjs +1 -1
- package/dist/stacks/supabase/vite.js +1 -1
- package/dist/stacks/tanstack/index.cjs +187 -209
- package/dist/stacks/tanstack/index.d.cts +1 -1
- package/dist/stacks/tanstack/index.d.ts +1 -1
- package/dist/stacks/tanstack/index.js +13 -10
- package/dist/{types-COY42xux.d.ts → types-CPkhCRxc.d.ts} +3 -3
- package/package.json +1 -1
|
@@ -50,19 +50,29 @@ function firstForwardedValue(request, header) {
|
|
|
50
50
|
return value ? value : void 0;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// src/
|
|
54
|
-
var
|
|
55
|
-
var
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
// src/metrics/config.ts
|
|
54
|
+
var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
|
|
55
|
+
var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
|
|
56
|
+
function assertMetricsEndpoint(endpoint) {
|
|
57
|
+
let url;
|
|
58
|
+
try {
|
|
59
|
+
url = new URL(endpoint);
|
|
60
|
+
} catch {
|
|
61
|
+
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
|
|
62
|
+
}
|
|
63
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
64
|
+
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
|
|
65
|
+
}
|
|
61
66
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
function resolveMetricsConfig(config = true) {
|
|
68
|
+
const options = typeof config === "boolean" ? { enabled: config } : config;
|
|
69
|
+
const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
|
|
70
|
+
assertMetricsEndpoint(endpoint);
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
enabled: options.enabled ?? true,
|
|
73
|
+
endpoint,
|
|
74
|
+
headers: options.headers ?? {},
|
|
75
|
+
apiKeyEnvVar: METRICS_API_KEY_ENV_VAR
|
|
66
76
|
});
|
|
67
77
|
}
|
|
68
78
|
|
|
@@ -90,9 +100,7 @@ function parseLogLevel(raw) {
|
|
|
90
100
|
return isLogLevel(normalized) ? normalized : void 0;
|
|
91
101
|
}
|
|
92
102
|
function applyLogLevelFromEnv(raw) {
|
|
93
|
-
|
|
94
|
-
if (level)
|
|
95
|
-
setLogLevel(level);
|
|
103
|
+
setLogLevel(parseLogLevel(raw) ?? "debug");
|
|
96
104
|
}
|
|
97
105
|
function enabled(level) {
|
|
98
106
|
return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
|
|
@@ -120,40 +128,8 @@ function describeError(err) {
|
|
|
120
128
|
return { value: String(err) };
|
|
121
129
|
}
|
|
122
130
|
|
|
123
|
-
// src/metrics/config.ts
|
|
124
|
-
var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
|
|
125
|
-
var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
|
|
126
|
-
var METRICS_SAMPLE_RATE = 1;
|
|
127
|
-
var METRICS_FLUSH_INTERVAL_MS = 5e3;
|
|
128
|
-
var METRICS_MAX_BATCH_SIZE = 50;
|
|
129
|
-
function assertMetricsEndpoint(endpoint) {
|
|
130
|
-
let url;
|
|
131
|
-
try {
|
|
132
|
-
url = new URL(endpoint);
|
|
133
|
-
} catch {
|
|
134
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
|
|
135
|
-
}
|
|
136
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
137
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
function resolveMetricsConfig(config = true) {
|
|
141
|
-
const options = typeof config === "boolean" ? { enabled: config } : config;
|
|
142
|
-
const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
|
|
143
|
-
assertMetricsEndpoint(endpoint);
|
|
144
|
-
return Object.freeze({
|
|
145
|
-
enabled: options.enabled ?? true,
|
|
146
|
-
endpoint,
|
|
147
|
-
headers: options.headers ?? {},
|
|
148
|
-
apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
|
|
149
|
-
sampleRate: METRICS_SAMPLE_RATE,
|
|
150
|
-
flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
|
|
151
|
-
maxBatchSize: METRICS_MAX_BATCH_SIZE
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
131
|
// package.json
|
|
156
|
-
var version = "0.
|
|
132
|
+
var version = "0.19.0";
|
|
157
133
|
|
|
158
134
|
// src/metrics/otlp.ts
|
|
159
135
|
var SCOPE_NAME = "@lovable.dev/mcp-js";
|
|
@@ -178,6 +154,7 @@ function toLogRecord(rec) {
|
|
|
178
154
|
attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
|
|
179
155
|
if (rec.resBytes !== void 0)
|
|
180
156
|
attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
|
|
157
|
+
attributes.push(strAttr("mcp.stack", rec.stack));
|
|
181
158
|
return {
|
|
182
159
|
timeUnixNano: rec.timeUnixNano,
|
|
183
160
|
observedTimeUnixNano: rec.timeUnixNano,
|
|
@@ -214,164 +191,164 @@ function nowUnixNano() {
|
|
|
214
191
|
return `${Date.now()}000000`;
|
|
215
192
|
}
|
|
216
193
|
|
|
217
|
-
// src/metrics/
|
|
194
|
+
// src/metrics/impl/base.ts
|
|
218
195
|
function nowMs() {
|
|
219
196
|
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
220
197
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
recorder.record(ev);
|
|
224
|
-
}
|
|
225
|
-
var NOOP_RECORDER = {
|
|
226
|
-
record() {
|
|
227
|
-
},
|
|
228
|
-
async flush() {
|
|
229
|
-
}
|
|
230
|
-
};
|
|
198
|
+
var NOOP_RECORDER = { async emit() {
|
|
199
|
+
} };
|
|
231
200
|
function createNoopRecorder() {
|
|
232
201
|
return NOOP_RECORDER;
|
|
233
202
|
}
|
|
234
|
-
function
|
|
235
|
-
try {
|
|
236
|
-
const denoEnv = globalThis.Deno?.env;
|
|
237
|
-
const value = denoEnv?.get?.(name);
|
|
238
|
-
if (value)
|
|
239
|
-
return value;
|
|
240
|
-
} catch {
|
|
241
|
-
}
|
|
203
|
+
function readProcessEnv(name) {
|
|
242
204
|
try {
|
|
243
|
-
|
|
244
|
-
const value = process.env?.[name];
|
|
245
|
-
if (value)
|
|
246
|
-
return value;
|
|
247
|
-
}
|
|
248
|
-
} catch {
|
|
249
|
-
}
|
|
250
|
-
return void 0;
|
|
251
|
-
}
|
|
252
|
-
var cloudflareEnvPromise;
|
|
253
|
-
async function readCloudflareEnv(name) {
|
|
254
|
-
try {
|
|
255
|
-
const moduleSpecifier = "cloudflare:workers";
|
|
256
|
-
cloudflareEnvPromise ??= import(
|
|
257
|
-
/* @vite-ignore */
|
|
258
|
-
moduleSpecifier
|
|
259
|
-
).then((m) => m.env).catch(() => void 0);
|
|
260
|
-
const env = await cloudflareEnvPromise;
|
|
261
|
-
const value = env?.[name];
|
|
262
|
-
return typeof value === "string" && value ? value : void 0;
|
|
205
|
+
return typeof process !== "undefined" ? process.env?.[name] || void 0 : void 0;
|
|
263
206
|
} catch {
|
|
264
207
|
return void 0;
|
|
265
208
|
}
|
|
266
209
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
return NOOP_RECORDER;
|
|
280
|
-
}
|
|
281
|
-
const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
|
|
282
|
-
const waitUntil = deps.waitUntil ?? probeWaitUntil();
|
|
283
|
-
const buffer = [];
|
|
284
|
-
let timer;
|
|
285
|
-
let lazyLogLevelApplied = false;
|
|
286
|
-
const applyLazyLogLevel = async () => {
|
|
287
|
-
if (lazyLogLevelApplied)
|
|
288
|
-
return;
|
|
289
|
-
lazyLogLevelApplied = true;
|
|
290
|
-
applyLogLevelFromEnv(await readCloudflareEnv(LOG_LEVEL_ENV_VAR));
|
|
291
|
-
};
|
|
292
|
-
let apiKey;
|
|
293
|
-
const resolveApiKey = async () => {
|
|
294
|
-
if (apiKey)
|
|
295
|
-
return apiKey;
|
|
296
|
-
apiKey = (deps.getApiKey ? deps.getApiKey() : readRuntimeEnv(config.apiKeyEnvVar)) ?? await readCloudflareEnv(config.apiKeyEnvVar);
|
|
297
|
-
return apiKey;
|
|
298
|
-
};
|
|
299
|
-
const schedule = (p) => {
|
|
300
|
-
if (waitUntil) {
|
|
301
|
-
try {
|
|
302
|
-
waitUntil(p);
|
|
303
|
-
return;
|
|
304
|
-
} catch (err) {
|
|
305
|
-
log.warn("metrics.wait_until_failed", describeError(err));
|
|
210
|
+
var BaseMetricRecorder = class {
|
|
211
|
+
constructor(config, server, deps = {}) {
|
|
212
|
+
this.config = config;
|
|
213
|
+
this.server = server;
|
|
214
|
+
this.deps = deps;
|
|
215
|
+
this.doFetch = deps.fetch ?? globalThis.fetch;
|
|
216
|
+
this.usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
|
|
217
|
+
const headers = {};
|
|
218
|
+
if (!this.usesLovableEndpoint) {
|
|
219
|
+
for (const [key, value] of Object.entries(config.headers)) {
|
|
220
|
+
if (key.toLowerCase() !== "content-type")
|
|
221
|
+
headers[key] = value;
|
|
306
222
|
}
|
|
307
223
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
};
|
|
311
|
-
const baseHeaders = {};
|
|
312
|
-
if (!usesLovableEndpoint) {
|
|
313
|
-
for (const [key, value] of Object.entries(config.headers)) {
|
|
314
|
-
if (key.toLowerCase() !== "content-type")
|
|
315
|
-
baseHeaders[key] = value;
|
|
316
|
-
}
|
|
224
|
+
headers["content-type"] = "application/json";
|
|
225
|
+
this.baseHeaders = headers;
|
|
317
226
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
227
|
+
doFetch;
|
|
228
|
+
usesLovableEndpoint;
|
|
229
|
+
baseHeaders;
|
|
230
|
+
apiKey;
|
|
231
|
+
lazyLogLevelApplied = false;
|
|
232
|
+
async emit(ev) {
|
|
233
|
+
await this.applyLazyLogLevel();
|
|
234
|
+
log.info("tool.invoked", {
|
|
235
|
+
tool: ev.tool,
|
|
236
|
+
method: ev.method,
|
|
237
|
+
outcome: ev.outcome,
|
|
238
|
+
durationMs: ev.durationMs,
|
|
239
|
+
stack: this.stack
|
|
240
|
+
});
|
|
241
|
+
if (!this.config.enabled)
|
|
242
|
+
return;
|
|
243
|
+
if (!this.doFetch) {
|
|
244
|
+
log.warn("metrics.disabled_no_fetch", { endpoint: this.config.endpoint });
|
|
322
245
|
return;
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const key = await resolveApiKey();
|
|
246
|
+
}
|
|
247
|
+
const headers = { ...this.baseHeaders };
|
|
248
|
+
if (this.usesLovableEndpoint) {
|
|
249
|
+
const key = await this.resolveApiKey();
|
|
328
250
|
if (!key) {
|
|
329
|
-
log.warn("metrics.disabled_no_api_key", {
|
|
330
|
-
envVar: config.apiKeyEnvVar,
|
|
331
|
-
endpoint: config.endpoint,
|
|
332
|
-
dropped
|
|
333
|
-
});
|
|
251
|
+
log.warn("metrics.disabled_no_api_key", { envVar: this.config.apiKeyEnvVar, endpoint: this.config.endpoint });
|
|
334
252
|
return;
|
|
335
253
|
}
|
|
336
254
|
headers.authorization = `Bearer ${key}`;
|
|
337
255
|
}
|
|
256
|
+
const body = buildLogsPayload([{ ...ev, timeUnixNano: nowUnixNano(), stack: this.stack }], this.server);
|
|
338
257
|
try {
|
|
339
|
-
const res = await doFetch(config.endpoint, {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
body: buildLogsPayload(records, server),
|
|
343
|
-
keepalive: true
|
|
344
|
-
});
|
|
345
|
-
if (!res.ok) {
|
|
346
|
-
log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
|
|
347
|
-
}
|
|
258
|
+
const res = await this.doFetch(this.config.endpoint, { method: "POST", headers, body, keepalive: true });
|
|
259
|
+
if (!res.ok)
|
|
260
|
+
log.warn("metrics.rejected", { status: res.status, endpoint: this.config.endpoint });
|
|
348
261
|
} catch (err) {
|
|
349
|
-
log.warn("metrics.
|
|
262
|
+
log.warn("metrics.failed", { ...describeError(err), endpoint: this.config.endpoint });
|
|
350
263
|
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
264
|
+
}
|
|
265
|
+
// Cache only a successful resolution so a miss is retried on the next emit (the
|
|
266
|
+
// Cloudflare binding may not be readable on the very first attempt in an isolate).
|
|
267
|
+
async resolveApiKey() {
|
|
268
|
+
if (this.apiKey)
|
|
269
|
+
return this.apiKey;
|
|
270
|
+
this.apiKey = this.deps.getApiKey ? this.deps.getApiKey() : await this.readEnv(this.config.apiKeyEnvVar);
|
|
271
|
+
return this.apiKey;
|
|
272
|
+
}
|
|
273
|
+
async applyLazyLogLevel() {
|
|
274
|
+
if (this.lazyLogLevelApplied)
|
|
354
275
|
return;
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
276
|
+
this.lazyLogLevelApplied = true;
|
|
277
|
+
applyLogLevelFromEnv(await this.readEnv(LOG_LEVEL_ENV_VAR));
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// src/metrics/impl/cloudflare.ts
|
|
282
|
+
var cloudflareEnvPromise;
|
|
283
|
+
async function readCloudflareEnv(name) {
|
|
284
|
+
try {
|
|
285
|
+
const moduleSpecifier = "cloudflare:workers";
|
|
286
|
+
cloudflareEnvPromise ??= import(
|
|
287
|
+
/* @vite-ignore */
|
|
288
|
+
moduleSpecifier
|
|
289
|
+
).then((m) => m.env).catch((err) => {
|
|
290
|
+
log.debug("metrics.cloudflare_env_import_failed", describeError(err));
|
|
291
|
+
return void 0;
|
|
292
|
+
});
|
|
293
|
+
const env = await cloudflareEnvPromise;
|
|
294
|
+
const raw = env?.[name];
|
|
295
|
+
const value = typeof raw === "string" && raw ? raw : void 0;
|
|
296
|
+
log.debug("metrics.read_cloudflare_env", { name, hasEnvBinding: !!env, found: value !== void 0 });
|
|
297
|
+
return value;
|
|
298
|
+
} catch (err) {
|
|
299
|
+
log.debug("metrics.read_cloudflare_env_error", { name, ...describeError(err) });
|
|
300
|
+
return void 0;
|
|
301
|
+
}
|
|
369
302
|
}
|
|
370
|
-
|
|
303
|
+
var TanStackMetricRecorder = class extends BaseMetricRecorder {
|
|
304
|
+
stack = "tanstack";
|
|
305
|
+
async readEnv(name) {
|
|
306
|
+
return readProcessEnv(name) ?? await readCloudflareEnv(name);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// src/metrics/impl/supabase.ts
|
|
311
|
+
function readDenoEnv(name) {
|
|
312
|
+
try {
|
|
313
|
+
const denoEnv = globalThis.Deno?.env;
|
|
314
|
+
const value = denoEnv?.get?.(name) || void 0;
|
|
315
|
+
log.debug("metrics.read_deno_env", { name, hasDenoEnv: !!denoEnv, found: !!value });
|
|
316
|
+
return value;
|
|
317
|
+
} catch (err) {
|
|
318
|
+
log.debug("metrics.read_deno_env_error", { name, ...describeError(err) });
|
|
319
|
+
return void 0;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
var SupabaseMetricRecorder = class extends BaseMetricRecorder {
|
|
323
|
+
stack = "supabase";
|
|
324
|
+
async readEnv(name) {
|
|
325
|
+
return readDenoEnv(name) ?? readProcessEnv(name);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// src/metrics/recorder.ts
|
|
330
|
+
function createRecorderForRuntime(mcp, opts) {
|
|
371
331
|
const config = resolveMetricsConfig(mcp.metrics);
|
|
332
|
+
const server = { name: mcp.name, version: mcp.version };
|
|
372
333
|
if (!config.enabled)
|
|
373
334
|
return createNoopRecorder();
|
|
374
|
-
return
|
|
335
|
+
return opts.stack === "tanstack" ? new TanStackMetricRecorder(config, server) : new SupabaseMetricRecorder(config, server);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/protocols/mcp/protocol.ts
|
|
339
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
340
|
+
var import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js");
|
|
341
|
+
|
|
342
|
+
// src/core/http.ts
|
|
343
|
+
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
344
|
+
function headResponse(response) {
|
|
345
|
+
return new Response(null, { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
346
|
+
}
|
|
347
|
+
function methodNotAllowed(allow) {
|
|
348
|
+
return new Response(JSON.stringify({ error: "method not allowed" }), {
|
|
349
|
+
status: 405,
|
|
350
|
+
headers: { ...JSON_HEADERS, Allow: allow }
|
|
351
|
+
});
|
|
375
352
|
}
|
|
376
353
|
|
|
377
354
|
// src/core/promise.ts
|
|
@@ -802,10 +779,10 @@ function assertRequiredScopes(auth, context) {
|
|
|
802
779
|
throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
|
|
803
780
|
}
|
|
804
781
|
}
|
|
805
|
-
function createRequestAuthorizer(mcp, options = {}
|
|
782
|
+
function createRequestAuthorizer(mcp, options = {}) {
|
|
806
783
|
const runtime = getOAuthRuntime(mcp, options);
|
|
807
784
|
return {
|
|
808
|
-
async authorize(request) {
|
|
785
|
+
async authorize(request, recorder) {
|
|
809
786
|
if (runtime.kind === "unconfigured")
|
|
810
787
|
return { ok: true };
|
|
811
788
|
const startedAt = nowMs();
|
|
@@ -821,7 +798,7 @@ function createRequestAuthorizer(mcp, options = {}, recorder) {
|
|
|
821
798
|
} catch (err) {
|
|
822
799
|
if (err instanceof OAuthConfigurationError) {
|
|
823
800
|
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
824
|
-
recorder?.
|
|
801
|
+
await recorder?.emit({
|
|
825
802
|
tool: null,
|
|
826
803
|
method: "authorize",
|
|
827
804
|
outcome: "auth_config_error",
|
|
@@ -932,7 +909,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
932
909
|
try {
|
|
933
910
|
result = await tool.handler(args, new ToolContext(auth));
|
|
934
911
|
} catch {
|
|
935
|
-
|
|
912
|
+
await recorder.emit({
|
|
936
913
|
tool: tool.name,
|
|
937
914
|
method: "tools/call",
|
|
938
915
|
outcome: "handler_error",
|
|
@@ -941,7 +918,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
941
918
|
return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
|
|
942
919
|
}
|
|
943
920
|
if (result == null) {
|
|
944
|
-
|
|
921
|
+
await recorder.emit({
|
|
945
922
|
tool: tool.name,
|
|
946
923
|
method: "tools/call",
|
|
947
924
|
outcome: "handler_error",
|
|
@@ -949,7 +926,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
949
926
|
});
|
|
950
927
|
return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
|
|
951
928
|
}
|
|
952
|
-
|
|
929
|
+
await recorder.emit({
|
|
953
930
|
tool: tool.name,
|
|
954
931
|
method: "tools/call",
|
|
955
932
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -958,10 +935,10 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
958
935
|
return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
|
|
959
936
|
};
|
|
960
937
|
}
|
|
961
|
-
function createMcpProtocolHandler(mcp, options = {}
|
|
962
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
963
|
-
const handle = async (request) => {
|
|
964
|
-
const authResult = await authorizer.authorize(request);
|
|
938
|
+
function createMcpProtocolHandler(mcp, options = {}) {
|
|
939
|
+
const authorizer = createRequestAuthorizer(mcp, options);
|
|
940
|
+
const handle = async (request, recorder) => {
|
|
941
|
+
const authResult = await authorizer.authorize(request, recorder);
|
|
965
942
|
if (!authResult.ok)
|
|
966
943
|
return authResult.response;
|
|
967
944
|
try {
|
|
@@ -988,7 +965,7 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
|
|
|
988
965
|
await server.connect(transport);
|
|
989
966
|
return await transport.handleRequest(request);
|
|
990
967
|
} catch (err) {
|
|
991
|
-
recorder.
|
|
968
|
+
await recorder.emit({ tool: null, method: "transport", outcome: "transport_error", durationMs: 0 });
|
|
992
969
|
log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
|
|
993
970
|
return Response.json(
|
|
994
971
|
{ jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
|
|
@@ -996,10 +973,10 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
|
|
|
996
973
|
);
|
|
997
974
|
}
|
|
998
975
|
};
|
|
999
|
-
return async (request) => {
|
|
976
|
+
return async (request, recorder = createNoopRecorder()) => {
|
|
1000
977
|
if (request.method === "OPTIONS")
|
|
1001
978
|
return corsPreflightResponse("GET, POST, DELETE, OPTIONS");
|
|
1002
|
-
return withCors(await handle(request));
|
|
979
|
+
return withCors(await handle(request, recorder));
|
|
1003
980
|
};
|
|
1004
981
|
}
|
|
1005
982
|
|
|
@@ -1089,11 +1066,11 @@ function buildMcpListing(mcp) {
|
|
|
1089
1066
|
}))
|
|
1090
1067
|
};
|
|
1091
1068
|
}
|
|
1092
|
-
function createListToolsHandler(mcp, options = {}
|
|
1069
|
+
function createListToolsHandler(mcp, options = {}) {
|
|
1093
1070
|
assertRestResourceBinding(mcp, options);
|
|
1094
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
1095
|
-
const handle = async (request) => {
|
|
1096
|
-
const authResult = await authorizer.authorize(request);
|
|
1071
|
+
const authorizer = createRequestAuthorizer(mcp, options);
|
|
1072
|
+
const handle = async (request, recorder) => {
|
|
1073
|
+
const authResult = await authorizer.authorize(request, recorder);
|
|
1097
1074
|
if (!authResult.ok)
|
|
1098
1075
|
return authResult.response;
|
|
1099
1076
|
if (request.method !== "GET" && request.method !== "HEAD")
|
|
@@ -1101,10 +1078,10 @@ function createListToolsHandler(mcp, options = {}, recorder = createRecorderForR
|
|
|
1101
1078
|
const response = Response.json(buildMcpListing(mcp));
|
|
1102
1079
|
return request.method === "HEAD" ? headResponse(response) : response;
|
|
1103
1080
|
};
|
|
1104
|
-
return async (request) => {
|
|
1081
|
+
return async (request, recorder = createNoopRecorder()) => {
|
|
1105
1082
|
if (request.method === "OPTIONS")
|
|
1106
1083
|
return corsPreflightResponse("GET, HEAD, OPTIONS");
|
|
1107
|
-
return withCors(await handle(request));
|
|
1084
|
+
return withCors(await handle(request, recorder));
|
|
1108
1085
|
};
|
|
1109
1086
|
}
|
|
1110
1087
|
|
|
@@ -1122,11 +1099,11 @@ function isEmptyArgs(value) {
|
|
|
1122
1099
|
return false;
|
|
1123
1100
|
return Object.keys(value).length === 0;
|
|
1124
1101
|
}
|
|
1125
|
-
function createInvokeToolHandler(mcp, options = {}
|
|
1102
|
+
function createInvokeToolHandler(mcp, options = {}) {
|
|
1126
1103
|
assertRestResourceBinding(mcp, options);
|
|
1127
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
1128
|
-
const handle = async (request, toolName) => {
|
|
1129
|
-
const authResult = await authorizer.authorize(request);
|
|
1104
|
+
const authorizer = createRequestAuthorizer(mcp, options);
|
|
1105
|
+
const handle = async (request, toolName, recorder) => {
|
|
1106
|
+
const authResult = await authorizer.authorize(request, recorder);
|
|
1130
1107
|
if (!authResult.ok)
|
|
1131
1108
|
return authResult.response;
|
|
1132
1109
|
if (request.method !== "POST")
|
|
@@ -1182,7 +1159,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1182
1159
|
try {
|
|
1183
1160
|
result = await tool.handler(args, new ToolContext(authResult.auth));
|
|
1184
1161
|
} catch {
|
|
1185
|
-
|
|
1162
|
+
await recorder.emit({
|
|
1186
1163
|
tool: tool.name,
|
|
1187
1164
|
method: "tools/call",
|
|
1188
1165
|
outcome: "handler_error",
|
|
@@ -1194,7 +1171,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1194
1171
|
});
|
|
1195
1172
|
}
|
|
1196
1173
|
if (result == null) {
|
|
1197
|
-
|
|
1174
|
+
await recorder.emit({
|
|
1198
1175
|
tool: tool.name,
|
|
1199
1176
|
method: "tools/call",
|
|
1200
1177
|
outcome: "handler_error",
|
|
@@ -1205,7 +1182,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1205
1182
|
headers: JSON_HEADERS
|
|
1206
1183
|
});
|
|
1207
1184
|
}
|
|
1208
|
-
|
|
1185
|
+
await recorder.emit({
|
|
1209
1186
|
tool: tool.name,
|
|
1210
1187
|
method: "tools/call",
|
|
1211
1188
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -1217,28 +1194,29 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1217
1194
|
isError: result.isError
|
|
1218
1195
|
});
|
|
1219
1196
|
};
|
|
1220
|
-
return async (request, toolName) => {
|
|
1197
|
+
return async (request, toolName, recorder = createNoopRecorder()) => {
|
|
1221
1198
|
if (request.method === "OPTIONS")
|
|
1222
1199
|
return corsPreflightResponse("POST, OPTIONS");
|
|
1223
|
-
return withCors(await handle(request, toolName));
|
|
1200
|
+
return withCors(await handle(request, toolName, recorder));
|
|
1224
1201
|
};
|
|
1225
1202
|
}
|
|
1226
1203
|
|
|
1227
1204
|
// src/stacks/tanstack/handlers.ts
|
|
1205
|
+
var STACK = "tanstack";
|
|
1228
1206
|
function forwarded(request, options) {
|
|
1229
1207
|
return applyForwardedOrigin(request, { trustForwardedHost: options.trustForwardedHost });
|
|
1230
1208
|
}
|
|
1231
1209
|
function createTanStackMcpHandler(mcp, options = {}) {
|
|
1232
1210
|
const handler = createMcpProtocolHandler(mcp, options);
|
|
1233
|
-
return ({ request }) => handler(forwarded(request, options));
|
|
1211
|
+
return ({ request }) => handler(forwarded(request, options), createRecorderForRuntime(mcp, { stack: STACK }));
|
|
1234
1212
|
}
|
|
1235
1213
|
function createTanStackListToolsHandler(mcp, options = {}) {
|
|
1236
1214
|
const handler = createListToolsHandler(mcp, options);
|
|
1237
|
-
return ({ request }) => handler(forwarded(request, options));
|
|
1215
|
+
return ({ request }) => handler(forwarded(request, options), createRecorderForRuntime(mcp, { stack: STACK }));
|
|
1238
1216
|
}
|
|
1239
1217
|
function createTanStackInvokeToolHandler(mcp, options = {}) {
|
|
1240
1218
|
const handler = createInvokeToolHandler(mcp, options);
|
|
1241
|
-
return ({ request, params }) => handler(forwarded(request, options), params.tool);
|
|
1219
|
+
return ({ request, params }) => handler(forwarded(request, options), params.tool, createRecorderForRuntime(mcp, { stack: STACK }));
|
|
1242
1220
|
}
|
|
1243
1221
|
function createTanStackOAuthProtectedResourceMetadataHandler(mcp, options = {}) {
|
|
1244
1222
|
const handler = createOAuthProtectedResourceMetadataHandler(mcp, options);
|
|
@@ -3,37 +3,40 @@ import {
|
|
|
3
3
|
} from "../../chunk-UQK5UO6C.js";
|
|
4
4
|
import {
|
|
5
5
|
createMcpProtocolHandler
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-CLXKPZZE.js";
|
|
7
7
|
import {
|
|
8
8
|
createOAuthProtectedResourceMetadataHandler
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-UEOBGHXF.js";
|
|
10
10
|
import {
|
|
11
11
|
createInvokeToolHandler
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-57ZH2WW5.js";
|
|
13
13
|
import {
|
|
14
14
|
createListToolsHandler
|
|
15
|
-
} from "../../chunk-
|
|
15
|
+
} from "../../chunk-OMWXY6WY.js";
|
|
16
16
|
import "../../chunk-MA5H6PSF.js";
|
|
17
|
-
import
|
|
18
|
-
|
|
17
|
+
import {
|
|
18
|
+
createRecorderForRuntime
|
|
19
|
+
} from "../../chunk-5FX6IIQ6.js";
|
|
20
|
+
import "../../chunk-H37EB22A.js";
|
|
19
21
|
import "../../chunk-6DXGZZA4.js";
|
|
20
|
-
import "../../chunk-
|
|
22
|
+
import "../../chunk-CDZ7MBXC.js";
|
|
21
23
|
|
|
22
24
|
// src/stacks/tanstack/handlers.ts
|
|
25
|
+
var STACK = "tanstack";
|
|
23
26
|
function forwarded(request, options) {
|
|
24
27
|
return applyForwardedOrigin(request, { trustForwardedHost: options.trustForwardedHost });
|
|
25
28
|
}
|
|
26
29
|
function createTanStackMcpHandler(mcp, options = {}) {
|
|
27
30
|
const handler = createMcpProtocolHandler(mcp, options);
|
|
28
|
-
return ({ request }) => handler(forwarded(request, options));
|
|
31
|
+
return ({ request }) => handler(forwarded(request, options), createRecorderForRuntime(mcp, { stack: STACK }));
|
|
29
32
|
}
|
|
30
33
|
function createTanStackListToolsHandler(mcp, options = {}) {
|
|
31
34
|
const handler = createListToolsHandler(mcp, options);
|
|
32
|
-
return ({ request }) => handler(forwarded(request, options));
|
|
35
|
+
return ({ request }) => handler(forwarded(request, options), createRecorderForRuntime(mcp, { stack: STACK }));
|
|
33
36
|
}
|
|
34
37
|
function createTanStackInvokeToolHandler(mcp, options = {}) {
|
|
35
38
|
const handler = createInvokeToolHandler(mcp, options);
|
|
36
|
-
return ({ request, params }) => handler(forwarded(request, options), params.tool);
|
|
39
|
+
return ({ request, params }) => handler(forwarded(request, options), params.tool, createRecorderForRuntime(mcp, { stack: STACK }));
|
|
37
40
|
}
|
|
38
41
|
function createTanStackOAuthProtectedResourceMetadataHandler(mcp, options = {}) {
|
|
39
42
|
const handler = createOAuthProtectedResourceMetadataHandler(mcp, options);
|
|
@@ -114,12 +114,12 @@ interface MetricsOptions {
|
|
|
114
114
|
/** Master switch. Default `true`; metrics still self-disable at runtime when
|
|
115
115
|
* the API key env var is absent. */
|
|
116
116
|
enabled?: boolean;
|
|
117
|
-
/** OTLP/HTTP (JSON) logs URL to POST
|
|
118
|
-
* any OTLP collector. Must be an absolute http(s) URL.
|
|
117
|
+
/** OTLP/HTTP (JSON) logs URL to POST each invocation to — the default Lovable
|
|
118
|
+
* route or any OTLP collector. Must be an absolute http(s) URL.
|
|
119
119
|
* @default "https://api.lovable.dev/v1/app-mcp-usage" */
|
|
120
120
|
endpoint?: string;
|
|
121
121
|
/** Extra request headers — e.g. the auth token for a private OTLP collector.
|
|
122
|
-
* Sent verbatim with every
|
|
122
|
+
* Sent verbatim with every request (and baked into the build output, so source
|
|
123
123
|
* your own build-time env for secrets). Ignored for the default Lovable
|
|
124
124
|
* endpoint, which authenticates with `LOVABLE_API_KEY` instead. `content-type`
|
|
125
125
|
* cannot be overridden (OTLP/HTTP JSON requires `application/json`). */
|