@lovable.dev/mcp-js 0.13.0 → 0.14.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.md CHANGED
@@ -49,7 +49,9 @@ That's the whole authoring surface. No imperative server construction, no route
49
49
  | `GET /.mcp/list-tools` | `src/routes/[.mcp]/list-tools.ts` | Tool catalog with JSON Schemas |
50
50
  | `POST /.mcp/invoke-tool/<tool>` | `src/routes/[.mcp]/invoke-tool/$tool.ts` | REST dispatcher; one handler call per tool |
51
51
 
52
- MCP (`POST /mcp`) is the public wire format clients speak directly. REST (`/.mcp/list-tools`, `/.mcp/invoke-tool/<tool>`) is internal RPC — only an upstream MCP proxy calls it; never a browser or a hand-rolled client. All runtime routes import the same `defineMcp` result, so they stay in sync on which tools exist and which auth policy protects them. If `defineMcp({ auth: ... })` is omitted, the handlers stay unauthenticated; if OAuth auth is configured, MCP and REST both require the proxy/client to pass `Authorization: Bearer <token>`. CORS and rate limiting still belong at the app host or edge.
52
+ The table shows the default `path: "/mcp"`. The REST companions co-locate under the parent of the configured `path`, so `mcpPlugin({ path: "/api/public/mcp" })` emits them at `/api/public/.mcp/list-tools` and `/api/public/.mcp/invoke-tool/<tool>` (file: `src/routes/api/public/[.mcp]/...`). Pass `restRoutes: false` to drop them.
53
+
54
+ MCP (`POST <path>`, `/mcp` by default) is the public wire format clients speak directly. REST (the `.mcp/*` companions, co-located under the `path` parent) is internal RPC — only an upstream MCP proxy calls it; never a browser or a hand-rolled client. All runtime routes import the same `defineMcp` result, so they stay in sync on which tools exist and which auth policy protects them. If `defineMcp({ auth: ... })` is omitted, the handlers stay unauthenticated; if OAuth auth is configured, MCP and REST both require the proxy/client to pass `Authorization: Bearer <token>`. CORS and rate limiting still belong at the app host or edge.
53
55
 
54
56
  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
57
 
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "0.13.0";
2
+ var version = "0.14.0";
3
3
 
4
4
  export {
5
5
  version
@@ -13,7 +13,7 @@ function isFileMissing(err) {
13
13
  }
14
14
 
15
15
  // package.json
16
- var version = "0.13.0";
16
+ var version = "0.14.0";
17
17
 
18
18
  // src/protocols/rest/list-tools.ts
19
19
  var import_zod_compat = require("@modelcontextprotocol/sdk/server/zod-compat.js");
@@ -7,7 +7,7 @@ import "../chunk-QC3DXQTH.js";
7
7
  import "../chunk-6DXGZZA4.js";
8
8
  import {
9
9
  version
10
- } from "../chunk-SIITHGUD.js";
10
+ } from "../chunk-NOFPRSSI.js";
11
11
  import {
12
12
  isFileMissing
13
13
  } from "../chunk-Y3ZFPEQH.js";
@@ -32,7 +32,7 @@ var import_node_fs = require("fs");
32
32
  var import_node_path = require("path");
33
33
 
34
34
  // package.json
35
- var version = "0.13.0";
35
+ var version = "0.14.0";
36
36
 
37
37
  // src/core/fs-errors.ts
38
38
  function isFileMissing(err) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  version
3
- } from "../../chunk-SIITHGUD.js";
3
+ } from "../../chunk-NOFPRSSI.js";
4
4
  import {
5
5
  isFileMissing
6
6
  } from "../../chunk-Y3ZFPEQH.js";
@@ -56,20 +56,28 @@ function wellKnownRouteFile(routesDir, urlPath) {
56
56
  });
57
57
  return (0, import_node_path.resolve)(routesDir, ...fileSegments);
58
58
  }
59
- function resolveAllRoutes(projectRoot, routesDirOption, routeFileName, metadataUrlPath) {
59
+ function resolveRestRoutes(routesDir, mcpUrlPath) {
60
+ const parentSegments = mcpUrlPath.replace(/^\/+/, "").replace(/\/+$/, "").split("/").slice(0, -1);
61
+ const restDir = (0, import_node_path.resolve)(routesDir, ...parentSegments, "[.mcp]");
62
+ const restUrlBase = `/${[...parentSegments, ".mcp"].join("/")}`;
63
+ return {
64
+ listToolsRouteFile: (0, import_node_path.resolve)(restDir, "list-tools.ts"),
65
+ invokeToolRouteFile: (0, import_node_path.resolve)(restDir, "invoke-tool", "$tool.ts"),
66
+ listToolsLiteral: `${restUrlBase}/list-tools`,
67
+ invokeToolLiteral: `${restUrlBase}/invoke-tool/$tool`
68
+ };
69
+ }
70
+ function resolveAllRoutes(projectRoot, routesDirOption, routeFileName, mcpUrlPath, metadataUrlPath) {
60
71
  const routesDir = (0, import_node_path.resolve)(projectRoot, routesDirOption);
61
72
  assertContains(projectRoot, routesDir, `routesDir "${routesDirOption}"`);
62
73
  const mcpRouteFile = (0, import_node_path.resolve)(routesDir, routeFileName);
63
74
  assertContains(routesDir, mcpRouteFile, `routeFileName "${routeFileName}"`);
64
75
  const metadataRouteFile = wellKnownRouteFile(routesDir, metadataUrlPath);
65
76
  assertContains(routesDir, metadataRouteFile, `metadataPath "${metadataUrlPath}"`);
66
- return {
67
- routesDir,
68
- mcpRouteFile,
69
- metadataRouteFile,
70
- listToolsRouteFile: (0, import_node_path.resolve)(routesDir, "[.mcp]", "list-tools.ts"),
71
- invokeToolRouteFile: (0, import_node_path.resolve)(routesDir, "[.mcp]", "invoke-tool", "$tool.ts")
72
- };
77
+ const rest = resolveRestRoutes(routesDir, mcpUrlPath);
78
+ assertContains(routesDir, rest.listToolsRouteFile, `REST routes for path "${mcpUrlPath}"`);
79
+ assertContains(routesDir, rest.invokeToolRouteFile, `REST routes for path "${mcpUrlPath}"`);
80
+ return { routesDir, mcpRouteFile, metadataRouteFile, ...rest };
73
81
  }
74
82
  function assertUrlPathShape(urlPath) {
75
83
  if (!urlPath.startsWith("/")) {
@@ -231,12 +239,15 @@ function mcpPlugin(options = {}) {
231
239
  const trustForwardedHost = options.trustForwardedHost !== false;
232
240
  let projectRoot = process.cwd();
233
241
  let mcpEntry = (0, import_node_path.resolve)(projectRoot, mcpEntryOption);
234
- let { routesDir, mcpRouteFile, metadataRouteFile, listToolsRouteFile, invokeToolRouteFile } = resolveAllRoutes(
235
- projectRoot,
236
- routesDirOption,
237
- routeFileName,
238
- metadataUrlPath
239
- );
242
+ let {
243
+ routesDir,
244
+ mcpRouteFile,
245
+ metadataRouteFile,
246
+ listToolsRouteFile,
247
+ invokeToolRouteFile,
248
+ listToolsLiteral,
249
+ invokeToolLiteral
250
+ } = resolveAllRoutes(projectRoot, routesDirOption, routeFileName, urlPath, metadataUrlPath);
240
251
  const regenerate = () => {
241
252
  let mcpEntryExists = true;
242
253
  try {
@@ -262,13 +273,13 @@ function mcpPlugin(options = {}) {
262
273
  routes.push(
263
274
  {
264
275
  file: listToolsRouteFile,
265
- routeLiteral: "/.mcp/list-tools",
276
+ routeLiteral: listToolsLiteral,
266
277
  handlerFactory: "createTanStackListToolsHandler",
267
278
  spaFallbackNote: true
268
279
  },
269
280
  {
270
281
  file: invokeToolRouteFile,
271
- routeLiteral: "/.mcp/invoke-tool/$tool",
282
+ routeLiteral: invokeToolLiteral,
272
283
  handlerFactory: "createTanStackInvokeToolHandler",
273
284
  spaFallbackNote: true
274
285
  }
@@ -310,12 +321,15 @@ function mcpPlugin(options = {}) {
310
321
  configResolved(config) {
311
322
  projectRoot = config.root;
312
323
  mcpEntry = (0, import_node_path.resolve)(projectRoot, mcpEntryOption);
313
- ({ routesDir, mcpRouteFile, metadataRouteFile, listToolsRouteFile, invokeToolRouteFile } = resolveAllRoutes(
314
- projectRoot,
315
- routesDirOption,
316
- routeFileName,
317
- metadataUrlPath
318
- ));
324
+ ({
325
+ routesDir,
326
+ mcpRouteFile,
327
+ metadataRouteFile,
328
+ listToolsRouteFile,
329
+ invokeToolRouteFile,
330
+ listToolsLiteral,
331
+ invokeToolLiteral
332
+ } = resolveAllRoutes(projectRoot, routesDirOption, routeFileName, urlPath, metadataUrlPath));
319
333
  regenerate();
320
334
  },
321
335
  configureServer(server) {
@@ -16,13 +16,15 @@ interface McpPluginOptions {
16
16
  */
17
17
  routesDir?: string;
18
18
  /**
19
- * Public URL path for the MCP-protocol route only.
19
+ * Public URL path for the MCP-protocol route.
20
20
  *
21
- * The REST companion routes are fixed at `/.mcp/list-tools` and
22
- * `/.mcp/invoke-tool/$tool` and are not affected by this option the
23
- * `[.mcp]` namespace exists to keep them from colliding with
24
- * user-defined URLs, so making them follow `path` would defeat the
25
- * purpose. Set `restRoutes: false` to drop the companions entirely.
21
+ * The REST companion routes co-locate with this path under a `.mcp`
22
+ * sub-segment of the same parent prefix: `/mcp` keeps them at
23
+ * `/.mcp/list-tools` and `/.mcp/invoke-tool/$tool`, while
24
+ * `/api/public/mcp` moves them to `/api/public/.mcp/*`. The `.mcp`
25
+ * namespace keeps them from colliding with user-defined URLs without
26
+ * anchoring them at the root. Set `restRoutes: false` to drop the
27
+ * companions entirely.
26
28
  *
27
29
  * Must start with `/` and use only `/mcp`-style segments; anything more
28
30
  * exotic doesn't map cleanly to a TanStack file name.
@@ -37,7 +39,7 @@ interface McpPluginOptions {
37
39
  routeFileName?: string;
38
40
  /**
39
41
  * Set to `false` to skip emitting the REST companion routes at
40
- * `/.mcp/list-tools` and `/.mcp/<tool>`. Set
42
+ * `<path-parent>/.mcp/list-tools` and `<path-parent>/.mcp/<tool>`. Set
41
43
  * `protectedResourceMetadataRoute: false` as well if you want the MCP
42
44
  * protocol route to be the only emitted surface.
43
45
  * @default true
@@ -16,13 +16,15 @@ interface McpPluginOptions {
16
16
  */
17
17
  routesDir?: string;
18
18
  /**
19
- * Public URL path for the MCP-protocol route only.
19
+ * Public URL path for the MCP-protocol route.
20
20
  *
21
- * The REST companion routes are fixed at `/.mcp/list-tools` and
22
- * `/.mcp/invoke-tool/$tool` and are not affected by this option the
23
- * `[.mcp]` namespace exists to keep them from colliding with
24
- * user-defined URLs, so making them follow `path` would defeat the
25
- * purpose. Set `restRoutes: false` to drop the companions entirely.
21
+ * The REST companion routes co-locate with this path under a `.mcp`
22
+ * sub-segment of the same parent prefix: `/mcp` keeps them at
23
+ * `/.mcp/list-tools` and `/.mcp/invoke-tool/$tool`, while
24
+ * `/api/public/mcp` moves them to `/api/public/.mcp/*`. The `.mcp`
25
+ * namespace keeps them from colliding with user-defined URLs without
26
+ * anchoring them at the root. Set `restRoutes: false` to drop the
27
+ * companions entirely.
26
28
  *
27
29
  * Must start with `/` and use only `/mcp`-style segments; anything more
28
30
  * exotic doesn't map cleanly to a TanStack file name.
@@ -37,7 +39,7 @@ interface McpPluginOptions {
37
39
  routeFileName?: string;
38
40
  /**
39
41
  * Set to `false` to skip emitting the REST companion routes at
40
- * `/.mcp/list-tools` and `/.mcp/<tool>`. Set
42
+ * `<path-parent>/.mcp/list-tools` and `<path-parent>/.mcp/<tool>`. Set
41
43
  * `protectedResourceMetadataRoute: false` as well if you want the MCP
42
44
  * protocol route to be the only emitted surface.
43
45
  * @default true
@@ -25,20 +25,28 @@ function wellKnownRouteFile(routesDir, urlPath) {
25
25
  });
26
26
  return resolve(routesDir, ...fileSegments);
27
27
  }
28
- function resolveAllRoutes(projectRoot, routesDirOption, routeFileName, metadataUrlPath) {
28
+ function resolveRestRoutes(routesDir, mcpUrlPath) {
29
+ const parentSegments = mcpUrlPath.replace(/^\/+/, "").replace(/\/+$/, "").split("/").slice(0, -1);
30
+ const restDir = resolve(routesDir, ...parentSegments, "[.mcp]");
31
+ const restUrlBase = `/${[...parentSegments, ".mcp"].join("/")}`;
32
+ return {
33
+ listToolsRouteFile: resolve(restDir, "list-tools.ts"),
34
+ invokeToolRouteFile: resolve(restDir, "invoke-tool", "$tool.ts"),
35
+ listToolsLiteral: `${restUrlBase}/list-tools`,
36
+ invokeToolLiteral: `${restUrlBase}/invoke-tool/$tool`
37
+ };
38
+ }
39
+ function resolveAllRoutes(projectRoot, routesDirOption, routeFileName, mcpUrlPath, metadataUrlPath) {
29
40
  const routesDir = resolve(projectRoot, routesDirOption);
30
41
  assertContains(projectRoot, routesDir, `routesDir "${routesDirOption}"`);
31
42
  const mcpRouteFile = resolve(routesDir, routeFileName);
32
43
  assertContains(routesDir, mcpRouteFile, `routeFileName "${routeFileName}"`);
33
44
  const metadataRouteFile = wellKnownRouteFile(routesDir, metadataUrlPath);
34
45
  assertContains(routesDir, metadataRouteFile, `metadataPath "${metadataUrlPath}"`);
35
- return {
36
- routesDir,
37
- mcpRouteFile,
38
- metadataRouteFile,
39
- listToolsRouteFile: resolve(routesDir, "[.mcp]", "list-tools.ts"),
40
- invokeToolRouteFile: resolve(routesDir, "[.mcp]", "invoke-tool", "$tool.ts")
41
- };
46
+ const rest = resolveRestRoutes(routesDir, mcpUrlPath);
47
+ assertContains(routesDir, rest.listToolsRouteFile, `REST routes for path "${mcpUrlPath}"`);
48
+ assertContains(routesDir, rest.invokeToolRouteFile, `REST routes for path "${mcpUrlPath}"`);
49
+ return { routesDir, mcpRouteFile, metadataRouteFile, ...rest };
42
50
  }
43
51
  function assertUrlPathShape(urlPath) {
44
52
  if (!urlPath.startsWith("/")) {
@@ -200,12 +208,15 @@ function mcpPlugin(options = {}) {
200
208
  const trustForwardedHost = options.trustForwardedHost !== false;
201
209
  let projectRoot = process.cwd();
202
210
  let mcpEntry = resolve(projectRoot, mcpEntryOption);
203
- let { routesDir, mcpRouteFile, metadataRouteFile, listToolsRouteFile, invokeToolRouteFile } = resolveAllRoutes(
204
- projectRoot,
205
- routesDirOption,
206
- routeFileName,
207
- metadataUrlPath
208
- );
211
+ let {
212
+ routesDir,
213
+ mcpRouteFile,
214
+ metadataRouteFile,
215
+ listToolsRouteFile,
216
+ invokeToolRouteFile,
217
+ listToolsLiteral,
218
+ invokeToolLiteral
219
+ } = resolveAllRoutes(projectRoot, routesDirOption, routeFileName, urlPath, metadataUrlPath);
209
220
  const regenerate = () => {
210
221
  let mcpEntryExists = true;
211
222
  try {
@@ -231,13 +242,13 @@ function mcpPlugin(options = {}) {
231
242
  routes.push(
232
243
  {
233
244
  file: listToolsRouteFile,
234
- routeLiteral: "/.mcp/list-tools",
245
+ routeLiteral: listToolsLiteral,
235
246
  handlerFactory: "createTanStackListToolsHandler",
236
247
  spaFallbackNote: true
237
248
  },
238
249
  {
239
250
  file: invokeToolRouteFile,
240
- routeLiteral: "/.mcp/invoke-tool/$tool",
251
+ routeLiteral: invokeToolLiteral,
241
252
  handlerFactory: "createTanStackInvokeToolHandler",
242
253
  spaFallbackNote: true
243
254
  }
@@ -279,12 +290,15 @@ function mcpPlugin(options = {}) {
279
290
  configResolved(config) {
280
291
  projectRoot = config.root;
281
292
  mcpEntry = resolve(projectRoot, mcpEntryOption);
282
- ({ routesDir, mcpRouteFile, metadataRouteFile, listToolsRouteFile, invokeToolRouteFile } = resolveAllRoutes(
283
- projectRoot,
284
- routesDirOption,
285
- routeFileName,
286
- metadataUrlPath
287
- ));
293
+ ({
294
+ routesDir,
295
+ mcpRouteFile,
296
+ metadataRouteFile,
297
+ listToolsRouteFile,
298
+ invokeToolRouteFile,
299
+ listToolsLiteral,
300
+ invokeToolLiteral
301
+ } = resolveAllRoutes(projectRoot, routesDirOption, routeFileName, urlPath, metadataUrlPath));
288
302
  regenerate();
289
303
  },
290
304
  configureServer(server) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovable.dev/mcp-js",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Author MCP servers for Lovable apps. Declare tools with defineTool, register them in defineMcp, and a framework adapter (TanStack or Supabase Edge Functions) emits the route(s) at build time.",
5
5
  "type": "module",
6
6
  "repository": {