@lovable.dev/mcp-js 0.14.0 → 0.15.1
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/README.md +28 -0
- package/dist/{chunk-YWLMMDET.js → chunk-CEDD57G2.js} +14 -3
- package/dist/{chunk-QC3DXQTH.js → chunk-DCWYK4CA.js} +34 -0
- package/dist/{chunk-NOFPRSSI.js → chunk-GIHCQT6L.js} +1 -1
- package/dist/{chunk-QDKOF4UF.js → chunk-ILYTJXDR.js} +2 -2
- package/dist/{chunk-VNRQPA4K.js → chunk-QT5L4CS3.js} +4 -3
- package/dist/{chunk-WLNT2FX7.js → chunk-UCPYLSNN.js} +18 -6
- package/dist/{chunk-G4XA7IJM.js → chunk-YTOMGJV5.js} +189 -2
- package/dist/cli/extract-manifest.cjs +1 -1
- package/dist/cli/extract-manifest.js +6 -6
- package/dist/index.cjs +33 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -1
- package/dist/protocols/mcp/index.cjs +230 -5
- package/dist/protocols/mcp/index.d.cts +3 -2
- package/dist/protocols/mcp/index.d.ts +3 -2
- package/dist/protocols/mcp/index.js +4 -3
- 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 -3
- package/dist/protocols/rest/index.cjs +229 -5
- package/dist/protocols/rest/index.d.cts +4 -3
- package/dist/protocols/rest/index.d.ts +4 -3
- package/dist/protocols/rest/index.js +5 -4
- package/dist/recorder-qlzUKFxG.d.ts +21 -0
- package/dist/stacks/supabase/index.cjs +263 -28
- package/dist/stacks/supabase/index.d.cts +1 -1
- package/dist/stacks/supabase/index.d.ts +1 -1
- package/dist/stacks/supabase/index.js +13 -10
- package/dist/stacks/supabase/vite.cjs +28 -4
- package/dist/stacks/supabase/vite.js +30 -6
- package/dist/stacks/tanstack/index.cjs +243 -9
- package/dist/stacks/tanstack/index.d.cts +1 -1
- package/dist/stacks/tanstack/index.d.ts +1 -1
- package/dist/stacks/tanstack/index.js +7 -6
- package/dist/{types-BanzncFh.d.ts → types-COY42xux.d.ts} +30 -1
- package/package.json +1 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { M as McpRuntimeOptions } from '../../authorize-BMeXd_Sh.js';
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
2
|
+
import { d as McpDefinition } from '../../types-COY42xux.js';
|
|
3
|
+
import { M as MetricsRecorder } from '../../recorder-qlzUKFxG.js';
|
|
3
4
|
import 'zod';
|
|
4
5
|
|
|
5
6
|
type RestListToolsHandler = (request: Request) => Promise<Response>;
|
|
6
|
-
declare function createListToolsHandler(mcp: McpDefinition, options?: McpRuntimeOptions): RestListToolsHandler;
|
|
7
|
+
declare function createListToolsHandler(mcp: McpDefinition, options?: McpRuntimeOptions, recorder?: MetricsRecorder): RestListToolsHandler;
|
|
7
8
|
|
|
8
9
|
type RestInvokeToolHandler = (request: Request, toolName: string) => Promise<Response>;
|
|
9
|
-
declare function createInvokeToolHandler(mcp: McpDefinition, options?: McpRuntimeOptions): RestInvokeToolHandler;
|
|
10
|
+
declare function createInvokeToolHandler(mcp: McpDefinition, options?: McpRuntimeOptions, recorder?: MetricsRecorder): RestInvokeToolHandler;
|
|
10
11
|
|
|
11
12
|
export { type RestInvokeToolHandler, type RestListToolsHandler, createInvokeToolHandler, createListToolsHandler };
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createInvokeToolHandler
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-CEDD57G2.js";
|
|
4
4
|
import {
|
|
5
5
|
createListToolsHandler
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-QT5L4CS3.js";
|
|
7
7
|
import "../../chunk-MA5H6PSF.js";
|
|
8
|
-
import "../../chunk-
|
|
9
|
-
import "../../chunk-
|
|
8
|
+
import "../../chunk-YTOMGJV5.js";
|
|
9
|
+
import "../../chunk-DCWYK4CA.js";
|
|
10
10
|
import "../../chunk-6DXGZZA4.js";
|
|
11
|
+
import "../../chunk-GIHCQT6L.js";
|
|
11
12
|
export {
|
|
12
13
|
createInvokeToolHandler,
|
|
13
14
|
createListToolsHandler
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type InvocationOutcome = "ok" | "tool_error" | "handler_error" | "transport_error" | "auth_config_error";
|
|
2
|
+
/** One recorded MCP call. Carries no arguments or response payloads — only the
|
|
3
|
+
* tool name, the JSON-RPC method, the result class, timing, and byte sizes. */
|
|
4
|
+
interface InvocationRecord {
|
|
5
|
+
/** Tool name for `tools/call`; `null` for list/initialize, transport faults, and auth-config failures. */
|
|
6
|
+
tool: string | null;
|
|
7
|
+
/** JSON-RPC method, e.g. `tools/call`; `authorize` for auth-config failures. */
|
|
8
|
+
method: string;
|
|
9
|
+
outcome: InvocationOutcome;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
reqBytes?: number;
|
|
12
|
+
resBytes?: number;
|
|
13
|
+
}
|
|
14
|
+
interface MetricsRecorder {
|
|
15
|
+
/** Buffer one invocation. Synchronous, sampled, and never throws. */
|
|
16
|
+
record(ev: InvocationRecord): void;
|
|
17
|
+
/** Best-effort POST of the current buffer. Resolves even on network failure. */
|
|
18
|
+
flush(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type { MetricsRecorder as M };
|
|
@@ -95,22 +95,6 @@ function firstForwardedValue(request, header) {
|
|
|
95
95
|
return value ? value : void 0;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
// src/protocols/mcp/protocol.ts
|
|
99
|
-
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
100
|
-
var import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js");
|
|
101
|
-
|
|
102
|
-
// src/core/http.ts
|
|
103
|
-
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
104
|
-
function headResponse(response) {
|
|
105
|
-
return new Response(null, { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
106
|
-
}
|
|
107
|
-
function methodNotAllowed(allow) {
|
|
108
|
-
return new Response(JSON.stringify({ error: "method not allowed" }), {
|
|
109
|
-
status: 405,
|
|
110
|
-
headers: { ...JSON_HEADERS, Allow: allow }
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
98
|
// src/core/logger.ts
|
|
115
99
|
var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
116
100
|
function isLogLevel(value) {
|
|
@@ -152,6 +136,230 @@ function describeError(err) {
|
|
|
152
136
|
return { value: String(err) };
|
|
153
137
|
}
|
|
154
138
|
|
|
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
|
+
// package.json
|
|
172
|
+
var version = "0.15.1";
|
|
173
|
+
|
|
174
|
+
// src/metrics/otlp.ts
|
|
175
|
+
var SCOPE_NAME = "@lovable.dev/mcp-js";
|
|
176
|
+
var EVENT_NAME = "mcp.tool.invocation";
|
|
177
|
+
var SEVERITY_INFO = 9;
|
|
178
|
+
function strAttr(key, value) {
|
|
179
|
+
return { key, value: { stringValue: value } };
|
|
180
|
+
}
|
|
181
|
+
function intAttr(key, value) {
|
|
182
|
+
return { key, value: { intValue: String(Math.round(value)) } };
|
|
183
|
+
}
|
|
184
|
+
function toLogRecord(rec) {
|
|
185
|
+
const attributes = [
|
|
186
|
+
strAttr("event.name", EVENT_NAME),
|
|
187
|
+
strAttr("mcp.method", rec.method),
|
|
188
|
+
strAttr("mcp.outcome", rec.outcome),
|
|
189
|
+
intAttr("mcp.duration_ms", rec.durationMs)
|
|
190
|
+
];
|
|
191
|
+
if (rec.tool !== null)
|
|
192
|
+
attributes.push(strAttr("mcp.tool", rec.tool));
|
|
193
|
+
if (rec.reqBytes !== void 0)
|
|
194
|
+
attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
|
|
195
|
+
if (rec.resBytes !== void 0)
|
|
196
|
+
attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
|
|
197
|
+
return {
|
|
198
|
+
timeUnixNano: rec.timeUnixNano,
|
|
199
|
+
observedTimeUnixNano: rec.timeUnixNano,
|
|
200
|
+
severityNumber: SEVERITY_INFO,
|
|
201
|
+
severityText: "INFO",
|
|
202
|
+
body: { stringValue: EVENT_NAME },
|
|
203
|
+
attributes
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function buildLogsPayload(records, server) {
|
|
207
|
+
return JSON.stringify({
|
|
208
|
+
resourceLogs: [
|
|
209
|
+
{
|
|
210
|
+
resource: {
|
|
211
|
+
attributes: [
|
|
212
|
+
strAttr("service.name", server.name),
|
|
213
|
+
strAttr("service.version", server.version),
|
|
214
|
+
strAttr("telemetry.sdk.name", SCOPE_NAME),
|
|
215
|
+
strAttr("telemetry.sdk.version", version),
|
|
216
|
+
strAttr("telemetry.sdk.language", "webjs")
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
scopeLogs: [
|
|
220
|
+
{
|
|
221
|
+
scope: { name: SCOPE_NAME, version },
|
|
222
|
+
logRecords: records.map(toLogRecord)
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function nowUnixNano() {
|
|
230
|
+
return `${Date.now()}000000`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/metrics/recorder.ts
|
|
234
|
+
function nowMs() {
|
|
235
|
+
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
236
|
+
}
|
|
237
|
+
var NOOP_RECORDER = {
|
|
238
|
+
record() {
|
|
239
|
+
},
|
|
240
|
+
async flush() {
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
function createNoopRecorder() {
|
|
244
|
+
return NOOP_RECORDER;
|
|
245
|
+
}
|
|
246
|
+
function readRuntimeEnv(name) {
|
|
247
|
+
try {
|
|
248
|
+
const denoEnv = globalThis.Deno?.env;
|
|
249
|
+
const value = denoEnv?.get?.(name);
|
|
250
|
+
if (value)
|
|
251
|
+
return value;
|
|
252
|
+
} catch {
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
if (typeof process !== "undefined") {
|
|
256
|
+
const value = process.env?.[name];
|
|
257
|
+
if (value)
|
|
258
|
+
return value;
|
|
259
|
+
}
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
262
|
+
return void 0;
|
|
263
|
+
}
|
|
264
|
+
function probeWaitUntil() {
|
|
265
|
+
const fn = globalThis.EdgeRuntime?.waitUntil;
|
|
266
|
+
return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
|
|
267
|
+
}
|
|
268
|
+
function createMetricsRecorder(ctx, deps = {}) {
|
|
269
|
+
const { config, server } = ctx;
|
|
270
|
+
if (!config.enabled)
|
|
271
|
+
return NOOP_RECORDER;
|
|
272
|
+
const doFetch = deps.fetch ?? globalThis.fetch;
|
|
273
|
+
if (!doFetch)
|
|
274
|
+
return NOOP_RECORDER;
|
|
275
|
+
const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
|
|
276
|
+
const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
|
|
277
|
+
if (usesLovableEndpoint && !apiKey) {
|
|
278
|
+
log.debug("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar });
|
|
279
|
+
return NOOP_RECORDER;
|
|
280
|
+
}
|
|
281
|
+
const waitUntil = deps.waitUntil ?? probeWaitUntil();
|
|
282
|
+
const buffer = [];
|
|
283
|
+
let timer;
|
|
284
|
+
const schedule = (p) => {
|
|
285
|
+
if (waitUntil) {
|
|
286
|
+
try {
|
|
287
|
+
waitUntil(p);
|
|
288
|
+
return;
|
|
289
|
+
} catch {
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
void p.catch(() => {
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
const headers = {};
|
|
296
|
+
if (!usesLovableEndpoint) {
|
|
297
|
+
for (const [key, value] of Object.entries(config.headers)) {
|
|
298
|
+
if (key.toLowerCase() !== "content-type")
|
|
299
|
+
headers[key] = value;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
headers["content-type"] = "application/json";
|
|
303
|
+
if (apiKey)
|
|
304
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
305
|
+
const flush = async () => {
|
|
306
|
+
if (buffer.length === 0)
|
|
307
|
+
return;
|
|
308
|
+
const records = buffer.splice(0, buffer.length);
|
|
309
|
+
try {
|
|
310
|
+
const res = await doFetch(config.endpoint, {
|
|
311
|
+
method: "POST",
|
|
312
|
+
headers,
|
|
313
|
+
body: buildLogsPayload(records, server),
|
|
314
|
+
keepalive: true
|
|
315
|
+
});
|
|
316
|
+
if (!res.ok)
|
|
317
|
+
log.debug("metrics.flush_rejected", { status: res.status });
|
|
318
|
+
} catch (err) {
|
|
319
|
+
log.debug("metrics.flush_failed", describeError(err));
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
const ensureTimer = () => {
|
|
323
|
+
if (timer !== void 0)
|
|
324
|
+
return;
|
|
325
|
+
timer = setInterval(() => schedule(flush()), config.flushIntervalMs);
|
|
326
|
+
timer.unref?.();
|
|
327
|
+
};
|
|
328
|
+
return {
|
|
329
|
+
record(ev) {
|
|
330
|
+
if (config.sampleRate < 1 && Math.random() >= config.sampleRate)
|
|
331
|
+
return;
|
|
332
|
+
buffer.push({ ...ev, timeUnixNano: nowUnixNano() });
|
|
333
|
+
ensureTimer();
|
|
334
|
+
if (buffer.length >= config.maxBatchSize)
|
|
335
|
+
schedule(flush());
|
|
336
|
+
},
|
|
337
|
+
flush
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function createRecorderForRuntime(mcp) {
|
|
341
|
+
const config = resolveMetricsConfig(mcp.metrics);
|
|
342
|
+
if (!config.enabled)
|
|
343
|
+
return createNoopRecorder();
|
|
344
|
+
return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/protocols/mcp/protocol.ts
|
|
348
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
349
|
+
var import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js");
|
|
350
|
+
|
|
351
|
+
// src/core/http.ts
|
|
352
|
+
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
353
|
+
function headResponse(response) {
|
|
354
|
+
return new Response(null, { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
355
|
+
}
|
|
356
|
+
function methodNotAllowed(allow) {
|
|
357
|
+
return new Response(JSON.stringify({ error: "method not allowed" }), {
|
|
358
|
+
status: 405,
|
|
359
|
+
headers: { ...JSON_HEADERS, Allow: allow }
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
155
363
|
// src/core/promise.ts
|
|
156
364
|
function cachedPromise(load, label) {
|
|
157
365
|
let settled = false;
|
|
@@ -532,12 +740,13 @@ function assertRequiredScopes(auth, context) {
|
|
|
532
740
|
throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
|
|
533
741
|
}
|
|
534
742
|
}
|
|
535
|
-
function createRequestAuthorizer(mcp, options = {}) {
|
|
743
|
+
function createRequestAuthorizer(mcp, options = {}, recorder) {
|
|
536
744
|
const runtime = getOAuthRuntime(mcp, options);
|
|
537
745
|
return {
|
|
538
746
|
async authorize(request) {
|
|
539
747
|
if (runtime.kind === "unconfigured")
|
|
540
748
|
return { ok: true };
|
|
749
|
+
const startedAt = nowMs();
|
|
541
750
|
const token = parseBearerToken(request);
|
|
542
751
|
if (!token) {
|
|
543
752
|
log.info("auth.no_bearer_token", { outcome: "401" });
|
|
@@ -550,6 +759,12 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
550
759
|
} catch (err) {
|
|
551
760
|
if (err instanceof OAuthConfigurationError) {
|
|
552
761
|
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
762
|
+
recorder?.record({
|
|
763
|
+
tool: null,
|
|
764
|
+
method: "authorize",
|
|
765
|
+
outcome: "auth_config_error",
|
|
766
|
+
durationMs: nowMs() - startedAt
|
|
767
|
+
});
|
|
553
768
|
return { ok: false, response: oauthConfigurationErrorResponse() };
|
|
554
769
|
}
|
|
555
770
|
if (err instanceof OAuthTokenError) {
|
|
@@ -647,23 +862,32 @@ function corsPreflightResponse(allowMethods) {
|
|
|
647
862
|
}
|
|
648
863
|
|
|
649
864
|
// src/protocols/mcp/protocol.ts
|
|
650
|
-
function adaptToolToSdkCallback(tool, auth) {
|
|
865
|
+
function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
651
866
|
return async (first) => {
|
|
652
867
|
const args = tool.inputSchema ? first ?? {} : {};
|
|
868
|
+
const start = nowMs();
|
|
653
869
|
let result;
|
|
654
870
|
try {
|
|
655
871
|
result = await tool.handler(args, new ToolContext(auth));
|
|
656
872
|
} catch {
|
|
873
|
+
recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
|
|
657
874
|
return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
|
|
658
875
|
}
|
|
659
876
|
if (result == null) {
|
|
877
|
+
recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
|
|
660
878
|
return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
|
|
661
879
|
}
|
|
880
|
+
recorder.record({
|
|
881
|
+
tool: tool.name,
|
|
882
|
+
method: "tools/call",
|
|
883
|
+
outcome: result.isError ? "tool_error" : "ok",
|
|
884
|
+
durationMs: nowMs() - start
|
|
885
|
+
});
|
|
662
886
|
return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
|
|
663
887
|
};
|
|
664
888
|
}
|
|
665
|
-
function createMcpProtocolHandler(mcp, options = {}) {
|
|
666
|
-
const authorizer = createRequestAuthorizer(mcp, options);
|
|
889
|
+
function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
|
|
890
|
+
const authorizer = createRequestAuthorizer(mcp, options, recorder);
|
|
667
891
|
const handle = async (request) => {
|
|
668
892
|
const authResult = await authorizer.authorize(request);
|
|
669
893
|
if (!authResult.ok)
|
|
@@ -683,7 +907,7 @@ function createMcpProtocolHandler(mcp, options = {}) {
|
|
|
683
907
|
outputSchema: tool.outputSchema,
|
|
684
908
|
annotations: tool.annotations
|
|
685
909
|
},
|
|
686
|
-
adaptToolToSdkCallback(tool, authResult.auth)
|
|
910
|
+
adaptToolToSdkCallback(tool, authResult.auth, recorder)
|
|
687
911
|
);
|
|
688
912
|
}
|
|
689
913
|
const transport = new import_webStandardStreamableHttp.WebStandardStreamableHTTPServerTransport({
|
|
@@ -692,6 +916,7 @@ function createMcpProtocolHandler(mcp, options = {}) {
|
|
|
692
916
|
await server.connect(transport);
|
|
693
917
|
return await transport.handleRequest(request);
|
|
694
918
|
} catch (err) {
|
|
919
|
+
recorder.record({ tool: null, method: "transport", outcome: "transport_error", durationMs: 0 });
|
|
695
920
|
log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
|
|
696
921
|
return Response.json(
|
|
697
922
|
{ jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
|
|
@@ -792,9 +1017,9 @@ function buildMcpListing(mcp) {
|
|
|
792
1017
|
}))
|
|
793
1018
|
};
|
|
794
1019
|
}
|
|
795
|
-
function createListToolsHandler(mcp, options = {}) {
|
|
1020
|
+
function createListToolsHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
|
|
796
1021
|
assertRestResourceBinding(mcp, options);
|
|
797
|
-
const authorizer = createRequestAuthorizer(mcp, options);
|
|
1022
|
+
const authorizer = createRequestAuthorizer(mcp, options, recorder);
|
|
798
1023
|
const handle = async (request) => {
|
|
799
1024
|
const authResult = await authorizer.authorize(request);
|
|
800
1025
|
if (!authResult.ok)
|
|
@@ -825,9 +1050,9 @@ function isEmptyArgs(value) {
|
|
|
825
1050
|
return false;
|
|
826
1051
|
return Object.keys(value).length === 0;
|
|
827
1052
|
}
|
|
828
|
-
function createInvokeToolHandler(mcp, options = {}) {
|
|
1053
|
+
function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
|
|
829
1054
|
assertRestResourceBinding(mcp, options);
|
|
830
|
-
const authorizer = createRequestAuthorizer(mcp, options);
|
|
1055
|
+
const authorizer = createRequestAuthorizer(mcp, options, recorder);
|
|
831
1056
|
const handle = async (request, toolName) => {
|
|
832
1057
|
const authResult = await authorizer.authorize(request);
|
|
833
1058
|
if (!authResult.ok)
|
|
@@ -881,20 +1106,29 @@ function createInvokeToolHandler(mcp, options = {}) {
|
|
|
881
1106
|
});
|
|
882
1107
|
}
|
|
883
1108
|
let result;
|
|
1109
|
+
const start = nowMs();
|
|
884
1110
|
try {
|
|
885
1111
|
result = await tool.handler(args, new ToolContext(authResult.auth));
|
|
886
1112
|
} catch {
|
|
1113
|
+
recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
|
|
887
1114
|
return new Response(JSON.stringify({ error: "handler threw", tool: toolName }), {
|
|
888
1115
|
status: 500,
|
|
889
1116
|
headers: JSON_HEADERS
|
|
890
1117
|
});
|
|
891
1118
|
}
|
|
892
1119
|
if (result == null) {
|
|
1120
|
+
recorder.record({ tool: tool.name, method: "tools/call", outcome: "handler_error", durationMs: nowMs() - start });
|
|
893
1121
|
return new Response(JSON.stringify({ error: `tool "${toolName}" returned no result` }), {
|
|
894
1122
|
status: 500,
|
|
895
1123
|
headers: JSON_HEADERS
|
|
896
1124
|
});
|
|
897
1125
|
}
|
|
1126
|
+
recorder.record({
|
|
1127
|
+
tool: tool.name,
|
|
1128
|
+
method: "tools/call",
|
|
1129
|
+
outcome: result.isError ? "tool_error" : "ok",
|
|
1130
|
+
durationMs: nowMs() - start
|
|
1131
|
+
});
|
|
898
1132
|
return Response.json({
|
|
899
1133
|
content: result.content ?? [],
|
|
900
1134
|
structuredContent: result.structuredContent,
|
|
@@ -954,9 +1188,10 @@ function createSupabaseHandler(mcp, options = {}) {
|
|
|
954
1188
|
const servesOwnPrm = !(mcp.auth?.type === "oauth" && mcp.auth.protectedResourceMetadataUrl !== void 0);
|
|
955
1189
|
const metadataPath = resourcePath === void 0 || !servesOwnPrm ? void 0 : `${trimTrailingSlash(resourcePath)}${OAUTH_PROTECTED_RESOURCE_METADATA_PATH}`;
|
|
956
1190
|
const runtimeOptions = resourcePath === void 0 ? {} : { resourcePath, ...metadataPath ? { metadataPath } : {} };
|
|
957
|
-
const
|
|
958
|
-
const
|
|
959
|
-
const
|
|
1191
|
+
const recorder = createRecorderForRuntime(mcp);
|
|
1192
|
+
const mcpHandler = createMcpProtocolHandler(mcp, runtimeOptions, recorder);
|
|
1193
|
+
const listToolsHandler = createListToolsHandler(mcp, runtimeOptions, recorder);
|
|
1194
|
+
const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions, recorder);
|
|
960
1195
|
const metadataHandler = createOAuthProtectedResourceMetadataHandler(mcp, runtimeOptions);
|
|
961
1196
|
return async (request) => {
|
|
962
1197
|
const req = applyForwardedOrigin(request, {
|
|
@@ -3,23 +3,24 @@ import {
|
|
|
3
3
|
} from "../../chunk-UQK5UO6C.js";
|
|
4
4
|
import {
|
|
5
5
|
createMcpProtocolHandler
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-UCPYLSNN.js";
|
|
7
7
|
import {
|
|
8
8
|
createOAuthProtectedResourceMetadataHandler
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-ILYTJXDR.js";
|
|
10
10
|
import {
|
|
11
11
|
createInvokeToolHandler
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-CEDD57G2.js";
|
|
13
13
|
import {
|
|
14
14
|
createListToolsHandler
|
|
15
|
-
} from "../../chunk-
|
|
15
|
+
} from "../../chunk-QT5L4CS3.js";
|
|
16
16
|
import "../../chunk-MA5H6PSF.js";
|
|
17
17
|
import {
|
|
18
|
-
assertResourcePathShape
|
|
19
|
-
|
|
18
|
+
assertResourcePathShape,
|
|
19
|
+
createRecorderForRuntime
|
|
20
|
+
} from "../../chunk-YTOMGJV5.js";
|
|
20
21
|
import {
|
|
21
22
|
trimTrailingSlash
|
|
22
|
-
} from "../../chunk-
|
|
23
|
+
} from "../../chunk-DCWYK4CA.js";
|
|
23
24
|
import {
|
|
24
25
|
OAUTH_PROTECTED_RESOURCE_METADATA_PATH
|
|
25
26
|
} from "../../chunk-6DXGZZA4.js";
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
FUNCTIONS_MOUNT_PREFIX,
|
|
28
29
|
assertFunctionName
|
|
29
30
|
} from "../../chunk-XQWJN6DC.js";
|
|
31
|
+
import "../../chunk-GIHCQT6L.js";
|
|
30
32
|
|
|
31
33
|
// src/stacks/supabase/handler.ts
|
|
32
34
|
function deriveResourcePath(options) {
|
|
@@ -64,9 +66,10 @@ function createSupabaseHandler(mcp, options = {}) {
|
|
|
64
66
|
const servesOwnPrm = !(mcp.auth?.type === "oauth" && mcp.auth.protectedResourceMetadataUrl !== void 0);
|
|
65
67
|
const metadataPath = resourcePath === void 0 || !servesOwnPrm ? void 0 : `${trimTrailingSlash(resourcePath)}${OAUTH_PROTECTED_RESOURCE_METADATA_PATH}`;
|
|
66
68
|
const runtimeOptions = resourcePath === void 0 ? {} : { resourcePath, ...metadataPath ? { metadataPath } : {} };
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
69
|
+
const recorder = createRecorderForRuntime(mcp);
|
|
70
|
+
const mcpHandler = createMcpProtocolHandler(mcp, runtimeOptions, recorder);
|
|
71
|
+
const listToolsHandler = createListToolsHandler(mcp, runtimeOptions, recorder);
|
|
72
|
+
const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions, recorder);
|
|
70
73
|
const metadataHandler = createOAuthProtectedResourceMetadataHandler(mcp, runtimeOptions);
|
|
71
74
|
return async (request) => {
|
|
72
75
|
const req = applyForwardedOrigin(request, {
|
|
@@ -25,6 +25,7 @@ __export(vite_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(vite_exports);
|
|
27
27
|
var import_node_path2 = require("path");
|
|
28
|
+
var import_vite = require("vite");
|
|
28
29
|
|
|
29
30
|
// src/stacks/supabase/emit.ts
|
|
30
31
|
var import_esbuild = require("esbuild");
|
|
@@ -32,7 +33,7 @@ var import_node_fs = require("fs");
|
|
|
32
33
|
var import_node_path = require("path");
|
|
33
34
|
|
|
34
35
|
// package.json
|
|
35
|
-
var version = "0.
|
|
36
|
+
var version = "0.15.1";
|
|
36
37
|
|
|
37
38
|
// src/core/fs-errors.ts
|
|
38
39
|
function isFileMissing(err) {
|
|
@@ -129,7 +130,16 @@ function externalizeBareAsNpm(versions) {
|
|
|
129
130
|
}
|
|
130
131
|
};
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
+
function importMetaEnvDefines(inlineEnv) {
|
|
134
|
+
const env = inlineEnv ?? {};
|
|
135
|
+
const defines = { "import.meta.env": JSON.stringify(env) };
|
|
136
|
+
for (const [key, value] of Object.entries(env)) {
|
|
137
|
+
if (/^[A-Za-z_$][\w$]*$/.test(key))
|
|
138
|
+
defines[`import.meta.env.${key}`] = JSON.stringify(value);
|
|
139
|
+
}
|
|
140
|
+
return defines;
|
|
141
|
+
}
|
|
142
|
+
async function bundleFunctionEntry(projectRoot, mcpEntryAbs, functionName, inlineEnv) {
|
|
133
143
|
const versions = readProjectDependencyVersions(projectRoot);
|
|
134
144
|
const wrapper = `import mcp from ${JSON.stringify(mcpEntryAbs)};
|
|
135
145
|
import { createSupabaseHandler } from "@lovable.dev/mcp-js/stacks/supabase";
|
|
@@ -145,6 +155,7 @@ Deno.serve(createSupabaseHandler(mcp, { functionName: ${JSON.stringify(functionN
|
|
|
145
155
|
target: "esnext",
|
|
146
156
|
absWorkingDir: projectRoot,
|
|
147
157
|
logLevel: "silent",
|
|
158
|
+
define: importMetaEnvDefines(inlineEnv),
|
|
148
159
|
plugins: [externalizeBareAsNpm(versions)]
|
|
149
160
|
});
|
|
150
161
|
const out = result.outputFiles[0]?.text ?? "";
|
|
@@ -183,7 +194,7 @@ async function syncSupabaseFunction(options) {
|
|
|
183
194
|
cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set(), removed);
|
|
184
195
|
return { written, removed, inputs: [] };
|
|
185
196
|
}
|
|
186
|
-
const { code, inputs } = await bundleFunctionEntry(projectRoot, paths.mcpEntryAbs, functionName);
|
|
197
|
+
const { code, inputs } = await bundleFunctionEntry(projectRoot, paths.mcpEntryAbs, functionName, options.inlineEnv);
|
|
187
198
|
if (writeIfChanged(paths.functionEntry, code, hasGeneratedBanner)) {
|
|
188
199
|
written.push(paths.functionEntry);
|
|
189
200
|
}
|
|
@@ -227,6 +238,16 @@ function cleanupOrphans(functionDir, expected, removed) {
|
|
|
227
238
|
function normalizePath(p) {
|
|
228
239
|
return p.split(import_node_path2.sep).join("/");
|
|
229
240
|
}
|
|
241
|
+
function productionImportMetaEnv(config) {
|
|
242
|
+
return {
|
|
243
|
+
...(0, import_vite.loadEnv)("production", config.envDir, config.envPrefix),
|
|
244
|
+
MODE: "production",
|
|
245
|
+
BASE_URL: config.base ?? "/",
|
|
246
|
+
DEV: false,
|
|
247
|
+
PROD: true,
|
|
248
|
+
SSR: false
|
|
249
|
+
};
|
|
250
|
+
}
|
|
230
251
|
function mcpPlugin(options = {}) {
|
|
231
252
|
const mcpEntryOption = options.mcpEntry ?? DEFAULT_MCP_ENTRY;
|
|
232
253
|
const functionsDirOption = options.functionsDir ?? DEFAULT_FUNCTIONS_DIR;
|
|
@@ -234,13 +255,15 @@ function mcpPlugin(options = {}) {
|
|
|
234
255
|
const urlPath = `${FUNCTIONS_MOUNT_PREFIX}${functionName}`;
|
|
235
256
|
let projectRoot = process.cwd();
|
|
236
257
|
let mcpEntryAbs = (0, import_node_path2.resolve)(projectRoot, mcpEntryOption);
|
|
258
|
+
let inlineEnv = {};
|
|
237
259
|
let watchedInputs = /* @__PURE__ */ new Set();
|
|
238
260
|
const regenerate = async () => {
|
|
239
261
|
const { inputs } = await syncSupabaseFunction({
|
|
240
262
|
projectRoot,
|
|
241
263
|
mcpEntry: mcpEntryOption,
|
|
242
264
|
functionsDir: functionsDirOption,
|
|
243
|
-
functionName
|
|
265
|
+
functionName,
|
|
266
|
+
inlineEnv
|
|
244
267
|
});
|
|
245
268
|
watchedInputs = new Set(inputs.map(normalizePath));
|
|
246
269
|
};
|
|
@@ -258,6 +281,7 @@ function mcpPlugin(options = {}) {
|
|
|
258
281
|
async configResolved(config) {
|
|
259
282
|
projectRoot = config.root;
|
|
260
283
|
mcpEntryAbs = (0, import_node_path2.resolve)(projectRoot, mcpEntryOption);
|
|
284
|
+
inlineEnv = productionImportMetaEnv(config);
|
|
261
285
|
await regenerate();
|
|
262
286
|
},
|
|
263
287
|
configureServer(server) {
|