@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
|
@@ -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
|
|
|
@@ -71,16 +81,27 @@ var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
|
71
81
|
function isLogLevel(value) {
|
|
72
82
|
return typeof value === "string" && value in LEVEL_RANK;
|
|
73
83
|
}
|
|
84
|
+
var LOG_LEVEL_ENV_VAR = "LOVABLE_MCP_LOG_LEVEL";
|
|
74
85
|
function readEnvLevel() {
|
|
75
86
|
try {
|
|
76
|
-
const raw = typeof process !== "undefined" ? process.env?.[
|
|
87
|
+
const raw = typeof process !== "undefined" ? process.env?.[LOG_LEVEL_ENV_VAR] : void 0;
|
|
77
88
|
const normalized = raw?.trim().toLowerCase();
|
|
78
89
|
return isLogLevel(normalized) ? normalized : void 0;
|
|
79
90
|
} catch {
|
|
80
91
|
return void 0;
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
|
-
var currentLevel = readEnvLevel() ?? "
|
|
94
|
+
var currentLevel = readEnvLevel() ?? "debug";
|
|
95
|
+
function setLogLevel(level) {
|
|
96
|
+
currentLevel = level;
|
|
97
|
+
}
|
|
98
|
+
function parseLogLevel(raw) {
|
|
99
|
+
const normalized = raw?.trim().toLowerCase();
|
|
100
|
+
return isLogLevel(normalized) ? normalized : void 0;
|
|
101
|
+
}
|
|
102
|
+
function applyLogLevelFromEnv(raw) {
|
|
103
|
+
setLogLevel(parseLogLevel(raw) ?? "debug");
|
|
104
|
+
}
|
|
84
105
|
function enabled(level) {
|
|
85
106
|
return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
|
|
86
107
|
}
|
|
@@ -107,40 +128,8 @@ function describeError(err) {
|
|
|
107
128
|
return { value: String(err) };
|
|
108
129
|
}
|
|
109
130
|
|
|
110
|
-
// src/metrics/config.ts
|
|
111
|
-
var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
|
|
112
|
-
var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
|
|
113
|
-
var METRICS_SAMPLE_RATE = 1;
|
|
114
|
-
var METRICS_FLUSH_INTERVAL_MS = 5e3;
|
|
115
|
-
var METRICS_MAX_BATCH_SIZE = 50;
|
|
116
|
-
function assertMetricsEndpoint(endpoint) {
|
|
117
|
-
let url;
|
|
118
|
-
try {
|
|
119
|
-
url = new URL(endpoint);
|
|
120
|
-
} catch {
|
|
121
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
|
|
122
|
-
}
|
|
123
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
124
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function resolveMetricsConfig(config = true) {
|
|
128
|
-
const options = typeof config === "boolean" ? { enabled: config } : config;
|
|
129
|
-
const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
|
|
130
|
-
assertMetricsEndpoint(endpoint);
|
|
131
|
-
return Object.freeze({
|
|
132
|
-
enabled: options.enabled ?? true,
|
|
133
|
-
endpoint,
|
|
134
|
-
headers: options.headers ?? {},
|
|
135
|
-
apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
|
|
136
|
-
sampleRate: METRICS_SAMPLE_RATE,
|
|
137
|
-
flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
|
|
138
|
-
maxBatchSize: METRICS_MAX_BATCH_SIZE
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
131
|
// package.json
|
|
143
|
-
var version = "0.
|
|
132
|
+
var version = "0.19.0";
|
|
144
133
|
|
|
145
134
|
// src/metrics/otlp.ts
|
|
146
135
|
var SCOPE_NAME = "@lovable.dev/mcp-js";
|
|
@@ -165,6 +154,7 @@ function toLogRecord(rec) {
|
|
|
165
154
|
attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
|
|
166
155
|
if (rec.resBytes !== void 0)
|
|
167
156
|
attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
|
|
157
|
+
attributes.push(strAttr("mcp.stack", rec.stack));
|
|
168
158
|
return {
|
|
169
159
|
timeUnixNano: rec.timeUnixNano,
|
|
170
160
|
observedTimeUnixNano: rec.timeUnixNano,
|
|
@@ -201,127 +191,164 @@ function nowUnixNano() {
|
|
|
201
191
|
return `${Date.now()}000000`;
|
|
202
192
|
}
|
|
203
193
|
|
|
204
|
-
// src/metrics/
|
|
194
|
+
// src/metrics/impl/base.ts
|
|
205
195
|
function nowMs() {
|
|
206
196
|
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
207
197
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
recorder.record(ev);
|
|
211
|
-
}
|
|
212
|
-
var NOOP_RECORDER = {
|
|
213
|
-
record() {
|
|
214
|
-
},
|
|
215
|
-
async flush() {
|
|
216
|
-
}
|
|
217
|
-
};
|
|
198
|
+
var NOOP_RECORDER = { async emit() {
|
|
199
|
+
} };
|
|
218
200
|
function createNoopRecorder() {
|
|
219
201
|
return NOOP_RECORDER;
|
|
220
202
|
}
|
|
221
|
-
function
|
|
222
|
-
try {
|
|
223
|
-
const denoEnv = globalThis.Deno?.env;
|
|
224
|
-
const value = denoEnv?.get?.(name);
|
|
225
|
-
if (value)
|
|
226
|
-
return value;
|
|
227
|
-
} catch {
|
|
228
|
-
}
|
|
203
|
+
function readProcessEnv(name) {
|
|
229
204
|
try {
|
|
230
|
-
|
|
231
|
-
const value = process.env?.[name];
|
|
232
|
-
if (value)
|
|
233
|
-
return value;
|
|
234
|
-
}
|
|
205
|
+
return typeof process !== "undefined" ? process.env?.[name] || void 0 : void 0;
|
|
235
206
|
} catch {
|
|
207
|
+
return void 0;
|
|
236
208
|
}
|
|
237
|
-
return void 0;
|
|
238
209
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
|
|
253
|
-
const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
|
|
254
|
-
if (usesLovableEndpoint && !apiKey) {
|
|
255
|
-
log.warn("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar, endpoint: config.endpoint });
|
|
256
|
-
return NOOP_RECORDER;
|
|
257
|
-
}
|
|
258
|
-
const waitUntil = deps.waitUntil ?? probeWaitUntil();
|
|
259
|
-
const buffer = [];
|
|
260
|
-
let timer;
|
|
261
|
-
const schedule = (p) => {
|
|
262
|
-
if (waitUntil) {
|
|
263
|
-
try {
|
|
264
|
-
waitUntil(p);
|
|
265
|
-
return;
|
|
266
|
-
} catch (err) {
|
|
267
|
-
log.warn("metrics.wait_until_failed", describeError(err));
|
|
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;
|
|
268
222
|
}
|
|
269
223
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
};
|
|
273
|
-
const headers = {};
|
|
274
|
-
if (!usesLovableEndpoint) {
|
|
275
|
-
for (const [key, value] of Object.entries(config.headers)) {
|
|
276
|
-
if (key.toLowerCase() !== "content-type")
|
|
277
|
-
headers[key] = value;
|
|
278
|
-
}
|
|
224
|
+
headers["content-type"] = "application/json";
|
|
225
|
+
this.baseHeaders = headers;
|
|
279
226
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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)
|
|
285
242
|
return;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
|
|
243
|
+
if (!this.doFetch) {
|
|
244
|
+
log.warn("metrics.disabled_no_fetch", { endpoint: this.config.endpoint });
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const headers = { ...this.baseHeaders };
|
|
248
|
+
if (this.usesLovableEndpoint) {
|
|
249
|
+
const key = await this.resolveApiKey();
|
|
250
|
+
if (!key) {
|
|
251
|
+
log.warn("metrics.disabled_no_api_key", { envVar: this.config.apiKeyEnvVar, endpoint: this.config.endpoint });
|
|
252
|
+
return;
|
|
297
253
|
}
|
|
254
|
+
headers.authorization = `Bearer ${key}`;
|
|
255
|
+
}
|
|
256
|
+
const body = buildLogsPayload([{ ...ev, timeUnixNano: nowUnixNano(), stack: this.stack }], this.server);
|
|
257
|
+
try {
|
|
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 });
|
|
298
261
|
} catch (err) {
|
|
299
|
-
log.warn("metrics.
|
|
262
|
+
log.warn("metrics.failed", { ...describeError(err), endpoint: this.config.endpoint });
|
|
300
263
|
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
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)
|
|
304
275
|
return;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
+
}
|
|
302
|
+
}
|
|
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
|
+
}
|
|
319
321
|
}
|
|
320
|
-
|
|
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) {
|
|
321
331
|
const config = resolveMetricsConfig(mcp.metrics);
|
|
332
|
+
const server = { name: mcp.name, version: mcp.version };
|
|
322
333
|
if (!config.enabled)
|
|
323
334
|
return createNoopRecorder();
|
|
324
|
-
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
|
+
});
|
|
325
352
|
}
|
|
326
353
|
|
|
327
354
|
// src/core/promise.ts
|
|
@@ -752,10 +779,10 @@ function assertRequiredScopes(auth, context) {
|
|
|
752
779
|
throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
|
|
753
780
|
}
|
|
754
781
|
}
|
|
755
|
-
function createRequestAuthorizer(mcp, options = {}
|
|
782
|
+
function createRequestAuthorizer(mcp, options = {}) {
|
|
756
783
|
const runtime = getOAuthRuntime(mcp, options);
|
|
757
784
|
return {
|
|
758
|
-
async authorize(request) {
|
|
785
|
+
async authorize(request, recorder) {
|
|
759
786
|
if (runtime.kind === "unconfigured")
|
|
760
787
|
return { ok: true };
|
|
761
788
|
const startedAt = nowMs();
|
|
@@ -771,7 +798,7 @@ function createRequestAuthorizer(mcp, options = {}, recorder) {
|
|
|
771
798
|
} catch (err) {
|
|
772
799
|
if (err instanceof OAuthConfigurationError) {
|
|
773
800
|
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
774
|
-
recorder?.
|
|
801
|
+
await recorder?.emit({
|
|
775
802
|
tool: null,
|
|
776
803
|
method: "authorize",
|
|
777
804
|
outcome: "auth_config_error",
|
|
@@ -882,7 +909,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
882
909
|
try {
|
|
883
910
|
result = await tool.handler(args, new ToolContext(auth));
|
|
884
911
|
} catch {
|
|
885
|
-
|
|
912
|
+
await recorder.emit({
|
|
886
913
|
tool: tool.name,
|
|
887
914
|
method: "tools/call",
|
|
888
915
|
outcome: "handler_error",
|
|
@@ -891,7 +918,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
891
918
|
return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
|
|
892
919
|
}
|
|
893
920
|
if (result == null) {
|
|
894
|
-
|
|
921
|
+
await recorder.emit({
|
|
895
922
|
tool: tool.name,
|
|
896
923
|
method: "tools/call",
|
|
897
924
|
outcome: "handler_error",
|
|
@@ -899,7 +926,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
899
926
|
});
|
|
900
927
|
return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
|
|
901
928
|
}
|
|
902
|
-
|
|
929
|
+
await recorder.emit({
|
|
903
930
|
tool: tool.name,
|
|
904
931
|
method: "tools/call",
|
|
905
932
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -908,10 +935,10 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
908
935
|
return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
|
|
909
936
|
};
|
|
910
937
|
}
|
|
911
|
-
function createMcpProtocolHandler(mcp, options = {}
|
|
912
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
913
|
-
const handle = async (request) => {
|
|
914
|
-
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);
|
|
915
942
|
if (!authResult.ok)
|
|
916
943
|
return authResult.response;
|
|
917
944
|
try {
|
|
@@ -938,7 +965,7 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
|
|
|
938
965
|
await server.connect(transport);
|
|
939
966
|
return await transport.handleRequest(request);
|
|
940
967
|
} catch (err) {
|
|
941
|
-
recorder.
|
|
968
|
+
await recorder.emit({ tool: null, method: "transport", outcome: "transport_error", durationMs: 0 });
|
|
942
969
|
log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
|
|
943
970
|
return Response.json(
|
|
944
971
|
{ jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
|
|
@@ -946,10 +973,10 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
|
|
|
946
973
|
);
|
|
947
974
|
}
|
|
948
975
|
};
|
|
949
|
-
return async (request) => {
|
|
976
|
+
return async (request, recorder = createNoopRecorder()) => {
|
|
950
977
|
if (request.method === "OPTIONS")
|
|
951
978
|
return corsPreflightResponse("GET, POST, DELETE, OPTIONS");
|
|
952
|
-
return withCors(await handle(request));
|
|
979
|
+
return withCors(await handle(request, recorder));
|
|
953
980
|
};
|
|
954
981
|
}
|
|
955
982
|
|
|
@@ -1039,11 +1066,11 @@ function buildMcpListing(mcp) {
|
|
|
1039
1066
|
}))
|
|
1040
1067
|
};
|
|
1041
1068
|
}
|
|
1042
|
-
function createListToolsHandler(mcp, options = {}
|
|
1069
|
+
function createListToolsHandler(mcp, options = {}) {
|
|
1043
1070
|
assertRestResourceBinding(mcp, options);
|
|
1044
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
1045
|
-
const handle = async (request) => {
|
|
1046
|
-
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);
|
|
1047
1074
|
if (!authResult.ok)
|
|
1048
1075
|
return authResult.response;
|
|
1049
1076
|
if (request.method !== "GET" && request.method !== "HEAD")
|
|
@@ -1051,10 +1078,10 @@ function createListToolsHandler(mcp, options = {}, recorder = createRecorderForR
|
|
|
1051
1078
|
const response = Response.json(buildMcpListing(mcp));
|
|
1052
1079
|
return request.method === "HEAD" ? headResponse(response) : response;
|
|
1053
1080
|
};
|
|
1054
|
-
return async (request) => {
|
|
1081
|
+
return async (request, recorder = createNoopRecorder()) => {
|
|
1055
1082
|
if (request.method === "OPTIONS")
|
|
1056
1083
|
return corsPreflightResponse("GET, HEAD, OPTIONS");
|
|
1057
|
-
return withCors(await handle(request));
|
|
1084
|
+
return withCors(await handle(request, recorder));
|
|
1058
1085
|
};
|
|
1059
1086
|
}
|
|
1060
1087
|
|
|
@@ -1072,11 +1099,11 @@ function isEmptyArgs(value) {
|
|
|
1072
1099
|
return false;
|
|
1073
1100
|
return Object.keys(value).length === 0;
|
|
1074
1101
|
}
|
|
1075
|
-
function createInvokeToolHandler(mcp, options = {}
|
|
1102
|
+
function createInvokeToolHandler(mcp, options = {}) {
|
|
1076
1103
|
assertRestResourceBinding(mcp, options);
|
|
1077
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
1078
|
-
const handle = async (request, toolName) => {
|
|
1079
|
-
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);
|
|
1080
1107
|
if (!authResult.ok)
|
|
1081
1108
|
return authResult.response;
|
|
1082
1109
|
if (request.method !== "POST")
|
|
@@ -1132,7 +1159,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1132
1159
|
try {
|
|
1133
1160
|
result = await tool.handler(args, new ToolContext(authResult.auth));
|
|
1134
1161
|
} catch {
|
|
1135
|
-
|
|
1162
|
+
await recorder.emit({
|
|
1136
1163
|
tool: tool.name,
|
|
1137
1164
|
method: "tools/call",
|
|
1138
1165
|
outcome: "handler_error",
|
|
@@ -1144,7 +1171,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1144
1171
|
});
|
|
1145
1172
|
}
|
|
1146
1173
|
if (result == null) {
|
|
1147
|
-
|
|
1174
|
+
await recorder.emit({
|
|
1148
1175
|
tool: tool.name,
|
|
1149
1176
|
method: "tools/call",
|
|
1150
1177
|
outcome: "handler_error",
|
|
@@ -1155,7 +1182,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1155
1182
|
headers: JSON_HEADERS
|
|
1156
1183
|
});
|
|
1157
1184
|
}
|
|
1158
|
-
|
|
1185
|
+
await recorder.emit({
|
|
1159
1186
|
tool: tool.name,
|
|
1160
1187
|
method: "tools/call",
|
|
1161
1188
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -1167,28 +1194,29 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
1167
1194
|
isError: result.isError
|
|
1168
1195
|
});
|
|
1169
1196
|
};
|
|
1170
|
-
return async (request, toolName) => {
|
|
1197
|
+
return async (request, toolName, recorder = createNoopRecorder()) => {
|
|
1171
1198
|
if (request.method === "OPTIONS")
|
|
1172
1199
|
return corsPreflightResponse("POST, OPTIONS");
|
|
1173
|
-
return withCors(await handle(request, toolName));
|
|
1200
|
+
return withCors(await handle(request, toolName, recorder));
|
|
1174
1201
|
};
|
|
1175
1202
|
}
|
|
1176
1203
|
|
|
1177
1204
|
// src/stacks/tanstack/handlers.ts
|
|
1205
|
+
var STACK = "tanstack";
|
|
1178
1206
|
function forwarded(request, options) {
|
|
1179
1207
|
return applyForwardedOrigin(request, { trustForwardedHost: options.trustForwardedHost });
|
|
1180
1208
|
}
|
|
1181
1209
|
function createTanStackMcpHandler(mcp, options = {}) {
|
|
1182
1210
|
const handler = createMcpProtocolHandler(mcp, options);
|
|
1183
|
-
return ({ request }) => handler(forwarded(request, options));
|
|
1211
|
+
return ({ request }) => handler(forwarded(request, options), createRecorderForRuntime(mcp, { stack: STACK }));
|
|
1184
1212
|
}
|
|
1185
1213
|
function createTanStackListToolsHandler(mcp, options = {}) {
|
|
1186
1214
|
const handler = createListToolsHandler(mcp, options);
|
|
1187
|
-
return ({ request }) => handler(forwarded(request, options));
|
|
1215
|
+
return ({ request }) => handler(forwarded(request, options), createRecorderForRuntime(mcp, { stack: STACK }));
|
|
1188
1216
|
}
|
|
1189
1217
|
function createTanStackInvokeToolHandler(mcp, options = {}) {
|
|
1190
1218
|
const handler = createInvokeToolHandler(mcp, options);
|
|
1191
|
-
return ({ request, params }) => handler(forwarded(request, options), params.tool);
|
|
1219
|
+
return ({ request, params }) => handler(forwarded(request, options), params.tool, createRecorderForRuntime(mcp, { stack: STACK }));
|
|
1192
1220
|
}
|
|
1193
1221
|
function createTanStackOAuthProtectedResourceMetadataHandler(mcp, options = {}) {
|
|
1194
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`). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovable.dev/mcp-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Author MCP servers for Lovable apps. Declare tools with defineTool, register them in defineMcp, and a framework adapter (TanStack or Supabase Edge Functions) emits the route(s) at build time.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|