@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.
- package/README.internal.md +15 -8
- package/README.md +50 -1
- package/dist/chunk-D6KB4Q7R.js +6 -0
- package/dist/{chunk-OXSSMKQE.js → chunk-DF3DD3KD.js} +1 -1
- package/dist/{chunk-YQBOGY7D.js → chunk-LAFJDJRY.js} +1 -0
- package/dist/{chunk-2RGXCDBU.js → chunk-PJXKJNSZ.js} +1 -1
- package/dist/{chunk-6N7B7NZY.js → chunk-SZLSAGDF.js} +1 -1
- package/dist/{chunk-4JO3BZQG.js → chunk-X7V2CL7C.js} +1 -1
- package/dist/chunk-XQWJN6DC.js +14 -0
- package/dist/cli/extract-manifest.cjs +1 -1
- package/dist/cli/extract-manifest.js +6 -6
- package/dist/io-D4srzRKc.d.ts +13 -0
- package/dist/protocols/mcp/index.js +2 -2
- package/dist/protocols/oauth-metadata.js +2 -2
- package/dist/protocols/rest/index.js +4 -4
- package/dist/stacks/supabase/index.cjs +950 -0
- package/dist/stacks/supabase/index.d.cts +39 -0
- package/dist/stacks/supabase/index.d.ts +39 -0
- package/dist/stacks/supabase/index.js +95 -0
- package/dist/stacks/supabase/vite.cjs +263 -0
- package/dist/stacks/supabase/vite.d.cts +47 -0
- package/dist/stacks/supabase/vite.d.ts +47 -0
- package/dist/stacks/supabase/vite.js +229 -0
- package/dist/stacks/tanstack/index.js +6 -6
- package/dist/stacks/tanstack/vite.d.cts +2 -13
- package/dist/stacks/tanstack/vite.d.ts +2 -13
- package/dist/stacks/tanstack/vite.js +3 -3
- package/package.json +26 -11
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { d as McpDefinition } from '../../types-C5SSORxe.js';
|
|
2
|
+
import 'zod';
|
|
3
|
+
|
|
4
|
+
type SupabaseHandler = (request: Request) => Promise<Response>;
|
|
5
|
+
interface SupabaseHandlerOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Supabase function directory name (the segment under `supabase/functions/`).
|
|
8
|
+
* Used to derive `resourcePath: /functions/v1/<functionName>` so OAuth
|
|
9
|
+
* protected-resource metadata binds to the canonical public MCP URL rather
|
|
10
|
+
* than the request path the dispatcher was reached through.
|
|
11
|
+
*
|
|
12
|
+
* Either `functionName` or `resourcePath` is required when OAuth auth is
|
|
13
|
+
* configured and `auth.resource` is not pinned.
|
|
14
|
+
*/
|
|
15
|
+
functionName?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Explicit override for the public MCP route path. Wins over `functionName`
|
|
18
|
+
* when both are set. Use when the function is fronted by a custom router or
|
|
19
|
+
* a Lovable Cloud–style host that rewrites the public URL.
|
|
20
|
+
*/
|
|
21
|
+
resourcePath?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build a single Web-Standard handler that serves the full MCP surface from
|
|
25
|
+
* one Supabase Edge Function. The returned handler dispatches by URL suffix:
|
|
26
|
+
*
|
|
27
|
+
* | Suffix | Target |
|
|
28
|
+
* | --- | --- |
|
|
29
|
+
* | `/.well-known/oauth-protected-resource` | RFC 9728 metadata |
|
|
30
|
+
* | `/.mcp/list-tools` | REST tool catalog |
|
|
31
|
+
* | `/.mcp/invoke-tool/<tool>` | REST tool dispatcher |
|
|
32
|
+
* | (anything else) | MCP streamable-HTTP protocol |
|
|
33
|
+
*
|
|
34
|
+
* Pass the handler to `Deno.serve(...)` in the emitted function entry.
|
|
35
|
+
* The same OAuth bearer gate protects every dispatch target.
|
|
36
|
+
*/
|
|
37
|
+
declare function createSupabaseHandler(mcp: McpDefinition, options?: SupabaseHandlerOptions): SupabaseHandler;
|
|
38
|
+
|
|
39
|
+
export { type SupabaseHandler, type SupabaseHandlerOptions, createSupabaseHandler };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { d as McpDefinition } from '../../types-C5SSORxe.js';
|
|
2
|
+
import 'zod';
|
|
3
|
+
|
|
4
|
+
type SupabaseHandler = (request: Request) => Promise<Response>;
|
|
5
|
+
interface SupabaseHandlerOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Supabase function directory name (the segment under `supabase/functions/`).
|
|
8
|
+
* Used to derive `resourcePath: /functions/v1/<functionName>` so OAuth
|
|
9
|
+
* protected-resource metadata binds to the canonical public MCP URL rather
|
|
10
|
+
* than the request path the dispatcher was reached through.
|
|
11
|
+
*
|
|
12
|
+
* Either `functionName` or `resourcePath` is required when OAuth auth is
|
|
13
|
+
* configured and `auth.resource` is not pinned.
|
|
14
|
+
*/
|
|
15
|
+
functionName?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Explicit override for the public MCP route path. Wins over `functionName`
|
|
18
|
+
* when both are set. Use when the function is fronted by a custom router or
|
|
19
|
+
* a Lovable Cloud–style host that rewrites the public URL.
|
|
20
|
+
*/
|
|
21
|
+
resourcePath?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build a single Web-Standard handler that serves the full MCP surface from
|
|
25
|
+
* one Supabase Edge Function. The returned handler dispatches by URL suffix:
|
|
26
|
+
*
|
|
27
|
+
* | Suffix | Target |
|
|
28
|
+
* | --- | --- |
|
|
29
|
+
* | `/.well-known/oauth-protected-resource` | RFC 9728 metadata |
|
|
30
|
+
* | `/.mcp/list-tools` | REST tool catalog |
|
|
31
|
+
* | `/.mcp/invoke-tool/<tool>` | REST tool dispatcher |
|
|
32
|
+
* | (anything else) | MCP streamable-HTTP protocol |
|
|
33
|
+
*
|
|
34
|
+
* Pass the handler to `Deno.serve(...)` in the emitted function entry.
|
|
35
|
+
* The same OAuth bearer gate protects every dispatch target.
|
|
36
|
+
*/
|
|
37
|
+
declare function createSupabaseHandler(mcp: McpDefinition, options?: SupabaseHandlerOptions): SupabaseHandler;
|
|
38
|
+
|
|
39
|
+
export { type SupabaseHandler, type SupabaseHandlerOptions, createSupabaseHandler };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMcpProtocolHandler
|
|
3
|
+
} from "../../chunk-DF3DD3KD.js";
|
|
4
|
+
import {
|
|
5
|
+
createOAuthProtectedResourceMetadataHandler
|
|
6
|
+
} from "../../chunk-X7V2CL7C.js";
|
|
7
|
+
import {
|
|
8
|
+
createInvokeToolHandler
|
|
9
|
+
} from "../../chunk-SZLSAGDF.js";
|
|
10
|
+
import {
|
|
11
|
+
createListToolsHandler
|
|
12
|
+
} from "../../chunk-PJXKJNSZ.js";
|
|
13
|
+
import "../../chunk-MA5H6PSF.js";
|
|
14
|
+
import {
|
|
15
|
+
assertResourcePathShape
|
|
16
|
+
} from "../../chunk-LAFJDJRY.js";
|
|
17
|
+
import {
|
|
18
|
+
trimTrailingSlash
|
|
19
|
+
} from "../../chunk-QC3DXQTH.js";
|
|
20
|
+
import {
|
|
21
|
+
OAUTH_PROTECTED_RESOURCE_METADATA_PATH
|
|
22
|
+
} from "../../chunk-6DXGZZA4.js";
|
|
23
|
+
import {
|
|
24
|
+
FUNCTIONS_MOUNT_PREFIX,
|
|
25
|
+
assertFunctionName
|
|
26
|
+
} from "../../chunk-XQWJN6DC.js";
|
|
27
|
+
|
|
28
|
+
// src/stacks/supabase/handler.ts
|
|
29
|
+
function deriveResourcePath(options) {
|
|
30
|
+
if (options.resourcePath !== void 0)
|
|
31
|
+
return options.resourcePath;
|
|
32
|
+
if (options.functionName === void 0)
|
|
33
|
+
return void 0;
|
|
34
|
+
assertFunctionName(options.functionName);
|
|
35
|
+
return `${FUNCTIONS_MOUNT_PREFIX}${options.functionName}`;
|
|
36
|
+
}
|
|
37
|
+
function dispatchFor(pathname) {
|
|
38
|
+
const path = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
|
|
39
|
+
if (path.endsWith(OAUTH_PROTECTED_RESOURCE_METADATA_PATH)) {
|
|
40
|
+
return { kind: "metadata" };
|
|
41
|
+
}
|
|
42
|
+
if (path.endsWith("/.mcp/list-tools")) {
|
|
43
|
+
return { kind: "list-tools" };
|
|
44
|
+
}
|
|
45
|
+
const invokeMatch = /\/\.mcp\/invoke-tool\/([^/]+)$/.exec(path);
|
|
46
|
+
if (invokeMatch) {
|
|
47
|
+
let toolName;
|
|
48
|
+
try {
|
|
49
|
+
toolName = decodeURIComponent(invokeMatch[1]);
|
|
50
|
+
} catch {
|
|
51
|
+
return { kind: "mcp" };
|
|
52
|
+
}
|
|
53
|
+
return { kind: "invoke-tool", toolName };
|
|
54
|
+
}
|
|
55
|
+
return { kind: "mcp" };
|
|
56
|
+
}
|
|
57
|
+
function applyForwardedProto(request) {
|
|
58
|
+
const proto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
59
|
+
if (!proto)
|
|
60
|
+
return request;
|
|
61
|
+
const url = new URL(request.url);
|
|
62
|
+
if (`${proto}:` === url.protocol)
|
|
63
|
+
return request;
|
|
64
|
+
url.protocol = `${proto}:`;
|
|
65
|
+
return new Request(url.href, request);
|
|
66
|
+
}
|
|
67
|
+
function createSupabaseHandler(mcp, options = {}) {
|
|
68
|
+
const resourcePath = deriveResourcePath(options);
|
|
69
|
+
if (resourcePath !== void 0)
|
|
70
|
+
assertResourcePathShape(resourcePath);
|
|
71
|
+
const servesOwnPrm = !(mcp.auth?.type === "oauth" && mcp.auth.protectedResourceMetadataUrl !== void 0);
|
|
72
|
+
const metadataPath = resourcePath === void 0 || !servesOwnPrm ? void 0 : `${trimTrailingSlash(resourcePath)}${OAUTH_PROTECTED_RESOURCE_METADATA_PATH}`;
|
|
73
|
+
const runtimeOptions = resourcePath === void 0 ? {} : { resourcePath, ...metadataPath ? { metadataPath } : {} };
|
|
74
|
+
const mcpHandler = createMcpProtocolHandler(mcp, runtimeOptions);
|
|
75
|
+
const listToolsHandler = createListToolsHandler(mcp, runtimeOptions);
|
|
76
|
+
const invokeToolHandler = createInvokeToolHandler(mcp, runtimeOptions);
|
|
77
|
+
const metadataHandler = createOAuthProtectedResourceMetadataHandler(mcp, runtimeOptions);
|
|
78
|
+
return async (request) => {
|
|
79
|
+
const req = applyForwardedProto(request);
|
|
80
|
+
const target = dispatchFor(new URL(req.url).pathname);
|
|
81
|
+
switch (target.kind) {
|
|
82
|
+
case "metadata":
|
|
83
|
+
return metadataHandler(req);
|
|
84
|
+
case "list-tools":
|
|
85
|
+
return listToolsHandler(req);
|
|
86
|
+
case "invoke-tool":
|
|
87
|
+
return invokeToolHandler(req, target.toolName);
|
|
88
|
+
case "mcp":
|
|
89
|
+
return mcpHandler(req);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
createSupabaseHandler
|
|
95
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/stacks/supabase/vite.ts
|
|
21
|
+
var vite_exports = {};
|
|
22
|
+
__export(vite_exports, {
|
|
23
|
+
default: () => vite_default,
|
|
24
|
+
mcpPlugin: () => mcpPlugin
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(vite_exports);
|
|
27
|
+
var import_node_path2 = require("path");
|
|
28
|
+
|
|
29
|
+
// src/stacks/supabase/emit.ts
|
|
30
|
+
var import_node_fs = require("fs");
|
|
31
|
+
var import_node_path = require("path");
|
|
32
|
+
|
|
33
|
+
// package.json
|
|
34
|
+
var version = "0.10.1";
|
|
35
|
+
|
|
36
|
+
// src/core/fs-errors.ts
|
|
37
|
+
function isFileMissing(err) {
|
|
38
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/stacks/supabase/paths.ts
|
|
42
|
+
var FUNCTIONS_MOUNT_PREFIX = "/functions/v1/";
|
|
43
|
+
function assertFunctionName(value, label = "functionName") {
|
|
44
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(value)) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`@lovable.dev/mcp-js: ${label} must be a single path segment matching [A-Za-z0-9_-], got ${JSON.stringify(value)}`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/stacks/supabase/emit.ts
|
|
52
|
+
var DEFAULT_MCP_ENTRY = "src/lib/mcp/index.ts";
|
|
53
|
+
var DEFAULT_FUNCTIONS_DIR = "supabase/functions";
|
|
54
|
+
var DEFAULT_FUNCTION_NAME = "mcp";
|
|
55
|
+
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.";
|
|
56
|
+
var GENERATED_JSON_MARKER_KEY = "_lovableMcpGenerated";
|
|
57
|
+
var GENERATED_JSON_MARKER_VALUE = "AUTO-GENERATED by @lovable.dev/mcp-js \u2014 do not edit. Regenerated by the Vite plugin. To take ownership, remove this key; the plugin then leaves the file alone.";
|
|
58
|
+
function normalizePath(p) {
|
|
59
|
+
return p.split(import_node_path.sep).join("/");
|
|
60
|
+
}
|
|
61
|
+
function fileExists(path) {
|
|
62
|
+
try {
|
|
63
|
+
(0, import_node_fs.lstatSync)(path);
|
|
64
|
+
return true;
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (isFileMissing(err))
|
|
67
|
+
return false;
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function hasGeneratedBanner(content) {
|
|
72
|
+
return content.replace(/\r\n/g, "\n").startsWith(GENERATED_BANNER);
|
|
73
|
+
}
|
|
74
|
+
function hasGeneratedJsonMarker(content) {
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(content);
|
|
77
|
+
return typeof parsed === "object" && parsed !== null && GENERATED_JSON_MARKER_KEY in parsed;
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function writeIfChanged(file, content, ownershipCheck) {
|
|
83
|
+
let existing;
|
|
84
|
+
try {
|
|
85
|
+
existing = (0, import_node_fs.readFileSync)(file, "utf8");
|
|
86
|
+
} catch (err) {
|
|
87
|
+
if (!isFileMissing(err))
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
if (existing !== void 0) {
|
|
91
|
+
if (!ownershipCheck(existing)) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`@lovable.dev/mcp-js: refusing to overwrite user-authored file at ${file}. Delete it or move it first.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (existing === content)
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(file), { recursive: true });
|
|
100
|
+
(0, import_node_fs.writeFileSync)(file, content, "utf8");
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
function relativeTsSpecifier(fromFile, toFile) {
|
|
104
|
+
const rel = normalizePath((0, import_node_path.relative)((0, import_node_path.dirname)(fromFile), toFile));
|
|
105
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
106
|
+
}
|
|
107
|
+
function buildFunctionEntry(mcpEntryAbs, functionEntryAbs, functionName) {
|
|
108
|
+
const entryImport = relativeTsSpecifier(functionEntryAbs, mcpEntryAbs);
|
|
109
|
+
return `${GENERATED_BANNER}
|
|
110
|
+
// supabase function: ${functionName}
|
|
111
|
+
// emitted to: supabase/functions/${functionName}/index.ts
|
|
112
|
+
|
|
113
|
+
import { createSupabaseHandler } from "npm:@lovable.dev/mcp-js@${version}/stacks/supabase";
|
|
114
|
+
|
|
115
|
+
import mcp from "${entryImport}";
|
|
116
|
+
|
|
117
|
+
Deno.serve(createSupabaseHandler(mcp, { functionName: ${JSON.stringify(functionName)} }));
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
function buildDenoJson() {
|
|
121
|
+
const config = {
|
|
122
|
+
[GENERATED_JSON_MARKER_KEY]: GENERATED_JSON_MARKER_VALUE,
|
|
123
|
+
imports: {
|
|
124
|
+
"@lovable.dev/mcp-js": `npm:@lovable.dev/mcp-js@${version}`
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
return `${JSON.stringify(config, null, 2)}
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
function resolvePaths(options) {
|
|
131
|
+
const projectRoot = (0, import_node_path.resolve)(options.projectRoot);
|
|
132
|
+
const mcpEntry = options.mcpEntry ?? DEFAULT_MCP_ENTRY;
|
|
133
|
+
const functionsDir = options.functionsDir ?? DEFAULT_FUNCTIONS_DIR;
|
|
134
|
+
const functionName = options.functionName ?? DEFAULT_FUNCTION_NAME;
|
|
135
|
+
assertFunctionName(functionName);
|
|
136
|
+
const functionDir = (0, import_node_path.resolve)(projectRoot, functionsDir, functionName);
|
|
137
|
+
if (functionDir !== projectRoot && !functionDir.startsWith(projectRoot + import_node_path.sep)) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`@lovable.dev/mcp-js: functionsDir must resolve within the project root, got ${JSON.stringify(functionsDir)}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
functionDir,
|
|
144
|
+
functionEntry: (0, import_node_path.join)(functionDir, "index.ts"),
|
|
145
|
+
denoJson: (0, import_node_path.join)(functionDir, "deno.json"),
|
|
146
|
+
mcpEntryAbs: (0, import_node_path.resolve)(projectRoot, mcpEntry)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function syncSupabaseFunction(options) {
|
|
150
|
+
const paths = resolvePaths(options);
|
|
151
|
+
const functionName = options.functionName ?? DEFAULT_FUNCTION_NAME;
|
|
152
|
+
const written = [];
|
|
153
|
+
const removed = [];
|
|
154
|
+
if (!fileExists(paths.mcpEntryAbs)) {
|
|
155
|
+
cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set(), removed);
|
|
156
|
+
return { written, removed };
|
|
157
|
+
}
|
|
158
|
+
if (writeIfChanged(
|
|
159
|
+
paths.functionEntry,
|
|
160
|
+
buildFunctionEntry(paths.mcpEntryAbs, paths.functionEntry, functionName),
|
|
161
|
+
hasGeneratedBanner
|
|
162
|
+
)) {
|
|
163
|
+
written.push(paths.functionEntry);
|
|
164
|
+
}
|
|
165
|
+
if (writeIfChanged(paths.denoJson, buildDenoJson(), hasGeneratedJsonMarker)) {
|
|
166
|
+
written.push(paths.denoJson);
|
|
167
|
+
}
|
|
168
|
+
cleanupOrphans(paths.functionDir, /* @__PURE__ */ new Set([paths.functionEntry, paths.denoJson]), removed);
|
|
169
|
+
return { written, removed };
|
|
170
|
+
}
|
|
171
|
+
function cleanupOrphans(functionDir, expected, removed) {
|
|
172
|
+
let entries;
|
|
173
|
+
try {
|
|
174
|
+
entries = (0, import_node_fs.readdirSync)(functionDir);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
if (isFileMissing(err))
|
|
177
|
+
return;
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
180
|
+
for (const entry of entries) {
|
|
181
|
+
const full = (0, import_node_path.join)(functionDir, entry);
|
|
182
|
+
if (expected.has(full))
|
|
183
|
+
continue;
|
|
184
|
+
let content;
|
|
185
|
+
try {
|
|
186
|
+
content = (0, import_node_fs.readFileSync)(full, "utf8");
|
|
187
|
+
} catch (err) {
|
|
188
|
+
if (isFileMissing(err) || err.code === "EISDIR")
|
|
189
|
+
continue;
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
if (hasGeneratedBanner(content) || hasGeneratedJsonMarker(content)) {
|
|
193
|
+
try {
|
|
194
|
+
(0, import_node_fs.unlinkSync)(full);
|
|
195
|
+
removed.push(full);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
if (!isFileMissing(err))
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/stacks/supabase/vite.ts
|
|
205
|
+
function normalizePath2(p) {
|
|
206
|
+
return p.split(import_node_path2.sep).join("/");
|
|
207
|
+
}
|
|
208
|
+
function mcpPlugin(options = {}) {
|
|
209
|
+
const mcpEntryOption = options.mcpEntry ?? DEFAULT_MCP_ENTRY;
|
|
210
|
+
const functionsDirOption = options.functionsDir ?? DEFAULT_FUNCTIONS_DIR;
|
|
211
|
+
const functionName = options.functionName ?? DEFAULT_FUNCTION_NAME;
|
|
212
|
+
const urlPath = `${FUNCTIONS_MOUNT_PREFIX}${functionName}`;
|
|
213
|
+
let projectRoot = process.cwd();
|
|
214
|
+
let mcpEntryAbs = (0, import_node_path2.resolve)(projectRoot, mcpEntryOption);
|
|
215
|
+
const regenerate = () => {
|
|
216
|
+
syncSupabaseFunction({
|
|
217
|
+
projectRoot,
|
|
218
|
+
mcpEntry: mcpEntryOption,
|
|
219
|
+
functionsDir: functionsDirOption,
|
|
220
|
+
functionName
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
return {
|
|
224
|
+
name: "@lovable.dev/mcp-js/supabase",
|
|
225
|
+
// The extract CLI reads these back via `resolveConfig().plugins`. Getter
|
|
226
|
+
// for `mcpEntry` because the absolute path is finalized in `configResolved`;
|
|
227
|
+
// `urlPath` is config-time-final so it's a plain field.
|
|
228
|
+
api: {
|
|
229
|
+
get mcpEntry() {
|
|
230
|
+
return mcpEntryAbs;
|
|
231
|
+
},
|
|
232
|
+
urlPath
|
|
233
|
+
},
|
|
234
|
+
configResolved(config) {
|
|
235
|
+
projectRoot = config.root;
|
|
236
|
+
mcpEntryAbs = (0, import_node_path2.resolve)(projectRoot, mcpEntryOption);
|
|
237
|
+
regenerate();
|
|
238
|
+
},
|
|
239
|
+
configureServer(server) {
|
|
240
|
+
const onChange = (file) => {
|
|
241
|
+
if (normalizePath2(file) === normalizePath2(mcpEntryAbs)) {
|
|
242
|
+
regenerate();
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
server.watcher.on("add", onChange);
|
|
246
|
+
server.watcher.on("change", onChange);
|
|
247
|
+
server.watcher.on("unlink", onChange);
|
|
248
|
+
server.watcher.once("close", () => {
|
|
249
|
+
server.watcher.off("add", onChange);
|
|
250
|
+
server.watcher.off("change", onChange);
|
|
251
|
+
server.watcher.off("unlink", onChange);
|
|
252
|
+
});
|
|
253
|
+
},
|
|
254
|
+
buildStart() {
|
|
255
|
+
regenerate();
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
var vite_default = mcpPlugin;
|
|
260
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
261
|
+
0 && (module.exports = {
|
|
262
|
+
mcpPlugin
|
|
263
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
export { M as McpPluginApi } from '../../io-D4srzRKc.js';
|
|
3
|
+
|
|
4
|
+
interface McpPluginOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Path to the MCP entry file (default export = `defineMcp(...)` result),
|
|
7
|
+
* relative to the project root.
|
|
8
|
+
* @default "src/lib/mcp/index.ts"
|
|
9
|
+
*/
|
|
10
|
+
mcpEntry?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Directory holding Supabase function folders (one per function), relative
|
|
13
|
+
* to the project root.
|
|
14
|
+
* @default "supabase/functions"
|
|
15
|
+
*/
|
|
16
|
+
functionsDir?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Name of the function directory under `functionsDir`. Becomes the public
|
|
19
|
+
* URL segment (`/functions/v1/<functionName>`) the dispatcher binds OAuth
|
|
20
|
+
* metadata to via `resourcePath`.
|
|
21
|
+
* @default "mcp"
|
|
22
|
+
*/
|
|
23
|
+
functionName?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Vite plugin that emits the Supabase Edge Function entry (and its
|
|
27
|
+
* `deno.json` import map) for an `@lovable.dev/mcp-js` MCP server.
|
|
28
|
+
*
|
|
29
|
+
* Place this in your `vite.config.ts` plugins array. It uses Vite as the
|
|
30
|
+
* trigger for codegen even though the emitted output (Deno function) is
|
|
31
|
+
* not part of the Vite bundle — Classic Lovable stacks already run Vite
|
|
32
|
+
* for the frontend, so it's the natural authoring surface.
|
|
33
|
+
*
|
|
34
|
+
* Lifecycle, mirroring `stacks/tanstack/vite`:
|
|
35
|
+
* - `configResolved` finalizes the project root and runs the first emit.
|
|
36
|
+
* - `buildStart` re-runs the emit so a `vite build` always picks up the
|
|
37
|
+
* latest entry.
|
|
38
|
+
* - `configureServer` re-emits when the entry file appears, changes, or
|
|
39
|
+
* is removed during `vite dev`, so a developer editing tools sees the
|
|
40
|
+
* function regenerate without restarting the server.
|
|
41
|
+
*
|
|
42
|
+
* Idempotent: `syncSupabaseFunction` writes only on byte-level change and
|
|
43
|
+
* cleans up SDK-owned files when the entry is deleted.
|
|
44
|
+
*/
|
|
45
|
+
declare function mcpPlugin(options?: McpPluginOptions): Plugin;
|
|
46
|
+
|
|
47
|
+
export { type McpPluginOptions, mcpPlugin as default, mcpPlugin };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
export { M as McpPluginApi } from '../../io-D4srzRKc.js';
|
|
3
|
+
|
|
4
|
+
interface McpPluginOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Path to the MCP entry file (default export = `defineMcp(...)` result),
|
|
7
|
+
* relative to the project root.
|
|
8
|
+
* @default "src/lib/mcp/index.ts"
|
|
9
|
+
*/
|
|
10
|
+
mcpEntry?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Directory holding Supabase function folders (one per function), relative
|
|
13
|
+
* to the project root.
|
|
14
|
+
* @default "supabase/functions"
|
|
15
|
+
*/
|
|
16
|
+
functionsDir?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Name of the function directory under `functionsDir`. Becomes the public
|
|
19
|
+
* URL segment (`/functions/v1/<functionName>`) the dispatcher binds OAuth
|
|
20
|
+
* metadata to via `resourcePath`.
|
|
21
|
+
* @default "mcp"
|
|
22
|
+
*/
|
|
23
|
+
functionName?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Vite plugin that emits the Supabase Edge Function entry (and its
|
|
27
|
+
* `deno.json` import map) for an `@lovable.dev/mcp-js` MCP server.
|
|
28
|
+
*
|
|
29
|
+
* Place this in your `vite.config.ts` plugins array. It uses Vite as the
|
|
30
|
+
* trigger for codegen even though the emitted output (Deno function) is
|
|
31
|
+
* not part of the Vite bundle — Classic Lovable stacks already run Vite
|
|
32
|
+
* for the frontend, so it's the natural authoring surface.
|
|
33
|
+
*
|
|
34
|
+
* Lifecycle, mirroring `stacks/tanstack/vite`:
|
|
35
|
+
* - `configResolved` finalizes the project root and runs the first emit.
|
|
36
|
+
* - `buildStart` re-runs the emit so a `vite build` always picks up the
|
|
37
|
+
* latest entry.
|
|
38
|
+
* - `configureServer` re-emits when the entry file appears, changes, or
|
|
39
|
+
* is removed during `vite dev`, so a developer editing tools sees the
|
|
40
|
+
* function regenerate without restarting the server.
|
|
41
|
+
*
|
|
42
|
+
* Idempotent: `syncSupabaseFunction` writes only on byte-level change and
|
|
43
|
+
* cleans up SDK-owned files when the entry is deleted.
|
|
44
|
+
*/
|
|
45
|
+
declare function mcpPlugin(options?: McpPluginOptions): Plugin;
|
|
46
|
+
|
|
47
|
+
export { type McpPluginOptions, mcpPlugin as default, mcpPlugin };
|