@lovable.dev/mcp-js 0.15.0 → 0.16.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/{chunk-QC3DXQTH.js → chunk-DCWYK4CA.js} +34 -0
- package/dist/{chunk-RHDEW56Y.js → chunk-EZQVUNXB.js} +20 -11
- package/dist/{chunk-G4XA7IJM.js → chunk-H6BKTNJY.js} +199 -2
- package/dist/{chunk-VNRQPA4K.js → chunk-I5CAQUQI.js} +4 -3
- package/dist/{chunk-P3F324UC.js → chunk-MA5H6PSF.js} +0 -34
- package/dist/{chunk-LWHVXXKQ.js → chunk-SKSKC747.js} +1 -1
- package/dist/{chunk-QDKOF4UF.js → chunk-T24Z753F.js} +2 -2
- package/dist/{chunk-T2RED2TG.js → chunk-W2EZWPJ5.js} +19 -10
- package/dist/cli/extract-manifest.cjs +1 -1
- package/dist/cli/extract-manifest.js +4 -4
- package/dist/index.js +4 -4
- package/dist/protocols/mcp/index.cjs +239 -213
- package/dist/protocols/mcp/index.d.cts +1 -1
- package/dist/protocols/mcp/index.d.ts +1 -1
- package/dist/protocols/mcp/index.js +5 -6
- package/dist/protocols/oauth-metadata.js +4 -3
- package/dist/protocols/rest/index.cjs +241 -215
- package/dist/protocols/rest/index.d.cts +2 -2
- package/dist/protocols/rest/index.d.ts +2 -2
- package/dist/protocols/rest/index.js +6 -7
- package/dist/{recorder-B-eJU4Qu.d.ts → recorder-qlzUKFxG.d.ts} +3 -3
- package/dist/stacks/supabase/index.cjs +55 -19
- package/dist/stacks/supabase/index.js +10 -12
- package/dist/stacks/supabase/vite.cjs +28 -4
- package/dist/stacks/supabase/vite.js +28 -4
- package/dist/stacks/tanstack/index.cjs +255 -219
- package/dist/stacks/tanstack/index.js +8 -9
- package/package.json +1 -1
- package/dist/chunk-XQSTN54Y.js +0 -189
|
@@ -42,6 +42,38 @@ function describeError(err) {
|
|
|
42
42
|
return { value: String(err) };
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// src/metrics/config.ts
|
|
46
|
+
var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
|
|
47
|
+
var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
|
|
48
|
+
var METRICS_SAMPLE_RATE = 1;
|
|
49
|
+
var METRICS_FLUSH_INTERVAL_MS = 5e3;
|
|
50
|
+
var METRICS_MAX_BATCH_SIZE = 50;
|
|
51
|
+
function assertMetricsEndpoint(endpoint) {
|
|
52
|
+
let url;
|
|
53
|
+
try {
|
|
54
|
+
url = new URL(endpoint);
|
|
55
|
+
} catch {
|
|
56
|
+
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
|
|
57
|
+
}
|
|
58
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
59
|
+
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function resolveMetricsConfig(config = true) {
|
|
63
|
+
const options = typeof config === "boolean" ? { enabled: config } : config;
|
|
64
|
+
const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
|
|
65
|
+
assertMetricsEndpoint(endpoint);
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
enabled: options.enabled ?? true,
|
|
68
|
+
endpoint,
|
|
69
|
+
headers: options.headers ?? {},
|
|
70
|
+
apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
|
|
71
|
+
sampleRate: METRICS_SAMPLE_RATE,
|
|
72
|
+
flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
|
|
73
|
+
maxBatchSize: METRICS_MAX_BATCH_SIZE
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
45
77
|
// src/core/url.ts
|
|
46
78
|
function trimTrailingSlash(value) {
|
|
47
79
|
return value.replace(/\/+$/, "");
|
|
@@ -79,6 +111,8 @@ function parseSafeUrl(subject, raw, ErrorClass = Error) {
|
|
|
79
111
|
}
|
|
80
112
|
|
|
81
113
|
export {
|
|
114
|
+
DEFAULT_METRICS_ENDPOINT,
|
|
115
|
+
resolveMetricsConfig,
|
|
82
116
|
trimTrailingSlash,
|
|
83
117
|
parseSafeUrl,
|
|
84
118
|
setLogLevel,
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createRecorderForRuntime,
|
|
3
|
-
nowMs
|
|
4
|
-
} from "./chunk-XQSTN54Y.js";
|
|
5
1
|
import {
|
|
6
2
|
ToolContext
|
|
7
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-MA5H6PSF.js";
|
|
8
4
|
import {
|
|
9
5
|
corsPreflightResponse,
|
|
6
|
+
createRecorderForRuntime,
|
|
10
7
|
createRequestAuthorizer,
|
|
8
|
+
nowMs,
|
|
9
|
+
reportInvocation,
|
|
11
10
|
withCors
|
|
12
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-H6BKTNJY.js";
|
|
13
12
|
import {
|
|
14
13
|
describeError,
|
|
15
14
|
log
|
|
16
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-DCWYK4CA.js";
|
|
17
16
|
|
|
18
17
|
// src/protocols/mcp/protocol.ts
|
|
19
18
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -26,14 +25,24 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
26
25
|
try {
|
|
27
26
|
result = await tool.handler(args, new ToolContext(auth));
|
|
28
27
|
} catch {
|
|
29
|
-
recorder
|
|
28
|
+
reportInvocation(recorder, {
|
|
29
|
+
tool: tool.name,
|
|
30
|
+
method: "tools/call",
|
|
31
|
+
outcome: "handler_error",
|
|
32
|
+
durationMs: nowMs() - start
|
|
33
|
+
});
|
|
30
34
|
return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
|
|
31
35
|
}
|
|
32
36
|
if (result == null) {
|
|
33
|
-
recorder
|
|
37
|
+
reportInvocation(recorder, {
|
|
38
|
+
tool: tool.name,
|
|
39
|
+
method: "tools/call",
|
|
40
|
+
outcome: "handler_error",
|
|
41
|
+
durationMs: nowMs() - start
|
|
42
|
+
});
|
|
34
43
|
return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
|
|
35
44
|
}
|
|
36
|
-
recorder
|
|
45
|
+
reportInvocation(recorder, {
|
|
37
46
|
tool: tool.name,
|
|
38
47
|
method: "tools/call",
|
|
39
48
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -43,7 +52,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
43
52
|
};
|
|
44
53
|
}
|
|
45
54
|
function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
|
|
46
|
-
const authorizer = createRequestAuthorizer(mcp, options);
|
|
55
|
+
const authorizer = createRequestAuthorizer(mcp, options, recorder);
|
|
47
56
|
const handle = async (request) => {
|
|
48
57
|
const authResult = await authorizer.authorize(request);
|
|
49
58
|
if (!authResult.ok)
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_METRICS_ENDPOINT,
|
|
2
3
|
describeError,
|
|
3
4
|
log,
|
|
4
5
|
parseSafeUrl,
|
|
6
|
+
resolveMetricsConfig,
|
|
5
7
|
trimTrailingSlash
|
|
6
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DCWYK4CA.js";
|
|
7
9
|
import {
|
|
8
10
|
OAUTH_PROTECTED_RESOURCE_METADATA_PATH
|
|
9
11
|
} from "./chunk-6DXGZZA4.js";
|
|
12
|
+
import {
|
|
13
|
+
version
|
|
14
|
+
} from "./chunk-SKSKC747.js";
|
|
10
15
|
|
|
11
16
|
// src/core/http.ts
|
|
12
17
|
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
@@ -44,6 +49,188 @@ function resolveResourcePath(resourcePath, request) {
|
|
|
44
49
|
return resourcePath;
|
|
45
50
|
}
|
|
46
51
|
|
|
52
|
+
// src/metrics/otlp.ts
|
|
53
|
+
var SCOPE_NAME = "@lovable.dev/mcp-js";
|
|
54
|
+
var EVENT_NAME = "mcp.tool.invocation";
|
|
55
|
+
var SEVERITY_INFO = 9;
|
|
56
|
+
function strAttr(key, value) {
|
|
57
|
+
return { key, value: { stringValue: value } };
|
|
58
|
+
}
|
|
59
|
+
function intAttr(key, value) {
|
|
60
|
+
return { key, value: { intValue: String(Math.round(value)) } };
|
|
61
|
+
}
|
|
62
|
+
function toLogRecord(rec) {
|
|
63
|
+
const attributes = [
|
|
64
|
+
strAttr("event.name", EVENT_NAME),
|
|
65
|
+
strAttr("mcp.method", rec.method),
|
|
66
|
+
strAttr("mcp.outcome", rec.outcome),
|
|
67
|
+
intAttr("mcp.duration_ms", rec.durationMs)
|
|
68
|
+
];
|
|
69
|
+
if (rec.tool !== null)
|
|
70
|
+
attributes.push(strAttr("mcp.tool", rec.tool));
|
|
71
|
+
if (rec.reqBytes !== void 0)
|
|
72
|
+
attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
|
|
73
|
+
if (rec.resBytes !== void 0)
|
|
74
|
+
attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
|
|
75
|
+
return {
|
|
76
|
+
timeUnixNano: rec.timeUnixNano,
|
|
77
|
+
observedTimeUnixNano: rec.timeUnixNano,
|
|
78
|
+
severityNumber: SEVERITY_INFO,
|
|
79
|
+
severityText: "INFO",
|
|
80
|
+
body: { stringValue: EVENT_NAME },
|
|
81
|
+
attributes
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function buildLogsPayload(records, server) {
|
|
85
|
+
return JSON.stringify({
|
|
86
|
+
resourceLogs: [
|
|
87
|
+
{
|
|
88
|
+
resource: {
|
|
89
|
+
attributes: [
|
|
90
|
+
strAttr("service.name", server.name),
|
|
91
|
+
strAttr("service.version", server.version),
|
|
92
|
+
strAttr("telemetry.sdk.name", SCOPE_NAME),
|
|
93
|
+
strAttr("telemetry.sdk.version", version),
|
|
94
|
+
strAttr("telemetry.sdk.language", "webjs")
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
scopeLogs: [
|
|
98
|
+
{
|
|
99
|
+
scope: { name: SCOPE_NAME, version },
|
|
100
|
+
logRecords: records.map(toLogRecord)
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function nowUnixNano() {
|
|
108
|
+
return `${Date.now()}000000`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/metrics/recorder.ts
|
|
112
|
+
function nowMs() {
|
|
113
|
+
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
114
|
+
}
|
|
115
|
+
function reportInvocation(recorder, ev) {
|
|
116
|
+
log.info("tool.invoked", { tool: ev.tool, method: ev.method, outcome: ev.outcome, durationMs: ev.durationMs });
|
|
117
|
+
recorder.record(ev);
|
|
118
|
+
}
|
|
119
|
+
var NOOP_RECORDER = {
|
|
120
|
+
record() {
|
|
121
|
+
},
|
|
122
|
+
async flush() {
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
function createNoopRecorder() {
|
|
126
|
+
return NOOP_RECORDER;
|
|
127
|
+
}
|
|
128
|
+
function readRuntimeEnv(name) {
|
|
129
|
+
try {
|
|
130
|
+
const denoEnv = globalThis.Deno?.env;
|
|
131
|
+
const value = denoEnv?.get?.(name);
|
|
132
|
+
if (value)
|
|
133
|
+
return value;
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
if (typeof process !== "undefined") {
|
|
138
|
+
const value = process.env?.[name];
|
|
139
|
+
if (value)
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
return void 0;
|
|
145
|
+
}
|
|
146
|
+
function probeWaitUntil() {
|
|
147
|
+
const fn = globalThis.EdgeRuntime?.waitUntil;
|
|
148
|
+
return typeof fn === "function" ? fn.bind(globalThis.EdgeRuntime) : void 0;
|
|
149
|
+
}
|
|
150
|
+
function createMetricsRecorder(ctx, deps = {}) {
|
|
151
|
+
const { config, server } = ctx;
|
|
152
|
+
if (!config.enabled)
|
|
153
|
+
return NOOP_RECORDER;
|
|
154
|
+
const doFetch = deps.fetch ?? globalThis.fetch;
|
|
155
|
+
if (!doFetch) {
|
|
156
|
+
log.warn("metrics.disabled_no_fetch", { endpoint: config.endpoint });
|
|
157
|
+
return NOOP_RECORDER;
|
|
158
|
+
}
|
|
159
|
+
const usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
|
|
160
|
+
const apiKey = usesLovableEndpoint ? (deps.getApiKey ?? (() => readRuntimeEnv(config.apiKeyEnvVar)))() : void 0;
|
|
161
|
+
if (usesLovableEndpoint && !apiKey) {
|
|
162
|
+
log.warn("metrics.disabled_no_api_key", { envVar: config.apiKeyEnvVar, endpoint: config.endpoint });
|
|
163
|
+
return NOOP_RECORDER;
|
|
164
|
+
}
|
|
165
|
+
const waitUntil = deps.waitUntil ?? probeWaitUntil();
|
|
166
|
+
const buffer = [];
|
|
167
|
+
let timer;
|
|
168
|
+
const schedule = (p) => {
|
|
169
|
+
if (waitUntil) {
|
|
170
|
+
try {
|
|
171
|
+
waitUntil(p);
|
|
172
|
+
return;
|
|
173
|
+
} catch (err) {
|
|
174
|
+
log.warn("metrics.wait_until_failed", describeError(err));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
void p.catch(() => {
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
const headers = {};
|
|
181
|
+
if (!usesLovableEndpoint) {
|
|
182
|
+
for (const [key, value] of Object.entries(config.headers)) {
|
|
183
|
+
if (key.toLowerCase() !== "content-type")
|
|
184
|
+
headers[key] = value;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
headers["content-type"] = "application/json";
|
|
188
|
+
if (apiKey)
|
|
189
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
190
|
+
const flush = async () => {
|
|
191
|
+
if (buffer.length === 0)
|
|
192
|
+
return;
|
|
193
|
+
const records = buffer.splice(0, buffer.length);
|
|
194
|
+
const dropped = records.length;
|
|
195
|
+
try {
|
|
196
|
+
const res = await doFetch(config.endpoint, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers,
|
|
199
|
+
body: buildLogsPayload(records, server),
|
|
200
|
+
keepalive: true
|
|
201
|
+
});
|
|
202
|
+
if (!res.ok) {
|
|
203
|
+
log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
|
|
204
|
+
}
|
|
205
|
+
} catch (err) {
|
|
206
|
+
log.warn("metrics.flush_failed", { ...describeError(err), dropped, endpoint: config.endpoint });
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
const ensureTimer = () => {
|
|
210
|
+
if (timer !== void 0)
|
|
211
|
+
return;
|
|
212
|
+
timer = setInterval(() => schedule(flush()), config.flushIntervalMs);
|
|
213
|
+
timer.unref?.();
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
record(ev) {
|
|
217
|
+
if (config.sampleRate < 1 && Math.random() >= config.sampleRate)
|
|
218
|
+
return;
|
|
219
|
+
buffer.push({ ...ev, timeUnixNano: nowUnixNano() });
|
|
220
|
+
ensureTimer();
|
|
221
|
+
if (buffer.length >= config.maxBatchSize)
|
|
222
|
+
schedule(flush());
|
|
223
|
+
},
|
|
224
|
+
flush
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function createRecorderForRuntime(mcp) {
|
|
228
|
+
const config = resolveMetricsConfig(mcp.metrics);
|
|
229
|
+
if (!config.enabled)
|
|
230
|
+
return createNoopRecorder();
|
|
231
|
+
return createMetricsRecorder({ config, server: { name: mcp.name, version: mcp.version } });
|
|
232
|
+
}
|
|
233
|
+
|
|
47
234
|
// src/core/promise.ts
|
|
48
235
|
function cachedPromise(load, label) {
|
|
49
236
|
let settled = false;
|
|
@@ -409,12 +596,13 @@ function assertRequiredScopes(auth, context) {
|
|
|
409
596
|
throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
|
|
410
597
|
}
|
|
411
598
|
}
|
|
412
|
-
function createRequestAuthorizer(mcp, options = {}) {
|
|
599
|
+
function createRequestAuthorizer(mcp, options = {}, recorder) {
|
|
413
600
|
const runtime = getOAuthRuntime(mcp, options);
|
|
414
601
|
return {
|
|
415
602
|
async authorize(request) {
|
|
416
603
|
if (runtime.kind === "unconfigured")
|
|
417
604
|
return { ok: true };
|
|
605
|
+
const startedAt = nowMs();
|
|
418
606
|
const token = parseBearerToken(request);
|
|
419
607
|
if (!token) {
|
|
420
608
|
log.info("auth.no_bearer_token", { outcome: "401" });
|
|
@@ -427,6 +615,12 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
427
615
|
} catch (err) {
|
|
428
616
|
if (err instanceof OAuthConfigurationError) {
|
|
429
617
|
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
618
|
+
recorder?.record({
|
|
619
|
+
tool: null,
|
|
620
|
+
method: "authorize",
|
|
621
|
+
outcome: "auth_config_error",
|
|
622
|
+
durationMs: nowMs() - startedAt
|
|
623
|
+
});
|
|
430
624
|
return { ok: false, response: oauthConfigurationErrorResponse() };
|
|
431
625
|
}
|
|
432
626
|
if (err instanceof OAuthTokenError) {
|
|
@@ -484,6 +678,9 @@ export {
|
|
|
484
678
|
JSON_HEADERS,
|
|
485
679
|
headResponse,
|
|
486
680
|
methodNotAllowed,
|
|
681
|
+
nowMs,
|
|
682
|
+
reportInvocation,
|
|
683
|
+
createRecorderForRuntime,
|
|
487
684
|
resolveProtectedResource,
|
|
488
685
|
assertResourcePathShape,
|
|
489
686
|
oauthConfigurationErrorResponse,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assertRestResourceBinding,
|
|
3
3
|
corsPreflightResponse,
|
|
4
|
+
createRecorderForRuntime,
|
|
4
5
|
createRequestAuthorizer,
|
|
5
6
|
headResponse,
|
|
6
7
|
methodNotAllowed,
|
|
7
8
|
withCors
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-H6BKTNJY.js";
|
|
9
10
|
|
|
10
11
|
// src/protocols/rest/list-tools.ts
|
|
11
12
|
import { objectFromShape } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
@@ -32,9 +33,9 @@ function buildMcpListing(mcp) {
|
|
|
32
33
|
}))
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
|
-
function createListToolsHandler(mcp, options = {}) {
|
|
36
|
+
function createListToolsHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
|
|
36
37
|
assertRestResourceBinding(mcp, options);
|
|
37
|
-
const authorizer = createRequestAuthorizer(mcp, options);
|
|
38
|
+
const authorizer = createRequestAuthorizer(mcp, options, recorder);
|
|
38
39
|
const handle = async (request) => {
|
|
39
40
|
const authResult = await authorizer.authorize(request);
|
|
40
41
|
if (!authResult.ok)
|
|
@@ -41,40 +41,6 @@ var ToolContext = class {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
// src/metrics/config.ts
|
|
45
|
-
var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
|
|
46
|
-
var METRICS_API_KEY_ENV_VAR = "LOVABLE_API_KEY";
|
|
47
|
-
var METRICS_SAMPLE_RATE = 1;
|
|
48
|
-
var METRICS_FLUSH_INTERVAL_MS = 5e3;
|
|
49
|
-
var METRICS_MAX_BATCH_SIZE = 50;
|
|
50
|
-
function assertMetricsEndpoint(endpoint) {
|
|
51
|
-
let url;
|
|
52
|
-
try {
|
|
53
|
-
url = new URL(endpoint);
|
|
54
|
-
} catch {
|
|
55
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be an absolute URL, got ${JSON.stringify(endpoint)}`);
|
|
56
|
-
}
|
|
57
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
58
|
-
throw new Error(`@lovable.dev/mcp-js: metrics.endpoint must be http(s), got ${JSON.stringify(endpoint)}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
function resolveMetricsConfig(config = true) {
|
|
62
|
-
const options = typeof config === "boolean" ? { enabled: config } : config;
|
|
63
|
-
const endpoint = options.endpoint ?? DEFAULT_METRICS_ENDPOINT;
|
|
64
|
-
assertMetricsEndpoint(endpoint);
|
|
65
|
-
return Object.freeze({
|
|
66
|
-
enabled: options.enabled ?? true,
|
|
67
|
-
endpoint,
|
|
68
|
-
headers: options.headers ?? {},
|
|
69
|
-
apiKeyEnvVar: METRICS_API_KEY_ENV_VAR,
|
|
70
|
-
sampleRate: METRICS_SAMPLE_RATE,
|
|
71
|
-
flushIntervalMs: METRICS_FLUSH_INTERVAL_MS,
|
|
72
|
-
maxBatchSize: METRICS_MAX_BATCH_SIZE
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
44
|
export {
|
|
77
|
-
DEFAULT_METRICS_ENDPOINT,
|
|
78
|
-
resolveMetricsConfig,
|
|
79
45
|
ToolContext
|
|
80
46
|
};
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
oauthConfigurationErrorResponse,
|
|
8
8
|
resolveProtectedResource,
|
|
9
9
|
withCors
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-H6BKTNJY.js";
|
|
11
11
|
import {
|
|
12
12
|
describeError,
|
|
13
13
|
log
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-DCWYK4CA.js";
|
|
15
15
|
|
|
16
16
|
// src/protocols/oauth-metadata.ts
|
|
17
17
|
function notFound() {
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createRecorderForRuntime,
|
|
3
|
-
nowMs
|
|
4
|
-
} from "./chunk-XQSTN54Y.js";
|
|
5
1
|
import {
|
|
6
2
|
ToolContext
|
|
7
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-MA5H6PSF.js";
|
|
8
4
|
import {
|
|
9
5
|
JSON_HEADERS,
|
|
10
6
|
assertRestResourceBinding,
|
|
11
7
|
corsPreflightResponse,
|
|
8
|
+
createRecorderForRuntime,
|
|
12
9
|
createRequestAuthorizer,
|
|
13
10
|
methodNotAllowed,
|
|
11
|
+
nowMs,
|
|
12
|
+
reportInvocation,
|
|
14
13
|
withCors
|
|
15
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-H6BKTNJY.js";
|
|
16
15
|
|
|
17
16
|
// src/protocols/rest/invoke-tool.ts
|
|
18
17
|
import { getParseErrorMessage, objectFromShape, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
@@ -30,7 +29,7 @@ function isEmptyArgs(value) {
|
|
|
30
29
|
}
|
|
31
30
|
function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderForRuntime(mcp)) {
|
|
32
31
|
assertRestResourceBinding(mcp, options);
|
|
33
|
-
const authorizer = createRequestAuthorizer(mcp, options);
|
|
32
|
+
const authorizer = createRequestAuthorizer(mcp, options, recorder);
|
|
34
33
|
const handle = async (request, toolName) => {
|
|
35
34
|
const authResult = await authorizer.authorize(request);
|
|
36
35
|
if (!authResult.ok)
|
|
@@ -88,20 +87,30 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
88
87
|
try {
|
|
89
88
|
result = await tool.handler(args, new ToolContext(authResult.auth));
|
|
90
89
|
} catch {
|
|
91
|
-
recorder
|
|
90
|
+
reportInvocation(recorder, {
|
|
91
|
+
tool: tool.name,
|
|
92
|
+
method: "tools/call",
|
|
93
|
+
outcome: "handler_error",
|
|
94
|
+
durationMs: nowMs() - start
|
|
95
|
+
});
|
|
92
96
|
return new Response(JSON.stringify({ error: "handler threw", tool: toolName }), {
|
|
93
97
|
status: 500,
|
|
94
98
|
headers: JSON_HEADERS
|
|
95
99
|
});
|
|
96
100
|
}
|
|
97
101
|
if (result == null) {
|
|
98
|
-
recorder
|
|
102
|
+
reportInvocation(recorder, {
|
|
103
|
+
tool: tool.name,
|
|
104
|
+
method: "tools/call",
|
|
105
|
+
outcome: "handler_error",
|
|
106
|
+
durationMs: nowMs() - start
|
|
107
|
+
});
|
|
99
108
|
return new Response(JSON.stringify({ error: `tool "${toolName}" returned no result` }), {
|
|
100
109
|
status: 500,
|
|
101
110
|
headers: JSON_HEADERS
|
|
102
111
|
});
|
|
103
112
|
}
|
|
104
|
-
recorder
|
|
113
|
+
reportInvocation(recorder, {
|
|
105
114
|
tool: tool.name,
|
|
106
115
|
method: "tools/call",
|
|
107
116
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
buildMcpListing
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-I5CAQUQI.js";
|
|
5
|
+
import "../chunk-H6BKTNJY.js";
|
|
6
|
+
import "../chunk-DCWYK4CA.js";
|
|
7
7
|
import "../chunk-6DXGZZA4.js";
|
|
8
8
|
import {
|
|
9
9
|
isFileMissing
|
|
10
10
|
} from "../chunk-Y3ZFPEQH.js";
|
|
11
11
|
import {
|
|
12
12
|
version
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-SKSKC747.js";
|
|
14
14
|
|
|
15
15
|
// src/manifest/io.ts
|
|
16
16
|
import { randomUUID } from "crypto";
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ToolContext
|
|
3
|
-
|
|
4
|
-
} from "./chunk-P3F324UC.js";
|
|
2
|
+
ToolContext
|
|
3
|
+
} from "./chunk-MA5H6PSF.js";
|
|
5
4
|
import {
|
|
6
5
|
parseSafeUrl,
|
|
6
|
+
resolveMetricsConfig,
|
|
7
7
|
setLogLevel
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DCWYK4CA.js";
|
|
9
9
|
|
|
10
10
|
// src/core/define.ts
|
|
11
11
|
function assertUniqueNames(mcp) {
|