@lovable.dev/mcp-js 0.9.4 → 0.10.1

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.
@@ -6,7 +6,7 @@ Architecture rationale, source layout, and dev scripts for contributors. The use
6
6
 
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
- - **`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.
9
+ - **`stacks/`** is the framework integration. Two are published: `stacks/tanstack/` (file-router routes emitted by a Vite plugin) and `stacks/supabase/` (a single Deno-served function with URL-suffix dispatch, emitted by the Supabase Vite plugin). Future entries (classic Vite, Cloudflare Workers, …) live alongside them. Each stack forwards to `protocols/{mcp,oauth-metadata,rest}` directly — protocol logic stays shared, only ctx unwrapping and route emission differ.
10
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.
11
11
 
12
12
  ## Design decisions
@@ -143,6 +143,11 @@ src/
143
143
  handlers.ts # TanStack route-ctx adapters
144
144
  vite.ts # mcpPlugin (route emission + stale-file cleanup; exposes McpPluginApi for the extractor)
145
145
  index.ts # barrel
146
+ supabase/
147
+ handler.ts # createSupabaseHandler (URL-suffix dispatcher to protocols/*)
148
+ emit.ts # syncSupabaseFunction (emits supabase/functions/<name>/{index.ts,deno.json})
149
+ vite.ts # mcpPlugin (Vite-lifecycle trigger; exposes McpPluginApi with urlPath=/functions/v1/<functionName>)
150
+ index.ts # barrel
146
151
  tests/
147
152
  core/{define,promise,url}.test.ts
148
153
  auth/{claims,config,context,oauth}.test.ts # OAuth config + bearer verification
@@ -153,6 +158,7 @@ tests/
153
158
  manifest/{extract,io,ssr-loader,run-extract}.test.ts # stack-agnostic extractor (run-extract covers TanStack + Supabase)
154
159
  stacks/
155
160
  tanstack/{handlers,vite}.test.ts
161
+ supabase/{handler,emit,vite}.test.ts
156
162
  integration/ # boots an example app, hits live HTTP
157
163
  global-setup.ts # spawns example/tanstack on :8080
158
164
  tools.test.ts # non-OAuth example
@@ -169,19 +175,20 @@ pnpm lint:fix # format + oxlint --fix
169
175
  pnpm typecheck # tsgo --noEmit
170
176
  pnpm test # vitest run --project unit (fast, hermetic)
171
177
  pnpm test:watch # vitest --project unit
172
- pnpm test:integration # build + install example + vitest run --project integration
173
- pnpm test:integration:oauth # build + install OAuth example + vitest run --project integration-oauth
178
+ pnpm test:integration:tanstack # build + install tanstack example + vitest --project integration
179
+ pnpm test:integration:tanstack:oauth # build + install OAuth example + vitest --project integration-oauth
174
180
  pnpm build # tsup → dist/{cjs,esm}/{index,protocols/*,stacks/*}
175
181
  pnpm pack:local # build + npm pack into /tmp/lovable.dev-mcp-js-<version>.tgz
176
- pnpm example:install # install example/tanstack deps (called by test:integration)
177
- pnpm example:oauth:install # install example/tanstack-supabase-oauth deps
178
- pnpm example:oauth:build # production-build the OAuth example
179
- pnpm example:oauth:smoke # smoke a running OAuth example, optionally with MCP_ACCESS_TOKEN
182
+ pnpm example:tanstack:install # install example/tanstack deps (called by test:integration:tanstack)
183
+ pnpm example:tanstack:oauth:install # install example/tanstack-supabase-oauth deps
184
+ pnpm example:tanstack:oauth:build # production-build the OAuth example
185
+ pnpm example:tanstack:oauth:smoke # smoke a running OAuth example, optionally with MCP_ACCESS_TOKEN
186
+ pnpm example:supabase:install # install example/supabase deps
180
187
  ```
181
188
 
182
189
  `lint` checks both formatting (oxfmt) and lint rules (oxlint) in one pass — the format check fails fast before the lint pass so you see the smaller diff first. `prepublishOnly` chains `clean → typecheck → test → build`; CI enforces `lint` separately.
183
190
 
184
- **Integration tests** live under `tests/integration/` and run against a live HTTP server. `pnpm test:integration` builds the SDK, installs `example/tanstack`, boots its dev server on port 8080 via a Vitest `globalSetup`, runs the suite, and tears the server down. If the server is already running at `MCP_BASE_URL` (default `http://localhost:8080`), the setup reuses it — useful when iterating on the suite. See `CHANGELOG.md` for version history.
191
+ **Integration tests** live under `tests/integration/` and run against a live HTTP server. `pnpm test:integration:tanstack` builds the SDK, installs `example/tanstack`, boots its dev server on port 8080 via a Vitest `globalSetup`, runs the suite, and tears the server down. If the server is already running at `MCP_BASE_URL` (default `http://localhost:8080`), the setup reuses it — useful when iterating on the suite. See `CHANGELOG.md` for version history.
185
192
 
186
193
  **OAuth integration test** (`tests/integration/oauth.test.ts`) is hermetic: `pnpm test:integration:oauth` builds + installs `example/tanstack-supabase-oauth`, then a self-contained `beforeAll` starts a mock RS256 issuer (JWKS + Supabase userinfo/PostgREST stubs), boots the example pointed at it on port 8081, signs a local test JWT, and asserts metadata, bearer challenges, and authorized REST + MCP calls. It runs in its own `integration-oauth` Vitest project (no shared `globalSetup`).
187
194
 
package/README.md CHANGED
@@ -215,6 +215,53 @@ LOVABLE_MCP_LOG_LEVEL=debug
215
215
 
216
216
  A `500` on an OAuth-protected route is always one of two causes, logged at `error`: an `OAuthConfigurationError` from issuer-metadata discovery or a JWKS *fetch* failure (`oauth.discovery.config_error` / `oauth.jwks.fetch_failed` → `auth.config_error`), or a transport-level fault in the MCP handler (`mcp.transport_error`). Auth outcomes are logged at `info`: a granted request as `oauth.verify.ok`, and a `401`/`403` as `auth.token_rejected` (with the `jose` reason) or `auth.no_bearer_token` — so a request rejected for insufficient scope shows both `oauth.verify.ok` and the `auth.token_rejected` that follows it.
217
217
 
218
+ ## Supabase Edge Functions
219
+
220
+ Ship the same `defineMcp` server as a single Supabase Edge Function. Authoring is identical to the TanStack flow; only the build-time step differs — a different Vite plugin emits a single Deno function entry (plus a `deno.json` import map) instead of a tree of file-router routes.
221
+
222
+ ```ts
223
+ // src/lib/mcp/index.ts — same as the TanStack flow
224
+ import { auth, defineMcp } from "@lovable.dev/mcp-js";
225
+ import echoTool from "./tools/echo";
226
+
227
+ export default defineMcp({
228
+ name: "my-app-mcp",
229
+ title: "My App MCP",
230
+ version: "0.1.0",
231
+ instructions: "Tools for interacting with My App.",
232
+ auth: auth.oauth.issuer({
233
+ issuer: `${process.env.SUPABASE_URL!.replace(/\/+$/, "")}/auth/v1`,
234
+ acceptedAudiences: "authenticated",
235
+ }),
236
+ tools: [echoTool],
237
+ });
238
+ ```
239
+
240
+ ```ts
241
+ // vite.config.ts — Lovable Classic projects already run Vite for the
242
+ // frontend, so the plugin is the natural authoring surface (the emitted
243
+ // output is a Deno function, not a Vite-bundled file; Vite is just the
244
+ // trigger). The export is named `mcpPlugin` in every stack — the import
245
+ // path is what discriminates the TanStack and Supabase variants.
246
+ import { defineConfig } from "vite";
247
+ import { mcpPlugin } from "@lovable.dev/mcp-js/stacks/supabase/vite";
248
+
249
+ export default defineConfig({ plugins: [mcpPlugin()] });
250
+ ```
251
+
252
+ The plugin emits `supabase/functions/mcp/index.ts` and `supabase/functions/mcp/deno.json` on `configResolved`, re-emits on `buildStart`, and watches `src/lib/mcp/index.ts` for changes during `vite dev`. The emitted function calls `Deno.serve(createSupabaseHandler(mcp, { functionName: "mcp" }))`. The handler is one Web-Standard `Request → Response` that dispatches by URL suffix to the same protocol handlers the TanStack stack uses:
253
+
254
+ | URL suffix | Target |
255
+ | --- | --- |
256
+ | (root) | MCP streamable-HTTP protocol |
257
+ | `/.mcp/list-tools` | REST tool catalog |
258
+ | `/.mcp/invoke-tool/<tool>` | REST tool dispatcher |
259
+ | `/.well-known/oauth-protected-resource` | RFC 9728 metadata |
260
+
261
+ In production the function is mounted at `https://<project-ref>.supabase.co/functions/v1/mcp` and the dispatcher binds the OAuth `resource` to that URL via `functionName`. Locally, run `supabase functions serve mcp --no-verify-jwt` so Supabase's edge-runtime doesn't 401 before the SDK's OAuth verifier sees the bearer.
262
+
263
+ Ownership: every emitted file carries a `// AUTO-GENERATED by @lovable.dev/mcp-js` banner. The plugin rewrites banner-tagged files in place and cleans up orphans; banner-less files (yours) are never touched. Delete the banner line to take ownership of a file. Deleting the `defineMcp` entry and re-running removes the SDK-owned function files.
264
+
218
265
  ## Subpath exports
219
266
 
220
267
  | Subpath | Contents |
@@ -225,5 +272,7 @@ A `500` on an OAuth-protected route is always one of two causes, logged at `erro
225
272
  | `@lovable.dev/mcp-js/protocols/rest` | `createListToolsHandler`, `createInvokeToolHandler` — Web-Standard |
226
273
  | `@lovable.dev/mcp-js/stacks/tanstack` | TanStack-route-ctx adapters (`createTanStack*Handler`) |
227
274
  | `@lovable.dev/mcp-js/stacks/tanstack/vite` | The Vite plugin |
275
+ | `@lovable.dev/mcp-js/stacks/supabase` | `createSupabaseHandler` — Web-Standard Supabase Edge Function dispatcher |
276
+ | `@lovable.dev/mcp-js/stacks/supabase/vite` | `mcpPlugin` — the Vite plugin (Supabase variant; same name as TanStack's, picked by import path) |
228
277
 
229
- End users only need the root import (`@lovable.dev/mcp-js`). Generated route files import from `@lovable.dev/mcp-js/stacks/tanstack`. The other subpaths are escape hatches for hand-wiring custom stacks against the protocol layer directly.
278
+ End users only need the root import (`@lovable.dev/mcp-js`). Generated route files import from `@lovable.dev/mcp-js/stacks/tanstack` (TanStack) or `@lovable.dev/mcp-js/stacks/supabase` (Supabase). The other subpaths are escape hatches for hand-wiring custom stacks against the protocol layer directly.
@@ -0,0 +1,6 @@
1
+ // package.json
2
+ var version = "0.10.1";
3
+
4
+ export {
5
+ version
6
+ };
@@ -5,7 +5,7 @@ import {
5
5
  corsPreflightResponse,
6
6
  createRequestAuthorizer,
7
7
  withCors
8
- } from "./chunk-YQBOGY7D.js";
8
+ } from "./chunk-LAFJDJRY.js";
9
9
  import {
10
10
  describeError,
11
11
  log
@@ -469,6 +469,7 @@ export {
469
469
  headResponse,
470
470
  methodNotAllowed,
471
471
  resolveProtectedResource,
472
+ assertResourcePathShape,
472
473
  oauthConfigurationErrorResponse,
473
474
  getOAuthRuntime,
474
475
  assertRestResourceBinding,
@@ -5,7 +5,7 @@ import {
5
5
  headResponse,
6
6
  methodNotAllowed,
7
7
  withCors
8
- } from "./chunk-YQBOGY7D.js";
8
+ } from "./chunk-LAFJDJRY.js";
9
9
 
10
10
  // src/protocols/rest/list-tools.ts
11
11
  import { objectFromShape } from "@modelcontextprotocol/sdk/server/zod-compat.js";
@@ -8,7 +8,7 @@ import {
8
8
  createRequestAuthorizer,
9
9
  methodNotAllowed,
10
10
  withCors
11
- } from "./chunk-YQBOGY7D.js";
11
+ } from "./chunk-LAFJDJRY.js";
12
12
 
13
13
  // src/protocols/rest/invoke-tool.ts
14
14
  import { getParseErrorMessage, objectFromShape, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
@@ -7,7 +7,7 @@ import {
7
7
  oauthConfigurationErrorResponse,
8
8
  resolveProtectedResource,
9
9
  withCors
10
- } from "./chunk-YQBOGY7D.js";
10
+ } from "./chunk-LAFJDJRY.js";
11
11
  import {
12
12
  describeError,
13
13
  log
@@ -0,0 +1,14 @@
1
+ // src/stacks/supabase/paths.ts
2
+ var FUNCTIONS_MOUNT_PREFIX = "/functions/v1/";
3
+ function assertFunctionName(value, label = "functionName") {
4
+ if (!/^[a-zA-Z0-9_-]+$/.test(value)) {
5
+ throw new Error(
6
+ `@lovable.dev/mcp-js: ${label} must be a single path segment matching [A-Za-z0-9_-], got ${JSON.stringify(value)}`
7
+ );
8
+ }
9
+ }
10
+
11
+ export {
12
+ FUNCTIONS_MOUNT_PREFIX,
13
+ assertFunctionName
14
+ };
@@ -13,7 +13,7 @@ function isFileMissing(err) {
13
13
  }
14
14
 
15
15
  // package.json
16
- var version = "0.9.4";
16
+ var version = "0.10.1";
17
17
 
18
18
  // src/protocols/rest/list-tools.ts
19
19
  var import_zod_compat = require("@modelcontextprotocol/sdk/server/zod-compat.js");
@@ -1,13 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildMcpListing
4
- } from "../chunk-2RGXCDBU.js";
5
- import "../chunk-YQBOGY7D.js";
4
+ } from "../chunk-PJXKJNSZ.js";
5
+ import "../chunk-LAFJDJRY.js";
6
6
  import "../chunk-QC3DXQTH.js";
7
+ import "../chunk-6DXGZZA4.js";
8
+ import {
9
+ version
10
+ } from "../chunk-D6KB4Q7R.js";
7
11
  import {
8
12
  isFileMissing
9
13
  } from "../chunk-Y3ZFPEQH.js";
10
- import "../chunk-6DXGZZA4.js";
11
14
 
12
15
  // src/manifest/io.ts
13
16
  import { randomUUID } from "crypto";
@@ -15,9 +18,6 @@ import { lstatSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSy
15
18
  import { dirname, resolve } from "path";
16
19
  import { resolveConfig } from "vite";
17
20
 
18
- // package.json
19
- var version = "0.9.4";
20
-
21
21
  // src/manifest/extract.ts
22
22
  var MANIFEST_VERSION = 1;
23
23
  var MANIFEST_RELATIVE_PATH = ".lovable/mcp/manifest.json";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shape every MCP Vite plugin exposes on its `api` so `runExtract` can read
3
+ * back the resolved entry path and the URL the stack mounts the MCP server at.
4
+ * TanStack sets `urlPath` to the configured route path (default `/mcp`);
5
+ * Supabase sets it to `/functions/v1/<functionName>` (the public mount under
6
+ * which the function and its companion routes live).
7
+ */
8
+ interface McpPluginApi {
9
+ readonly mcpEntry: string;
10
+ readonly urlPath: string;
11
+ }
12
+
13
+ export type { McpPluginApi as M };
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  createMcpProtocolHandler
3
- } from "../../chunk-OXSSMKQE.js";
3
+ } from "../../chunk-DF3DD3KD.js";
4
4
  import "../../chunk-MA5H6PSF.js";
5
- import "../../chunk-YQBOGY7D.js";
5
+ import "../../chunk-LAFJDJRY.js";
6
6
  import "../../chunk-QC3DXQTH.js";
7
7
  import "../../chunk-6DXGZZA4.js";
8
8
  export {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createOAuthProtectedResourceMetadataHandler
3
- } from "../chunk-4JO3BZQG.js";
4
- import "../chunk-YQBOGY7D.js";
3
+ } from "../chunk-X7V2CL7C.js";
4
+ import "../chunk-LAFJDJRY.js";
5
5
  import "../chunk-QC3DXQTH.js";
6
6
  import "../chunk-6DXGZZA4.js";
7
7
  export {
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createInvokeToolHandler
3
- } from "../../chunk-6N7B7NZY.js";
4
- import "../../chunk-MA5H6PSF.js";
3
+ } from "../../chunk-SZLSAGDF.js";
5
4
  import {
6
5
  createListToolsHandler
7
- } from "../../chunk-2RGXCDBU.js";
8
- import "../../chunk-YQBOGY7D.js";
6
+ } from "../../chunk-PJXKJNSZ.js";
7
+ import "../../chunk-MA5H6PSF.js";
8
+ import "../../chunk-LAFJDJRY.js";
9
9
  import "../../chunk-QC3DXQTH.js";
10
10
  import "../../chunk-6DXGZZA4.js";
11
11
  export {