@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
|
@@ -95,6 +95,32 @@ function firstForwardedValue(request, header) {
|
|
|
95
95
|
return value ? value : void 0;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// src/metrics/config.ts
|
|
99
|
+
var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
|
|
100
|
+
var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
|
|
101
|
+
function assertMetricsEndpoint(endpoint) {
|
|
102
|
+
let url;
|
|
103
|
+
try {
|
|
104
|
+
url = new URL(endpoint);
|
|
105
|
+
} catch {
|
|
106
|
+
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
|
|
107
|
+
}
|
|
108
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
109
|
+
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function resolveMetricsConfig(config = true) {
|
|
113
|
+
const options = typeof config === "boolean" ? { enabled: config } : config;
|
|
114
|
+
const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
|
|
115
|
+
assertMetricsEndpoint(endpoint);
|
|
116
|
+
return Object.freeze({
|
|
117
|
+
enabled: options.enabled ?? true,
|
|
118
|
+
endpoint,
|
|
119
|
+
headers: options.headers ?? {},
|
|
120
|
+
apiKeyEnvVar: METRICS_API_KEY_ENV_VAR
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
98
124
|
// src/core/logger.ts
|
|
99
125
|
var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
100
126
|
function isLogLevel(value) {
|
|
@@ -119,9 +145,7 @@ function parseLogLevel(raw) {
|
|
|
119
145
|
return isLogLevel(normalized) ? normalized : void 0;
|
|
120
146
|
}
|
|
121
147
|
function applyLogLevelFromEnv(raw) {
|
|
122
|
-
|
|
123
|
-
if (level)
|
|
124
|
-
setLogLevel(level);
|
|
148
|
+
setLogLevel(parseLogLevel(raw) ?? "debug");
|
|
125
149
|
}
|
|
126
150
|
function enabled(level) {
|
|
127
151
|
return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
|
|
@@ -149,40 +173,8 @@ function describeError(err) {
|
|
|
149
173
|
return { value: String(err) };
|
|
150
174
|
}
|
|
151
175
|
|
|
152
|
-
// src/metrics/config.ts
|
|
153
|
-
var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
|
|
154
|
-
var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
|
|
155
|
-
var METRICS_SAMPLE_RATE = 1;
|
|
156
|
-
var METRICS_FLUSH_INTERVAL_MS = 5e3;
|
|
157
|
-
var METRICS_MAX_BATCH_SIZE = 50;
|
|
158
|
-
function assertMetricsEndpoint(endpoint) {
|
|
159
|
-
let url;
|
|
160
|
-
try {
|
|
161
|
-
url = new URL(endpoint);
|
|
162
|
-
} catch {
|
|
163
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
|
|
164
|
-
}
|
|
165
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
166
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
function resolveMetricsConfig(config = true) {
|
|
170
|
-
const options = typeof config === "boolean" ? { enabled: config } : config;
|
|
171
|
-
const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
|
|
172
|
-
assertMetricsEndpoint(endpoint);
|
|
173
|
-
return Object.freeze({
|
|
174
|
-
enabled: options.enabled ?? true,
|
|
175
|
-
endpoint,
|
|
176
|
-
headers: options.headers ?? {},
|
|
177
|
-
apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
|
|
178
|
-
sampleRate: METRICS_SAMPLE_RATE,
|
|
179
|
-
flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
|
|
180
|
-
maxBatchSize: METRICS_MAX_BATCH_SIZE
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
176
|
// package.json
|
|
185
|
-
var version = "0.
|
|
177
|
+
var version = "0.19.0";
|
|
186
178
|
|
|
187
179
|
// src/metrics/otlp.ts
|
|
188
180
|
var SCOPE_NAME = "@lovable.dev/mcp-js";
|
|
@@ -207,6 +199,7 @@ function toLogRecord(rec) {
|
|
|
207
199
|
attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
|
|
208
200
|
if (rec.resBytes !== void 0)
|
|
209
201
|
attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
|
|
202
|
+
attributes.push(strAttr("mcp.stack", rec.stack));
|
|
210
203
|
return {
|
|
211
204
|
timeUnixNano: rec.timeUnixNano,
|
|
212
205
|
observedTimeUnixNano: rec.timeUnixNano,
|
|
@@ -243,164 +236,148 @@ function nowUnixNano() {
|
|
|
243
236
|
return `${Date.now()}000000`;
|
|
244
237
|
}
|
|
245
238
|
|
|
246
|
-
// src/metrics/
|
|
239
|
+
// src/metrics/impl/base.ts
|
|
247
240
|
function nowMs() {
|
|
248
241
|
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
249
242
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
recorder.record(ev);
|
|
253
|
-
}
|
|
254
|
-
var NOOP_RECORDER = {
|
|
255
|
-
record() {
|
|
256
|
-
},
|
|
257
|
-
async flush() {
|
|
258
|
-
}
|
|
259
|
-
};
|
|
243
|
+
var NOOP_RECORDER = { async emit() {
|
|
244
|
+
} };
|
|
260
245
|
function createNoopRecorder() {
|
|
261
246
|
return NOOP_RECORDER;
|
|
262
247
|
}
|
|
263
|
-
function
|
|
264
|
-
try {
|
|
265
|
-
const denoEnv = globalThis.Deno?.env;
|
|
266
|
-
const value = denoEnv?.get?.(name);
|
|
267
|
-
if (value)
|
|
268
|
-
return value;
|
|
269
|
-
} catch {
|
|
270
|
-
}
|
|
271
|
-
try {
|
|
272
|
-
if (typeof process !== "undefined") {
|
|
273
|
-
const value = process.env?.[name];
|
|
274
|
-
if (value)
|
|
275
|
-
return value;
|
|
276
|
-
}
|
|
277
|
-
} catch {
|
|
278
|
-
}
|
|
279
|
-
return void 0;
|
|
280
|
-
}
|
|
281
|
-
var cloudflareEnvPromise;
|
|
282
|
-
async function readCloudflareEnv(name) {
|
|
248
|
+
function readProcessEnv(name) {
|
|
283
249
|
try {
|
|
284
|
-
|
|
285
|
-
cloudflareEnvPromise ??= import(
|
|
286
|
-
/* @vite-ignore */
|
|
287
|
-
moduleSpecifier
|
|
288
|
-
).then((m) => m.env).catch(() => void 0);
|
|
289
|
-
const env = await cloudflareEnvPromise;
|
|
290
|
-
const value = env?.[name];
|
|
291
|
-
return typeof value === "string" && value ? value : void 0;
|
|
250
|
+
return typeof process !== "undefined" ? process.env?.[name] || void 0 : void 0;
|
|
292
251
|
} catch {
|
|
293
252
|
return void 0;
|
|
294
253
|
}
|
|
295
254
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return NOOP_RECORDER;
|
|
309
|
-
}
|
|
310
|
-
const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
|
|
311
|
-
const waitUntil = deps.waitUntil ?? probeWaitUntil();
|
|
312
|
-
const buffer = [];
|
|
313
|
-
let timer;
|
|
314
|
-
let lazyLogLevelApplied = false;
|
|
315
|
-
const applyLazyLogLevel = async () => {
|
|
316
|
-
if (lazyLogLevelApplied)
|
|
317
|
-
return;
|
|
318
|
-
lazyLogLevelApplied = true;
|
|
319
|
-
applyLogLevelFromEnv(await readCloudflareEnv(LOG_LEVEL_ENV_VAR));
|
|
320
|
-
};
|
|
321
|
-
let apiKey;
|
|
322
|
-
const resolveApiKey = async () => {
|
|
323
|
-
if (apiKey)
|
|
324
|
-
return apiKey;
|
|
325
|
-
apiKey = (deps.getApiKey ? deps.getApiKey() : readRuntimeEnv(config.apiKeyEnvVar)) ?? await readCloudflareEnv(config.apiKeyEnvVar);
|
|
326
|
-
return apiKey;
|
|
327
|
-
};
|
|
328
|
-
const schedule = (p) => {
|
|
329
|
-
if (waitUntil) {
|
|
330
|
-
try {
|
|
331
|
-
waitUntil(p);
|
|
332
|
-
return;
|
|
333
|
-
} catch (err) {
|
|
334
|
-
log.warn("metrics.wait_until_failed", describeError(err));
|
|
255
|
+
var BaseMetricRecorder = class {
|
|
256
|
+
constructor(config, server, deps = {}) {
|
|
257
|
+
this.config = config;
|
|
258
|
+
this.server = server;
|
|
259
|
+
this.deps = deps;
|
|
260
|
+
this.doFetch = deps.fetch ?? globalThis.fetch;
|
|
261
|
+
this.usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
|
|
262
|
+
const headers = {};
|
|
263
|
+
if (!this.usesLovableEndpoint) {
|
|
264
|
+
for (const [key, value] of Object.entries(config.headers)) {
|
|
265
|
+
if (key.toLowerCase() !== "content-type")
|
|
266
|
+
headers[key] = value;
|
|
335
267
|
}
|
|
336
268
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
};
|
|
340
|
-
const baseHeaders = {};
|
|
341
|
-
if (!usesLovableEndpoint) {
|
|
342
|
-
for (const [key, value] of Object.entries(config.headers)) {
|
|
343
|
-
if (key.toLowerCase() !== "content-type")
|
|
344
|
-
baseHeaders[key] = value;
|
|
345
|
-
}
|
|
269
|
+
headers["content-type"] = "application/json";
|
|
270
|
+
this.baseHeaders = headers;
|
|
346
271
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
272
|
+
doFetch;
|
|
273
|
+
usesLovableEndpoint;
|
|
274
|
+
baseHeaders;
|
|
275
|
+
apiKey;
|
|
276
|
+
lazyLogLevelApplied = false;
|
|
277
|
+
async emit(ev) {
|
|
278
|
+
await this.applyLazyLogLevel();
|
|
279
|
+
log.info("tool.invoked", {
|
|
280
|
+
tool: ev.tool,
|
|
281
|
+
method: ev.method,
|
|
282
|
+
outcome: ev.outcome,
|
|
283
|
+
durationMs: ev.durationMs,
|
|
284
|
+
stack: this.stack
|
|
285
|
+
});
|
|
286
|
+
if (!this.config.enabled)
|
|
287
|
+
return;
|
|
288
|
+
if (!this.doFetch) {
|
|
289
|
+
log.warn("metrics.disabled_no_fetch", { endpoint: this.config.endpoint });
|
|
351
290
|
return;
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const key = await resolveApiKey();
|
|
291
|
+
}
|
|
292
|
+
const headers = { ...this.baseHeaders };
|
|
293
|
+
if (this.usesLovableEndpoint) {
|
|
294
|
+
const key = await this.resolveApiKey();
|
|
357
295
|
if (!key) {
|
|
358
|
-
log.warn("metrics.disabled_no_api_key", {
|
|
359
|
-
envVar: config.apiKeyEnvVar,
|
|
360
|
-
endpoint: config.endpoint,
|
|
361
|
-
dropped
|
|
362
|
-
});
|
|
296
|
+
log.warn("metrics.disabled_no_api_key", { envVar: this.config.apiKeyEnvVar, endpoint: this.config.endpoint });
|
|
363
297
|
return;
|
|
364
298
|
}
|
|
365
299
|
headers.authorization = `Bearer ${key}`;
|
|
366
300
|
}
|
|
301
|
+
const body = buildLogsPayload([{ ...ev, timeUnixNano: nowUnixNano(), stack: this.stack }], this.server);
|
|
367
302
|
try {
|
|
368
|
-
const res = await doFetch(config.endpoint, {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
body: buildLogsPayload(records, server),
|
|
372
|
-
keepalive: true
|
|
373
|
-
});
|
|
374
|
-
if (!res.ok) {
|
|
375
|
-
log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
|
|
376
|
-
}
|
|
303
|
+
const res = await this.doFetch(this.config.endpoint, { method: "POST", headers, body, keepalive: true });
|
|
304
|
+
if (!res.ok)
|
|
305
|
+
log.warn("metrics.rejected", { status: res.status, endpoint: this.config.endpoint });
|
|
377
306
|
} catch (err) {
|
|
378
|
-
log.warn("metrics.
|
|
307
|
+
log.warn("metrics.failed", { ...describeError(err), endpoint: this.config.endpoint });
|
|
379
308
|
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
309
|
+
}
|
|
310
|
+
// Cache only a successful resolution so a miss is retried on the next emit (the
|
|
311
|
+
// Cloudflare binding may not be readable on the very first attempt in an isolate).
|
|
312
|
+
async resolveApiKey() {
|
|
313
|
+
if (this.apiKey)
|
|
314
|
+
return this.apiKey;
|
|
315
|
+
this.apiKey = this.deps.getApiKey ? this.deps.getApiKey() : await this.readEnv(this.config.apiKeyEnvVar);
|
|
316
|
+
return this.apiKey;
|
|
317
|
+
}
|
|
318
|
+
async applyLazyLogLevel() {
|
|
319
|
+
if (this.lazyLogLevelApplied)
|
|
383
320
|
return;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
321
|
+
this.lazyLogLevelApplied = true;
|
|
322
|
+
applyLogLevelFromEnv(await this.readEnv(LOG_LEVEL_ENV_VAR));
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// src/metrics/impl/cloudflare.ts
|
|
327
|
+
var cloudflareEnvPromise;
|
|
328
|
+
async function readCloudflareEnv(name) {
|
|
329
|
+
try {
|
|
330
|
+
const moduleSpecifier = "cloudflare:workers";
|
|
331
|
+
cloudflareEnvPromise ??= import(
|
|
332
|
+
/* @vite-ignore */
|
|
333
|
+
moduleSpecifier
|
|
334
|
+
).then((m) => m.env).catch((err) => {
|
|
335
|
+
log.debug("metrics.cloudflare_env_import_failed", describeError(err));
|
|
336
|
+
return void 0;
|
|
337
|
+
});
|
|
338
|
+
const env = await cloudflareEnvPromise;
|
|
339
|
+
const raw = env?.[name];
|
|
340
|
+
const value = typeof raw === "string" && raw ? raw : void 0;
|
|
341
|
+
log.debug("metrics.read_cloudflare_env", { name, hasEnvBinding: !!env, found: value !== void 0 });
|
|
342
|
+
return value;
|
|
343
|
+
} catch (err) {
|
|
344
|
+
log.debug("metrics.read_cloudflare_env_error", { name, ...describeError(err) });
|
|
345
|
+
return void 0;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
var TanStackMetricRecorder = class extends BaseMetricRecorder {
|
|
349
|
+
stack = "tanstack";
|
|
350
|
+
async readEnv(name) {
|
|
351
|
+
return readProcessEnv(name) ?? await readCloudflareEnv(name);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// src/metrics/impl/supabase.ts
|
|
356
|
+
function readDenoEnv(name) {
|
|
357
|
+
try {
|
|
358
|
+
const denoEnv = globalThis.Deno?.env;
|
|
359
|
+
const value = denoEnv?.get?.(name) || void 0;
|
|
360
|
+
log.debug("metrics.read_deno_env", { name, hasDenoEnv: !!denoEnv, found: !!value });
|
|
361
|
+
return value;
|
|
362
|
+
} catch (err) {
|
|
363
|
+
log.debug("metrics.read_deno_env_error", { name, ...describeError(err) });
|
|
364
|
+
return void 0;
|
|
365
|
+
}
|
|
398
366
|
}
|
|
399
|
-
|
|
367
|
+
var SupabaseMetricRecorder = class extends BaseMetricRecorder {
|
|
368
|
+
stack = "supabase";
|
|
369
|
+
async readEnv(name) {
|
|
370
|
+
return readDenoEnv(name) ?? readProcessEnv(name);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// src/metrics/recorder.ts
|
|
375
|
+
function createRecorderForRuntime(mcp, opts) {
|
|
400
376
|
const config = resolveMetricsConfig(mcp.metrics);
|
|
377
|
+
const server = { name: mcp.name, version: mcp.version };
|
|
401
378
|
if (!config.enabled)
|
|
402
379
|
return createNoopRecorder();
|
|
403
|
-
return
|
|
380
|
+
return opts.stack === "tanstack" ? new TanStackMetricRecorder(config, server) : new SupabaseMetricRecorder(config, server);
|
|
404
381
|
}
|
|
405
382
|
|
|
406
383
|
// src/protocols/mcp/protocol.ts
|
|
@@ -799,10 +776,10 @@ function assertRequiredScopes(auth, context) {
|
|
|
799
776
|
throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
|
|
800
777
|
}
|
|
801
778
|
}
|
|
802
|
-
function createRequestAuthorizer(mcp, options = {}
|
|
779
|
+
function createRequestAuthorizer(mcp, options = {}) {
|
|
803
780
|
const runtime = getOAuthRuntime(mcp, options);
|
|
804
781
|
return {
|
|
805
|
-
async authorize(request) {
|
|
782
|
+
async authorize(request, recorder) {
|
|
806
783
|
if (runtime.kind === "unconfigured")
|
|
807
784
|
return { ok: true };
|
|
808
785
|
const startedAt = nowMs();
|
|
@@ -818,7 +795,7 @@ function createRequestAuthorizer(mcp, options = {}, recorder) {
|
|
|
818
795
|
} catch (err) {
|
|
819
796
|
if (err instanceof OAuthConfigurationError) {
|
|
820
797
|
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
821
|
-
recorder?.
|
|
798
|
+
await recorder?.emit({
|
|
822
799
|
tool: null,
|
|
823
800
|
method: "authorize",
|
|
824
801
|
outcome: "auth_config_error",
|
|
@@ -929,7 +906,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
929
906
|
try {
|
|
930
907
|
result = await tool.handler(args, new ToolContext(auth));
|
|
931
908
|
} catch {
|
|
932
|
-
|
|
909
|
+
await recorder.emit({
|
|
933
910
|
tool: tool.name,
|
|
934
911
|
method: "tools/call",
|
|
935
912
|
outcome: "handler_error",
|
|
@@ -938,7 +915,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
938
915
|
return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
|
|
939
916
|
}
|
|
940
917
|
if (result == null) {
|
|
941
|
-
|
|
918
|
+
await recorder.emit({
|
|
942
919
|
tool: tool.name,
|
|
943
920
|
method: "tools/call",
|
|
944
921
|
outcome: "handler_error",
|
|
@@ -946,7 +923,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
946
923
|
});
|
|
947
924
|
return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
|
|
948
925
|
}
|
|
949
|
-
|
|
926
|
+
await recorder.emit({
|
|
950
927
|
tool: tool.name,
|
|
951
928
|
method: "tools/call",
|
|
952
929
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -955,10 +932,10 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
955
932
|
return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
|
|
956
933
|
};
|
|
957
934
|
}
|
|
958
|
-
function createMcpProtocolHandler(mcp, options = {}
|
|
959
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
960
|
-
const handle = async (request) => {
|
|
961
|
-
const authResult = await authorizer.authorize(request);
|
|
935
|
+
function createMcpProtocolHandler(mcp, options = {}) {
|
|
936
|
+
const authorizer = createRequestAuthorizer(mcp, options);
|
|
937
|
+
const handle = async (request, recorder) => {
|
|
938
|
+
const authResult = await authorizer.authorize(request, recorder);
|
|
962
939
|
if (!authResult.ok)
|
|
963
940
|
return authResult.response;
|
|
964
941
|
try {
|
|
@@ -985,7 +962,7 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
|
|
|
985
962
|
await server.connect(transport);
|
|
986
963
|
return await transport.handleRequest(request);
|
|
987
964
|
} catch (err) {
|
|
988
|
-
recorder.
|
|
965
|
+
await recorder.emit({ tool: null, method: "transport", outcome: "transport_error", durationMs: 0 });
|
|
989
966
|
log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
|
|
990
967
|
return Response.json(
|
|
991
968
|
{ jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
|
|
@@ -993,10 +970,10 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
|
|
|
993
970
|
);
|
|
994
971
|
}
|
|
995
972
|
};
|
|
996
|
-
return async (request) => {
|
|
973
|
+
return async (request, recorder = createNoopRecorder()) => {
|
|
997
974
|
if (request.method === "OPTIONS")
|
|
998
975
|
return corsPreflightResponse("GET, POST, DELETE, OPTIONS");
|
|
999
|
-
return withCors(await handle(request));
|
|
976
|
+
return withCors(await handle(request, recorder));
|
|
1000
977
|
};
|
|
1001
978
|
}
|
|
1002
979
|
|
|
@@ -1086,11 +1063,11 @@ function buildMcpListing(mcp) {
|
|
|
1086
1063
|
}))
|
|
1087
1064
|
};
|
|
1088
1065
|
}
|
|
1089
|
-
function createListToolsHandler(mcp, options = {}
|
|
1066
|
+
function createListToolsHandler(mcp, options = {}) {
|
|
1090
1067
|
assertRestResourceBinding(mcp, options);
|
|
1091
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
1092
|
-
const handle = async (request) => {
|
|
1093
|
-
const authResult = await authorizer.authorize(request);
|
|
1068
|
+
const authorizer = createRequestAuthorizer(mcp, options);
|
|
1069
|
+
const handle = async (request, recorder) => {
|
|
1070
|
+
const authResult = await authorizer.authorize(request, recorder);
|
|
1094
1071
|
if (!authResult.ok)
|
|
1095
1072
|
return authResult.response;
|
|
1096
1073
|
if (request.method !== "GET" && request.method !== "HEAD")
|
|
@@ -1098,10 +1075,10 @@ function createListToolsHandler(mcp, options = {}, recorder = createRecorderForR
|
|
|
1098
1075
|
const response = Response.json(buildMcpListing(mcp));
|
|
1099
1076
|
return request.method === "HEAD" ? headResponse(response) : response;
|
|
1100
1077
|
};
|
|
1101
|
-
return async (request) => {
|
|
1078
|
+
return async (request, recorder = createNoopRecorder()) => {
|
|
1102
1079
|
if (request.method === "OPTIONS")
|
|
1103
1080
|
return corsPreflightResponse("GET, HEAD, OPTIONS");
|
|
1104
|
-
return withCors(await handle(request));
|
|
1081
|
+
return withCors(await handle(request, recorder));
|
|
1105
1082
|
};
|
|
1106
1083
|
}
|
|
1107
1084
|
|
|
@@ -1119,11 +1096,11 @@ function isEmptyArgs(value) {
|
|
|
1119
1096
|
return false;
|
|
1120
1097
|
return Object.keys(value).length === 0;
|
|
1121
1098
|
}
|
|
1122
|
-
function createInvokeToolHandler(mcp, options = {}
|
|
1099
|
+
function createInvokeToolHandler(mcp, options = {}) {
|
|
1123
1100
|
assertRestResourceBinding(mcp, options);
|
|
1124
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
1125
|
-
const handle = async (request, toolName) => {
|
|
1126
|
-
const authResult = await authorizer.authorize(request);
|
|
1101
|
+
const authorizer = createRequestAuthorizer(mcp, options);
|
|
1102
|
+
const handle = async (request, toolName, recorder) => {
|
|
1103
|
+
const authResult = await authorizer.authorize(request, recorder);
|
|
1127
1104
|
if (!authResult.ok)
|
|
1128
1105
|
return authResult.response;
|
|
1129
1106
|
if (request.method !== "POST")
|
|
@@ -1179,7 +1156,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1179
1156
|
try {
|
|
1180
1157
|
result = await tool.handler(args, new ToolContext(authResult.auth));
|
|
1181
1158
|
} catch {
|
|
1182
|
-
|
|
1159
|
+
await recorder.emit({
|
|
1183
1160
|
tool: tool.name,
|
|
1184
1161
|
method: "tools/call",
|
|
1185
1162
|
outcome: "handler_error",
|
|
@@ -1191,7 +1168,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1191
1168
|
});
|
|
1192
1169
|
}
|
|
1193
1170
|
if (result == null) {
|
|
1194
|
-
|
|
1171
|
+
await recorder.emit({
|
|
1195
1172
|
tool: tool.name,
|
|
1196
1173
|
method: "tools/call",
|
|
1197
1174
|
outcome: "handler_error",
|
|
@@ -1202,7 +1179,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1202
1179
|
headers: JSON_HEADERS
|
|
1203
1180
|
});
|
|
1204
1181
|
}
|
|
1205
|
-
|
|
1182
|
+
await recorder.emit({
|
|
1206
1183
|
tool: tool.name,
|
|
1207
1184
|
method: "tools/call",
|
|
1208
1185
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -1214,10 +1191,10 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1214
1191
|
isError: result.isError
|
|
1215
1192
|
});
|
|
1216
1193
|
};
|
|
1217
|
-
return async (request, toolName) => {
|
|
1194
|
+
return async (request, toolName, recorder = createNoopRecorder()) => {
|
|
1218
1195
|
if (request.method === "OPTIONS")
|
|
1219
1196
|
return corsPreflightResponse("POST, OPTIONS");
|
|
1220
|
-
return withCors(await handle(request, toolName));
|
|
1197
|
+
return withCors(await handle(request, toolName, recorder));
|
|
1221
1198
|
};
|
|
1222
1199
|
}
|
|
1223
1200
|
|
|
@@ -1267,26 +1244,26 @@ function createSupabaseHandler(mcp, options = {}) {
|
|
|
1267
1244
|
const servesOwnPrm = !(mcp.auth?.type === "oauth" && mcp.auth.protectedResourceMetadataUrl !== void 0);
|
|
1268
1245
|
const metadataPath = resourcePath === void 0 || !servesOwnPrm ? void 0 : `${trimTrailingSlash(resourcePath)}${OAUTH_PROTECTED_RESOURCE_METADATA_PATH}`;
|
|
1269
1246
|
const runtimeOptions = resourcePath === void 0 ? {} : { resourcePath, ...metadataPath ? { metadataPath } : {} };
|
|
1270
|
-
const
|
|
1271
|
-
const
|
|
1272
|
-
const
|
|
1273
|
-
const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions, recorder);
|
|
1247
|
+
const mcpHandler = createMcpProtocolHandler(mcp, runtimeOptions);
|
|
1248
|
+
const listToolsHandler = createListToolsHandler(mcp, runtimeOptions);
|
|
1249
|
+
const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions);
|
|
1274
1250
|
const metadataHandler = createOAuthProtectedResourceMetadataHandler(mcp, runtimeOptions);
|
|
1275
1251
|
return async (request) => {
|
|
1276
1252
|
const req = applyForwardedOrigin(request, {
|
|
1277
1253
|
trustForwardedProto: true,
|
|
1278
1254
|
trustForwardedHost: options.trustForwardedHost
|
|
1279
1255
|
});
|
|
1256
|
+
const recorder = createRecorderForRuntime(mcp, { stack: "supabase" });
|
|
1280
1257
|
const target = dispatchFor(new URL(req.url).pathname);
|
|
1281
1258
|
switch (target.kind) {
|
|
1282
1259
|
case "metadata":
|
|
1283
1260
|
return metadataHandler(req);
|
|
1284
1261
|
case "list-tools":
|
|
1285
|
-
return listToolsHandler(req);
|
|
1262
|
+
return listToolsHandler(req, recorder);
|
|
1286
1263
|
case "invoke-tool":
|
|
1287
|
-
return invokeToolHandler(req, target.toolName);
|
|
1264
|
+
return invokeToolHandler(req, target.toolName, recorder);
|
|
1288
1265
|
case "mcp":
|
|
1289
|
-
return mcpHandler(req);
|
|
1266
|
+
return mcpHandler(req, recorder);
|
|
1290
1267
|
}
|
|
1291
1268
|
};
|
|
1292
1269
|
}
|
|
@@ -3,24 +3,24 @@ 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
17
|
import {
|
|
18
18
|
assertResourcePathShape,
|
|
19
19
|
createRecorderForRuntime
|
|
20
|
-
} from "../../chunk-
|
|
20
|
+
} from "../../chunk-5FX6IIQ6.js";
|
|
21
21
|
import {
|
|
22
22
|
trimTrailingSlash
|
|
23
|
-
} from "../../chunk-
|
|
23
|
+
} from "../../chunk-H37EB22A.js";
|
|
24
24
|
import {
|
|
25
25
|
OAUTH_PROTECTED_RESOURCE_METADATA_PATH
|
|
26
26
|
} from "../../chunk-6DXGZZA4.js";
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
FUNCTIONS_MOUNT_PREFIX,
|
|
29
29
|
assertFunctionName
|
|
30
30
|
} from "../../chunk-XQWJN6DC.js";
|
|
31
|
-
import "../../chunk-
|
|
31
|
+
import "../../chunk-CDZ7MBXC.js";
|
|
32
32
|
|
|
33
33
|
// src/stacks/supabase/handler.ts
|
|
34
34
|
function deriveResourcePath(options) {
|
|
@@ -66,26 +66,26 @@ function createSupabaseHandler(mcp, options = {}) {
|
|
|
66
66
|
const servesOwnPrm = !(mcp.auth?.type === "oauth" && mcp.auth.protectedResourceMetadataUrl !== void 0);
|
|
67
67
|
const metadataPath = resourcePath === void 0 || !servesOwnPrm ? void 0 : `${trimTrailingSlash(resourcePath)}${OAUTH_PROTECTED_RESOURCE_METADATA_PATH}`;
|
|
68
68
|
const runtimeOptions = resourcePath === void 0 ? {} : { resourcePath, ...metadataPath ? { metadataPath } : {} };
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions, recorder);
|
|
69
|
+
const mcpHandler = createMcpProtocolHandler(mcp, runtimeOptions);
|
|
70
|
+
const listToolsHandler = createListToolsHandler(mcp, runtimeOptions);
|
|
71
|
+
const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions);
|
|
73
72
|
const metadataHandler = createOAuthProtectedResourceMetadataHandler(mcp, runtimeOptions);
|
|
74
73
|
return async (request) => {
|
|
75
74
|
const req = applyForwardedOrigin(request, {
|
|
76
75
|
trustForwardedProto: true,
|
|
77
76
|
trustForwardedHost: options.trustForwardedHost
|
|
78
77
|
});
|
|
78
|
+
const recorder = createRecorderForRuntime(mcp, { stack: "supabase" });
|
|
79
79
|
const target = dispatchFor(new URL(req.url).pathname);
|
|
80
80
|
switch (target.kind) {
|
|
81
81
|
case "metadata":
|
|
82
82
|
return metadataHandler(req);
|
|
83
83
|
case "list-tools":
|
|
84
|
-
return listToolsHandler(req);
|
|
84
|
+
return listToolsHandler(req, recorder);
|
|
85
85
|
case "invoke-tool":
|
|
86
|
-
return invokeToolHandler(req, target.toolName);
|
|
86
|
+
return invokeToolHandler(req, target.toolName, recorder);
|
|
87
87
|
case "mcp":
|
|
88
|
-
return mcpHandler(req);
|
|
88
|
+
return mcpHandler(req, recorder);
|
|
89
89
|
}
|
|
90
90
|
};
|
|
91
91
|
}
|