@lovable.dev/mcp-js 0.9.1 → 0.9.2

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.
@@ -7,6 +7,7 @@ Architecture rationale, source layout, and dev scripts for contributors. The use
7
7
  - **`protocols/`** is the wire layer — one folder per externally observable HTTP surface. `protocols/mcp` is the public MCP-over-HTTP surface; `protocols/oauth-metadata` serves the RFC 9728 protected-resource metadata required to bootstrap protected MCP clients; `protocols/rest` is internal RPC for the upstream MCP proxy. Every backend wires both MCP and REST, plus the metadata endpoint when OAuth is configured.
8
8
  - **`auth/`** (unpublished) is the cross-cutting OAuth middleware — bearer verification, issuer/JWKS discovery, the `auth.oauth.*` config namespace. It depends only on `core/`; both `protocols/mcp` and `protocols/rest` depend on it for the shared bearer gate, so it isn't a wire-format peer of `mcp`/`rest` and doesn't live under `protocols/`.
9
9
  - **`stacks/`** is the framework integration: TanStack today; future entries (Supabase Edge Functions, classic Vite, …) live next to it under the same parent, forwarding to `protocols/{mcp,oauth-metadata,rest}` directly — protocol logic stays shared, only ctx unwrapping differs.
10
+ - **`manifest/`** is the stack-agnostic snapshot writer. `lovable-mcp-extract-manifest` is one CLI for every stack: it asks Vite for the resolved config, finds any plugin whose name matches `@lovable.dev/mcp-js*`, and reads `mcpEntry` + `urlPath` off its `api`. TanStack publishes its configured route path (`/mcp`); Supabase publishes `/functions/v1/<functionName>`. The projection (`extract.ts`) is pure and shares `buildMcpListing` with the live `GET /.mcp/list-tools` route so the committed manifest and the served catalog can't drift.
10
11
 
11
12
  ## Design decisions
12
13
 
@@ -111,6 +112,7 @@ src/
111
112
  url.ts # URL parsing/validation
112
113
  validation.ts # config assertions
113
114
  promise.ts # cachedPromise (resolved-value cache)
115
+ fs-errors.ts # ENOENT-only catch guard (shared by manifest/io + stacks)
114
116
  auth/ # cross-cutting OAuth middleware (unpublished); depends only on core/
115
117
  config.ts # auth namespace: auth.oauth.issuer
116
118
  authorize.ts # request authorizer, bearer parsing, challenges
@@ -130,14 +132,16 @@ src/
130
132
  list-tools.ts # createListToolsHandler (Web-Standard, GET)
131
133
  invoke-tool.ts # createInvokeToolHandler (Web-Standard, POST)
132
134
  index.ts # barrel
135
+ manifest/ # stack-agnostic manifest extraction (TanStack + Supabase both feed it)
136
+ extract.ts # project a defineMcp result to the manifest (types + manifestFromDefinition)
137
+ io.ts # runExtract/syncManifest: read any @lovable.dev/mcp-js* plugin's api + write .lovable/mcp/manifest.json
138
+ ssr-loader.ts # produceViaSsr: load the entry via a throwaway Vite SSR server
139
+ cli/
140
+ extract-manifest.ts # lovable-mcp-extract-manifest bin (runs runExtract on cwd)
133
141
  stacks/
134
142
  tanstack/
135
143
  handlers.ts # TanStack route-ctx adapters
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)
144
+ vite.ts # mcpPlugin (route emission + stale-file cleanup; exposes McpPluginApi for the extractor)
141
145
  index.ts # barrel
142
146
  tests/
143
147
  core/{define,promise,url}.test.ts
@@ -146,8 +150,9 @@ tests/
146
150
  mcp/protocol.test.ts
147
151
  rest/{list-tools,invoke-tool}.test.ts
148
152
  parity.test.ts # REST↔MCP equivalence contract
153
+ manifest/{extract,io,ssr-loader,run-extract}.test.ts # stack-agnostic extractor (run-extract covers TanStack + Supabase)
149
154
  stacks/
150
- tanstack/{handlers,vite,extract-manifest,manifest-io,ssr-loader}.test.ts
155
+ tanstack/{handlers,vite}.test.ts
151
156
  integration/ # boots an example app, hits live HTTP
152
157
  global-setup.ts # spawns example/tanstack on :8080
153
158
  tools.test.ts # non-OAuth example
@@ -1,4 +1,4 @@
1
- // src/stacks/tanstack/fs-errors.ts
1
+ // src/core/fs-errors.ts
2
2
  function isFileMissing(err) {
3
3
  return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
4
4
  }
@@ -1,14 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- // src/stacks/tanstack/manifest-io.ts
4
+ // src/manifest/io.ts
5
5
  var import_node_crypto = require("crypto");
6
6
  var import_node_fs = require("fs");
7
7
  var import_node_path = require("path");
8
8
  var import_vite2 = require("vite");
9
9
 
10
+ // src/core/fs-errors.ts
11
+ function isFileMissing(err) {
12
+ return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
13
+ }
14
+
10
15
  // package.json
11
- var version = "0.9.1";
16
+ var version = "0.9.2";
12
17
 
13
18
  // src/protocols/rest/list-tools.ts
14
19
  var import_zod_compat = require("@modelcontextprotocol/sdk/server/zod-compat.js");
@@ -57,7 +62,7 @@ function buildMcpListing(mcp) {
57
62
  };
58
63
  }
59
64
 
60
- // src/stacks/tanstack/extract-manifest.ts
65
+ // src/manifest/extract.ts
61
66
  var MANIFEST_VERSION = 1;
62
67
  var MANIFEST_RELATIVE_PATH = ".lovable/mcp/manifest.json";
63
68
  function manifestAuth(auth) {
@@ -116,12 +121,7 @@ function manifestFromDefinition(definition, urlPath, source = "the MCP entry") {
116
121
  };
117
122
  }
118
123
 
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
124
+ // src/manifest/ssr-loader.ts
125
125
  var import_vite = require("vite");
126
126
  async function produceViaSsr(projectRoot2, alias, entryAbs, urlPath) {
127
127
  const server = await (0, import_vite.createServer)({
@@ -140,8 +140,8 @@ async function produceViaSsr(projectRoot2, alias, entryAbs, urlPath) {
140
140
  }
141
141
  }
142
142
 
143
- // src/stacks/tanstack/manifest-io.ts
144
- var MCP_PLUGIN_NAME = "@lovable.dev/mcp-js";
143
+ // src/manifest/io.ts
144
+ var MCP_PLUGIN_NAME_PREFIX = "@lovable.dev/mcp-js";
145
145
  var DEFAULT_MCP_ENTRY = "src/lib/mcp/index.ts";
146
146
  var DEFAULT_MCP_PATH = "/mcp";
147
147
  function writeManifestIfChanged(file, manifest) {
@@ -195,7 +195,9 @@ async function syncManifest(manifestFile, entryAbs, urlPath, produce) {
195
195
  async function runExtract(projectRoot2) {
196
196
  const resolved = await (0, import_vite2.resolveConfig)({ root: projectRoot2, logLevel: "silent" }, "serve");
197
197
  const alias = resolved.resolve?.alias ?? [];
198
- const pluginApi = resolved.plugins.find((p) => p.name === MCP_PLUGIN_NAME)?.api;
198
+ const pluginApi = resolved.plugins.find(
199
+ (p) => typeof p.name === "string" && p.name.startsWith(MCP_PLUGIN_NAME_PREFIX)
200
+ )?.api;
199
201
  const entryAbs = pluginApi?.mcpEntry ?? (0, import_node_path.resolve)(projectRoot2, DEFAULT_MCP_ENTRY);
200
202
  const urlPath = pluginApi?.urlPath ?? DEFAULT_MCP_PATH;
201
203
  await syncManifest(
@@ -206,7 +208,7 @@ async function runExtract(projectRoot2) {
206
208
  );
207
209
  }
208
210
 
209
- // src/stacks/tanstack/cli/extract-manifest.ts
211
+ // src/cli/extract-manifest.ts
210
212
  var projectRoot = process.argv[2] ?? process.cwd();
211
213
  runExtract(projectRoot).catch((err) => {
212
214
  console.error(err instanceof Error ? err.message : String(err));
@@ -1,24 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildMcpListing
4
- } from "../../../chunk-2RGXCDBU.js";
5
- import "../../../chunk-YQBOGY7D.js";
6
- import "../../../chunk-QC3DXQTH.js";
4
+ } from "../chunk-2RGXCDBU.js";
5
+ import "../chunk-YQBOGY7D.js";
6
+ import "../chunk-QC3DXQTH.js";
7
7
  import {
8
8
  isFileMissing
9
- } from "../../../chunk-QJ7XEKT3.js";
10
- import "../../../chunk-6DXGZZA4.js";
9
+ } from "../chunk-Y3ZFPEQH.js";
10
+ import "../chunk-6DXGZZA4.js";
11
11
 
12
- // src/stacks/tanstack/manifest-io.ts
12
+ // src/manifest/io.ts
13
13
  import { randomUUID } from "crypto";
14
14
  import { lstatSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
15
15
  import { dirname, resolve } from "path";
16
16
  import { resolveConfig } from "vite";
17
17
 
18
18
  // package.json
19
- var version = "0.9.1";
19
+ var version = "0.9.2";
20
20
 
21
- // src/stacks/tanstack/extract-manifest.ts
21
+ // src/manifest/extract.ts
22
22
  var MANIFEST_VERSION = 1;
23
23
  var MANIFEST_RELATIVE_PATH = ".lovable/mcp/manifest.json";
24
24
  function manifestAuth(auth) {
@@ -77,7 +77,7 @@ function manifestFromDefinition(definition, urlPath, source = "the MCP entry") {
77
77
  };
78
78
  }
79
79
 
80
- // src/stacks/tanstack/ssr-loader.ts
80
+ // src/manifest/ssr-loader.ts
81
81
  import { createServer } from "vite";
82
82
  async function produceViaSsr(projectRoot2, alias, entryAbs, urlPath) {
83
83
  const server = await createServer({
@@ -96,8 +96,8 @@ async function produceViaSsr(projectRoot2, alias, entryAbs, urlPath) {
96
96
  }
97
97
  }
98
98
 
99
- // src/stacks/tanstack/manifest-io.ts
100
- var MCP_PLUGIN_NAME = "@lovable.dev/mcp-js";
99
+ // src/manifest/io.ts
100
+ var MCP_PLUGIN_NAME_PREFIX = "@lovable.dev/mcp-js";
101
101
  var DEFAULT_MCP_ENTRY = "src/lib/mcp/index.ts";
102
102
  var DEFAULT_MCP_PATH = "/mcp";
103
103
  function writeManifestIfChanged(file, manifest) {
@@ -151,7 +151,9 @@ async function syncManifest(manifestFile, entryAbs, urlPath, produce) {
151
151
  async function runExtract(projectRoot2) {
152
152
  const resolved = await resolveConfig({ root: projectRoot2, logLevel: "silent" }, "serve");
153
153
  const alias = resolved.resolve?.alias ?? [];
154
- const pluginApi = resolved.plugins.find((p) => p.name === MCP_PLUGIN_NAME)?.api;
154
+ const pluginApi = resolved.plugins.find(
155
+ (p) => typeof p.name === "string" && p.name.startsWith(MCP_PLUGIN_NAME_PREFIX)
156
+ )?.api;
155
157
  const entryAbs = pluginApi?.mcpEntry ?? resolve(projectRoot2, DEFAULT_MCP_ENTRY);
156
158
  const urlPath = pluginApi?.urlPath ?? DEFAULT_MCP_PATH;
157
159
  await syncManifest(
@@ -162,7 +164,7 @@ async function runExtract(projectRoot2) {
162
164
  );
163
165
  }
164
166
 
165
- // src/stacks/tanstack/cli/extract-manifest.ts
167
+ // src/cli/extract-manifest.ts
166
168
  var projectRoot = process.argv[2] ?? process.cwd();
167
169
  runExtract(projectRoot).catch((err) => {
168
170
  console.error(err instanceof Error ? err.message : String(err));
@@ -33,7 +33,7 @@ var import_node_path = require("path");
33
33
  // src/auth/metadata-path.ts
34
34
  var OAUTH_PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
35
35
 
36
- // src/stacks/tanstack/fs-errors.ts
36
+ // src/core/fs-errors.ts
37
37
  function isFileMissing(err) {
38
38
  return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
39
39
  }
@@ -1,5 +1,17 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
+ /**
4
+ * Shape every MCP Vite plugin exposes on its `api` so `runExtract` can read
5
+ * back the resolved entry path and the URL the stack mounts the MCP server at.
6
+ * TanStack sets `urlPath` to the configured route path (default `/mcp`);
7
+ * Supabase sets it to `/functions/v1/<functionName>` (the public mount under
8
+ * which the function and its companion routes live).
9
+ */
10
+ interface McpPluginApi {
11
+ readonly mcpEntry: string;
12
+ readonly urlPath: string;
13
+ }
14
+
3
15
  interface McpPluginOptions {
4
16
  /**
5
17
  * Path to the MCP entry file (default export = `defineMcp(...)` result),
@@ -64,10 +76,7 @@ interface McpPluginOptions {
64
76
  */
65
77
  metadataPath?: string;
66
78
  }
67
- interface McpPluginApi {
68
- readonly mcpEntry: string;
69
- readonly urlPath: string;
70
- }
79
+
71
80
  declare function assertUrlPathShape(urlPath: string): string;
72
81
  declare function deriveRouteFileName(urlPath: string): string;
73
82
  declare function assertMetadataPathShape(metadataPath: string): void;
@@ -1,5 +1,17 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
+ /**
4
+ * Shape every MCP Vite plugin exposes on its `api` so `runExtract` can read
5
+ * back the resolved entry path and the URL the stack mounts the MCP server at.
6
+ * TanStack sets `urlPath` to the configured route path (default `/mcp`);
7
+ * Supabase sets it to `/functions/v1/<functionName>` (the public mount under
8
+ * which the function and its companion routes live).
9
+ */
10
+ interface McpPluginApi {
11
+ readonly mcpEntry: string;
12
+ readonly urlPath: string;
13
+ }
14
+
3
15
  interface McpPluginOptions {
4
16
  /**
5
17
  * Path to the MCP entry file (default export = `defineMcp(...)` result),
@@ -64,10 +76,7 @@ interface McpPluginOptions {
64
76
  */
65
77
  metadataPath?: string;
66
78
  }
67
- interface McpPluginApi {
68
- readonly mcpEntry: string;
69
- readonly urlPath: string;
70
- }
79
+
71
80
  declare function assertUrlPathShape(urlPath: string): string;
72
81
  declare function deriveRouteFileName(urlPath: string): string;
73
82
  declare function assertMetadataPathShape(metadataPath: string): void;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isFileMissing
3
- } from "../../chunk-QJ7XEKT3.js";
3
+ } from "../../chunk-Y3ZFPEQH.js";
4
4
  import {
5
5
  OAUTH_PROTECTED_RESOURCE_METADATA_PATH
6
6
  } from "../../chunk-6DXGZZA4.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovable.dev/mcp-js",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
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": {
@@ -51,7 +51,7 @@
51
51
  }
52
52
  },
53
53
  "bin": {
54
- "lovable-mcp-extract-manifest": "./dist/stacks/tanstack/cli/extract-manifest.cjs"
54
+ "lovable-mcp-extract-manifest": "./dist/cli/extract-manifest.cjs"
55
55
  },
56
56
  "files": [
57
57
  "dist"
@@ -82,8 +82,8 @@
82
82
  "zod": "^4.1.13"
83
83
  },
84
84
  "scripts": {
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",
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/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/cli/extract-manifest.ts --format cjs,esm --dts --watch",
87
87
  "typecheck": "tsgo --noEmit",
88
88
  "format": "oxfmt --write src/ tests/",
89
89
  "format:check": "oxfmt --check src/ tests/",