@lovable.dev/mcp-js 0.16.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-W2EZWPJ5.js → chunk-57ZH2WW5.js} +11 -12
- package/dist/{chunk-H6BKTNJY.js → chunk-5FX6IIQ6.js} +129 -105
- package/dist/{chunk-SKSKC747.js → chunk-CDZ7MBXC.js} +1 -1
- package/dist/{chunk-EZQVUNXB.js → chunk-CLXKPZZE.js} +13 -14
- package/dist/{chunk-DCWYK4CA.js → chunk-H37EB22A.js} +13 -9
- package/dist/{chunk-I5CAQUQI.js → chunk-OMWXY6WY.js} +8 -8
- package/dist/{chunk-T24Z753F.js → chunk-UEOBGHXF.js} +2 -2
- package/dist/cli/extract-manifest.cjs +4 -3
- package/dist/cli/extract-manifest.js +4 -4
- package/dist/index.cjs +4 -9
- 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 +19 -225
- 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.cjs +3 -2
- 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 +24 -230
- 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 +196 -169
- 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 +205 -177
- 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,21 +95,58 @@ 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) {
|
|
101
127
|
return typeof value === "string" && value in LEVEL_RANK;
|
|
102
128
|
}
|
|
129
|
+
var LOG_LEVEL_ENV_VAR = "LOVABLE_MCP_LOG_LEVEL";
|
|
103
130
|
function readEnvLevel() {
|
|
104
131
|
try {
|
|
105
|
-
const raw = typeof process !== "undefined" ? process.env?.[
|
|
132
|
+
const raw = typeof process !== "undefined" ? process.env?.[LOG_LEVEL_ENV_VAR] : void 0;
|
|
106
133
|
const normalized = raw?.trim().toLowerCase();
|
|
107
134
|
return isLogLevel(normalized) ? normalized : void 0;
|
|
108
135
|
} catch {
|
|
109
136
|
return void 0;
|
|
110
137
|
}
|
|
111
138
|
}
|
|
112
|
-
var currentLevel = readEnvLevel() ?? "
|
|
139
|
+
var currentLevel = readEnvLevel() ?? "debug";
|
|
140
|
+
function setLogLevel(level) {
|
|
141
|
+
currentLevel = level;
|
|
142
|
+
}
|
|
143
|
+
function parseLogLevel(raw) {
|
|
144
|
+
const normalized = raw?.trim().toLowerCase();
|
|
145
|
+
return isLogLevel(normalized) ? normalized : void 0;
|
|
146
|
+
}
|
|
147
|
+
function applyLogLevelFromEnv(raw) {
|
|
148
|
+
setLogLevel(parseLogLevel(raw) ?? "debug");
|
|
149
|
+
}
|
|
113
150
|
function enabled(level) {
|
|
114
151
|
return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
|
|
115
152
|
}
|
|
@@ -136,40 +173,8 @@ function describeError(err) {
|
|
|
136
173
|
return { value: String(err) };
|
|
137
174
|
}
|
|
138
175
|
|
|
139
|
-
// src/metrics/config.ts
|
|
140
|
-
var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
|
|
141
|
-
var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
|
|
142
|
-
var METRICS_SAMPLE_RATE = 1;
|
|
143
|
-
var METRICS_FLUSH_INTERVAL_MS = 5e3;
|
|
144
|
-
var METRICS_MAX_BATCH_SIZE = 50;
|
|
145
|
-
function assertMetricsEndpoint(endpoint) {
|
|
146
|
-
let url;
|
|
147
|
-
try {
|
|
148
|
-
url = new URL(endpoint);
|
|
149
|
-
} catch {
|
|
150
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
|
|
151
|
-
}
|
|
152
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
153
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
function resolveMetricsConfig(config = true) {
|
|
157
|
-
const options = typeof config === "boolean" ? { enabled: config } : config;
|
|
158
|
-
const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
|
|
159
|
-
assertMetricsEndpoint(endpoint);
|
|
160
|
-
return Object.freeze({
|
|
161
|
-
enabled: options.enabled ?? true,
|
|
162
|
-
endpoint,
|
|
163
|
-
headers: options.headers ?? {},
|
|
164
|
-
apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
|
|
165
|
-
sampleRate: METRICS_SAMPLE_RATE,
|
|
166
|
-
flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
|
|
167
|
-
maxBatchSize: METRICS_MAX_BATCH_SIZE
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
176
|
// package.json
|
|
172
|
-
var version = "0.
|
|
177
|
+
var version = "0.19.0";
|
|
173
178
|
|
|
174
179
|
// src/metrics/otlp.ts
|
|
175
180
|
var SCOPE_NAME = "@lovable.dev/mcp-js";
|
|
@@ -194,6 +199,7 @@ function toLogRecord(rec) {
|
|
|
194
199
|
attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
|
|
195
200
|
if (rec.resBytes !== void 0)
|
|
196
201
|
attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
|
|
202
|
+
attributes.push(strAttr("mcp.stack", rec.stack));
|
|
197
203
|
return {
|
|
198
204
|
timeUnixNano: rec.timeUnixNano,
|
|
199
205
|
observedTimeUnixNano: rec.timeUnixNano,
|
|
@@ -230,127 +236,148 @@ function nowUnixNano() {
|
|
|
230
236
|
return `${Date.now()}000000`;
|
|
231
237
|
}
|
|
232
238
|
|
|
233
|
-
// src/metrics/
|
|
239
|
+
// src/metrics/impl/base.ts
|
|
234
240
|
function nowMs() {
|
|
235
241
|
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
236
242
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
recorder.record(ev);
|
|
240
|
-
}
|
|
241
|
-
var NOOP_RECORDER = {
|
|
242
|
-
record() {
|
|
243
|
-
},
|
|
244
|
-
async flush() {
|
|
245
|
-
}
|
|
246
|
-
};
|
|
243
|
+
var NOOP_RECORDER = { async emit() {
|
|
244
|
+
} };
|
|
247
245
|
function createNoopRecorder() {
|
|
248
246
|
return NOOP_RECORDER;
|
|
249
247
|
}
|
|
250
|
-
function
|
|
248
|
+
function readProcessEnv(name) {
|
|
251
249
|
try {
|
|
252
|
-
|
|
253
|
-
const value = denoEnv?.get?.(name);
|
|
254
|
-
if (value)
|
|
255
|
-
return value;
|
|
256
|
-
} catch {
|
|
257
|
-
}
|
|
258
|
-
try {
|
|
259
|
-
if (typeof process !== "undefined") {
|
|
260
|
-
const value = process.env?.[name];
|
|
261
|
-
if (value)
|
|
262
|
-
return value;
|
|
263
|
-
}
|
|
250
|
+
return typeof process !== "undefined" ? process.env?.[name] || void 0 : void 0;
|
|
264
251
|
} catch {
|
|
252
|
+
return void 0;
|
|
265
253
|
}
|
|
266
|
-
return void 0;
|
|
267
254
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
|
|
282
|
-
const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
|
|
283
|
-
if (usesLovableEndpoint && !apiKey) {
|
|
284
|
-
log.warn("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar, endpoint: config.endpoint });
|
|
285
|
-
return NOOP_RECORDER;
|
|
286
|
-
}
|
|
287
|
-
const waitUntil = deps.waitUntil ?? probeWaitUntil();
|
|
288
|
-
const buffer = [];
|
|
289
|
-
let timer;
|
|
290
|
-
const schedule = (p) => {
|
|
291
|
-
if (waitUntil) {
|
|
292
|
-
try {
|
|
293
|
-
waitUntil(p);
|
|
294
|
-
return;
|
|
295
|
-
} catch (err) {
|
|
296
|
-
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;
|
|
297
267
|
}
|
|
298
268
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
};
|
|
302
|
-
const headers = {};
|
|
303
|
-
if (!usesLovableEndpoint) {
|
|
304
|
-
for (const [key, value] of Object.entries(config.headers)) {
|
|
305
|
-
if (key.toLowerCase() !== "content-type")
|
|
306
|
-
headers[key] = value;
|
|
307
|
-
}
|
|
269
|
+
headers["content-type"] = "application/json";
|
|
270
|
+
this.baseHeaders = headers;
|
|
308
271
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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)
|
|
314
287
|
return;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
|
|
288
|
+
if (!this.doFetch) {
|
|
289
|
+
log.warn("metrics.disabled_no_fetch", { endpoint: this.config.endpoint });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const headers = { ...this.baseHeaders };
|
|
293
|
+
if (this.usesLovableEndpoint) {
|
|
294
|
+
const key = await this.resolveApiKey();
|
|
295
|
+
if (!key) {
|
|
296
|
+
log.warn("metrics.disabled_no_api_key", { envVar: this.config.apiKeyEnvVar, endpoint: this.config.endpoint });
|
|
297
|
+
return;
|
|
326
298
|
}
|
|
299
|
+
headers.authorization = `Bearer ${key}`;
|
|
300
|
+
}
|
|
301
|
+
const body = buildLogsPayload([{ ...ev, timeUnixNano: nowUnixNano(), stack: this.stack }], this.server);
|
|
302
|
+
try {
|
|
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 });
|
|
327
306
|
} catch (err) {
|
|
328
|
-
log.warn("metrics.
|
|
307
|
+
log.warn("metrics.failed", { ...describeError(err), endpoint: this.config.endpoint });
|
|
329
308
|
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
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)
|
|
333
320
|
return;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
+
}
|
|
348
366
|
}
|
|
349
|
-
|
|
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) {
|
|
350
376
|
const config = resolveMetricsConfig(mcp.metrics);
|
|
377
|
+
const server = { name: mcp.name, version: mcp.version };
|
|
351
378
|
if (!config.enabled)
|
|
352
379
|
return createNoopRecorder();
|
|
353
|
-
return
|
|
380
|
+
return opts.stack === "tanstack" ? new TanStackMetricRecorder(config, server) : new SupabaseMetricRecorder(config, server);
|
|
354
381
|
}
|
|
355
382
|
|
|
356
383
|
// src/protocols/mcp/protocol.ts
|
|
@@ -749,10 +776,10 @@ function assertRequiredScopes(auth, context) {
|
|
|
749
776
|
throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
|
|
750
777
|
}
|
|
751
778
|
}
|
|
752
|
-
function createRequestAuthorizer(mcp, options = {}
|
|
779
|
+
function createRequestAuthorizer(mcp, options = {}) {
|
|
753
780
|
const runtime = getOAuthRuntime(mcp, options);
|
|
754
781
|
return {
|
|
755
|
-
async authorize(request) {
|
|
782
|
+
async authorize(request, recorder) {
|
|
756
783
|
if (runtime.kind === "unconfigured")
|
|
757
784
|
return { ok: true };
|
|
758
785
|
const startedAt = nowMs();
|
|
@@ -768,7 +795,7 @@ function createRequestAuthorizer(mcp, options = {}, recorder) {
|
|
|
768
795
|
} catch (err) {
|
|
769
796
|
if (err instanceof OAuthConfigurationError) {
|
|
770
797
|
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
771
|
-
recorder?.
|
|
798
|
+
await recorder?.emit({
|
|
772
799
|
tool: null,
|
|
773
800
|
method: "authorize",
|
|
774
801
|
outcome: "auth_config_error",
|
|
@@ -879,7 +906,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
879
906
|
try {
|
|
880
907
|
result = await tool.handler(args, new ToolContext(auth));
|
|
881
908
|
} catch {
|
|
882
|
-
|
|
909
|
+
await recorder.emit({
|
|
883
910
|
tool: tool.name,
|
|
884
911
|
method: "tools/call",
|
|
885
912
|
outcome: "handler_error",
|
|
@@ -888,7 +915,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
888
915
|
return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
|
|
889
916
|
}
|
|
890
917
|
if (result == null) {
|
|
891
|
-
|
|
918
|
+
await recorder.emit({
|
|
892
919
|
tool: tool.name,
|
|
893
920
|
method: "tools/call",
|
|
894
921
|
outcome: "handler_error",
|
|
@@ -896,7 +923,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
896
923
|
});
|
|
897
924
|
return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
|
|
898
925
|
}
|
|
899
|
-
|
|
926
|
+
await recorder.emit({
|
|
900
927
|
tool: tool.name,
|
|
901
928
|
method: "tools/call",
|
|
902
929
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -905,10 +932,10 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
905
932
|
return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
|
|
906
933
|
};
|
|
907
934
|
}
|
|
908
|
-
function createMcpProtocolHandler(mcp, options = {}
|
|
909
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
910
|
-
const handle = async (request) => {
|
|
911
|
-
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);
|
|
912
939
|
if (!authResult.ok)
|
|
913
940
|
return authResult.response;
|
|
914
941
|
try {
|
|
@@ -935,7 +962,7 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
|
|
|
935
962
|
await server.connect(transport);
|
|
936
963
|
return await transport.handleRequest(request);
|
|
937
964
|
} catch (err) {
|
|
938
|
-
recorder.
|
|
965
|
+
await recorder.emit({ tool: null, method: "transport", outcome: "transport_error", durationMs: 0 });
|
|
939
966
|
log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
|
|
940
967
|
return Response.json(
|
|
941
968
|
{ jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
|
|
@@ -943,10 +970,10 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
|
|
|
943
970
|
);
|
|
944
971
|
}
|
|
945
972
|
};
|
|
946
|
-
return async (request) => {
|
|
973
|
+
return async (request, recorder = createNoopRecorder()) => {
|
|
947
974
|
if (request.method === "OPTIONS")
|
|
948
975
|
return corsPreflightResponse("GET, POST, DELETE, OPTIONS");
|
|
949
|
-
return withCors(await handle(request));
|
|
976
|
+
return withCors(await handle(request, recorder));
|
|
950
977
|
};
|
|
951
978
|
}
|
|
952
979
|
|
|
@@ -1036,11 +1063,11 @@ function buildMcpListing(mcp) {
|
|
|
1036
1063
|
}))
|
|
1037
1064
|
};
|
|
1038
1065
|
}
|
|
1039
|
-
function createListToolsHandler(mcp, options = {}
|
|
1066
|
+
function createListToolsHandler(mcp, options = {}) {
|
|
1040
1067
|
assertRestResourceBinding(mcp, options);
|
|
1041
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
1042
|
-
const handle = async (request) => {
|
|
1043
|
-
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);
|
|
1044
1071
|
if (!authResult.ok)
|
|
1045
1072
|
return authResult.response;
|
|
1046
1073
|
if (request.method !== "GET" && request.method !== "HEAD")
|
|
@@ -1048,10 +1075,10 @@ function createListToolsHandler(mcp, options = {}, recorder = createRecorderForR
|
|
|
1048
1075
|
const response = Response.json(buildMcpListing(mcp));
|
|
1049
1076
|
return request.method === "HEAD" ? headResponse(response) : response;
|
|
1050
1077
|
};
|
|
1051
|
-
return async (request) => {
|
|
1078
|
+
return async (request, recorder = createNoopRecorder()) => {
|
|
1052
1079
|
if (request.method === "OPTIONS")
|
|
1053
1080
|
return corsPreflightResponse("GET, HEAD, OPTIONS");
|
|
1054
|
-
return withCors(await handle(request));
|
|
1081
|
+
return withCors(await handle(request, recorder));
|
|
1055
1082
|
};
|
|
1056
1083
|
}
|
|
1057
1084
|
|
|
@@ -1069,11 +1096,11 @@ function isEmptyArgs(value) {
|
|
|
1069
1096
|
return false;
|
|
1070
1097
|
return Object.keys(value).length === 0;
|
|
1071
1098
|
}
|
|
1072
|
-
function createInvokeToolHandler(mcp, options = {}
|
|
1099
|
+
function createInvokeToolHandler(mcp, options = {}) {
|
|
1073
1100
|
assertRestResourceBinding(mcp, options);
|
|
1074
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
1075
|
-
const handle = async (request, toolName) => {
|
|
1076
|
-
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);
|
|
1077
1104
|
if (!authResult.ok)
|
|
1078
1105
|
return authResult.response;
|
|
1079
1106
|
if (request.method !== "POST")
|
|
@@ -1129,7 +1156,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1129
1156
|
try {
|
|
1130
1157
|
result = await tool.handler(args, new ToolContext(authResult.auth));
|
|
1131
1158
|
} catch {
|
|
1132
|
-
|
|
1159
|
+
await recorder.emit({
|
|
1133
1160
|
tool: tool.name,
|
|
1134
1161
|
method: "tools/call",
|
|
1135
1162
|
outcome: "handler_error",
|
|
@@ -1141,7 +1168,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1141
1168
|
});
|
|
1142
1169
|
}
|
|
1143
1170
|
if (result == null) {
|
|
1144
|
-
|
|
1171
|
+
await recorder.emit({
|
|
1145
1172
|
tool: tool.name,
|
|
1146
1173
|
method: "tools/call",
|
|
1147
1174
|
outcome: "handler_error",
|
|
@@ -1152,7 +1179,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1152
1179
|
headers: JSON_HEADERS
|
|
1153
1180
|
});
|
|
1154
1181
|
}
|
|
1155
|
-
|
|
1182
|
+
await recorder.emit({
|
|
1156
1183
|
tool: tool.name,
|
|
1157
1184
|
method: "tools/call",
|
|
1158
1185
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -1164,10 +1191,10 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1164
1191
|
isError: result.isError
|
|
1165
1192
|
});
|
|
1166
1193
|
};
|
|
1167
|
-
return async (request, toolName) => {
|
|
1194
|
+
return async (request, toolName, recorder = createNoopRecorder()) => {
|
|
1168
1195
|
if (request.method === "OPTIONS")
|
|
1169
1196
|
return corsPreflightResponse("POST, OPTIONS");
|
|
1170
|
-
return withCors(await handle(request, toolName));
|
|
1197
|
+
return withCors(await handle(request, toolName, recorder));
|
|
1171
1198
|
};
|
|
1172
1199
|
}
|
|
1173
1200
|
|
|
@@ -1217,26 +1244,26 @@ function createSupabaseHandler(mcp, options = {}) {
|
|
|
1217
1244
|
const servesOwnPrm = !(mcp.auth?.type === "oauth" && mcp.auth.protectedResourceMetadataUrl !== void 0);
|
|
1218
1245
|
const metadataPath = resourcePath === void 0 || !servesOwnPrm ? void 0 : `${trimTrailingSlash(resourcePath)}${OAUTH_PROTECTED_RESOURCE_METADATA_PATH}`;
|
|
1219
1246
|
const runtimeOptions = resourcePath === void 0 ? {} : { resourcePath, ...metadataPath ? { metadataPath } : {} };
|
|
1220
|
-
const
|
|
1221
|
-
const
|
|
1222
|
-
const
|
|
1223
|
-
const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions, recorder);
|
|
1247
|
+
const mcpHandler = createMcpProtocolHandler(mcp, runtimeOptions);
|
|
1248
|
+
const listToolsHandler = createListToolsHandler(mcp, runtimeOptions);
|
|
1249
|
+
const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions);
|
|
1224
1250
|
const metadataHandler = createOAuthProtectedResourceMetadataHandler(mcp, runtimeOptions);
|
|
1225
1251
|
return async (request) => {
|
|
1226
1252
|
const req = applyForwardedOrigin(request, {
|
|
1227
1253
|
trustForwardedProto: true,
|
|
1228
1254
|
trustForwardedHost: options.trustForwardedHost
|
|
1229
1255
|
});
|
|
1256
|
+
const recorder = createRecorderForRuntime(mcp, { stack: "supabase" });
|
|
1230
1257
|
const target = dispatchFor(new URL(req.url).pathname);
|
|
1231
1258
|
switch (target.kind) {
|
|
1232
1259
|
case "metadata":
|
|
1233
1260
|
return metadataHandler(req);
|
|
1234
1261
|
case "list-tools":
|
|
1235
|
-
return listToolsHandler(req);
|
|
1262
|
+
return listToolsHandler(req, recorder);
|
|
1236
1263
|
case "invoke-tool":
|
|
1237
|
-
return invokeToolHandler(req, target.toolName);
|
|
1264
|
+
return invokeToolHandler(req, target.toolName, recorder);
|
|
1238
1265
|
case "mcp":
|
|
1239
|
-
return mcpHandler(req);
|
|
1266
|
+
return mcpHandler(req, recorder);
|
|
1240
1267
|
}
|
|
1241
1268
|
};
|
|
1242
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
|
}
|