@lovable.dev/mcp-js 0.29.0-rc.4 → 0.30.0-rc.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/README.internal.md +3 -3
- package/README.md +25 -2
- package/dist/{authorize-DIYS7zBP.d.cts → authorize-9hTTG-qJ.d.cts} +1 -1
- package/dist/{authorize-CqYsQYfa.d.ts → authorize-DNUlPT7k.d.ts} +1 -1
- package/dist/base-7cq6HCPO.d.ts +36 -0
- package/dist/base-CHNFFrMG.d.cts +36 -0
- package/dist/cli/extract-manifest.cjs +7 -12
- package/dist/cli/extract-manifest.js +7 -12
- package/dist/{authorize-b3VqTC_7.js → cors-5P69YRDK.js} +79 -28
- package/dist/{authorize-CKMlIxm9.cjs → cors-DIQIXIHY.cjs} +90 -33
- package/dist/{errors-DSe96Ffd.js → errors-Bwd1L0Rf.js} +23 -8
- package/dist/{errors-CN6Da8iz.cjs → errors-Cr95rSkB.cjs} +23 -8
- package/dist/forwarded-DG83ubwT.js +26 -0
- package/dist/forwarded-zNAIofM2.cjs +31 -0
- package/dist/index.cjs +38 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +38 -1
- package/dist/{io-Dx0nPc1-.d.cts → io-CDV3mud0.d.cts} +1 -1
- package/dist/{io-DP3icuxa.d.ts → io-D_5cuaEL.d.ts} +1 -1
- package/dist/mcp-Cb5SavFT.js +348 -0
- package/dist/mcp-Dq6dXUKT.cjs +353 -0
- package/dist/{package-Xk8p8Q_E.cjs → package-QViIguII.cjs} +1 -1
- package/dist/{package-DMs4EVwG.js → package-ZFJI6B7S.js} +1 -1
- package/dist/protocols/mcp/index.cjs +1 -1
- package/dist/protocols/mcp/index.d.cts +3 -33
- package/dist/protocols/mcp/index.d.ts +3 -33
- package/dist/protocols/mcp/index.js +1 -1
- package/dist/protocols/oauth-metadata.cjs +27 -10
- package/dist/protocols/oauth-metadata.d.cts +4 -3
- package/dist/protocols/oauth-metadata.d.ts +4 -3
- package/dist/protocols/oauth-metadata.js +21 -4
- package/dist/schema-CW1jKvio.js +65 -0
- package/dist/schema-D3vClxb7.cjs +76 -0
- package/dist/stacks/supabase/index.cjs +6 -6
- package/dist/stacks/supabase/index.d.cts +1 -1
- package/dist/stacks/supabase/index.d.ts +1 -1
- package/dist/stacks/supabase/index.js +4 -4
- package/dist/stacks/supabase/vite.cjs +1 -1
- package/dist/stacks/supabase/vite.d.cts +1 -1
- package/dist/stacks/supabase/vite.d.ts +1 -1
- package/dist/stacks/supabase/vite.js +1 -1
- package/dist/stacks/tanstack/index.cjs +5 -5
- package/dist/stacks/tanstack/index.d.cts +2 -2
- package/dist/stacks/tanstack/index.d.ts +2 -2
- package/dist/stacks/tanstack/index.js +4 -4
- package/dist/stacks/tanstack/vite.cjs +8 -4
- package/dist/stacks/tanstack/vite.d.cts +4 -1
- package/dist/stacks/tanstack/vite.d.ts +4 -1
- package/dist/stacks/tanstack/vite.js +8 -4
- package/dist/{types-C0Wgm9zp.d.cts → types-3CZPz364.d.cts} +65 -29
- package/dist/{types-C0Wgm9zp.d.ts → types-3CZPz364.d.ts} +65 -29
- package/package.json +5 -1
- package/dist/forwarded-Bhwcj22u.cjs +0 -48
- package/dist/forwarded-BlLF3dfc.js +0 -43
- package/dist/mcp-BLs6_g8i.js +0 -174
- package/dist/mcp-DtHM-41c.cjs +0 -179
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
const require_logger = require("./logger-CN1q-KNn.cjs");
|
|
2
|
+
const require_schema = require("./schema-D3vClxb7.cjs");
|
|
3
|
+
const require_errors = require("./errors-Cr95rSkB.cjs");
|
|
4
|
+
const require_cors = require("./cors-DIQIXIHY.cjs");
|
|
5
|
+
let _modelcontextprotocol_sdk_server_zod_compat_js = require("@modelcontextprotocol/sdk/server/zod-compat.js");
|
|
6
|
+
let _modelcontextprotocol_server = require("@modelcontextprotocol/server");
|
|
7
|
+
//#region src/core/content.ts
|
|
8
|
+
/** Join the text of a result's `type: "text"` blocks into one string, dropping
|
|
9
|
+
* binary/resource blocks. Used to surface a `tool_error` message in the local
|
|
10
|
+
* log; never sent over the telemetry wire. Returns undefined when there is no
|
|
11
|
+
* text to show. */
|
|
12
|
+
function extractTextContent(content) {
|
|
13
|
+
if (!content) return void 0;
|
|
14
|
+
const text = content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
|
|
15
|
+
return text.length > 0 ? text : void 0;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/core/tool-result.ts
|
|
19
|
+
function normalizeResourceLinkIcon(icon) {
|
|
20
|
+
if (typeof icon.sizes !== "string") return icon;
|
|
21
|
+
const { sizes, ...rest } = icon;
|
|
22
|
+
const normalized = sizes.trim();
|
|
23
|
+
return normalized === "" ? rest : {
|
|
24
|
+
...rest,
|
|
25
|
+
sizes: normalized.split(/\s+/)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function normalizeToolContent(content) {
|
|
29
|
+
return (content ?? []).map((block) => {
|
|
30
|
+
if (block.type !== "resource_link" || block.icons === void 0) return block;
|
|
31
|
+
return {
|
|
32
|
+
...block,
|
|
33
|
+
icons: block.icons.map(normalizeResourceLinkIcon)
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function outputValidationError(text) {
|
|
38
|
+
return {
|
|
39
|
+
content: [{
|
|
40
|
+
type: "text",
|
|
41
|
+
text
|
|
42
|
+
}],
|
|
43
|
+
isError: true
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function isJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
47
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") return true;
|
|
48
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
49
|
+
if (typeof value !== "object" || ancestors.has(value)) return false;
|
|
50
|
+
const prototype = Object.getPrototypeOf(value);
|
|
51
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) return false;
|
|
52
|
+
ancestors.add(value);
|
|
53
|
+
const valid = (Array.isArray(value) ? value : Object.values(value).filter((entry) => entry !== void 0)).every((entry) => isJsonValue(entry, ancestors));
|
|
54
|
+
ancestors.delete(value);
|
|
55
|
+
return valid;
|
|
56
|
+
}
|
|
57
|
+
async function finalizeToolResult(tool, result) {
|
|
58
|
+
const content = normalizeToolContent(result.content);
|
|
59
|
+
let structuredContent = result.structuredContent;
|
|
60
|
+
if (tool.outputSchema && !result.isError) {
|
|
61
|
+
if (structuredContent === void 0) return outputValidationError(`Output validation error: Tool ${tool.name} has an output schema but no structured content was provided`);
|
|
62
|
+
try {
|
|
63
|
+
const parsed = await (0, _modelcontextprotocol_sdk_server_zod_compat_js.safeParseAsync)(require_schema.schemaFromDefinition(tool.outputSchema), structuredContent);
|
|
64
|
+
if (!parsed.success) return outputValidationError(`Output validation error: Invalid structured content for tool ${tool.name}: ${(0, _modelcontextprotocol_sdk_server_zod_compat_js.getParseErrorMessage)(parsed.error)}`);
|
|
65
|
+
structuredContent = parsed.data;
|
|
66
|
+
} catch {
|
|
67
|
+
return outputValidationError(`Output validation error: Could not validate structured content for tool ${tool.name}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (structuredContent !== void 0 && !isJsonValue(structuredContent)) {
|
|
71
|
+
if (!result.isError) return outputValidationError(`Output validation error: Tool ${tool.name} returned a non-JSON structured value`);
|
|
72
|
+
structuredContent = void 0;
|
|
73
|
+
}
|
|
74
|
+
if (structuredContent !== void 0 && (structuredContent === null || typeof structuredContent !== "object" || Array.isArray(structuredContent)) && !content.some((block) => block.type === "text")) content.push({
|
|
75
|
+
type: "text",
|
|
76
|
+
text: JSON.stringify(structuredContent)
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
content,
|
|
80
|
+
structuredContent,
|
|
81
|
+
isError: result.isError
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/protocols/gate.ts
|
|
86
|
+
function createGatedHandler(gate) {
|
|
87
|
+
const allow = [...gate.methods, "OPTIONS"].join(", ");
|
|
88
|
+
const respond = async (request, recorder) => {
|
|
89
|
+
const authResult = await gate.authorizer.authorize(request, recorder);
|
|
90
|
+
if (!authResult.ok) return authResult.response;
|
|
91
|
+
if (!gate.methods.includes(request.method)) return gate.methodNotAllowed(allow);
|
|
92
|
+
return gate.handle(request, authResult, recorder);
|
|
93
|
+
};
|
|
94
|
+
return async (request, recorder = require_cors.createNoopRecorder()) => {
|
|
95
|
+
const cors = require_cors.evaluateCors(request, gate.allowedOrigins);
|
|
96
|
+
if (!cors.ok) {
|
|
97
|
+
const httpMethod = require_cors.normalizeHttpMethod(request.method);
|
|
98
|
+
require_logger.log.info("cors.origin_rejected", {
|
|
99
|
+
origin: request.headers.get("Origin"),
|
|
100
|
+
httpMethod
|
|
101
|
+
});
|
|
102
|
+
await recorder.emit({
|
|
103
|
+
tool: null,
|
|
104
|
+
method: "authorize",
|
|
105
|
+
outcome: "origin_rejected",
|
|
106
|
+
httpMethod,
|
|
107
|
+
durationMs: 0
|
|
108
|
+
});
|
|
109
|
+
return require_cors.finalizeWireResponse(request, cors.response);
|
|
110
|
+
}
|
|
111
|
+
if (request.method === "OPTIONS") return cors.preflight(allow);
|
|
112
|
+
const final = require_cors.finalizeWireResponse(request, cors.withHeaders(await respond(request, recorder)));
|
|
113
|
+
if (!final.headers.has("Cache-Control")) final.headers.set("Cache-Control", "no-store");
|
|
114
|
+
return final;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/protocols/mcp/protocol.ts
|
|
119
|
+
const MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
120
|
+
function statelessMethodNotAllowed(allow) {
|
|
121
|
+
return Response.json({
|
|
122
|
+
jsonrpc: "2.0",
|
|
123
|
+
id: null,
|
|
124
|
+
error: {
|
|
125
|
+
code: -32e3,
|
|
126
|
+
message: "Method not allowed: stateless server offers no standalone SSE stream (GET) or session (DELETE)."
|
|
127
|
+
}
|
|
128
|
+
}, {
|
|
129
|
+
status: 405,
|
|
130
|
+
headers: { Allow: allow }
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function zodIssues(error) {
|
|
134
|
+
const issues = error?.issues;
|
|
135
|
+
if (!Array.isArray(issues)) return [{ message: String(error) }];
|
|
136
|
+
return issues.map((issue) => {
|
|
137
|
+
const value = issue;
|
|
138
|
+
return {
|
|
139
|
+
message: typeof value.message === "string" ? value.message : String(value.message),
|
|
140
|
+
...Array.isArray(value.path) ? { path: value.path } : {}
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function standardSchemaFromDefinition(definition) {
|
|
145
|
+
const schema = require_schema.schemaFromDefinition(definition);
|
|
146
|
+
return { "~standard": {
|
|
147
|
+
version: 1,
|
|
148
|
+
vendor: "zod",
|
|
149
|
+
validate: async (value) => {
|
|
150
|
+
const parsed = await (0, _modelcontextprotocol_sdk_server_zod_compat_js.safeParseAsync)(schema, value);
|
|
151
|
+
return parsed.success ? { value: parsed.data } : { issues: zodIssues(parsed.error) };
|
|
152
|
+
},
|
|
153
|
+
jsonSchema: {
|
|
154
|
+
input: () => require_schema.jsonSchemaFromDefinition(definition, "input"),
|
|
155
|
+
output: () => require_schema.jsonSchemaFromDefinition(definition, "output")
|
|
156
|
+
}
|
|
157
|
+
} };
|
|
158
|
+
}
|
|
159
|
+
function isPlainObject(value) {
|
|
160
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
161
|
+
}
|
|
162
|
+
function toolRequestContext(sdkCtx, protocol) {
|
|
163
|
+
const mcpReq = sdkCtx?.mcpReq;
|
|
164
|
+
const client = { protocol };
|
|
165
|
+
const envelope = mcpReq?.envelope;
|
|
166
|
+
const info = envelope?.[_modelcontextprotocol_server.CLIENT_INFO_META_KEY];
|
|
167
|
+
if (isPlainObject(info) && typeof info.name === "string" && typeof info.version === "string") client.info = info;
|
|
168
|
+
const capabilities = envelope?.[_modelcontextprotocol_server.CLIENT_CAPABILITIES_META_KEY];
|
|
169
|
+
if (isPlainObject(capabilities)) {
|
|
170
|
+
client.capabilities = capabilities;
|
|
171
|
+
if (isPlainObject(capabilities.extensions)) client.extensions = capabilities.extensions;
|
|
172
|
+
}
|
|
173
|
+
const progressToken = (mcpReq?._meta)?.progressToken;
|
|
174
|
+
const notify = mcpReq?.notify;
|
|
175
|
+
const sendProgress = progressToken !== void 0 && notify ? async (update) => {
|
|
176
|
+
try {
|
|
177
|
+
await notify({
|
|
178
|
+
method: "notifications/progress",
|
|
179
|
+
params: {
|
|
180
|
+
progressToken,
|
|
181
|
+
...update
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
} catch (err) {
|
|
185
|
+
require_logger.log.debug("mcp.progress_notify_failed", require_logger.describeError(err));
|
|
186
|
+
}
|
|
187
|
+
} : void 0;
|
|
188
|
+
return {
|
|
189
|
+
signal: mcpReq?.signal,
|
|
190
|
+
client,
|
|
191
|
+
sendProgress
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
async function invokeTool(tool, auth, recorder, args, protocol, sdkCtx) {
|
|
195
|
+
const endUserId = auth?.principal.sub;
|
|
196
|
+
const start = require_cors.nowMs();
|
|
197
|
+
let result;
|
|
198
|
+
try {
|
|
199
|
+
result = await tool.handler(args, new require_errors.ToolContext(auth, toolRequestContext(sdkCtx, protocol)));
|
|
200
|
+
} catch (err) {
|
|
201
|
+
const callerMessage = require_errors.resolveToolErrorMessage(err);
|
|
202
|
+
if (callerMessage === void 0) {
|
|
203
|
+
await recorder.emit({
|
|
204
|
+
tool: tool.name,
|
|
205
|
+
method: "tools/call",
|
|
206
|
+
outcome: "handler_error",
|
|
207
|
+
durationMs: require_cors.nowMs() - start,
|
|
208
|
+
errorText: require_errors.errorSummary(err),
|
|
209
|
+
endUserId,
|
|
210
|
+
protocol
|
|
211
|
+
});
|
|
212
|
+
return finalizeToolResult(tool, {
|
|
213
|
+
content: [{
|
|
214
|
+
type: "text",
|
|
215
|
+
text: "tool execution failed"
|
|
216
|
+
}],
|
|
217
|
+
isError: true
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
result = {
|
|
221
|
+
content: [{
|
|
222
|
+
type: "text",
|
|
223
|
+
text: callerMessage
|
|
224
|
+
}],
|
|
225
|
+
isError: true
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (result == null) {
|
|
229
|
+
await recorder.emit({
|
|
230
|
+
tool: tool.name,
|
|
231
|
+
method: "tools/call",
|
|
232
|
+
outcome: "handler_error",
|
|
233
|
+
durationMs: require_cors.nowMs() - start,
|
|
234
|
+
endUserId,
|
|
235
|
+
protocol
|
|
236
|
+
});
|
|
237
|
+
return finalizeToolResult(tool, {
|
|
238
|
+
content: [{
|
|
239
|
+
type: "text",
|
|
240
|
+
text: `tool "${tool.name}" returned no result`
|
|
241
|
+
}],
|
|
242
|
+
isError: true
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
result = await finalizeToolResult(tool, result);
|
|
246
|
+
await recorder.emit({
|
|
247
|
+
tool: tool.name,
|
|
248
|
+
method: "tools/call",
|
|
249
|
+
outcome: result.isError ? "tool_error" : "ok",
|
|
250
|
+
durationMs: require_cors.nowMs() - start,
|
|
251
|
+
errorText: result.isError ? extractTextContent(result.content) : void 0,
|
|
252
|
+
endUserId,
|
|
253
|
+
protocol
|
|
254
|
+
});
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
function adaptToolCallback(tool, auth, recorder, protocol) {
|
|
258
|
+
return async (first, second) => {
|
|
259
|
+
const sdkCtx = tool.inputSchema ? second : first;
|
|
260
|
+
const result = await invokeTool(tool, auth, recorder, tool.inputSchema ? first ?? {} : {}, protocol, sdkCtx);
|
|
261
|
+
return {
|
|
262
|
+
content: result.content ?? [],
|
|
263
|
+
structuredContent: result.structuredContent,
|
|
264
|
+
isError: result.isError
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function buildServer(mcp, auth, recorder, era) {
|
|
269
|
+
const server = new _modelcontextprotocol_server.McpServer({
|
|
270
|
+
name: mcp.name,
|
|
271
|
+
version: mcp.version,
|
|
272
|
+
title: mcp.title
|
|
273
|
+
}, {
|
|
274
|
+
instructions: mcp.instructions,
|
|
275
|
+
capabilities: { tools: { listChanged: false } }
|
|
276
|
+
});
|
|
277
|
+
for (const tool of mcp.tools) {
|
|
278
|
+
const config = {
|
|
279
|
+
title: tool.title,
|
|
280
|
+
description: tool.description,
|
|
281
|
+
annotations: tool.annotations,
|
|
282
|
+
...tool.outputSchema ? { outputSchema: standardSchemaFromDefinition(tool.outputSchema) } : {}
|
|
283
|
+
};
|
|
284
|
+
const callback = adaptToolCallback(tool, auth, recorder, era === "legacy" ? "legacy" : MODERN_PROTOCOL_VERSION);
|
|
285
|
+
if (tool.inputSchema) server.registerTool(tool.name, {
|
|
286
|
+
...config,
|
|
287
|
+
inputSchema: standardSchemaFromDefinition(tool.inputSchema)
|
|
288
|
+
}, callback);
|
|
289
|
+
else server.registerTool(tool.name, config, callback);
|
|
290
|
+
}
|
|
291
|
+
return server;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Build a Web-Standard request handler that serves the MCP protocol over
|
|
295
|
+
* streamable HTTP. A fresh `McpServer` + transport is constructed per
|
|
296
|
+
* request — the transport is stateless, so this is cheap and there is no
|
|
297
|
+
* cross-request state to leak. The per-request `recorder` is supplied by the
|
|
298
|
+
* caller (the stack handler) so telemetry reads the runtime env in-flight.
|
|
299
|
+
*/
|
|
300
|
+
function createMcpProtocolHandler(mcp, options = {}) {
|
|
301
|
+
const authorizer = require_cors.createRequestAuthorizer(mcp, options);
|
|
302
|
+
const handle = async (request, authResult, recorder) => {
|
|
303
|
+
let protocolError;
|
|
304
|
+
const reportTransportError = async (error) => {
|
|
305
|
+
await recorder.emit({
|
|
306
|
+
tool: null,
|
|
307
|
+
method: "transport",
|
|
308
|
+
outcome: "transport_error",
|
|
309
|
+
durationMs: 0,
|
|
310
|
+
endUserId: authResult.auth?.principal.sub
|
|
311
|
+
});
|
|
312
|
+
require_logger.log.error("mcp.transport_error", {
|
|
313
|
+
...error === void 0 ? {} : require_logger.describeError(error),
|
|
314
|
+
outcome: "500 internal error"
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
try {
|
|
318
|
+
const response = await (0, _modelcontextprotocol_server.createMcpHandler)(({ era }) => buildServer(mcp, authResult.auth, recorder, era), {
|
|
319
|
+
legacy: "stateless",
|
|
320
|
+
maxSubscriptions: 0,
|
|
321
|
+
onerror: (error) => {
|
|
322
|
+
protocolError = error;
|
|
323
|
+
}
|
|
324
|
+
}).fetch(request);
|
|
325
|
+
if (response.status >= 500) await reportTransportError(protocolError);
|
|
326
|
+
return response;
|
|
327
|
+
} catch (err) {
|
|
328
|
+
await reportTransportError(err);
|
|
329
|
+
return Response.json({
|
|
330
|
+
jsonrpc: "2.0",
|
|
331
|
+
id: null,
|
|
332
|
+
error: {
|
|
333
|
+
code: -32603,
|
|
334
|
+
message: "internal error"
|
|
335
|
+
}
|
|
336
|
+
}, { status: 500 });
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
return createGatedHandler({
|
|
340
|
+
authorizer,
|
|
341
|
+
allowedOrigins: mcp.allowedOrigins,
|
|
342
|
+
methods: ["POST"],
|
|
343
|
+
methodNotAllowed: statelessMethodNotAllowed,
|
|
344
|
+
handle
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
//#endregion
|
|
348
|
+
Object.defineProperty(exports, "createMcpProtocolHandler", {
|
|
349
|
+
enumerable: true,
|
|
350
|
+
get: function() {
|
|
351
|
+
return createMcpProtocolHandler;
|
|
352
|
+
}
|
|
353
|
+
});
|
|
@@ -1,36 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as
|
|
3
|
-
|
|
4
|
-
type InvocationOutcome = "ok" | "tool_error" | "handler_error" | "transport_error" | "auth_challenge" | "auth_discovery_config_error" | "auth_discovery_unreachable" | "auth_jwks_config_error" | "auth_jwks_unreachable";
|
|
5
|
-
/** One recorded MCP call. Carries no arguments or response payloads — only the
|
|
6
|
-
* tool name, the JSON-RPC method, the result class, timing, and byte sizes.
|
|
7
|
-
* `errorText` is the sole exception: it is logged locally for debugging and
|
|
8
|
-
* deliberately never included in the OTLP payload (see `emit` / `buildLogsPayload`). */
|
|
9
|
-
interface InvocationRecord {
|
|
10
|
-
/** Tool name for `tools/call`; `null` for list/initialize, transport faults, and `authorize` records. */
|
|
11
|
-
tool: string | null;
|
|
12
|
-
/** JSON-RPC method, e.g. `tools/call`; `authorize` for records from the authorization gate. */
|
|
13
|
-
method: string;
|
|
14
|
-
outcome: InvocationOutcome;
|
|
15
|
-
durationMs: number;
|
|
16
|
-
reqBytes?: number;
|
|
17
|
-
resBytes?: number;
|
|
18
|
-
/** HTTP request method. Set on `auth_challenge` so probe traffic (GET vs
|
|
19
|
-
* POST discovery) is distinguishable; absent on tool-call records. */
|
|
20
|
-
httpMethod?: string;
|
|
21
|
-
/** The app end-user that made the call (the access token `sub`), or
|
|
22
|
-
* `undefined` when the request is unauthenticated. Not a Lovable identity. */
|
|
23
|
-
endUserId?: string;
|
|
24
|
-
/** Caller-visible `isError` text (`tool_error`) or a bounded internal-exception
|
|
25
|
-
* summary (`handler_error`). Local log only — never sent to the collector. */
|
|
26
|
-
errorText?: string;
|
|
27
|
-
}
|
|
28
|
-
/** A per-request telemetry recorder. `emit` logs the invocation and sends it as a
|
|
29
|
-
* single OTLP POST, awaited so it lands before the request ends. */
|
|
30
|
-
interface MetricsRecorder {
|
|
31
|
-
emit(ev: InvocationRecord): Promise<void>;
|
|
32
|
-
}
|
|
33
|
-
//#endregion
|
|
1
|
+
import { d as McpDefinition } from "../../types-3CZPz364.cjs";
|
|
2
|
+
import { t as MetricsRecorder } from "../../base-CHNFFrMG.cjs";
|
|
3
|
+
import { t as McpRuntimeOptions } from "../../authorize-9hTTG-qJ.cjs";
|
|
34
4
|
//#region src/protocols/mcp/protocol.d.ts
|
|
35
5
|
type McpProtocolHandler = (request: Request, recorder?: MetricsRecorder) => Promise<Response>;
|
|
36
6
|
/**
|
|
@@ -1,36 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as
|
|
3
|
-
|
|
4
|
-
type InvocationOutcome = "ok" | "tool_error" | "handler_error" | "transport_error" | "auth_challenge" | "auth_discovery_config_error" | "auth_discovery_unreachable" | "auth_jwks_config_error" | "auth_jwks_unreachable";
|
|
5
|
-
/** One recorded MCP call. Carries no arguments or response payloads — only the
|
|
6
|
-
* tool name, the JSON-RPC method, the result class, timing, and byte sizes.
|
|
7
|
-
* `errorText` is the sole exception: it is logged locally for debugging and
|
|
8
|
-
* deliberately never included in the OTLP payload (see `emit` / `buildLogsPayload`). */
|
|
9
|
-
interface InvocationRecord {
|
|
10
|
-
/** Tool name for `tools/call`; `null` for list/initialize, transport faults, and `authorize` records. */
|
|
11
|
-
tool: string | null;
|
|
12
|
-
/** JSON-RPC method, e.g. `tools/call`; `authorize` for records from the authorization gate. */
|
|
13
|
-
method: string;
|
|
14
|
-
outcome: InvocationOutcome;
|
|
15
|
-
durationMs: number;
|
|
16
|
-
reqBytes?: number;
|
|
17
|
-
resBytes?: number;
|
|
18
|
-
/** HTTP request method. Set on `auth_challenge` so probe traffic (GET vs
|
|
19
|
-
* POST discovery) is distinguishable; absent on tool-call records. */
|
|
20
|
-
httpMethod?: string;
|
|
21
|
-
/** The app end-user that made the call (the access token `sub`), or
|
|
22
|
-
* `undefined` when the request is unauthenticated. Not a Lovable identity. */
|
|
23
|
-
endUserId?: string;
|
|
24
|
-
/** Caller-visible `isError` text (`tool_error`) or a bounded internal-exception
|
|
25
|
-
* summary (`handler_error`). Local log only — never sent to the collector. */
|
|
26
|
-
errorText?: string;
|
|
27
|
-
}
|
|
28
|
-
/** A per-request telemetry recorder. `emit` logs the invocation and sends it as a
|
|
29
|
-
* single OTLP POST, awaited so it lands before the request ends. */
|
|
30
|
-
interface MetricsRecorder {
|
|
31
|
-
emit(ev: InvocationRecord): Promise<void>;
|
|
32
|
-
}
|
|
33
|
-
//#endregion
|
|
1
|
+
import { d as McpDefinition } from "../../types-3CZPz364.js";
|
|
2
|
+
import { t as MetricsRecorder } from "../../base-7cq6HCPO.js";
|
|
3
|
+
import { t as McpRuntimeOptions } from "../../authorize-DNUlPT7k.js";
|
|
34
4
|
//#region src/protocols/mcp/protocol.d.ts
|
|
35
5
|
type McpProtocolHandler = (request: Request, recorder?: MetricsRecorder) => Promise<Response>;
|
|
36
6
|
/**
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createMcpProtocolHandler } from "../../mcp-
|
|
1
|
+
import { t as createMcpProtocolHandler } from "../../mcp-Cb5SavFT.js";
|
|
2
2
|
export { createMcpProtocolHandler };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_logger = require("../logger-CN1q-KNn.cjs");
|
|
3
|
-
const
|
|
3
|
+
const require_cors = require("../cors-DIQIXIHY.cjs");
|
|
4
4
|
//#region src/protocols/oauth-metadata.ts
|
|
5
5
|
function notFound() {
|
|
6
6
|
return new Response(JSON.stringify({ error: "not found" }), {
|
|
7
7
|
status: 404,
|
|
8
8
|
headers: {
|
|
9
|
-
...
|
|
9
|
+
...require_cors.JSON_HEADERS,
|
|
10
10
|
"Cache-Control": "no-store"
|
|
11
11
|
}
|
|
12
12
|
});
|
|
@@ -14,7 +14,7 @@ function notFound() {
|
|
|
14
14
|
async function buildProtectedResourceMetadata(mcp, auth, request, options, discovery) {
|
|
15
15
|
const issuer = await discovery.resolveIssuer();
|
|
16
16
|
const body = {
|
|
17
|
-
resource:
|
|
17
|
+
resource: require_cors.resolveProtectedResource(auth, request, options),
|
|
18
18
|
authorization_servers: [issuer],
|
|
19
19
|
bearer_methods_supported: ["header"],
|
|
20
20
|
resource_name: auth.resourceName ?? mcp.title,
|
|
@@ -24,7 +24,7 @@ async function buildProtectedResourceMetadata(mcp, auth, request, options, disco
|
|
|
24
24
|
return body;
|
|
25
25
|
}
|
|
26
26
|
function createOAuthProtectedResourceMetadataHandler(mcp, options = {}) {
|
|
27
|
-
const runtime =
|
|
27
|
+
const runtime = require_cors.getOAuthRuntime(mcp, options);
|
|
28
28
|
if (runtime.kind === "configured" && runtime.auth.protectedResourceMetadataUrl === void 0 && runtime.auth.resource === void 0) {
|
|
29
29
|
if (runtime.options.resourcePath === void 0) throw new Error(`@lovable.dev/mcp-js: auth.resource or a resourcePath is required so the protected-resource metadata doesn't advertise the well-known URL as the resource`);
|
|
30
30
|
if (runtime.options.trustForwardedHost === true && runtime.options.forwardedHostTrustedByPlatform !== true) require_logger.log.warn("oauth.metadata.host_derived_resource", { detail: "trustForwardedHost is on and auth.resource is unpinned; the advertised resource follows X-Forwarded-Host. Pin auth.resource unless a trusted proxy overwrites that header." });
|
|
@@ -35,9 +35,9 @@ function createOAuthProtectedResourceMetadataHandler(mcp, options = {}) {
|
|
|
35
35
|
const vary = varyHeaders.join(", ");
|
|
36
36
|
const respond = async (request) => {
|
|
37
37
|
if (runtime.kind !== "configured" || runtime.auth.protectedResourceMetadataUrl !== void 0) return notFound();
|
|
38
|
-
if (request.method !== "GET" && request.method !== "HEAD") return
|
|
38
|
+
if (request.method !== "GET" && request.method !== "HEAD") return require_cors.methodNotAllowed("GET, HEAD, OPTIONS");
|
|
39
39
|
const headers = {
|
|
40
|
-
...
|
|
40
|
+
...require_cors.JSON_HEADERS,
|
|
41
41
|
"Cache-Control": "public, max-age=300",
|
|
42
42
|
Vary: vary
|
|
43
43
|
};
|
|
@@ -49,12 +49,29 @@ function createOAuthProtectedResourceMetadataHandler(mcp, options = {}) {
|
|
|
49
49
|
...require_logger.describeError(err),
|
|
50
50
|
outcome: "500 oauth configuration error"
|
|
51
51
|
});
|
|
52
|
-
return
|
|
52
|
+
return require_cors.oauthConfigurationErrorResponse();
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
|
-
return async (request) => {
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
return async (request, recorder = require_cors.createNoopRecorder()) => {
|
|
56
|
+
const cors = require_cors.evaluateCors(request, mcp.allowedOrigins);
|
|
57
|
+
if (!cors.ok) {
|
|
58
|
+
const httpMethod = require_cors.normalizeHttpMethod(request.method);
|
|
59
|
+
require_logger.log.info("cors.origin_rejected", {
|
|
60
|
+
origin: request.headers.get("Origin"),
|
|
61
|
+
httpMethod,
|
|
62
|
+
route: "oauth-metadata"
|
|
63
|
+
});
|
|
64
|
+
await recorder.emit({
|
|
65
|
+
tool: null,
|
|
66
|
+
method: "authorize",
|
|
67
|
+
outcome: "origin_rejected",
|
|
68
|
+
httpMethod,
|
|
69
|
+
durationMs: 0
|
|
70
|
+
});
|
|
71
|
+
return require_cors.finalizeWireResponse(request, cors.response);
|
|
72
|
+
}
|
|
73
|
+
if (request.method === "OPTIONS") return cors.preflight("GET, HEAD, OPTIONS");
|
|
74
|
+
return require_cors.finalizeWireResponse(request, cors.withHeaders(await respond(request)));
|
|
58
75
|
};
|
|
59
76
|
}
|
|
60
77
|
//#endregion
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as
|
|
1
|
+
import { d as McpDefinition } from "../types-3CZPz364.cjs";
|
|
2
|
+
import { t as MetricsRecorder } from "../base-CHNFFrMG.cjs";
|
|
3
|
+
import { t as McpRuntimeOptions } from "../authorize-9hTTG-qJ.cjs";
|
|
3
4
|
//#region src/protocols/oauth-metadata.d.ts
|
|
4
|
-
type OAuthProtectedResourceMetadataHandler = (request: Request) => Promise<Response>;
|
|
5
|
+
type OAuthProtectedResourceMetadataHandler = (request: Request, recorder?: MetricsRecorder) => Promise<Response>;
|
|
5
6
|
declare function createOAuthProtectedResourceMetadataHandler(mcp: McpDefinition, options?: McpRuntimeOptions): OAuthProtectedResourceMetadataHandler;
|
|
6
7
|
//#endregion
|
|
7
8
|
export { OAuthProtectedResourceMetadataHandler, createOAuthProtectedResourceMetadataHandler };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as
|
|
1
|
+
import { d as McpDefinition } from "../types-3CZPz364.js";
|
|
2
|
+
import { t as MetricsRecorder } from "../base-7cq6HCPO.js";
|
|
3
|
+
import { t as McpRuntimeOptions } from "../authorize-DNUlPT7k.js";
|
|
3
4
|
//#region src/protocols/oauth-metadata.d.ts
|
|
4
|
-
type OAuthProtectedResourceMetadataHandler = (request: Request) => Promise<Response>;
|
|
5
|
+
type OAuthProtectedResourceMetadataHandler = (request: Request, recorder?: MetricsRecorder) => Promise<Response>;
|
|
5
6
|
declare function createOAuthProtectedResourceMetadataHandler(mcp: McpDefinition, options?: McpRuntimeOptions): OAuthProtectedResourceMetadataHandler;
|
|
6
7
|
//#endregion
|
|
7
8
|
export { OAuthProtectedResourceMetadataHandler, createOAuthProtectedResourceMetadataHandler };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as log, r as describeError } from "../logger-Ctv5xUSD.js";
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as oauthConfigurationErrorResponse, d as JSON_HEADERS, f as finalizeWireResponse, i as normalizeHttpMethod, l as createNoopRecorder, p as methodNotAllowed, r as getOAuthRuntime, s as resolveProtectedResource, t as evaluateCors } from "../cors-5P69YRDK.js";
|
|
3
3
|
//#region src/protocols/oauth-metadata.ts
|
|
4
4
|
function notFound() {
|
|
5
5
|
return new Response(JSON.stringify({ error: "not found" }), {
|
|
@@ -51,9 +51,26 @@ function createOAuthProtectedResourceMetadataHandler(mcp, options = {}) {
|
|
|
51
51
|
return oauthConfigurationErrorResponse();
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
|
-
return async (request) => {
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
return async (request, recorder = createNoopRecorder()) => {
|
|
55
|
+
const cors = evaluateCors(request, mcp.allowedOrigins);
|
|
56
|
+
if (!cors.ok) {
|
|
57
|
+
const httpMethod = normalizeHttpMethod(request.method);
|
|
58
|
+
log.info("cors.origin_rejected", {
|
|
59
|
+
origin: request.headers.get("Origin"),
|
|
60
|
+
httpMethod,
|
|
61
|
+
route: "oauth-metadata"
|
|
62
|
+
});
|
|
63
|
+
await recorder.emit({
|
|
64
|
+
tool: null,
|
|
65
|
+
method: "authorize",
|
|
66
|
+
outcome: "origin_rejected",
|
|
67
|
+
httpMethod,
|
|
68
|
+
durationMs: 0
|
|
69
|
+
});
|
|
70
|
+
return finalizeWireResponse(request, cors.response);
|
|
71
|
+
}
|
|
72
|
+
if (request.method === "OPTIONS") return cors.preflight("GET, HEAD, OPTIONS");
|
|
73
|
+
return finalizeWireResponse(request, cors.withHeaders(await respond(request)));
|
|
57
74
|
};
|
|
58
75
|
}
|
|
59
76
|
//#endregion
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { objectFromShape } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
2
|
+
import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-schema-compat.js";
|
|
3
|
+
//#region src/core/schema.ts
|
|
4
|
+
function isZodSchema(definition) {
|
|
5
|
+
return (typeof definition === "object" || typeof definition === "function") && definition !== null && typeof definition.safeParse === "function";
|
|
6
|
+
}
|
|
7
|
+
function schemaFromDefinition(definition) {
|
|
8
|
+
return isZodSchema(definition) ? definition : objectFromShape(definition);
|
|
9
|
+
}
|
|
10
|
+
const NON_JSON_ZOD3_OUTPUTS = /* @__PURE__ */ new Set([
|
|
11
|
+
"ZodBigInt",
|
|
12
|
+
"ZodDate",
|
|
13
|
+
"ZodFunction",
|
|
14
|
+
"ZodMap",
|
|
15
|
+
"ZodNaN",
|
|
16
|
+
"ZodPromise",
|
|
17
|
+
"ZodSet",
|
|
18
|
+
"ZodSymbol",
|
|
19
|
+
"ZodUndefined",
|
|
20
|
+
"ZodVoid"
|
|
21
|
+
]);
|
|
22
|
+
function unsupportedZod3Output(schema, seen = /* @__PURE__ */ new Set()) {
|
|
23
|
+
if (seen.has(schema)) return void 0;
|
|
24
|
+
seen.add(schema);
|
|
25
|
+
const definition = schema._def;
|
|
26
|
+
if (!definition) return void 0;
|
|
27
|
+
if (definition.effect?.type === "transform") return "Transforms cannot be represented in JSON Schema";
|
|
28
|
+
const typeName = definition.typeName;
|
|
29
|
+
if (typeof typeName === "string" && NON_JSON_ZOD3_OUTPUTS.has(typeName)) return `${typeName} does not produce a JSON value`;
|
|
30
|
+
const shape = definition.shape;
|
|
31
|
+
if (typeof shape === "function") {
|
|
32
|
+
const fields = shape();
|
|
33
|
+
for (const field of Object.values(fields)) {
|
|
34
|
+
const issue = unsupportedZod3Output(field, seen);
|
|
35
|
+
if (issue) return issue;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const getter = definition.getter;
|
|
39
|
+
if (typeof getter === "function") {
|
|
40
|
+
const issue = unsupportedZod3Output(getter(), seen);
|
|
41
|
+
if (issue) return issue;
|
|
42
|
+
}
|
|
43
|
+
for (const value of Object.values(definition)) if (Array.isArray(value)) for (const entry of value) {
|
|
44
|
+
if (!isZodSchema(entry)) continue;
|
|
45
|
+
const issue = unsupportedZod3Output(entry, seen);
|
|
46
|
+
if (issue) return issue;
|
|
47
|
+
}
|
|
48
|
+
else if (value && typeof value === "object" && isZodSchema(value)) {
|
|
49
|
+
const issue = unsupportedZod3Output(value, seen);
|
|
50
|
+
if (issue) return issue;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function jsonSchemaFromDefinition(definition, pipeStrategy) {
|
|
54
|
+
const schema = schemaFromDefinition(definition);
|
|
55
|
+
if (pipeStrategy === "output") {
|
|
56
|
+
const issue = unsupportedZod3Output(schema);
|
|
57
|
+
if (issue) throw new Error(issue);
|
|
58
|
+
}
|
|
59
|
+
return toJsonSchemaCompat(schema, {
|
|
60
|
+
target: "draft-2020-12",
|
|
61
|
+
pipeStrategy
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { schemaFromDefinition as n, jsonSchemaFromDefinition as t };
|