@lovable.dev/mcp-js 0.7.1 → 0.9.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 +6 -2
- package/README.md +13 -1
- package/dist/authorize-HTd0GKmB.d.ts +18 -0
- package/dist/{chunk-W7F6JRDB.js → chunk-3GQXQBYQ.js} +1 -1
- package/dist/chunk-BG3HOZXZ.js +57 -0
- package/dist/{chunk-3XPKR4G6.js → chunk-CJMHLE7F.js} +1 -1
- package/dist/{chunk-HRMLGCXV.js → chunk-FQLM4JDU.js} +36 -12
- package/dist/chunk-QJ7XEKT3.js +8 -0
- package/dist/{chunk-LMT6RXUF.js → chunk-SBKIKJNB.js} +3 -47
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/protocols/mcp/index.cjs +36 -12
- package/dist/protocols/mcp/index.d.cts +2 -2
- package/dist/protocols/mcp/index.d.ts +2 -2
- package/dist/protocols/mcp/index.js +2 -2
- package/dist/protocols/oauth-metadata.cjs +10 -3
- package/dist/protocols/oauth-metadata.d.cts +2 -2
- package/dist/protocols/oauth-metadata.d.ts +2 -2
- package/dist/protocols/oauth-metadata.js +2 -2
- package/dist/protocols/rest/index.cjs +50 -24
- 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 -4
- package/dist/stacks/tanstack/cli/extract-manifest.cjs +214 -0
- package/dist/stacks/tanstack/cli/extract-manifest.d.cts +1 -0
- package/dist/stacks/tanstack/cli/extract-manifest.d.ts +1 -0
- package/dist/stacks/tanstack/cli/extract-manifest.js +170 -0
- package/dist/stacks/tanstack/index.cjs +50 -24
- package/dist/stacks/tanstack/index.d.cts +2 -2
- package/dist/stacks/tanstack/index.d.ts +2 -2
- package/dist/stacks/tanstack/index.js +8 -6
- package/dist/stacks/tanstack/vite.cjs +67 -19
- package/dist/stacks/tanstack/vite.d.cts +26 -2
- package/dist/stacks/tanstack/vite.d.ts +26 -2
- package/dist/stacks/tanstack/vite.js +65 -20
- package/dist/{types-zMlr1mOK.d.ts → types-C5SSORxe.d.ts} +4 -0
- package/package.json +6 -3
- package/dist/authorize-DpTEIFvs.d.ts +0 -9
|
@@ -256,10 +256,10 @@ function resolveProtectedResource(auth, request, options) {
|
|
|
256
256
|
resourceURL.hash = "";
|
|
257
257
|
return trimTrailingSlash(resourceURL.toString());
|
|
258
258
|
}
|
|
259
|
-
function assertResourcePathShape(resourcePath) {
|
|
259
|
+
function assertResourcePathShape(resourcePath, label = "resourcePath") {
|
|
260
260
|
if (!resourcePath.startsWith("/") || resourcePath.startsWith("//") || resourcePath.includes("\\") || /(^|\/)\.\.(\/|$)/.test(resourcePath)) {
|
|
261
261
|
throw new Error(
|
|
262
|
-
`@lovable.dev/mcp-js:
|
|
262
|
+
`@lovable.dev/mcp-js: ${label} must be an absolute path beginning with "/" without ".." segments or backslashes (got ${JSON.stringify(resourcePath)})`
|
|
263
263
|
);
|
|
264
264
|
}
|
|
265
265
|
}
|
|
@@ -400,16 +400,16 @@ function quoteAuthenticateParam(value) {
|
|
|
400
400
|
const sanitized = value.replace(/[\u0000-\u001F\u007F]/g, "");
|
|
401
401
|
return `"${sanitized.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
402
402
|
}
|
|
403
|
-
function resolveProtectedResourceMetadataUrl(auth, request) {
|
|
403
|
+
function resolveProtectedResourceMetadataUrl(auth, request, options) {
|
|
404
404
|
if (auth.protectedResourceMetadataUrl !== void 0)
|
|
405
405
|
return auth.protectedResourceMetadataUrl;
|
|
406
406
|
const base = auth.resource ?? request.url;
|
|
407
|
-
return new URL(OAUTH_PROTECTED_RESOURCE_METADATA_PATH, base).toString();
|
|
407
|
+
return new URL(options.metadataPath ?? OAUTH_PROTECTED_RESOURCE_METADATA_PATH, base).toString();
|
|
408
408
|
}
|
|
409
|
-
function wwwAuthenticateHeader(auth, request, params = {}) {
|
|
409
|
+
function wwwAuthenticateHeader(auth, request, options, params = {}) {
|
|
410
410
|
const values = [
|
|
411
411
|
`realm=${quoteAuthenticateParam("mcp")}`,
|
|
412
|
-
`resource_metadata=${quoteAuthenticateParam(resolveProtectedResourceMetadataUrl(auth, request))}`
|
|
412
|
+
`resource_metadata=${quoteAuthenticateParam(resolveProtectedResourceMetadataUrl(auth, request, options))}`
|
|
413
413
|
];
|
|
414
414
|
if (auth.requiredScopes && auth.requiredScopes.length > 0) {
|
|
415
415
|
values.push(`scope=${quoteAuthenticateParam(auth.requiredScopes.join(" "))}`);
|
|
@@ -420,9 +420,12 @@ function wwwAuthenticateHeader(auth, request, params = {}) {
|
|
|
420
420
|
values.push(`error_description=${quoteAuthenticateParam(params.errorDescription)}`);
|
|
421
421
|
return `Bearer ${values.join(", ")}`;
|
|
422
422
|
}
|
|
423
|
-
function challengeResponse(auth, request, status, oauthError, errorDescription) {
|
|
423
|
+
function challengeResponse(auth, request, options, status, oauthError, errorDescription) {
|
|
424
424
|
const headers = new Headers(JSON_HEADERS);
|
|
425
|
-
headers.set(
|
|
425
|
+
headers.set(
|
|
426
|
+
"WWW-Authenticate",
|
|
427
|
+
wwwAuthenticateHeader(auth, request, options, { error: oauthError, errorDescription })
|
|
428
|
+
);
|
|
426
429
|
headers.set("Cache-Control", "no-store");
|
|
427
430
|
return new Response(JSON.stringify({ error: status === 403 ? "forbidden" : "unauthorized" }), {
|
|
428
431
|
status,
|
|
@@ -450,10 +453,17 @@ function parseBearerToken(request) {
|
|
|
450
453
|
function getOAuthRuntime(mcp, options = {}) {
|
|
451
454
|
if (options.resourcePath !== void 0)
|
|
452
455
|
assertResourcePathShape(options.resourcePath);
|
|
453
|
-
|
|
456
|
+
if (options.metadataPath !== void 0)
|
|
457
|
+
assertResourcePathShape(options.metadataPath, "metadataPath");
|
|
458
|
+
const stableOptions = { resourcePath: options.resourcePath, metadataPath: options.metadataPath };
|
|
454
459
|
const auth = mcp.auth?.type === "oauth" ? mcp.auth : void 0;
|
|
455
460
|
if (!auth)
|
|
456
461
|
return { kind: "unconfigured", options: stableOptions };
|
|
462
|
+
if (options.metadataPath !== void 0 && auth.protectedResourceMetadataUrl !== void 0) {
|
|
463
|
+
throw new Error(
|
|
464
|
+
`@lovable.dev/mcp-js: the Vite plugin generates protected-resource metadata (metadataPath set), so auth.protectedResourceMetadataUrl must not also be set. Drop protectedResourceMetadataUrl, or set protectedResourceMetadataRoute: false in mcpPlugin(...) to host the document yourself.`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
457
467
|
const discovery = createOAuthDiscoveryResolver(auth);
|
|
458
468
|
return {
|
|
459
469
|
kind: "configured",
|
|
@@ -492,7 +502,7 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
492
502
|
const token = parseBearerToken(request);
|
|
493
503
|
if (!token) {
|
|
494
504
|
log.info("auth.no_bearer_token", { outcome: "401" });
|
|
495
|
-
return { ok: false, response: challengeResponse(runtime.auth, request, 401) };
|
|
505
|
+
return { ok: false, response: challengeResponse(runtime.auth, request, runtime.options, 401) };
|
|
496
506
|
}
|
|
497
507
|
try {
|
|
498
508
|
const auth = await runtime.verify(token, request, runtime.options);
|
|
@@ -507,13 +517,27 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
507
517
|
log.info("auth.token_rejected", { status: err.status, oauthError: err.oauthError });
|
|
508
518
|
return {
|
|
509
519
|
ok: false,
|
|
510
|
-
response: challengeResponse(
|
|
520
|
+
response: challengeResponse(
|
|
521
|
+
runtime.auth,
|
|
522
|
+
request,
|
|
523
|
+
runtime.options,
|
|
524
|
+
err.status,
|
|
525
|
+
err.oauthError,
|
|
526
|
+
err.message
|
|
527
|
+
)
|
|
511
528
|
};
|
|
512
529
|
}
|
|
513
530
|
log.error("auth.unexpected_error", { ...describeError(err), outcome: "401" });
|
|
514
531
|
return {
|
|
515
532
|
ok: false,
|
|
516
|
-
response: challengeResponse(
|
|
533
|
+
response: challengeResponse(
|
|
534
|
+
runtime.auth,
|
|
535
|
+
request,
|
|
536
|
+
runtime.options,
|
|
537
|
+
401,
|
|
538
|
+
"invalid_token",
|
|
539
|
+
"Invalid access token"
|
|
540
|
+
)
|
|
517
541
|
};
|
|
518
542
|
}
|
|
519
543
|
}
|
|
@@ -550,6 +574,19 @@ function shapeToJsonSchema(shape) {
|
|
|
550
574
|
return null;
|
|
551
575
|
}
|
|
552
576
|
}
|
|
577
|
+
function buildMcpListing(mcp) {
|
|
578
|
+
return {
|
|
579
|
+
server: { name: mcp.name, version: mcp.version, title: mcp.title },
|
|
580
|
+
tools: mcp.tools.map((tool) => ({
|
|
581
|
+
name: tool.name,
|
|
582
|
+
title: tool.title,
|
|
583
|
+
description: tool.description,
|
|
584
|
+
annotations: tool.annotations,
|
|
585
|
+
inputSchema: shapeToJsonSchema(tool.inputSchema),
|
|
586
|
+
outputSchema: shapeToJsonSchema(tool.outputSchema)
|
|
587
|
+
}))
|
|
588
|
+
};
|
|
589
|
+
}
|
|
553
590
|
function createListToolsHandler(mcp, options = {}) {
|
|
554
591
|
assertRestResourceBinding(mcp, options);
|
|
555
592
|
const authorizer = createRequestAuthorizer(mcp, options);
|
|
@@ -559,18 +596,7 @@ function createListToolsHandler(mcp, options = {}) {
|
|
|
559
596
|
return authResult.response;
|
|
560
597
|
if (request.method !== "GET" && request.method !== "HEAD")
|
|
561
598
|
return methodNotAllowed("GET, HEAD, OPTIONS");
|
|
562
|
-
const
|
|
563
|
-
server: { name: mcp.name, version: mcp.version, title: mcp.title },
|
|
564
|
-
tools: mcp.tools.map((tool) => ({
|
|
565
|
-
name: tool.name,
|
|
566
|
-
title: tool.title,
|
|
567
|
-
description: tool.description,
|
|
568
|
-
annotations: tool.annotations,
|
|
569
|
-
inputSchema: shapeToJsonSchema(tool.inputSchema),
|
|
570
|
-
outputSchema: shapeToJsonSchema(tool.outputSchema)
|
|
571
|
-
}))
|
|
572
|
-
};
|
|
573
|
-
const response = Response.json(body);
|
|
599
|
+
const response = Response.json(buildMcpListing(mcp));
|
|
574
600
|
return request.method === "HEAD" ? headResponse(response) : response;
|
|
575
601
|
};
|
|
576
602
|
return async (request) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as McpRuntimeOptions } from '../../authorize-
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
1
|
+
import { M as McpRuntimeOptions } from '../../authorize-HTd0GKmB.js';
|
|
2
|
+
import { d as McpDefinition } from '../../types-C5SSORxe.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type RestListToolsHandler = (request: Request) => Promise<Response>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as McpRuntimeOptions } from '../../authorize-
|
|
2
|
-
import { d as McpDefinition } from '../../types-
|
|
1
|
+
import { M as McpRuntimeOptions } from '../../authorize-HTd0GKmB.js';
|
|
2
|
+
import { d as McpDefinition } from '../../types-C5SSORxe.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type RestListToolsHandler = (request: Request) => Promise<Response>;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createInvokeToolHandler
|
|
3
|
-
|
|
4
|
-
} from "../../chunk-LMT6RXUF.js";
|
|
2
|
+
createInvokeToolHandler
|
|
3
|
+
} from "../../chunk-SBKIKJNB.js";
|
|
5
4
|
import "../../chunk-MA5H6PSF.js";
|
|
6
|
-
import
|
|
5
|
+
import {
|
|
6
|
+
createListToolsHandler
|
|
7
|
+
} from "../../chunk-BG3HOZXZ.js";
|
|
8
|
+
import "../../chunk-FQLM4JDU.js";
|
|
7
9
|
import "../../chunk-QC3DXQTH.js";
|
|
8
10
|
import "../../chunk-6DXGZZA4.js";
|
|
9
11
|
export {
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// src/stacks/tanstack/manifest-io.ts
|
|
5
|
+
var import_node_crypto = require("crypto");
|
|
6
|
+
var import_node_fs = require("fs");
|
|
7
|
+
var import_node_path = require("path");
|
|
8
|
+
var import_vite2 = require("vite");
|
|
9
|
+
|
|
10
|
+
// package.json
|
|
11
|
+
var version = "0.9.0";
|
|
12
|
+
|
|
13
|
+
// src/protocols/rest/list-tools.ts
|
|
14
|
+
var import_zod_compat = require("@modelcontextprotocol/sdk/server/zod-compat.js");
|
|
15
|
+
var import_zod_json_schema_compat = require("@modelcontextprotocol/sdk/server/zod-json-schema-compat.js");
|
|
16
|
+
|
|
17
|
+
// src/core/logger.ts
|
|
18
|
+
var LEVEL_RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
19
|
+
function isLogLevel(value) {
|
|
20
|
+
return typeof value === "string" && value in LEVEL_RANK;
|
|
21
|
+
}
|
|
22
|
+
function readEnvLevel() {
|
|
23
|
+
try {
|
|
24
|
+
const raw = typeof process !== "undefined" ? process.env?.["LOVABLE_MCP_LOG_LEVEL"] : void 0;
|
|
25
|
+
const normalized = raw?.trim().toLowerCase();
|
|
26
|
+
return isLogLevel(normalized) ? normalized : void 0;
|
|
27
|
+
} catch {
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
var currentLevel = readEnvLevel() ?? "silent";
|
|
32
|
+
|
|
33
|
+
// src/auth/verifier.ts
|
|
34
|
+
var import_jose = require("jose");
|
|
35
|
+
|
|
36
|
+
// src/protocols/rest/list-tools.ts
|
|
37
|
+
function shapeToJsonSchema(shape) {
|
|
38
|
+
if (!shape)
|
|
39
|
+
return null;
|
|
40
|
+
try {
|
|
41
|
+
return (0, import_zod_json_schema_compat.toJsonSchemaCompat)((0, import_zod_compat.objectFromShape)(shape));
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function buildMcpListing(mcp) {
|
|
47
|
+
return {
|
|
48
|
+
server: { name: mcp.name, version: mcp.version, title: mcp.title },
|
|
49
|
+
tools: mcp.tools.map((tool) => ({
|
|
50
|
+
name: tool.name,
|
|
51
|
+
title: tool.title,
|
|
52
|
+
description: tool.description,
|
|
53
|
+
annotations: tool.annotations,
|
|
54
|
+
inputSchema: shapeToJsonSchema(tool.inputSchema),
|
|
55
|
+
outputSchema: shapeToJsonSchema(tool.outputSchema)
|
|
56
|
+
}))
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/stacks/tanstack/extract-manifest.ts
|
|
61
|
+
var MANIFEST_VERSION = 1;
|
|
62
|
+
var MANIFEST_RELATIVE_PATH = ".lovable/mcp/manifest.json";
|
|
63
|
+
function manifestAuth(auth) {
|
|
64
|
+
if (!auth)
|
|
65
|
+
return { type: "none" };
|
|
66
|
+
const oauth = { type: "oauth", issuer: auth.issuer };
|
|
67
|
+
if (auth.resource !== void 0)
|
|
68
|
+
oauth.resource = auth.resource;
|
|
69
|
+
if (auth.resourceName !== void 0)
|
|
70
|
+
oauth.resource_name = auth.resourceName;
|
|
71
|
+
if (auth.resourceDocumentation !== void 0)
|
|
72
|
+
oauth.resource_documentation = auth.resourceDocumentation;
|
|
73
|
+
if (auth.protectedResourceMetadataUrl !== void 0)
|
|
74
|
+
oauth.protected_resource_metadata_url = auth.protectedResourceMetadataUrl;
|
|
75
|
+
if (auth.requireOAuthClientClaim !== void 0)
|
|
76
|
+
oauth.require_oauth_client_claim = auth.requireOAuthClientClaim;
|
|
77
|
+
if (auth.acceptedAudiences !== void 0)
|
|
78
|
+
oauth.accepted_audiences = auth.acceptedAudiences;
|
|
79
|
+
if (auth.requiredScopes !== void 0)
|
|
80
|
+
oauth.required_scopes = auth.requiredScopes;
|
|
81
|
+
if (auth.jwksUri !== void 0)
|
|
82
|
+
oauth.jwks_uri = auth.jwksUri;
|
|
83
|
+
if (auth.algorithms !== void 0)
|
|
84
|
+
oauth.algorithms = auth.algorithms;
|
|
85
|
+
if (auth.clockToleranceSeconds !== void 0)
|
|
86
|
+
oauth.clock_tolerance_seconds = auth.clockToleranceSeconds;
|
|
87
|
+
return oauth;
|
|
88
|
+
}
|
|
89
|
+
function isToolShape(tool) {
|
|
90
|
+
if (typeof tool !== "object" || tool === null)
|
|
91
|
+
return false;
|
|
92
|
+
const t = tool;
|
|
93
|
+
return typeof t.name === "string" && typeof t.title === "string" && typeof t.description === "string";
|
|
94
|
+
}
|
|
95
|
+
function isAuthShape(auth) {
|
|
96
|
+
if (auth === void 0)
|
|
97
|
+
return true;
|
|
98
|
+
if (typeof auth !== "object" || auth === null)
|
|
99
|
+
return false;
|
|
100
|
+
const a = auth;
|
|
101
|
+
return a.type === "oauth" && typeof a.issuer === "string" && a.issuer.length > 0;
|
|
102
|
+
}
|
|
103
|
+
function manifestFromDefinition(definition, urlPath, source = "the MCP entry") {
|
|
104
|
+
const def = definition;
|
|
105
|
+
const validShape = !!def && typeof def === "object" && typeof def.name === "string" && typeof def.title === "string" && typeof def.version === "string" && Array.isArray(def.tools) && def.tools.every(isToolShape) && isAuthShape(def.auth);
|
|
106
|
+
if (!validShape) {
|
|
107
|
+
const got = definition == null ? "no default export" : typeof definition;
|
|
108
|
+
throw new Error(`@lovable.dev/mcp-js: ${source} must \`export default defineMcp(...)\` (got ${got}).`);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
version: MANIFEST_VERSION,
|
|
112
|
+
sdk_version: version,
|
|
113
|
+
path: urlPath,
|
|
114
|
+
auth: manifestAuth(def.auth),
|
|
115
|
+
mcp: buildMcpListing(def)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/stacks/tanstack/fs-errors.ts
|
|
120
|
+
function isFileMissing(err) {
|
|
121
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/stacks/tanstack/ssr-loader.ts
|
|
125
|
+
var import_vite = require("vite");
|
|
126
|
+
async function produceViaSsr(projectRoot2, alias, entryAbs, urlPath) {
|
|
127
|
+
const server = await (0, import_vite.createServer)({
|
|
128
|
+
configFile: false,
|
|
129
|
+
root: projectRoot2,
|
|
130
|
+
logLevel: "silent",
|
|
131
|
+
server: { middlewareMode: true, hmr: false, watch: null },
|
|
132
|
+
optimizeDeps: { noDiscovery: true },
|
|
133
|
+
resolve: { alias }
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
const mod = await server.ssrLoadModule(entryAbs);
|
|
137
|
+
return manifestFromDefinition(mod.default, urlPath, entryAbs);
|
|
138
|
+
} finally {
|
|
139
|
+
await server.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/stacks/tanstack/manifest-io.ts
|
|
144
|
+
var MCP_PLUGIN_NAME = "@lovable.dev/mcp-js";
|
|
145
|
+
var DEFAULT_MCP_ENTRY = "src/lib/mcp/index.ts";
|
|
146
|
+
var DEFAULT_MCP_PATH = "/mcp";
|
|
147
|
+
function writeManifestIfChanged(file, manifest) {
|
|
148
|
+
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
149
|
+
let existing;
|
|
150
|
+
try {
|
|
151
|
+
existing = (0, import_node_fs.readFileSync)(file, "utf8");
|
|
152
|
+
} catch (err) {
|
|
153
|
+
if (!isFileMissing(err))
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
if (existing === content)
|
|
157
|
+
return;
|
|
158
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(file), { recursive: true });
|
|
159
|
+
const tmp = `${file}.${(0, import_node_crypto.randomUUID)()}.tmp`;
|
|
160
|
+
try {
|
|
161
|
+
(0, import_node_fs.writeFileSync)(tmp, content, "utf8");
|
|
162
|
+
(0, import_node_fs.renameSync)(tmp, file);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
try {
|
|
165
|
+
(0, import_node_fs.unlinkSync)(tmp);
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function removeManifest(file) {
|
|
172
|
+
try {
|
|
173
|
+
(0, import_node_fs.unlinkSync)(file);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
if (!isFileMissing(err))
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function syncManifest(manifestFile, entryAbs, urlPath, produce) {
|
|
180
|
+
let entryExists = true;
|
|
181
|
+
try {
|
|
182
|
+
(0, import_node_fs.lstatSync)(entryAbs);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
if (!isFileMissing(err))
|
|
185
|
+
throw err;
|
|
186
|
+
entryExists = false;
|
|
187
|
+
}
|
|
188
|
+
if (!entryExists) {
|
|
189
|
+
removeManifest(manifestFile);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const manifest = await produce(entryAbs, urlPath);
|
|
193
|
+
writeManifestIfChanged(manifestFile, manifest);
|
|
194
|
+
}
|
|
195
|
+
async function runExtract(projectRoot2) {
|
|
196
|
+
const resolved = await (0, import_vite2.resolveConfig)({ root: projectRoot2, logLevel: "silent" }, "serve");
|
|
197
|
+
const alias = resolved.resolve?.alias ?? [];
|
|
198
|
+
const pluginApi = resolved.plugins.find((p) => p.name === MCP_PLUGIN_NAME)?.api;
|
|
199
|
+
const entryAbs = pluginApi?.mcpEntry ?? (0, import_node_path.resolve)(projectRoot2, DEFAULT_MCP_ENTRY);
|
|
200
|
+
const urlPath = pluginApi?.urlPath ?? DEFAULT_MCP_PATH;
|
|
201
|
+
await syncManifest(
|
|
202
|
+
(0, import_node_path.resolve)(projectRoot2, MANIFEST_RELATIVE_PATH),
|
|
203
|
+
entryAbs,
|
|
204
|
+
urlPath,
|
|
205
|
+
(entry, path) => produceViaSsr(projectRoot2, alias, entry, path)
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/stacks/tanstack/cli/extract-manifest.ts
|
|
210
|
+
var projectRoot = process.argv[2] ?? process.cwd();
|
|
211
|
+
runExtract(projectRoot).catch((err) => {
|
|
212
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
213
|
+
process.exit(1);
|
|
214
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildMcpListing
|
|
4
|
+
} from "../../../chunk-BG3HOZXZ.js";
|
|
5
|
+
import "../../../chunk-FQLM4JDU.js";
|
|
6
|
+
import "../../../chunk-QC3DXQTH.js";
|
|
7
|
+
import {
|
|
8
|
+
isFileMissing
|
|
9
|
+
} from "../../../chunk-QJ7XEKT3.js";
|
|
10
|
+
import "../../../chunk-6DXGZZA4.js";
|
|
11
|
+
|
|
12
|
+
// src/stacks/tanstack/manifest-io.ts
|
|
13
|
+
import { randomUUID } from "crypto";
|
|
14
|
+
import { lstatSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
15
|
+
import { dirname, resolve } from "path";
|
|
16
|
+
import { resolveConfig } from "vite";
|
|
17
|
+
|
|
18
|
+
// package.json
|
|
19
|
+
var version = "0.9.0";
|
|
20
|
+
|
|
21
|
+
// src/stacks/tanstack/extract-manifest.ts
|
|
22
|
+
var MANIFEST_VERSION = 1;
|
|
23
|
+
var MANIFEST_RELATIVE_PATH = ".lovable/mcp/manifest.json";
|
|
24
|
+
function manifestAuth(auth) {
|
|
25
|
+
if (!auth)
|
|
26
|
+
return { type: "none" };
|
|
27
|
+
const oauth = { type: "oauth", issuer: auth.issuer };
|
|
28
|
+
if (auth.resource !== void 0)
|
|
29
|
+
oauth.resource = auth.resource;
|
|
30
|
+
if (auth.resourceName !== void 0)
|
|
31
|
+
oauth.resource_name = auth.resourceName;
|
|
32
|
+
if (auth.resourceDocumentation !== void 0)
|
|
33
|
+
oauth.resource_documentation = auth.resourceDocumentation;
|
|
34
|
+
if (auth.protectedResourceMetadataUrl !== void 0)
|
|
35
|
+
oauth.protected_resource_metadata_url = auth.protectedResourceMetadataUrl;
|
|
36
|
+
if (auth.requireOAuthClientClaim !== void 0)
|
|
37
|
+
oauth.require_oauth_client_claim = auth.requireOAuthClientClaim;
|
|
38
|
+
if (auth.acceptedAudiences !== void 0)
|
|
39
|
+
oauth.accepted_audiences = auth.acceptedAudiences;
|
|
40
|
+
if (auth.requiredScopes !== void 0)
|
|
41
|
+
oauth.required_scopes = auth.requiredScopes;
|
|
42
|
+
if (auth.jwksUri !== void 0)
|
|
43
|
+
oauth.jwks_uri = auth.jwksUri;
|
|
44
|
+
if (auth.algorithms !== void 0)
|
|
45
|
+
oauth.algorithms = auth.algorithms;
|
|
46
|
+
if (auth.clockToleranceSeconds !== void 0)
|
|
47
|
+
oauth.clock_tolerance_seconds = auth.clockToleranceSeconds;
|
|
48
|
+
return oauth;
|
|
49
|
+
}
|
|
50
|
+
function isToolShape(tool) {
|
|
51
|
+
if (typeof tool !== "object" || tool === null)
|
|
52
|
+
return false;
|
|
53
|
+
const t = tool;
|
|
54
|
+
return typeof t.name === "string" && typeof t.title === "string" && typeof t.description === "string";
|
|
55
|
+
}
|
|
56
|
+
function isAuthShape(auth) {
|
|
57
|
+
if (auth === void 0)
|
|
58
|
+
return true;
|
|
59
|
+
if (typeof auth !== "object" || auth === null)
|
|
60
|
+
return false;
|
|
61
|
+
const a = auth;
|
|
62
|
+
return a.type === "oauth" && typeof a.issuer === "string" && a.issuer.length > 0;
|
|
63
|
+
}
|
|
64
|
+
function manifestFromDefinition(definition, urlPath, source = "the MCP entry") {
|
|
65
|
+
const def = definition;
|
|
66
|
+
const validShape = !!def && typeof def === "object" && typeof def.name === "string" && typeof def.title === "string" && typeof def.version === "string" && Array.isArray(def.tools) && def.tools.every(isToolShape) && isAuthShape(def.auth);
|
|
67
|
+
if (!validShape) {
|
|
68
|
+
const got = definition == null ? "no default export" : typeof definition;
|
|
69
|
+
throw new Error(`@lovable.dev/mcp-js: ${source} must \`export default defineMcp(...)\` (got ${got}).`);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
version: MANIFEST_VERSION,
|
|
73
|
+
sdk_version: version,
|
|
74
|
+
path: urlPath,
|
|
75
|
+
auth: manifestAuth(def.auth),
|
|
76
|
+
mcp: buildMcpListing(def)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/stacks/tanstack/ssr-loader.ts
|
|
81
|
+
import { createServer } from "vite";
|
|
82
|
+
async function produceViaSsr(projectRoot2, alias, entryAbs, urlPath) {
|
|
83
|
+
const server = await createServer({
|
|
84
|
+
configFile: false,
|
|
85
|
+
root: projectRoot2,
|
|
86
|
+
logLevel: "silent",
|
|
87
|
+
server: { middlewareMode: true, hmr: false, watch: null },
|
|
88
|
+
optimizeDeps: { noDiscovery: true },
|
|
89
|
+
resolve: { alias }
|
|
90
|
+
});
|
|
91
|
+
try {
|
|
92
|
+
const mod = await server.ssrLoadModule(entryAbs);
|
|
93
|
+
return manifestFromDefinition(mod.default, urlPath, entryAbs);
|
|
94
|
+
} finally {
|
|
95
|
+
await server.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/stacks/tanstack/manifest-io.ts
|
|
100
|
+
var MCP_PLUGIN_NAME = "@lovable.dev/mcp-js";
|
|
101
|
+
var DEFAULT_MCP_ENTRY = "src/lib/mcp/index.ts";
|
|
102
|
+
var DEFAULT_MCP_PATH = "/mcp";
|
|
103
|
+
function writeManifestIfChanged(file, manifest) {
|
|
104
|
+
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
105
|
+
let existing;
|
|
106
|
+
try {
|
|
107
|
+
existing = readFileSync(file, "utf8");
|
|
108
|
+
} catch (err) {
|
|
109
|
+
if (!isFileMissing(err))
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
if (existing === content)
|
|
113
|
+
return;
|
|
114
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
115
|
+
const tmp = `${file}.${randomUUID()}.tmp`;
|
|
116
|
+
try {
|
|
117
|
+
writeFileSync(tmp, content, "utf8");
|
|
118
|
+
renameSync(tmp, file);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
try {
|
|
121
|
+
unlinkSync(tmp);
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function removeManifest(file) {
|
|
128
|
+
try {
|
|
129
|
+
unlinkSync(file);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
if (!isFileMissing(err))
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function syncManifest(manifestFile, entryAbs, urlPath, produce) {
|
|
136
|
+
let entryExists = true;
|
|
137
|
+
try {
|
|
138
|
+
lstatSync(entryAbs);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (!isFileMissing(err))
|
|
141
|
+
throw err;
|
|
142
|
+
entryExists = false;
|
|
143
|
+
}
|
|
144
|
+
if (!entryExists) {
|
|
145
|
+
removeManifest(manifestFile);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const manifest = await produce(entryAbs, urlPath);
|
|
149
|
+
writeManifestIfChanged(manifestFile, manifest);
|
|
150
|
+
}
|
|
151
|
+
async function runExtract(projectRoot2) {
|
|
152
|
+
const resolved = await resolveConfig({ root: projectRoot2, logLevel: "silent" }, "serve");
|
|
153
|
+
const alias = resolved.resolve?.alias ?? [];
|
|
154
|
+
const pluginApi = resolved.plugins.find((p) => p.name === MCP_PLUGIN_NAME)?.api;
|
|
155
|
+
const entryAbs = pluginApi?.mcpEntry ?? resolve(projectRoot2, DEFAULT_MCP_ENTRY);
|
|
156
|
+
const urlPath = pluginApi?.urlPath ?? DEFAULT_MCP_PATH;
|
|
157
|
+
await syncManifest(
|
|
158
|
+
resolve(projectRoot2, MANIFEST_RELATIVE_PATH),
|
|
159
|
+
entryAbs,
|
|
160
|
+
urlPath,
|
|
161
|
+
(entry, path) => produceViaSsr(projectRoot2, alias, entry, path)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/stacks/tanstack/cli/extract-manifest.ts
|
|
166
|
+
var projectRoot = process.argv[2] ?? process.cwd();
|
|
167
|
+
runExtract(projectRoot).catch((err) => {
|
|
168
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|