@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
|
@@ -11,11 +11,10 @@ interface InvocationRecord {
|
|
|
11
11
|
reqBytes?: number;
|
|
12
12
|
resBytes?: number;
|
|
13
13
|
}
|
|
14
|
+
/** A per-request telemetry recorder. `emit` logs the invocation and sends it as a
|
|
15
|
+
* single OTLP POST, awaited so it lands before the request ends. */
|
|
14
16
|
interface MetricsRecorder {
|
|
15
|
-
|
|
16
|
-
record(ev: InvocationRecord): void;
|
|
17
|
-
/** Best-effort POST of the current buffer. Resolves even on network failure. */
|
|
18
|
-
flush(): Promise<void>;
|
|
17
|
+
emit(ev: InvocationRecord): Promise<void>;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
export type { MetricsRecorder as M };
|
|
@@ -5,13 +5,12 @@ import {
|
|
|
5
5
|
JSON_HEADERS,
|
|
6
6
|
assertRestResourceBinding,
|
|
7
7
|
corsPreflightResponse,
|
|
8
|
-
|
|
8
|
+
createNoopRecorder,
|
|
9
9
|
createRequestAuthorizer,
|
|
10
10
|
methodNotAllowed,
|
|
11
11
|
nowMs,
|
|
12
|
-
reportInvocation,
|
|
13
12
|
withCors
|
|
14
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-5FX6IIQ6.js";
|
|
15
14
|
|
|
16
15
|
// src/protocols/rest/invoke-tool.ts
|
|
17
16
|
import { getParseErrorMessage, objectFromShape, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
@@ -27,11 +26,11 @@ function isEmptyArgs(value) {
|
|
|
27
26
|
return false;
|
|
28
27
|
return Object.keys(value).length === 0;
|
|
29
28
|
}
|
|
30
|
-
function createInvokeToolHandler(mcp, options = {}
|
|
29
|
+
function createInvokeToolHandler(mcp, options = {}) {
|
|
31
30
|
assertRestResourceBinding(mcp, options);
|
|
32
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
33
|
-
const handle = async (request, toolName) => {
|
|
34
|
-
const authResult = await authorizer.authorize(request);
|
|
31
|
+
const authorizer = createRequestAuthorizer(mcp, options);
|
|
32
|
+
const handle = async (request, toolName, recorder) => {
|
|
33
|
+
const authResult = await authorizer.authorize(request, recorder);
|
|
35
34
|
if (!authResult.ok)
|
|
36
35
|
return authResult.response;
|
|
37
36
|
if (request.method !== "POST")
|
|
@@ -87,7 +86,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
87
86
|
try {
|
|
88
87
|
result = await tool.handler(args, new ToolContext(authResult.auth));
|
|
89
88
|
} catch {
|
|
90
|
-
|
|
89
|
+
await recorder.emit({
|
|
91
90
|
tool: tool.name,
|
|
92
91
|
method: "tools/call",
|
|
93
92
|
outcome: "handler_error",
|
|
@@ -99,7 +98,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
99
98
|
});
|
|
100
99
|
}
|
|
101
100
|
if (result == null) {
|
|
102
|
-
|
|
101
|
+
await recorder.emit({
|
|
103
102
|
tool: tool.name,
|
|
104
103
|
method: "tools/call",
|
|
105
104
|
outcome: "handler_error",
|
|
@@ -110,7 +109,7 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
110
109
|
headers: JSON_HEADERS
|
|
111
110
|
});
|
|
112
111
|
}
|
|
113
|
-
|
|
112
|
+
await recorder.emit({
|
|
114
113
|
tool: tool.name,
|
|
115
114
|
method: "tools/call",
|
|
116
115
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -122,10 +121,10 @@ function createInvokeToolHandler(mcp, options = {}, recorder = createRecorderFor
|
|
|
122
121
|
isError: result.isError
|
|
123
122
|
});
|
|
124
123
|
};
|
|
125
|
-
return async (request, toolName) => {
|
|
124
|
+
return async (request, toolName, recorder = createNoopRecorder()) => {
|
|
126
125
|
if (request.method === "OPTIONS")
|
|
127
126
|
return corsPreflightResponse("POST, OPTIONS");
|
|
128
|
-
return withCors(await handle(request, toolName));
|
|
127
|
+
return withCors(await handle(request, toolName, recorder));
|
|
129
128
|
};
|
|
130
129
|
}
|
|
131
130
|
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_METRICS_ENDPOINT,
|
|
3
|
+
LOG_LEVEL_ENV_VAR,
|
|
4
|
+
applyLogLevelFromEnv,
|
|
3
5
|
describeError,
|
|
4
6
|
log,
|
|
5
7
|
parseSafeUrl,
|
|
6
8
|
resolveMetricsConfig,
|
|
7
9
|
trimTrailingSlash
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-H37EB22A.js";
|
|
9
11
|
import {
|
|
10
12
|
OAUTH_PROTECTED_RESOURCE_METADATA_PATH
|
|
11
13
|
} from "./chunk-6DXGZZA4.js";
|
|
12
14
|
import {
|
|
13
15
|
version
|
|
14
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-CDZ7MBXC.js";
|
|
15
17
|
|
|
16
18
|
// src/core/http.ts
|
|
17
19
|
var JSON_HEADERS = { "Content-Type": "application/json" };
|
|
@@ -72,6 +74,7 @@ function toLogRecord(rec) {
|
|
|
72
74
|
attributes.push(intAttr("mcp.request_size_bytes", rec.reqBytes));
|
|
73
75
|
if (rec.resBytes !== void 0)
|
|
74
76
|
attributes.push(intAttr("mcp.response_size_bytes", rec.resBytes));
|
|
77
|
+
attributes.push(strAttr("mcp.stack", rec.stack));
|
|
75
78
|
return {
|
|
76
79
|
timeUnixNano: rec.timeUnixNano,
|
|
77
80
|
observedTimeUnixNano: rec.timeUnixNano,
|
|
@@ -108,127 +111,148 @@ function nowUnixNano() {
|
|
|
108
111
|
return `${Date.now()}000000`;
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
// src/metrics/
|
|
114
|
+
// src/metrics/impl/base.ts
|
|
112
115
|
function nowMs() {
|
|
113
116
|
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
114
117
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
recorder.record(ev);
|
|
118
|
-
}
|
|
119
|
-
var NOOP_RECORDER = {
|
|
120
|
-
record() {
|
|
121
|
-
},
|
|
122
|
-
async flush() {
|
|
123
|
-
}
|
|
124
|
-
};
|
|
118
|
+
var NOOP_RECORDER = { async emit() {
|
|
119
|
+
} };
|
|
125
120
|
function createNoopRecorder() {
|
|
126
121
|
return NOOP_RECORDER;
|
|
127
122
|
}
|
|
128
|
-
function
|
|
123
|
+
function readProcessEnv(name) {
|
|
129
124
|
try {
|
|
130
|
-
|
|
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
|
-
}
|
|
125
|
+
return typeof process !== "undefined" ? process.env?.[name] || void 0 : void 0;
|
|
142
126
|
} catch {
|
|
127
|
+
return void 0;
|
|
143
128
|
}
|
|
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
129
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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));
|
|
130
|
+
var BaseMetricRecorder = class {
|
|
131
|
+
constructor(config, server, deps = {}) {
|
|
132
|
+
this.config = config;
|
|
133
|
+
this.server = server;
|
|
134
|
+
this.deps = deps;
|
|
135
|
+
this.doFetch = deps.fetch ?? globalThis.fetch;
|
|
136
|
+
this.usesLovableEndpoint = config.endpoint === DEFAULT_METRICS_ENDPOINT;
|
|
137
|
+
const headers = {};
|
|
138
|
+
if (!this.usesLovableEndpoint) {
|
|
139
|
+
for (const [key, value] of Object.entries(config.headers)) {
|
|
140
|
+
if (key.toLowerCase() !== "content-type")
|
|
141
|
+
headers[key] = value;
|
|
175
142
|
}
|
|
176
143
|
}
|
|
177
|
-
|
|
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
|
-
}
|
|
144
|
+
headers["content-type"] = "application/json";
|
|
145
|
+
this.baseHeaders = headers;
|
|
186
146
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
147
|
+
doFetch;
|
|
148
|
+
usesLovableEndpoint;
|
|
149
|
+
baseHeaders;
|
|
150
|
+
apiKey;
|
|
151
|
+
lazyLogLevelApplied = false;
|
|
152
|
+
async emit(ev) {
|
|
153
|
+
await this.applyLazyLogLevel();
|
|
154
|
+
log.info("tool.invoked", {
|
|
155
|
+
tool: ev.tool,
|
|
156
|
+
method: ev.method,
|
|
157
|
+
outcome: ev.outcome,
|
|
158
|
+
durationMs: ev.durationMs,
|
|
159
|
+
stack: this.stack
|
|
160
|
+
});
|
|
161
|
+
if (!this.config.enabled)
|
|
192
162
|
return;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
log.warn("metrics.flush_rejected", { status: res.status, dropped, endpoint: config.endpoint });
|
|
163
|
+
if (!this.doFetch) {
|
|
164
|
+
log.warn("metrics.disabled_no_fetch", { endpoint: this.config.endpoint });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const headers = { ...this.baseHeaders };
|
|
168
|
+
if (this.usesLovableEndpoint) {
|
|
169
|
+
const key = await this.resolveApiKey();
|
|
170
|
+
if (!key) {
|
|
171
|
+
log.warn("metrics.disabled_no_api_key", { envVar: this.config.apiKeyEnvVar, endpoint: this.config.endpoint });
|
|
172
|
+
return;
|
|
204
173
|
}
|
|
174
|
+
headers.authorization = `Bearer ${key}`;
|
|
175
|
+
}
|
|
176
|
+
const body = buildLogsPayload([{ ...ev, timeUnixNano: nowUnixNano(), stack: this.stack }], this.server);
|
|
177
|
+
try {
|
|
178
|
+
const res = await this.doFetch(this.config.endpoint, { method: "POST", headers, body, keepalive: true });
|
|
179
|
+
if (!res.ok)
|
|
180
|
+
log.warn("metrics.rejected", { status: res.status, endpoint: this.config.endpoint });
|
|
205
181
|
} catch (err) {
|
|
206
|
-
log.warn("metrics.
|
|
182
|
+
log.warn("metrics.failed", { ...describeError(err), endpoint: this.config.endpoint });
|
|
207
183
|
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
184
|
+
}
|
|
185
|
+
// Cache only a successful resolution so a miss is retried on the next emit (the
|
|
186
|
+
// Cloudflare binding may not be readable on the very first attempt in an isolate).
|
|
187
|
+
async resolveApiKey() {
|
|
188
|
+
if (this.apiKey)
|
|
189
|
+
return this.apiKey;
|
|
190
|
+
this.apiKey = this.deps.getApiKey ? this.deps.getApiKey() : await this.readEnv(this.config.apiKeyEnvVar);
|
|
191
|
+
return this.apiKey;
|
|
192
|
+
}
|
|
193
|
+
async applyLazyLogLevel() {
|
|
194
|
+
if (this.lazyLogLevelApplied)
|
|
211
195
|
return;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
196
|
+
this.lazyLogLevelApplied = true;
|
|
197
|
+
applyLogLevelFromEnv(await this.readEnv(LOG_LEVEL_ENV_VAR));
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/metrics/impl/cloudflare.ts
|
|
202
|
+
var cloudflareEnvPromise;
|
|
203
|
+
async function readCloudflareEnv(name) {
|
|
204
|
+
try {
|
|
205
|
+
const moduleSpecifier = "cloudflare:workers";
|
|
206
|
+
cloudflareEnvPromise ??= import(
|
|
207
|
+
/* @vite-ignore */
|
|
208
|
+
moduleSpecifier
|
|
209
|
+
).then((m) => m.env).catch((err) => {
|
|
210
|
+
log.debug("metrics.cloudflare_env_import_failed", describeError(err));
|
|
211
|
+
return void 0;
|
|
212
|
+
});
|
|
213
|
+
const env = await cloudflareEnvPromise;
|
|
214
|
+
const raw = env?.[name];
|
|
215
|
+
const value = typeof raw === "string" && raw ? raw : void 0;
|
|
216
|
+
log.debug("metrics.read_cloudflare_env", { name, hasEnvBinding: !!env, found: value !== void 0 });
|
|
217
|
+
return value;
|
|
218
|
+
} catch (err) {
|
|
219
|
+
log.debug("metrics.read_cloudflare_env_error", { name, ...describeError(err) });
|
|
220
|
+
return void 0;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
var TanStackMetricRecorder = class extends BaseMetricRecorder {
|
|
224
|
+
stack = "tanstack";
|
|
225
|
+
async readEnv(name) {
|
|
226
|
+
return readProcessEnv(name) ?? await readCloudflareEnv(name);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// src/metrics/impl/supabase.ts
|
|
231
|
+
function readDenoEnv(name) {
|
|
232
|
+
try {
|
|
233
|
+
const denoEnv = globalThis.Deno?.env;
|
|
234
|
+
const value = denoEnv?.get?.(name) || void 0;
|
|
235
|
+
log.debug("metrics.read_deno_env", { name, hasDenoEnv: !!denoEnv, found: !!value });
|
|
236
|
+
return value;
|
|
237
|
+
} catch (err) {
|
|
238
|
+
log.debug("metrics.read_deno_env_error", { name, ...describeError(err) });
|
|
239
|
+
return void 0;
|
|
240
|
+
}
|
|
226
241
|
}
|
|
227
|
-
|
|
242
|
+
var SupabaseMetricRecorder = class extends BaseMetricRecorder {
|
|
243
|
+
stack = "supabase";
|
|
244
|
+
async readEnv(name) {
|
|
245
|
+
return readDenoEnv(name) ?? readProcessEnv(name);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// src/metrics/recorder.ts
|
|
250
|
+
function createRecorderForRuntime(mcp, opts) {
|
|
228
251
|
const config = resolveMetricsConfig(mcp.metrics);
|
|
252
|
+
const server = { name: mcp.name, version: mcp.version };
|
|
229
253
|
if (!config.enabled)
|
|
230
254
|
return createNoopRecorder();
|
|
231
|
-
return
|
|
255
|
+
return opts.stack === "tanstack" ? new TanStackMetricRecorder(config, server) : new SupabaseMetricRecorder(config, server);
|
|
232
256
|
}
|
|
233
257
|
|
|
234
258
|
// src/core/promise.ts
|
|
@@ -596,10 +620,10 @@ function assertRequiredScopes(auth, context) {
|
|
|
596
620
|
throw new OAuthTokenError(403, "insufficient_scope", "Additional OAuth scope is required");
|
|
597
621
|
}
|
|
598
622
|
}
|
|
599
|
-
function createRequestAuthorizer(mcp, options = {}
|
|
623
|
+
function createRequestAuthorizer(mcp, options = {}) {
|
|
600
624
|
const runtime = getOAuthRuntime(mcp, options);
|
|
601
625
|
return {
|
|
602
|
-
async authorize(request) {
|
|
626
|
+
async authorize(request, recorder) {
|
|
603
627
|
if (runtime.kind === "unconfigured")
|
|
604
628
|
return { ok: true };
|
|
605
629
|
const startedAt = nowMs();
|
|
@@ -615,7 +639,7 @@ function createRequestAuthorizer(mcp, options = {}, recorder) {
|
|
|
615
639
|
} catch (err) {
|
|
616
640
|
if (err instanceof OAuthConfigurationError) {
|
|
617
641
|
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
618
|
-
recorder?.
|
|
642
|
+
await recorder?.emit({
|
|
619
643
|
tool: null,
|
|
620
644
|
method: "authorize",
|
|
621
645
|
outcome: "auth_config_error",
|
|
@@ -679,7 +703,7 @@ export {
|
|
|
679
703
|
headResponse,
|
|
680
704
|
methodNotAllowed,
|
|
681
705
|
nowMs,
|
|
682
|
-
|
|
706
|
+
createNoopRecorder,
|
|
683
707
|
createRecorderForRuntime,
|
|
684
708
|
resolveProtectedResource,
|
|
685
709
|
assertResourcePathShape,
|
|
@@ -3,16 +3,15 @@ import {
|
|
|
3
3
|
} from "./chunk-MA5H6PSF.js";
|
|
4
4
|
import {
|
|
5
5
|
corsPreflightResponse,
|
|
6
|
-
|
|
6
|
+
createNoopRecorder,
|
|
7
7
|
createRequestAuthorizer,
|
|
8
8
|
nowMs,
|
|
9
|
-
reportInvocation,
|
|
10
9
|
withCors
|
|
11
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5FX6IIQ6.js";
|
|
12
11
|
import {
|
|
13
12
|
describeError,
|
|
14
13
|
log
|
|
15
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-H37EB22A.js";
|
|
16
15
|
|
|
17
16
|
// src/protocols/mcp/protocol.ts
|
|
18
17
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -25,7 +24,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
25
24
|
try {
|
|
26
25
|
result = await tool.handler(args, new ToolContext(auth));
|
|
27
26
|
} catch {
|
|
28
|
-
|
|
27
|
+
await recorder.emit({
|
|
29
28
|
tool: tool.name,
|
|
30
29
|
method: "tools/call",
|
|
31
30
|
outcome: "handler_error",
|
|
@@ -34,7 +33,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
34
33
|
return { content: [{ type: "text", text: "tool execution failed" }], isError: true };
|
|
35
34
|
}
|
|
36
35
|
if (result == null) {
|
|
37
|
-
|
|
36
|
+
await recorder.emit({
|
|
38
37
|
tool: tool.name,
|
|
39
38
|
method: "tools/call",
|
|
40
39
|
outcome: "handler_error",
|
|
@@ -42,7 +41,7 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
42
41
|
});
|
|
43
42
|
return { content: [{ type: "text", text: `tool "${tool.name}" returned no result` }], isError: true };
|
|
44
43
|
}
|
|
45
|
-
|
|
44
|
+
await recorder.emit({
|
|
46
45
|
tool: tool.name,
|
|
47
46
|
method: "tools/call",
|
|
48
47
|
outcome: result.isError ? "tool_error" : "ok",
|
|
@@ -51,10 +50,10 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
51
50
|
return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
|
|
52
51
|
};
|
|
53
52
|
}
|
|
54
|
-
function createMcpProtocolHandler(mcp, options = {}
|
|
55
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
56
|
-
const handle = async (request) => {
|
|
57
|
-
const authResult = await authorizer.authorize(request);
|
|
53
|
+
function createMcpProtocolHandler(mcp, options = {}) {
|
|
54
|
+
const authorizer = createRequestAuthorizer(mcp, options);
|
|
55
|
+
const handle = async (request, recorder) => {
|
|
56
|
+
const authResult = await authorizer.authorize(request, recorder);
|
|
58
57
|
if (!authResult.ok)
|
|
59
58
|
return authResult.response;
|
|
60
59
|
try {
|
|
@@ -81,7 +80,7 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
|
|
|
81
80
|
await server.connect(transport);
|
|
82
81
|
return await transport.handleRequest(request);
|
|
83
82
|
} catch (err) {
|
|
84
|
-
recorder.
|
|
83
|
+
await recorder.emit({ tool: null, method: "transport", outcome: "transport_error", durationMs: 0 });
|
|
85
84
|
log.error("mcp.transport_error", { ...describeError(err), outcome: "500 internal error" });
|
|
86
85
|
return Response.json(
|
|
87
86
|
{ jsonrpc: "2.0", id: null, error: { code: -32603, message: "internal error" } },
|
|
@@ -89,10 +88,10 @@ function createMcpProtocolHandler(mcp, options = {}, recorder = createRecorderFo
|
|
|
89
88
|
);
|
|
90
89
|
}
|
|
91
90
|
};
|
|
92
|
-
return async (request) => {
|
|
91
|
+
return async (request, recorder = createNoopRecorder()) => {
|
|
93
92
|
if (request.method === "OPTIONS")
|
|
94
93
|
return corsPreflightResponse("GET, POST, DELETE, OPTIONS");
|
|
95
|
-
return withCors(await handle(request));
|
|
94
|
+
return withCors(await handle(request, recorder));
|
|
96
95
|
};
|
|
97
96
|
}
|
|
98
97
|
|
|
@@ -3,19 +3,27 @@ var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
|
3
3
|
function isLogLevel(value) {
|
|
4
4
|
return typeof value === "string" && value in LEVEL_RANK;
|
|
5
5
|
}
|
|
6
|
+
var LOG_LEVEL_ENV_VAR = "LOVABLE_MCP_LOG_LEVEL";
|
|
6
7
|
function readEnvLevel() {
|
|
7
8
|
try {
|
|
8
|
-
const raw = typeof process !== "undefined" ? process.env?.[
|
|
9
|
+
const raw = typeof process !== "undefined" ? process.env?.[LOG_LEVEL_ENV_VAR] : void 0;
|
|
9
10
|
const normalized = raw?.trim().toLowerCase();
|
|
10
11
|
return isLogLevel(normalized) ? normalized : void 0;
|
|
11
12
|
} catch {
|
|
12
13
|
return void 0;
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
|
-
var currentLevel = readEnvLevel() ?? "
|
|
16
|
+
var currentLevel = readEnvLevel() ?? "debug";
|
|
16
17
|
function setLogLevel(level) {
|
|
17
18
|
currentLevel = level;
|
|
18
19
|
}
|
|
20
|
+
function parseLogLevel(raw) {
|
|
21
|
+
const normalized = raw?.trim().toLowerCase();
|
|
22
|
+
return isLogLevel(normalized) ? normalized : void 0;
|
|
23
|
+
}
|
|
24
|
+
function applyLogLevelFromEnv(raw) {
|
|
25
|
+
setLogLevel(parseLogLevel(raw) ?? "debug");
|
|
26
|
+
}
|
|
19
27
|
function enabled(level) {
|
|
20
28
|
return LEVEL_RANK[level] <= LEVEL_RANK[currentLevel];
|
|
21
29
|
}
|
|
@@ -45,9 +53,6 @@ function describeError(err) {
|
|
|
45
53
|
// src/metrics/config.ts
|
|
46
54
|
var DEFAULT_METRICS_ENDPOINT = "https://api.lovable.dev/v1/app-mcp-usage";
|
|
47
55
|
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
56
|
function assertMetricsEndpoint(endpoint) {
|
|
52
57
|
let url;
|
|
53
58
|
try {
|
|
@@ -67,10 +72,7 @@ function resolveMetricsConfig(config = true) {
|
|
|
67
72
|
enabled: options.enabled ?? true,
|
|
68
73
|
endpoint,
|
|
69
74
|
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
|
|
75
|
+
apiKeyEnvVar: METRICS_API_KEY_ENV_VAR
|
|
74
76
|
});
|
|
75
77
|
}
|
|
76
78
|
|
|
@@ -115,7 +117,9 @@ export {
|
|
|
115
117
|
resolveMetricsConfig,
|
|
116
118
|
trimTrailingSlash,
|
|
117
119
|
parseSafeUrl,
|
|
120
|
+
LOG_LEVEL_ENV_VAR,
|
|
118
121
|
setLogLevel,
|
|
122
|
+
applyLogLevelFromEnv,
|
|
119
123
|
log,
|
|
120
124
|
describeError
|
|
121
125
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assertRestResourceBinding,
|
|
3
3
|
corsPreflightResponse,
|
|
4
|
-
|
|
4
|
+
createNoopRecorder,
|
|
5
5
|
createRequestAuthorizer,
|
|
6
6
|
headResponse,
|
|
7
7
|
methodNotAllowed,
|
|
8
8
|
withCors
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5FX6IIQ6.js";
|
|
10
10
|
|
|
11
11
|
// src/protocols/rest/list-tools.ts
|
|
12
12
|
import { objectFromShape } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
@@ -33,11 +33,11 @@ function buildMcpListing(mcp) {
|
|
|
33
33
|
}))
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
function createListToolsHandler(mcp, options = {}
|
|
36
|
+
function createListToolsHandler(mcp, options = {}) {
|
|
37
37
|
assertRestResourceBinding(mcp, options);
|
|
38
|
-
const authorizer = createRequestAuthorizer(mcp, options
|
|
39
|
-
const handle = async (request) => {
|
|
40
|
-
const authResult = await authorizer.authorize(request);
|
|
38
|
+
const authorizer = createRequestAuthorizer(mcp, options);
|
|
39
|
+
const handle = async (request, recorder) => {
|
|
40
|
+
const authResult = await authorizer.authorize(request, recorder);
|
|
41
41
|
if (!authResult.ok)
|
|
42
42
|
return authResult.response;
|
|
43
43
|
if (request.method !== "GET" && request.method !== "HEAD")
|
|
@@ -45,10 +45,10 @@ function createListToolsHandler(mcp, options = {}, recorder = createRecorderForR
|
|
|
45
45
|
const response = Response.json(buildMcpListing(mcp));
|
|
46
46
|
return request.method === "HEAD" ? headResponse(response) : response;
|
|
47
47
|
};
|
|
48
|
-
return async (request) => {
|
|
48
|
+
return async (request, recorder = createNoopRecorder()) => {
|
|
49
49
|
if (request.method === "OPTIONS")
|
|
50
50
|
return corsPreflightResponse("GET, HEAD, OPTIONS");
|
|
51
|
-
return withCors(await handle(request));
|
|
51
|
+
return withCors(await handle(request, recorder));
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
oauthConfigurationErrorResponse,
|
|
8
8
|
resolveProtectedResource,
|
|
9
9
|
withCors
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5FX6IIQ6.js";
|
|
11
11
|
import {
|
|
12
12
|
describeError,
|
|
13
13
|
log
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-H37EB22A.js";
|
|
15
15
|
|
|
16
16
|
// src/protocols/oauth-metadata.ts
|
|
17
17
|
function notFound() {
|
|
@@ -13,7 +13,7 @@ function isFileMissing(err) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// package.json
|
|
16
|
-
var version = "0.
|
|
16
|
+
var version = "0.19.0";
|
|
17
17
|
|
|
18
18
|
// src/protocols/rest/list-tools.ts
|
|
19
19
|
var import_zod_compat = require("@modelcontextprotocol/sdk/server/zod-compat.js");
|
|
@@ -24,16 +24,17 @@ var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
|
24
24
|
function isLogLevel(value) {
|
|
25
25
|
return typeof value === "string" && value in LEVEL_RANK;
|
|
26
26
|
}
|
|
27
|
+
var LOG_LEVEL_ENV_VAR = "LOVABLE_MCP_LOG_LEVEL";
|
|
27
28
|
function readEnvLevel() {
|
|
28
29
|
try {
|
|
29
|
-
const raw = typeof process !== "undefined" ? process.env?.[
|
|
30
|
+
const raw = typeof process !== "undefined" ? process.env?.[LOG_LEVEL_ENV_VAR] : void 0;
|
|
30
31
|
const normalized = raw?.trim().toLowerCase();
|
|
31
32
|
return isLogLevel(normalized) ? normalized : void 0;
|
|
32
33
|
} catch {
|
|
33
34
|
return void 0;
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
|
-
var currentLevel = readEnvLevel() ?? "
|
|
37
|
+
var currentLevel = readEnvLevel() ?? "debug";
|
|
37
38
|
|
|
38
39
|
// src/auth/verifier.ts
|
|
39
40
|
var import_jose = require("jose");
|
|
@@ -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-OMWXY6WY.js";
|
|
5
|
+
import "../chunk-5FX6IIQ6.js";
|
|
6
|
+
import "../chunk-H37EB22A.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-CDZ7MBXC.js";
|
|
14
14
|
|
|
15
15
|
// src/manifest/io.ts
|
|
16
16
|
import { randomUUID } from "crypto";
|