@lovable.dev/mcp-js 0.7.1 → 0.8.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 +10 -0
- package/dist/{chunk-LMT6RXUF.js → chunk-4Y72OCPS.js} +2 -46
- package/dist/chunk-E335BBVM.js +57 -0
- package/dist/chunk-QJ7XEKT3.js +8 -0
- package/dist/protocols/rest/index.cjs +14 -12
- package/dist/protocols/rest/index.js +5 -3
- 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 +14 -12
- package/dist/stacks/tanstack/index.js +5 -3
- package/dist/stacks/tanstack/vite.cjs +24 -11
- package/dist/stacks/tanstack/vite.d.cts +10 -2
- package/dist/stacks/tanstack/vite.d.ts +10 -2
- package/dist/stacks/tanstack/vite.js +23 -12
- package/package.json +6 -3
package/README.internal.md
CHANGED
|
@@ -133,7 +133,11 @@ src/
|
|
|
133
133
|
stacks/
|
|
134
134
|
tanstack/
|
|
135
135
|
handlers.ts # TanStack route-ctx adapters
|
|
136
|
-
vite.ts # mcpPlugin (route emission + stale-file cleanup)
|
|
136
|
+
vite.ts # mcpPlugin (route emission + stale-file cleanup; no manifest)
|
|
137
|
+
ssr-loader.ts # produceViaSsr: load the entry via a throwaway Vite SSR server
|
|
138
|
+
extract-manifest.ts # project a defineMcp result to the manifest (types + manifestFromDefinition)
|
|
139
|
+
manifest-io.ts # runExtract/syncManifest: write/remove .lovable/mcp/manifest.json
|
|
140
|
+
cli/extract-manifest.ts # lovable-mcp-extract-manifest bin (runs runExtract on cwd)
|
|
137
141
|
index.ts # barrel
|
|
138
142
|
tests/
|
|
139
143
|
core/{define,promise,url}.test.ts
|
|
@@ -143,7 +147,7 @@ tests/
|
|
|
143
147
|
rest/{list-tools,invoke-tool}.test.ts
|
|
144
148
|
parity.test.ts # REST↔MCP equivalence contract
|
|
145
149
|
stacks/
|
|
146
|
-
tanstack/{handlers,vite}.test.ts
|
|
150
|
+
tanstack/{handlers,vite,extract-manifest,manifest-io,ssr-loader}.test.ts
|
|
147
151
|
integration/ # boots an example app, hits live HTTP
|
|
148
152
|
global-setup.ts # spawns example/tanstack on :8080
|
|
149
153
|
tools.test.ts # non-OAuth example
|
package/README.md
CHANGED
|
@@ -53,6 +53,16 @@ MCP (`POST /mcp`) is the public wire format clients speak directly. REST (`/.mcp
|
|
|
53
53
|
|
|
54
54
|
The OAuth metadata route is emitted by default and returns `404` until OAuth auth is configured. Disable it with `mcpPlugin({ protectedResourceMetadataRoute: false })` only if the app owns `/.well-known/oauth-protected-resource` itself.
|
|
55
55
|
|
|
56
|
+
### `.lovable/mcp/manifest.json`
|
|
57
|
+
|
|
58
|
+
`.lovable/mcp/manifest.json` is a snapshot the Lovable platform reads to register the MCP server. Envelope fields: `version` (manifest schema version), `sdk_version` (the `@lovable.dev/mcp-js` release that wrote the snapshot), `path`, and `auth`. `auth` is the server's auth configuration, lifted into the envelope (not into `mcp`): `{ "type": "none" }`, or `{ "type": "oauth", ... }` mirroring the `defineMcp({ auth })` config (snake_case — `issuer`, `accepted_audiences`, `required_scopes`, `resource`, …). The `mcp` field is **exactly the `GET /.mcp/list-tools` body** — `server` plus the tool catalog (`name`/`title`/`description`/`annotations` and JSON-Schema `inputSchema`/`outputSchema`) — so the committed snapshot can't drift from what the live route serves. It's produced by loading the entry and reading the catalog off the `defineMcp` result, so the manifest reflects exactly what the server exposes, including tools built programmatically (spreads, computed names, tools from npm). **The `lovable-mcp-extract-manifest` CLI writes it** (loading the entry through Vite's SSR module loader, so it works under Node/Bun), and removes it when the entry is deleted. The Lovable platform runs the CLI in its commit pipeline; the Vite plugin only generates routes. Commit it: the platform reads the committed file.
|
|
59
|
+
|
|
60
|
+
Three caveats:
|
|
61
|
+
|
|
62
|
+
- **The entry must import cleanly.** Because extraction imports the entry, it must not throw at module load — read env vars and do I/O *inside* tool handlers, not at module top level. A top-level throw also breaks Worker cold-start, so this is correct authoring regardless. An entry that can't be imported (or doesn't `export default defineMcp(...)`) **makes the extract CLI exit non-zero with a clear error** rather than emitting a partial manifest.
|
|
63
|
+
- **Tool `title`/`description` and the `auth` config are serialized verbatim.** They land in a committed file. The `auth` config is whatever the entry resolves at build time — an issuer read from env resolves to its build-time value (e.g. a fallback like `https://supabase.invalid/auth/v1` when the env var is unset), so don't put secrets in it.
|
|
64
|
+
- **The `version` field is a wire-format version, not the package version.** The platform reader must accept a manifest `version` before this package starts emitting it, so bumping it is a coordinated deploy with the Lovable backend — not something an app author changes.
|
|
65
|
+
|
|
56
66
|
## OAuth resource-server auth
|
|
57
67
|
|
|
58
68
|
`@lovable.dev/mcp-js` can protect an app-hosted MCP server as an OAuth 2.1 resource server. The package does not implement `/authorize`, `/token`, client registration, refresh tokens, or consent UI; those stay with the authorization server (for today's Supabase-backed Lovable Cloud apps, Supabase Auth). The MCP runtime validates bearer JWTs, publishes RFC 9728 protected-resource metadata, returns `WWW-Authenticate: Bearer ... resource_metadata="..."` challenges, and passes verified claims to tools.
|
|
@@ -6,55 +6,12 @@ import {
|
|
|
6
6
|
assertRestResourceBinding,
|
|
7
7
|
corsPreflightResponse,
|
|
8
8
|
createRequestAuthorizer,
|
|
9
|
-
headResponse,
|
|
10
9
|
methodNotAllowed,
|
|
11
10
|
withCors
|
|
12
11
|
} from "./chunk-HRMLGCXV.js";
|
|
13
12
|
|
|
14
|
-
// src/protocols/rest/list-tools.ts
|
|
15
|
-
import { objectFromShape } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
16
|
-
import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-schema-compat.js";
|
|
17
|
-
function shapeToJsonSchema(shape) {
|
|
18
|
-
if (!shape)
|
|
19
|
-
return null;
|
|
20
|
-
try {
|
|
21
|
-
return toJsonSchemaCompat(objectFromShape(shape));
|
|
22
|
-
} catch {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function createListToolsHandler(mcp, options = {}) {
|
|
27
|
-
assertRestResourceBinding(mcp, options);
|
|
28
|
-
const authorizer = createRequestAuthorizer(mcp, options);
|
|
29
|
-
const handle = async (request) => {
|
|
30
|
-
const authResult = await authorizer.authorize(request);
|
|
31
|
-
if (!authResult.ok)
|
|
32
|
-
return authResult.response;
|
|
33
|
-
if (request.method !== "GET" && request.method !== "HEAD")
|
|
34
|
-
return methodNotAllowed("GET, HEAD, OPTIONS");
|
|
35
|
-
const body = {
|
|
36
|
-
server: { name: mcp.name, version: mcp.version, title: mcp.title },
|
|
37
|
-
tools: mcp.tools.map((tool) => ({
|
|
38
|
-
name: tool.name,
|
|
39
|
-
title: tool.title,
|
|
40
|
-
description: tool.description,
|
|
41
|
-
annotations: tool.annotations,
|
|
42
|
-
inputSchema: shapeToJsonSchema(tool.inputSchema),
|
|
43
|
-
outputSchema: shapeToJsonSchema(tool.outputSchema)
|
|
44
|
-
}))
|
|
45
|
-
};
|
|
46
|
-
const response = Response.json(body);
|
|
47
|
-
return request.method === "HEAD" ? headResponse(response) : response;
|
|
48
|
-
};
|
|
49
|
-
return async (request) => {
|
|
50
|
-
if (request.method === "OPTIONS")
|
|
51
|
-
return corsPreflightResponse("GET, HEAD, OPTIONS");
|
|
52
|
-
return withCors(await handle(request));
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
13
|
// src/protocols/rest/invoke-tool.ts
|
|
57
|
-
import { getParseErrorMessage, objectFromShape
|
|
14
|
+
import { getParseErrorMessage, objectFromShape, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
58
15
|
var MAX_REFLECTED_TOOL_NAME = 256;
|
|
59
16
|
function safeReflectName(name) {
|
|
60
17
|
const text = String(name);
|
|
@@ -98,7 +55,7 @@ function createInvokeToolHandler(mcp, options = {}) {
|
|
|
98
55
|
let args = rawArgs;
|
|
99
56
|
if (tool.inputSchema) {
|
|
100
57
|
try {
|
|
101
|
-
const schema =
|
|
58
|
+
const schema = objectFromShape(tool.inputSchema);
|
|
102
59
|
const parsed = await safeParseAsync(schema, rawArgs);
|
|
103
60
|
if (!parsed.success) {
|
|
104
61
|
return new Response(
|
|
@@ -151,6 +108,5 @@ function createInvokeToolHandler(mcp, options = {}) {
|
|
|
151
108
|
}
|
|
152
109
|
|
|
153
110
|
export {
|
|
154
|
-
createListToolsHandler,
|
|
155
111
|
createInvokeToolHandler
|
|
156
112
|
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertRestResourceBinding,
|
|
3
|
+
corsPreflightResponse,
|
|
4
|
+
createRequestAuthorizer,
|
|
5
|
+
headResponse,
|
|
6
|
+
methodNotAllowed,
|
|
7
|
+
withCors
|
|
8
|
+
} from "./chunk-HRMLGCXV.js";
|
|
9
|
+
|
|
10
|
+
// src/protocols/rest/list-tools.ts
|
|
11
|
+
import { objectFromShape } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
12
|
+
import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-schema-compat.js";
|
|
13
|
+
function shapeToJsonSchema(shape) {
|
|
14
|
+
if (!shape)
|
|
15
|
+
return null;
|
|
16
|
+
try {
|
|
17
|
+
return toJsonSchemaCompat(objectFromShape(shape));
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function buildMcpListing(mcp) {
|
|
23
|
+
return {
|
|
24
|
+
server: { name: mcp.name, version: mcp.version, title: mcp.title },
|
|
25
|
+
tools: mcp.tools.map((tool) => ({
|
|
26
|
+
name: tool.name,
|
|
27
|
+
title: tool.title,
|
|
28
|
+
description: tool.description,
|
|
29
|
+
annotations: tool.annotations,
|
|
30
|
+
inputSchema: shapeToJsonSchema(tool.inputSchema),
|
|
31
|
+
outputSchema: shapeToJsonSchema(tool.outputSchema)
|
|
32
|
+
}))
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function createListToolsHandler(mcp, options = {}) {
|
|
36
|
+
assertRestResourceBinding(mcp, options);
|
|
37
|
+
const authorizer = createRequestAuthorizer(mcp, options);
|
|
38
|
+
const handle = async (request) => {
|
|
39
|
+
const authResult = await authorizer.authorize(request);
|
|
40
|
+
if (!authResult.ok)
|
|
41
|
+
return authResult.response;
|
|
42
|
+
if (request.method !== "GET" && request.method !== "HEAD")
|
|
43
|
+
return methodNotAllowed("GET, HEAD, OPTIONS");
|
|
44
|
+
const response = Response.json(buildMcpListing(mcp));
|
|
45
|
+
return request.method === "HEAD" ? headResponse(response) : response;
|
|
46
|
+
};
|
|
47
|
+
return async (request) => {
|
|
48
|
+
if (request.method === "OPTIONS")
|
|
49
|
+
return corsPreflightResponse("GET, HEAD, OPTIONS");
|
|
50
|
+
return withCors(await handle(request));
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
buildMcpListing,
|
|
56
|
+
createListToolsHandler
|
|
57
|
+
};
|
|
@@ -550,6 +550,19 @@ function shapeToJsonSchema(shape) {
|
|
|
550
550
|
return null;
|
|
551
551
|
}
|
|
552
552
|
}
|
|
553
|
+
function buildMcpListing(mcp) {
|
|
554
|
+
return {
|
|
555
|
+
server: { name: mcp.name, version: mcp.version, title: mcp.title },
|
|
556
|
+
tools: mcp.tools.map((tool) => ({
|
|
557
|
+
name: tool.name,
|
|
558
|
+
title: tool.title,
|
|
559
|
+
description: tool.description,
|
|
560
|
+
annotations: tool.annotations,
|
|
561
|
+
inputSchema: shapeToJsonSchema(tool.inputSchema),
|
|
562
|
+
outputSchema: shapeToJsonSchema(tool.outputSchema)
|
|
563
|
+
}))
|
|
564
|
+
};
|
|
565
|
+
}
|
|
553
566
|
function createListToolsHandler(mcp, options = {}) {
|
|
554
567
|
assertRestResourceBinding(mcp, options);
|
|
555
568
|
const authorizer = createRequestAuthorizer(mcp, options);
|
|
@@ -559,18 +572,7 @@ function createListToolsHandler(mcp, options = {}) {
|
|
|
559
572
|
return authResult.response;
|
|
560
573
|
if (request.method !== "GET" && request.method !== "HEAD")
|
|
561
574
|
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);
|
|
575
|
+
const response = Response.json(buildMcpListing(mcp));
|
|
574
576
|
return request.method === "HEAD" ? headResponse(response) : response;
|
|
575
577
|
};
|
|
576
578
|
return async (request) => {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createInvokeToolHandler
|
|
3
|
-
|
|
4
|
-
} from "../../chunk-LMT6RXUF.js";
|
|
2
|
+
createInvokeToolHandler
|
|
3
|
+
} from "../../chunk-4Y72OCPS.js";
|
|
5
4
|
import "../../chunk-MA5H6PSF.js";
|
|
5
|
+
import {
|
|
6
|
+
createListToolsHandler
|
|
7
|
+
} from "../../chunk-E335BBVM.js";
|
|
6
8
|
import "../../chunk-HRMLGCXV.js";
|
|
7
9
|
import "../../chunk-QC3DXQTH.js";
|
|
8
10
|
import "../../chunk-6DXGZZA4.js";
|
|
@@ -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.8.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-E335BBVM.js";
|
|
5
|
+
import "../../../chunk-HRMLGCXV.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.8.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
|
+
});
|
|
@@ -718,6 +718,19 @@ function shapeToJsonSchema(shape) {
|
|
|
718
718
|
return null;
|
|
719
719
|
}
|
|
720
720
|
}
|
|
721
|
+
function buildMcpListing(mcp) {
|
|
722
|
+
return {
|
|
723
|
+
server: { name: mcp.name, version: mcp.version, title: mcp.title },
|
|
724
|
+
tools: mcp.tools.map((tool) => ({
|
|
725
|
+
name: tool.name,
|
|
726
|
+
title: tool.title,
|
|
727
|
+
description: tool.description,
|
|
728
|
+
annotations: tool.annotations,
|
|
729
|
+
inputSchema: shapeToJsonSchema(tool.inputSchema),
|
|
730
|
+
outputSchema: shapeToJsonSchema(tool.outputSchema)
|
|
731
|
+
}))
|
|
732
|
+
};
|
|
733
|
+
}
|
|
721
734
|
function createListToolsHandler(mcp, options = {}) {
|
|
722
735
|
assertRestResourceBinding(mcp, options);
|
|
723
736
|
const authorizer = createRequestAuthorizer(mcp, options);
|
|
@@ -727,18 +740,7 @@ function createListToolsHandler(mcp, options = {}) {
|
|
|
727
740
|
return authResult.response;
|
|
728
741
|
if (request.method !== "GET" && request.method !== "HEAD")
|
|
729
742
|
return methodNotAllowed("GET, HEAD, OPTIONS");
|
|
730
|
-
const
|
|
731
|
-
server: { name: mcp.name, version: mcp.version, title: mcp.title },
|
|
732
|
-
tools: mcp.tools.map((tool) => ({
|
|
733
|
-
name: tool.name,
|
|
734
|
-
title: tool.title,
|
|
735
|
-
description: tool.description,
|
|
736
|
-
annotations: tool.annotations,
|
|
737
|
-
inputSchema: shapeToJsonSchema(tool.inputSchema),
|
|
738
|
-
outputSchema: shapeToJsonSchema(tool.outputSchema)
|
|
739
|
-
}))
|
|
740
|
-
};
|
|
741
|
-
const response = Response.json(body);
|
|
743
|
+
const response = Response.json(buildMcpListing(mcp));
|
|
742
744
|
return request.method === "HEAD" ? headResponse(response) : response;
|
|
743
745
|
};
|
|
744
746
|
return async (request) => {
|
|
@@ -5,10 +5,12 @@ import {
|
|
|
5
5
|
createOAuthProtectedResourceMetadataHandler
|
|
6
6
|
} from "../../chunk-W7F6JRDB.js";
|
|
7
7
|
import {
|
|
8
|
-
createInvokeToolHandler
|
|
9
|
-
|
|
10
|
-
} from "../../chunk-LMT6RXUF.js";
|
|
8
|
+
createInvokeToolHandler
|
|
9
|
+
} from "../../chunk-4Y72OCPS.js";
|
|
11
10
|
import "../../chunk-MA5H6PSF.js";
|
|
11
|
+
import {
|
|
12
|
+
createListToolsHandler
|
|
13
|
+
} from "../../chunk-E335BBVM.js";
|
|
12
14
|
import "../../chunk-HRMLGCXV.js";
|
|
13
15
|
import "../../chunk-QC3DXQTH.js";
|
|
14
16
|
import "../../chunk-6DXGZZA4.js";
|
|
@@ -32,11 +32,13 @@ var import_node_path = require("path");
|
|
|
32
32
|
// src/auth/metadata-path.ts
|
|
33
33
|
var OAUTH_PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
|
|
34
34
|
|
|
35
|
-
// src/stacks/tanstack/
|
|
36
|
-
var GENERATED_BANNER = "// AUTO-GENERATED by @lovable.dev/mcp-js \u2014 do not edit. Regenerated by the Vite plugin.\n// To take ownership, delete this banner line; the plugin then leaves the file alone.";
|
|
35
|
+
// src/stacks/tanstack/fs-errors.ts
|
|
37
36
|
function isFileMissing(err) {
|
|
38
37
|
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
39
38
|
}
|
|
39
|
+
|
|
40
|
+
// src/stacks/tanstack/vite.ts
|
|
41
|
+
var GENERATED_BANNER = "// AUTO-GENERATED by @lovable.dev/mcp-js \u2014 do not edit. Regenerated by the Vite plugin.\n// To take ownership, delete this banner line; the plugin then leaves the file alone.";
|
|
40
42
|
function normalizePath(p) {
|
|
41
43
|
return p.split(import_node_path.sep).join("/");
|
|
42
44
|
}
|
|
@@ -251,6 +253,18 @@ function mcpPlugin(options = {}) {
|
|
|
251
253
|
};
|
|
252
254
|
return {
|
|
253
255
|
name: "@lovable.dev/mcp-js",
|
|
256
|
+
// The extract CLI reads the resolved entry + URL path back through this
|
|
257
|
+
// (via `resolveConfig().plugins`) so the manifest honors custom
|
|
258
|
+
// `mcpEntry`/`path` instead of assuming the defaults. Getters because
|
|
259
|
+
// `mcpEntry` is finalized in `configResolved`.
|
|
260
|
+
api: {
|
|
261
|
+
get mcpEntry() {
|
|
262
|
+
return mcpEntry;
|
|
263
|
+
},
|
|
264
|
+
get urlPath() {
|
|
265
|
+
return urlPath;
|
|
266
|
+
}
|
|
267
|
+
},
|
|
254
268
|
configResolved(config) {
|
|
255
269
|
projectRoot = config.root;
|
|
256
270
|
mcpEntry = (0, import_node_path.resolve)(projectRoot, mcpEntryOption);
|
|
@@ -262,19 +276,18 @@ function mcpPlugin(options = {}) {
|
|
|
262
276
|
regenerate();
|
|
263
277
|
},
|
|
264
278
|
configureServer(server) {
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
if (normalizePath(file) === normalizePath(watchedEntry)) {
|
|
279
|
+
const onChange = (file) => {
|
|
280
|
+
if (normalizePath(file) === normalizePath(mcpEntry)) {
|
|
268
281
|
regenerate();
|
|
269
282
|
}
|
|
270
283
|
};
|
|
271
|
-
server.watcher.on("add",
|
|
272
|
-
server.watcher.on("change",
|
|
273
|
-
server.watcher.on("unlink",
|
|
284
|
+
server.watcher.on("add", onChange);
|
|
285
|
+
server.watcher.on("change", onChange);
|
|
286
|
+
server.watcher.on("unlink", onChange);
|
|
274
287
|
server.watcher.once("close", () => {
|
|
275
|
-
server.watcher.off("add",
|
|
276
|
-
server.watcher.off("change",
|
|
277
|
-
server.watcher.off("unlink",
|
|
288
|
+
server.watcher.off("add", onChange);
|
|
289
|
+
server.watcher.off("change", onChange);
|
|
290
|
+
server.watcher.off("unlink", onChange);
|
|
278
291
|
});
|
|
279
292
|
},
|
|
280
293
|
buildStart() {
|
|
@@ -49,6 +49,10 @@ interface McpPluginOptions {
|
|
|
49
49
|
*/
|
|
50
50
|
protectedResourceMetadataRoute?: boolean;
|
|
51
51
|
}
|
|
52
|
+
interface McpPluginApi {
|
|
53
|
+
readonly mcpEntry: string;
|
|
54
|
+
readonly urlPath: string;
|
|
55
|
+
}
|
|
52
56
|
declare function assertUrlPathShape(urlPath: string): string;
|
|
53
57
|
declare function deriveRouteFileName(urlPath: string): string;
|
|
54
58
|
/**
|
|
@@ -57,8 +61,12 @@ declare function deriveRouteFileName(urlPath: string): string;
|
|
|
57
61
|
* Place this in your `vite.config.ts` plugins array. Emits during
|
|
58
62
|
* `configResolved` (so dev-server boot and the TanStack file-router both
|
|
59
63
|
* see the routes on the first request) and re-emits when the MCP entry
|
|
60
|
-
* file
|
|
64
|
+
* file appears or disappears during dev.
|
|
65
|
+
*
|
|
66
|
+
* The `.lovable/mcp/manifest.json` snapshot is produced separately by the
|
|
67
|
+
* `lovable-mcp-extract-manifest` CLI (run by a host platform at build/commit
|
|
68
|
+
* time) — not by this plugin.
|
|
61
69
|
*/
|
|
62
70
|
declare function mcpPlugin(options?: McpPluginOptions): Plugin;
|
|
63
71
|
|
|
64
|
-
export { type McpPluginOptions, assertUrlPathShape, mcpPlugin as default, deriveRouteFileName, mcpPlugin };
|
|
72
|
+
export { type McpPluginApi, type McpPluginOptions, assertUrlPathShape, mcpPlugin as default, deriveRouteFileName, mcpPlugin };
|
|
@@ -49,6 +49,10 @@ interface McpPluginOptions {
|
|
|
49
49
|
*/
|
|
50
50
|
protectedResourceMetadataRoute?: boolean;
|
|
51
51
|
}
|
|
52
|
+
interface McpPluginApi {
|
|
53
|
+
readonly mcpEntry: string;
|
|
54
|
+
readonly urlPath: string;
|
|
55
|
+
}
|
|
52
56
|
declare function assertUrlPathShape(urlPath: string): string;
|
|
53
57
|
declare function deriveRouteFileName(urlPath: string): string;
|
|
54
58
|
/**
|
|
@@ -57,8 +61,12 @@ declare function deriveRouteFileName(urlPath: string): string;
|
|
|
57
61
|
* Place this in your `vite.config.ts` plugins array. Emits during
|
|
58
62
|
* `configResolved` (so dev-server boot and the TanStack file-router both
|
|
59
63
|
* see the routes on the first request) and re-emits when the MCP entry
|
|
60
|
-
* file
|
|
64
|
+
* file appears or disappears during dev.
|
|
65
|
+
*
|
|
66
|
+
* The `.lovable/mcp/manifest.json` snapshot is produced separately by the
|
|
67
|
+
* `lovable-mcp-extract-manifest` CLI (run by a host platform at build/commit
|
|
68
|
+
* time) — not by this plugin.
|
|
61
69
|
*/
|
|
62
70
|
declare function mcpPlugin(options?: McpPluginOptions): Plugin;
|
|
63
71
|
|
|
64
|
-
export { type McpPluginOptions, assertUrlPathShape, mcpPlugin as default, deriveRouteFileName, mcpPlugin };
|
|
72
|
+
export { type McpPluginApi, type McpPluginOptions, assertUrlPathShape, mcpPlugin as default, deriveRouteFileName, mcpPlugin };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isFileMissing
|
|
3
|
+
} from "../../chunk-QJ7XEKT3.js";
|
|
1
4
|
import {
|
|
2
5
|
OAUTH_PROTECTED_RESOURCE_METADATA_PATH
|
|
3
6
|
} from "../../chunk-6DXGZZA4.js";
|
|
@@ -6,9 +9,6 @@ import {
|
|
|
6
9
|
import { lstatSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
|
|
7
10
|
import { dirname, join, relative, resolve, sep } from "path";
|
|
8
11
|
var GENERATED_BANNER = "// AUTO-GENERATED by @lovable.dev/mcp-js \u2014 do not edit. Regenerated by the Vite plugin.\n// To take ownership, delete this banner line; the plugin then leaves the file alone.";
|
|
9
|
-
function isFileMissing(err) {
|
|
10
|
-
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
11
|
-
}
|
|
12
12
|
function normalizePath(p) {
|
|
13
13
|
return p.split(sep).join("/");
|
|
14
14
|
}
|
|
@@ -223,6 +223,18 @@ function mcpPlugin(options = {}) {
|
|
|
223
223
|
};
|
|
224
224
|
return {
|
|
225
225
|
name: "@lovable.dev/mcp-js",
|
|
226
|
+
// The extract CLI reads the resolved entry + URL path back through this
|
|
227
|
+
// (via `resolveConfig().plugins`) so the manifest honors custom
|
|
228
|
+
// `mcpEntry`/`path` instead of assuming the defaults. Getters because
|
|
229
|
+
// `mcpEntry` is finalized in `configResolved`.
|
|
230
|
+
api: {
|
|
231
|
+
get mcpEntry() {
|
|
232
|
+
return mcpEntry;
|
|
233
|
+
},
|
|
234
|
+
get urlPath() {
|
|
235
|
+
return urlPath;
|
|
236
|
+
}
|
|
237
|
+
},
|
|
226
238
|
configResolved(config) {
|
|
227
239
|
projectRoot = config.root;
|
|
228
240
|
mcpEntry = resolve(projectRoot, mcpEntryOption);
|
|
@@ -234,19 +246,18 @@ function mcpPlugin(options = {}) {
|
|
|
234
246
|
regenerate();
|
|
235
247
|
},
|
|
236
248
|
configureServer(server) {
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
if (normalizePath(file) === normalizePath(watchedEntry)) {
|
|
249
|
+
const onChange = (file) => {
|
|
250
|
+
if (normalizePath(file) === normalizePath(mcpEntry)) {
|
|
240
251
|
regenerate();
|
|
241
252
|
}
|
|
242
253
|
};
|
|
243
|
-
server.watcher.on("add",
|
|
244
|
-
server.watcher.on("change",
|
|
245
|
-
server.watcher.on("unlink",
|
|
254
|
+
server.watcher.on("add", onChange);
|
|
255
|
+
server.watcher.on("change", onChange);
|
|
256
|
+
server.watcher.on("unlink", onChange);
|
|
246
257
|
server.watcher.once("close", () => {
|
|
247
|
-
server.watcher.off("add",
|
|
248
|
-
server.watcher.off("change",
|
|
249
|
-
server.watcher.off("unlink",
|
|
258
|
+
server.watcher.off("add", onChange);
|
|
259
|
+
server.watcher.off("change", onChange);
|
|
260
|
+
server.watcher.off("unlink", onChange);
|
|
250
261
|
});
|
|
251
262
|
},
|
|
252
263
|
buildStart() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovable.dev/mcp-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Author MCP servers for Lovable apps. Declare tools with defineTool, register them in defineMcp, and the framework adapter (TanStack today, Supabase Edge Functions next) emits the route(s) at build time.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -50,6 +50,9 @@
|
|
|
50
50
|
"require": "./dist/stacks/tanstack/vite.cjs"
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
|
+
"bin": {
|
|
54
|
+
"lovable-mcp-extract-manifest": "./dist/stacks/tanstack/cli/extract-manifest.cjs"
|
|
55
|
+
},
|
|
53
56
|
"files": [
|
|
54
57
|
"dist"
|
|
55
58
|
],
|
|
@@ -79,8 +82,8 @@
|
|
|
79
82
|
"zod": "^4.1.13"
|
|
80
83
|
},
|
|
81
84
|
"scripts": {
|
|
82
|
-
"build": "tsup src/index.ts src/protocols/mcp/index.ts src/protocols/oauth-metadata.ts src/protocols/rest/index.ts src/stacks/tanstack/index.ts src/stacks/tanstack/vite.ts --format cjs,esm --dts --outDir dist",
|
|
83
|
-
"dev": "tsup src/index.ts src/protocols/mcp/index.ts src/protocols/oauth-metadata.ts src/protocols/rest/index.ts src/stacks/tanstack/index.ts src/stacks/tanstack/vite.ts --format cjs,esm --dts --watch",
|
|
85
|
+
"build": "tsup src/index.ts src/protocols/mcp/index.ts src/protocols/oauth-metadata.ts src/protocols/rest/index.ts src/stacks/tanstack/index.ts src/stacks/tanstack/vite.ts src/stacks/tanstack/cli/extract-manifest.ts --format cjs,esm --dts --outDir dist",
|
|
86
|
+
"dev": "tsup src/index.ts src/protocols/mcp/index.ts src/protocols/oauth-metadata.ts src/protocols/rest/index.ts src/stacks/tanstack/index.ts src/stacks/tanstack/vite.ts src/stacks/tanstack/cli/extract-manifest.ts --format cjs,esm --dts --watch",
|
|
84
87
|
"typecheck": "tsgo --noEmit",
|
|
85
88
|
"format": "oxfmt --write src/ tests/",
|
|
86
89
|
"format:check": "oxfmt --check src/ tests/",
|