@lovable.dev/mcp-js 0.5.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/LICENSE +21 -0
- package/README.md +386 -0
- package/dist/authorize-DpTEIFvs.d.ts +9 -0
- package/dist/chunk-6DXGZZA4.js +6 -0
- package/dist/chunk-DDF63QWG.js +80 -0
- package/dist/chunk-GLG5RZGE.js +66 -0
- package/dist/chunk-MA5H6PSF.js +46 -0
- package/dist/chunk-QA3FWDUV.js +40 -0
- package/dist/chunk-VD6CS7Y6.js +144 -0
- package/dist/chunk-XEDRJFAR.js +371 -0
- package/dist/index.cjs +271 -0
- package/dist/index.d.cts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +172 -0
- package/dist/protocols/mcp/index.cjs +505 -0
- package/dist/protocols/mcp/index.d.cts +14 -0
- package/dist/protocols/mcp/index.d.ts +14 -0
- package/dist/protocols/mcp/index.js +10 -0
- package/dist/protocols/oauth-metadata.cjs +390 -0
- package/dist/protocols/oauth-metadata.d.cts +8 -0
- package/dist/protocols/oauth-metadata.d.ts +8 -0
- package/dist/protocols/oauth-metadata.js +9 -0
- package/dist/protocols/rest/index.cjs +599 -0
- package/dist/protocols/rest/index.d.cts +11 -0
- package/dist/protocols/rest/index.d.ts +11 -0
- package/dist/protocols/rest/index.js +12 -0
- package/dist/stacks/tanstack/index.cjs +743 -0
- package/dist/stacks/tanstack/index.d.cts +38 -0
- package/dist/stacks/tanstack/index.d.ts +38 -0
- package/dist/stacks/tanstack/index.js +38 -0
- package/dist/stacks/tanstack/vite.cjs +291 -0
- package/dist/stacks/tanstack/vite.d.cts +64 -0
- package/dist/stacks/tanstack/vite.d.ts +64 -0
- package/dist/stacks/tanstack/vite.js +263 -0
- package/dist/types-zMlr1mOK.d.ts +270 -0
- package/package.json +101 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OAUTH_PROTECTED_RESOURCE_METADATA_PATH
|
|
3
|
+
} from "../../chunk-6DXGZZA4.js";
|
|
4
|
+
|
|
5
|
+
// src/stacks/tanstack/vite.ts
|
|
6
|
+
import { lstatSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
|
|
7
|
+
import { dirname, join, relative, resolve, sep } from "path";
|
|
8
|
+
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
|
+
function normalizePath(p) {
|
|
13
|
+
return p.split(sep).join("/");
|
|
14
|
+
}
|
|
15
|
+
function assertContains(parent, child, label) {
|
|
16
|
+
if (child !== parent && !child.startsWith(parent + sep)) {
|
|
17
|
+
throw new Error(`@lovable.dev/mcp-js: ${label} must resolve under ${parent}, got ${child}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function wellKnownRouteFile(routesDir, urlPath) {
|
|
21
|
+
const segments = urlPath.replace(/^\//, "").split("/");
|
|
22
|
+
const fileSegments = segments.map((segment, index) => {
|
|
23
|
+
const escaped = segment.startsWith(".") ? `[${segment}]` : segment;
|
|
24
|
+
return index === segments.length - 1 ? `${escaped}.ts` : escaped;
|
|
25
|
+
});
|
|
26
|
+
return resolve(routesDir, ...fileSegments);
|
|
27
|
+
}
|
|
28
|
+
function resolveAllRoutes(projectRoot, routesDirOption, routeFileName) {
|
|
29
|
+
const routesDir = resolve(projectRoot, routesDirOption);
|
|
30
|
+
assertContains(projectRoot, routesDir, `routesDir "${routesDirOption}"`);
|
|
31
|
+
const mcpRouteFile = resolve(routesDir, routeFileName);
|
|
32
|
+
assertContains(routesDir, mcpRouteFile, `routeFileName "${routeFileName}"`);
|
|
33
|
+
return {
|
|
34
|
+
routesDir,
|
|
35
|
+
mcpRouteFile,
|
|
36
|
+
metadataRouteFile: wellKnownRouteFile(routesDir, OAUTH_PROTECTED_RESOURCE_METADATA_PATH),
|
|
37
|
+
listToolsRouteFile: resolve(routesDir, "[.mcp]", "list-tools.ts"),
|
|
38
|
+
invokeToolRouteFile: resolve(routesDir, "[.mcp]", "invoke-tool", "$tool.ts")
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function assertUrlPathShape(urlPath) {
|
|
42
|
+
if (!urlPath.startsWith("/")) {
|
|
43
|
+
throw new Error(`@lovable.dev/mcp-js: path must start with "/", got "${urlPath}"`);
|
|
44
|
+
}
|
|
45
|
+
if (/\/{2,}/.test(urlPath)) {
|
|
46
|
+
throw new Error(`@lovable.dev/mcp-js: path "${urlPath}" contains empty segments ("//")`);
|
|
47
|
+
}
|
|
48
|
+
const trimmed = urlPath.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
49
|
+
if (!trimmed) {
|
|
50
|
+
throw new Error(`@lovable.dev/mcp-js: path "${urlPath}" has no route segment`);
|
|
51
|
+
}
|
|
52
|
+
if (!/^[a-zA-Z0-9_\-/]+$/.test(trimmed)) {
|
|
53
|
+
throw new Error(`@lovable.dev/mcp-js: path "${urlPath}" contains unsupported characters`);
|
|
54
|
+
}
|
|
55
|
+
return trimmed;
|
|
56
|
+
}
|
|
57
|
+
function deriveRouteFileName(urlPath) {
|
|
58
|
+
return `${assertUrlPathShape(urlPath)}.ts`;
|
|
59
|
+
}
|
|
60
|
+
function canonicalUrlPath(urlPath) {
|
|
61
|
+
return `/${urlPath.replace(/^\/+/, "").replace(/\/+$/, "")}`;
|
|
62
|
+
}
|
|
63
|
+
function relativeImportSpecifier(routeFile, target) {
|
|
64
|
+
const rel = normalizePath(relative(dirname(routeFile), target));
|
|
65
|
+
const withoutExt = rel.replace(/\.ts$/, "");
|
|
66
|
+
return withoutExt.startsWith(".") ? withoutExt : `./${withoutExt}`;
|
|
67
|
+
}
|
|
68
|
+
function buildRouteSource(route, resourcePath, mcpEntry, projectRoot) {
|
|
69
|
+
const mcpImport = relativeImportSpecifier(route.file, mcpEntry);
|
|
70
|
+
const routeRel = normalizePath(relative(projectRoot, route.file));
|
|
71
|
+
const note = route.spaFallbackNote ? " // ANY: TanStack returns SPA HTML for methods not in `handlers`; the SDK 405s instead.\n" : "";
|
|
72
|
+
return `${GENERATED_BANNER}
|
|
73
|
+
// route: ${route.routeLiteral}
|
|
74
|
+
// emitted to: ${routeRel}
|
|
75
|
+
|
|
76
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
77
|
+
|
|
78
|
+
import { ${route.handlerFactory} } from "@lovable.dev/mcp-js/stacks/tanstack";
|
|
79
|
+
|
|
80
|
+
import mcp from "${mcpImport}";
|
|
81
|
+
|
|
82
|
+
export const Route = createFileRoute("${route.routeLiteral}")({
|
|
83
|
+
server: {
|
|
84
|
+
handlers: {
|
|
85
|
+
${note} ANY: ${route.handlerFactory}(mcp, { resourcePath: "${resourcePath}" }),
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
function hasGeneratedBanner(content) {
|
|
92
|
+
return content.replace(/\r\n/g, "\n").startsWith(GENERATED_BANNER);
|
|
93
|
+
}
|
|
94
|
+
function writeIfChanged(file, content) {
|
|
95
|
+
let existing;
|
|
96
|
+
try {
|
|
97
|
+
existing = readFileSync(file, "utf8");
|
|
98
|
+
} catch (err) {
|
|
99
|
+
if (!isFileMissing(err))
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
if (existing !== void 0) {
|
|
103
|
+
if (!hasGeneratedBanner(existing)) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`@lovable.dev/mcp-js: refusing to overwrite user-authored route at ${file}. Delete it or move it first.`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (existing === content)
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
112
|
+
writeFileSync(file, content, "utf8");
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
function fileStartsWithBanner(file) {
|
|
116
|
+
try {
|
|
117
|
+
return hasGeneratedBanner(readFileSync(file, "utf8"));
|
|
118
|
+
} catch (err) {
|
|
119
|
+
if (isFileMissing(err))
|
|
120
|
+
return false;
|
|
121
|
+
throw err;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function findAdoptedFiles(routesDir) {
|
|
125
|
+
const adopted = [];
|
|
126
|
+
const visit = (dir) => {
|
|
127
|
+
let entries;
|
|
128
|
+
try {
|
|
129
|
+
entries = readdirSync(dir);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
if (isFileMissing(err))
|
|
132
|
+
return;
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
const full = join(dir, entry);
|
|
137
|
+
let stat;
|
|
138
|
+
try {
|
|
139
|
+
stat = lstatSync(full);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (isFileMissing(err))
|
|
142
|
+
continue;
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
if (stat.isDirectory()) {
|
|
146
|
+
visit(full);
|
|
147
|
+
} else if (entry.endsWith(".ts") && fileStartsWithBanner(full)) {
|
|
148
|
+
adopted.push(full);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
visit(routesDir);
|
|
153
|
+
return adopted;
|
|
154
|
+
}
|
|
155
|
+
function mcpPlugin(options = {}) {
|
|
156
|
+
const mcpEntryOption = options.mcpEntry ?? "src/lib/mcp/index.ts";
|
|
157
|
+
const routesDirOption = options.routesDir ?? "src/routes";
|
|
158
|
+
const rawUrlPath = options.path ?? "/mcp";
|
|
159
|
+
assertUrlPathShape(rawUrlPath);
|
|
160
|
+
const routeFileName = options.routeFileName ?? deriveRouteFileName(rawUrlPath);
|
|
161
|
+
const urlPath = canonicalUrlPath(rawUrlPath);
|
|
162
|
+
const emitRestRoutes = options.restRoutes !== false;
|
|
163
|
+
const emitProtectedResourceMetadataRoute = options.protectedResourceMetadataRoute !== false;
|
|
164
|
+
let projectRoot = process.cwd();
|
|
165
|
+
let mcpEntry = resolve(projectRoot, mcpEntryOption);
|
|
166
|
+
let { routesDir, mcpRouteFile, metadataRouteFile, listToolsRouteFile, invokeToolRouteFile } = resolveAllRoutes(
|
|
167
|
+
projectRoot,
|
|
168
|
+
routesDirOption,
|
|
169
|
+
routeFileName
|
|
170
|
+
);
|
|
171
|
+
const regenerate = () => {
|
|
172
|
+
let mcpEntryExists = true;
|
|
173
|
+
try {
|
|
174
|
+
lstatSync(mcpEntry);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
if (!isFileMissing(err))
|
|
177
|
+
throw err;
|
|
178
|
+
mcpEntryExists = false;
|
|
179
|
+
}
|
|
180
|
+
const expected = /* @__PURE__ */ new Set();
|
|
181
|
+
if (mcpEntryExists) {
|
|
182
|
+
const routes = [
|
|
183
|
+
{ file: mcpRouteFile, routeLiteral: urlPath, handlerFactory: "createTanStackMcpHandler" }
|
|
184
|
+
];
|
|
185
|
+
if (emitProtectedResourceMetadataRoute) {
|
|
186
|
+
routes.push({
|
|
187
|
+
file: metadataRouteFile,
|
|
188
|
+
routeLiteral: OAUTH_PROTECTED_RESOURCE_METADATA_PATH,
|
|
189
|
+
handlerFactory: "createTanStackOAuthProtectedResourceMetadataHandler"
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
if (emitRestRoutes) {
|
|
193
|
+
routes.push(
|
|
194
|
+
{
|
|
195
|
+
file: listToolsRouteFile,
|
|
196
|
+
routeLiteral: "/.mcp/list-tools",
|
|
197
|
+
handlerFactory: "createTanStackListToolsHandler",
|
|
198
|
+
spaFallbackNote: true
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
file: invokeToolRouteFile,
|
|
202
|
+
routeLiteral: "/.mcp/invoke-tool/$tool",
|
|
203
|
+
handlerFactory: "createTanStackInvokeToolHandler",
|
|
204
|
+
spaFallbackNote: true
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
for (const route of routes) {
|
|
209
|
+
expected.add(route.file);
|
|
210
|
+
writeIfChanged(route.file, buildRouteSource(route, urlPath, mcpEntry, projectRoot));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
for (const file of findAdoptedFiles(routesDir)) {
|
|
214
|
+
if (!expected.has(file)) {
|
|
215
|
+
try {
|
|
216
|
+
unlinkSync(file);
|
|
217
|
+
} catch (err) {
|
|
218
|
+
if (!isFileMissing(err))
|
|
219
|
+
throw err;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
return {
|
|
225
|
+
name: "@lovable.dev/mcp-js",
|
|
226
|
+
configResolved(config) {
|
|
227
|
+
projectRoot = config.root;
|
|
228
|
+
mcpEntry = resolve(projectRoot, mcpEntryOption);
|
|
229
|
+
({ routesDir, mcpRouteFile, metadataRouteFile, listToolsRouteFile, invokeToolRouteFile } = resolveAllRoutes(
|
|
230
|
+
projectRoot,
|
|
231
|
+
routesDirOption,
|
|
232
|
+
routeFileName
|
|
233
|
+
));
|
|
234
|
+
regenerate();
|
|
235
|
+
},
|
|
236
|
+
configureServer(server) {
|
|
237
|
+
const watchedEntry = mcpEntry;
|
|
238
|
+
const onEntryChange = (file) => {
|
|
239
|
+
if (normalizePath(file) === normalizePath(watchedEntry)) {
|
|
240
|
+
regenerate();
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
server.watcher.on("add", onEntryChange);
|
|
244
|
+
server.watcher.on("change", onEntryChange);
|
|
245
|
+
server.watcher.on("unlink", onEntryChange);
|
|
246
|
+
server.watcher.once("close", () => {
|
|
247
|
+
server.watcher.off("add", onEntryChange);
|
|
248
|
+
server.watcher.off("change", onEntryChange);
|
|
249
|
+
server.watcher.off("unlink", onEntryChange);
|
|
250
|
+
});
|
|
251
|
+
},
|
|
252
|
+
buildStart() {
|
|
253
|
+
regenerate();
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
var vite_default = mcpPlugin;
|
|
258
|
+
export {
|
|
259
|
+
assertUrlPathShape,
|
|
260
|
+
vite_default as default,
|
|
261
|
+
deriveRouteFileName,
|
|
262
|
+
mcpPlugin
|
|
263
|
+
};
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { ZodRawShape as ZodRawShape$1, ZodType, TypeOf } from 'zod';
|
|
2
|
+
|
|
3
|
+
type JwtClaims = Record<string, unknown> & {
|
|
4
|
+
iss?: string;
|
|
5
|
+
sub?: string;
|
|
6
|
+
aud?: string | string[];
|
|
7
|
+
exp?: number;
|
|
8
|
+
iat?: number;
|
|
9
|
+
nbf?: number;
|
|
10
|
+
scope?: string;
|
|
11
|
+
client_id?: string;
|
|
12
|
+
email?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The safe-to-surface half of the auth context, reachable by tools only through
|
|
16
|
+
* `ToolContext` accessors (`getUserId()`, `getClaims()`, …). Carries no
|
|
17
|
+
* credential — the bearer is isolated in `OAuthBearer`.
|
|
18
|
+
*/
|
|
19
|
+
interface OAuthPrincipal {
|
|
20
|
+
claims: JwtClaims;
|
|
21
|
+
issuer: string;
|
|
22
|
+
resource: string;
|
|
23
|
+
acceptedAudiences: readonly string[];
|
|
24
|
+
scopes: string[];
|
|
25
|
+
sub?: string;
|
|
26
|
+
email?: string;
|
|
27
|
+
clientId?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The raw bearer, isolated from the safe fields. Pass it only into outbound
|
|
31
|
+
* `fetch`/client construction; never return or log it — a leaked token stays
|
|
32
|
+
* valid at the AS until it expires. `token` is a non-enumerable property, so
|
|
33
|
+
* `JSON.stringify`, object spread, and `console.log` omit it by construction.
|
|
34
|
+
*/
|
|
35
|
+
interface OAuthBearer {
|
|
36
|
+
readonly token: string;
|
|
37
|
+
}
|
|
38
|
+
interface McpAuthContext {
|
|
39
|
+
type: "oauth";
|
|
40
|
+
principal: OAuthPrincipal;
|
|
41
|
+
bearer: OAuthBearer;
|
|
42
|
+
}
|
|
43
|
+
interface McpAuthConfig {
|
|
44
|
+
readonly type: "oauth";
|
|
45
|
+
readonly issuer: string;
|
|
46
|
+
readonly resource?: string;
|
|
47
|
+
readonly resourceName?: string;
|
|
48
|
+
readonly resourceDocumentation?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Absolute URL of an externally hosted RFC 9728 protected-resource metadata
|
|
51
|
+
* document. When set, the SDK advertises this URL in the 401
|
|
52
|
+
* `WWW-Authenticate: resource_metadata` challenge and does not generate or
|
|
53
|
+
* serve its own metadata (the `/.well-known/oauth-protected-resource` handler
|
|
54
|
+
* 404s). Use it when the resource server already publishes its own PRM.
|
|
55
|
+
*/
|
|
56
|
+
readonly protectedResourceMetadataUrl?: string;
|
|
57
|
+
readonly requireOAuthClientClaim?: boolean;
|
|
58
|
+
readonly acceptedAudiences?: readonly string[];
|
|
59
|
+
readonly requiredScopes?: readonly string[];
|
|
60
|
+
readonly jwksUri?: string;
|
|
61
|
+
readonly algorithms?: readonly string[];
|
|
62
|
+
readonly clockToleranceSeconds?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The auth surface passed to every tool handler as its second argument. It wraps
|
|
67
|
+
* the verified per-request auth context in a private field, so the context — and
|
|
68
|
+
* the bearer token inside it — can't be read, enumerated, spread, or
|
|
69
|
+
* `JSON.stringify`'d off the instance; the accessors below are the only way out.
|
|
70
|
+
* `getToken()` is the single intentional escape hatch for the raw bearer.
|
|
71
|
+
*/
|
|
72
|
+
declare class ToolContext {
|
|
73
|
+
#private;
|
|
74
|
+
constructor(auth: McpAuthContext | undefined);
|
|
75
|
+
/** Whether the in-flight tool call carries a verified auth context. */
|
|
76
|
+
isAuthenticated(): boolean;
|
|
77
|
+
/** The verified bearer token, or `undefined` when unauthenticated. Pass it to downstream APIs; never return or log it. */
|
|
78
|
+
getToken(): string | undefined;
|
|
79
|
+
/** The verified user id (the token `sub`), or `undefined`. */
|
|
80
|
+
getUserId(): string | undefined;
|
|
81
|
+
/** The verified user email, or `undefined` when absent. */
|
|
82
|
+
getUserEmail(): string | undefined;
|
|
83
|
+
/** The verified OAuth `client_id`, or `undefined`. */
|
|
84
|
+
getClientId(): string | undefined;
|
|
85
|
+
/** The verified OAuth scopes, or `undefined` when unauthenticated. */
|
|
86
|
+
getScopes(): string[] | undefined;
|
|
87
|
+
/** The verified token issuer, or `undefined`. */
|
|
88
|
+
getIssuer(): string | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* The full verified JWT claims, or `undefined`. Use this for app/business
|
|
91
|
+
* authorization on issuer-specific claims that have no dedicated accessor.
|
|
92
|
+
*/
|
|
93
|
+
getClaims(): JwtClaims | undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Any zod schema. Aliased to zod's `ZodType` (no generics) — non-deprecated
|
|
98
|
+
* in both v3 and v4. Use this when you need to type an individual schema;
|
|
99
|
+
* for the `{ text: z.string() }` shape passed to `defineTool`'s
|
|
100
|
+
* `inputSchema`, use `ZodRawShape`.
|
|
101
|
+
*/
|
|
102
|
+
type ZodSchema = ZodType;
|
|
103
|
+
/**
|
|
104
|
+
* A raw zod shape (`{ text: z.string(), n: z.number() }`) — the format
|
|
105
|
+
* `defineTool`'s `inputSchema`/`outputSchema` expects.
|
|
106
|
+
*/
|
|
107
|
+
type ZodRawShape = ZodRawShape$1;
|
|
108
|
+
/**
|
|
109
|
+
* Infer the parsed-output type of a zod raw shape — given
|
|
110
|
+
* `{ text: ZodString, n: ZodNumber }`, produces `{ text: string, n: number }`.
|
|
111
|
+
*/
|
|
112
|
+
type ShapeOutput<Shape extends ZodRawShape> = {
|
|
113
|
+
[K in keyof Shape]: Shape[K] extends ZodType ? TypeOf<Shape[K]> : unknown;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Per-content-block metadata. Distinct from `ToolAnnotations`, which
|
|
117
|
+
* decorates the tool as a whole — these decorate a single piece of
|
|
118
|
+
* content (an image, a text block, etc.) within the tool's result.
|
|
119
|
+
*/
|
|
120
|
+
interface ContentAnnotations {
|
|
121
|
+
/** Which side of the conversation the content is addressed at. */
|
|
122
|
+
audience?: ("user" | "assistant")[];
|
|
123
|
+
/** Higher = more important. Used by clients that summarise content. */
|
|
124
|
+
priority?: number;
|
|
125
|
+
/** ISO 8601 timestamp. */
|
|
126
|
+
lastModified?: string;
|
|
127
|
+
}
|
|
128
|
+
interface ContentBlockBase {
|
|
129
|
+
annotations?: ContentAnnotations;
|
|
130
|
+
_meta?: Record<string, unknown>;
|
|
131
|
+
}
|
|
132
|
+
interface TextContent extends ContentBlockBase {
|
|
133
|
+
type: "text";
|
|
134
|
+
text: string;
|
|
135
|
+
}
|
|
136
|
+
interface ImageContent extends ContentBlockBase {
|
|
137
|
+
type: "image";
|
|
138
|
+
data: string;
|
|
139
|
+
mimeType: string;
|
|
140
|
+
}
|
|
141
|
+
interface AudioContent extends ContentBlockBase {
|
|
142
|
+
type: "audio";
|
|
143
|
+
data: string;
|
|
144
|
+
mimeType: string;
|
|
145
|
+
}
|
|
146
|
+
interface EmbeddedTextResource extends ContentBlockBase {
|
|
147
|
+
type: "resource";
|
|
148
|
+
resource: {
|
|
149
|
+
uri: string;
|
|
150
|
+
mimeType?: string;
|
|
151
|
+
text: string;
|
|
152
|
+
_meta?: Record<string, unknown>;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
interface EmbeddedBlobResource extends ContentBlockBase {
|
|
156
|
+
type: "resource";
|
|
157
|
+
resource: {
|
|
158
|
+
uri: string;
|
|
159
|
+
mimeType?: string;
|
|
160
|
+
blob: string;
|
|
161
|
+
_meta?: Record<string, unknown>;
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
type EmbeddedResource = EmbeddedTextResource | EmbeddedBlobResource;
|
|
165
|
+
/**
|
|
166
|
+
* Optional icon entry on `ResourceLink`. Clients that display a resource
|
|
167
|
+
* with a glyph (file browsers, side panels, etc.) pick the best match by
|
|
168
|
+
* MIME type, `sizes`, and the active `theme`. All fields besides `src`
|
|
169
|
+
* are optional — `src` is typically a `file://`, `data:`, or `https:` URI.
|
|
170
|
+
*/
|
|
171
|
+
interface ResourceLinkIcon {
|
|
172
|
+
src: string;
|
|
173
|
+
mimeType?: string;
|
|
174
|
+
/** Space-separated dimension string, HTML `<link sizes>` convention (e.g. `"16x16 32x32"`). */
|
|
175
|
+
sizes?: string;
|
|
176
|
+
theme?: "light" | "dark";
|
|
177
|
+
}
|
|
178
|
+
interface ResourceLink extends ContentBlockBase {
|
|
179
|
+
type: "resource_link";
|
|
180
|
+
uri: string;
|
|
181
|
+
name: string;
|
|
182
|
+
title?: string;
|
|
183
|
+
description?: string;
|
|
184
|
+
mimeType?: string;
|
|
185
|
+
icons?: ResourceLinkIcon[];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* A single content block in a tool result. Matches the MCP wire format
|
|
189
|
+
* for `tools/call` responses (`type: "text" | "image" | "audio" |
|
|
190
|
+
* "resource" | "resource_link"`).
|
|
191
|
+
*/
|
|
192
|
+
type ContentBlock = TextContent | ImageContent | AudioContent | EmbeddedResource | ResourceLink;
|
|
193
|
+
/**
|
|
194
|
+
* The shape of `ToolHandlerResult.content` — an array of content blocks
|
|
195
|
+
* the tool returns to the caller.
|
|
196
|
+
*/
|
|
197
|
+
type ToolContent = ContentBlock[];
|
|
198
|
+
/**
|
|
199
|
+
* Per-tool client hints (not enforcement). `annotations.title` is
|
|
200
|
+
* intentionally omitted — use `ToolDefinition.title`, which the MCP
|
|
201
|
+
* 2025-06-18+ spec promoted to top-level.
|
|
202
|
+
*/
|
|
203
|
+
interface ToolAnnotations {
|
|
204
|
+
/** Tool does not modify state. */
|
|
205
|
+
readOnlyHint?: boolean;
|
|
206
|
+
/** Tool may make irreversible changes. Only meaningful when readOnlyHint is false. */
|
|
207
|
+
destructiveHint?: boolean;
|
|
208
|
+
/** Calling N times has the same effect as calling once. */
|
|
209
|
+
idempotentHint?: boolean;
|
|
210
|
+
/** Tool interacts with external/unbounded systems (DB, network, third-party APIs). */
|
|
211
|
+
openWorldHint?: boolean;
|
|
212
|
+
}
|
|
213
|
+
interface ToolHandlerResult {
|
|
214
|
+
content?: ToolContent;
|
|
215
|
+
structuredContent?: Record<string, unknown>;
|
|
216
|
+
isError?: boolean;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
interface ToolDefinition<TInput extends ZodRawShape | undefined = ZodRawShape | undefined, TOutput extends ZodRawShape | undefined = ZodRawShape | undefined> {
|
|
220
|
+
readonly name: string;
|
|
221
|
+
/** Human-readable display name shown to the user in MCP clients. Required. */
|
|
222
|
+
readonly title: string;
|
|
223
|
+
/** Prose read by the LLM to decide whether to call this tool. Required. */
|
|
224
|
+
readonly description: string;
|
|
225
|
+
readonly inputSchema?: TInput;
|
|
226
|
+
readonly outputSchema?: TOutput;
|
|
227
|
+
readonly annotations?: ToolAnnotations;
|
|
228
|
+
readonly handler: TInput extends ZodRawShape ? (args: ShapeOutput<TInput>, ctx: ToolContext) => ToolHandlerResult | Promise<ToolHandlerResult> : (args: Record<string, never> | undefined, ctx: ToolContext) => ToolHandlerResult | Promise<ToolHandlerResult>;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Type-erased element of `McpDefinition.tools`. Per-tool generic typing
|
|
232
|
+
* happens at the `defineTool` call site; see README §6 for the rationale.
|
|
233
|
+
*/
|
|
234
|
+
interface AnyToolDefinition {
|
|
235
|
+
readonly name: string;
|
|
236
|
+
readonly title: string;
|
|
237
|
+
readonly description: string;
|
|
238
|
+
readonly inputSchema?: ZodRawShape;
|
|
239
|
+
readonly outputSchema?: ZodRawShape;
|
|
240
|
+
readonly annotations?: ToolAnnotations;
|
|
241
|
+
readonly handler: (args: any, ctx: ToolContext) => ToolHandlerResult | Promise<ToolHandlerResult>;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Fields a caller supplies when constructing an MCP definition. Pass this
|
|
245
|
+
* shape to `defineMcp(...)` — its return type is the branded `McpDefinition`,
|
|
246
|
+
* which is the type every internal factory (`createInvokeToolHandler`,
|
|
247
|
+
* `createMcpProtocolHandler`, …) consumes. The brand forces consumers
|
|
248
|
+
* through `defineMcp`'s `assertUniqueNames` + freeze.
|
|
249
|
+
*/
|
|
250
|
+
interface McpDefinitionInput {
|
|
251
|
+
readonly name: string;
|
|
252
|
+
/** Human-readable display name shown to MCP clients. Required. */
|
|
253
|
+
readonly title: string;
|
|
254
|
+
readonly version: string;
|
|
255
|
+
readonly auth?: McpAuthConfig;
|
|
256
|
+
/**
|
|
257
|
+
* Server-level prose the LLM reads as context for all tools on this server.
|
|
258
|
+
* Required for the same reason `ToolDefinition.description` is — optional
|
|
259
|
+
* fields encourage drift in production agent behavior. Pass an explicit
|
|
260
|
+
* empty string to opt out.
|
|
261
|
+
*/
|
|
262
|
+
readonly instructions: string;
|
|
263
|
+
readonly tools: readonly AnyToolDefinition[];
|
|
264
|
+
}
|
|
265
|
+
declare const McpDefinitionBrand: unique symbol;
|
|
266
|
+
type McpDefinition = McpDefinitionInput & {
|
|
267
|
+
readonly [McpDefinitionBrand]: never;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export { type AudioContent as A, type ContentAnnotations as C, type EmbeddedBlobResource as E, type ImageContent as I, type JwtClaims as J, type McpAuthConfig as M, type ResourceLink as R, type TextContent as T, type ZodRawShape as Z, type ContentBlock as a, type EmbeddedResource as b, type EmbeddedTextResource as c, type McpDefinition as d, type McpDefinitionInput as e, type ResourceLinkIcon as f, type ToolAnnotations as g, type ToolContent as h, ToolContext as i, type ToolDefinition as j, type ToolHandlerResult as k, type ZodSchema as l };
|
package/package.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lovable.dev/mcp-js",
|
|
3
|
+
"version": "0.5.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/lovablelabs/lovable.git",
|
|
9
|
+
"directory": "npm-packages/lovable-mcp-js"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/lovablelabs/lovable/tree/main/npm-packages/lovable-mcp-js#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/lovablelabs/lovable/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"main": "./dist/index.cjs",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js",
|
|
25
|
+
"require": "./dist/index.cjs"
|
|
26
|
+
},
|
|
27
|
+
"./protocols/mcp": {
|
|
28
|
+
"types": "./dist/protocols/mcp/index.d.ts",
|
|
29
|
+
"import": "./dist/protocols/mcp/index.js",
|
|
30
|
+
"require": "./dist/protocols/mcp/index.cjs"
|
|
31
|
+
},
|
|
32
|
+
"./protocols/oauth-metadata": {
|
|
33
|
+
"types": "./dist/protocols/oauth-metadata.d.ts",
|
|
34
|
+
"import": "./dist/protocols/oauth-metadata.js",
|
|
35
|
+
"require": "./dist/protocols/oauth-metadata.cjs"
|
|
36
|
+
},
|
|
37
|
+
"./protocols/rest": {
|
|
38
|
+
"types": "./dist/protocols/rest/index.d.ts",
|
|
39
|
+
"import": "./dist/protocols/rest/index.js",
|
|
40
|
+
"require": "./dist/protocols/rest/index.cjs"
|
|
41
|
+
},
|
|
42
|
+
"./stacks/tanstack": {
|
|
43
|
+
"types": "./dist/stacks/tanstack/index.d.ts",
|
|
44
|
+
"import": "./dist/stacks/tanstack/index.js",
|
|
45
|
+
"require": "./dist/stacks/tanstack/index.cjs"
|
|
46
|
+
},
|
|
47
|
+
"./stacks/tanstack/vite": {
|
|
48
|
+
"types": "./dist/stacks/tanstack/vite.d.ts",
|
|
49
|
+
"import": "./dist/stacks/tanstack/vite.js",
|
|
50
|
+
"require": "./dist/stacks/tanstack/vite.cjs"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"dist"
|
|
55
|
+
],
|
|
56
|
+
"keywords": [
|
|
57
|
+
"mcp",
|
|
58
|
+
"model-context-protocol",
|
|
59
|
+
"tanstack",
|
|
60
|
+
"vite",
|
|
61
|
+
"lovable"
|
|
62
|
+
],
|
|
63
|
+
"license": "MIT",
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@modelcontextprotocol/sdk": "1.28.0",
|
|
66
|
+
"jose": "^6.2.2"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"vite": ">=5.0.0 <9.0.0",
|
|
70
|
+
"zod": ">=3.23.0"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/node": "22.13.13",
|
|
74
|
+
"rimraf": "^5.0.0",
|
|
75
|
+
"tsup": "^7.2.0",
|
|
76
|
+
"typescript": "^5.9.3",
|
|
77
|
+
"vite": "^7.3.1",
|
|
78
|
+
"vitest": "^4.1.5",
|
|
79
|
+
"zod": "^4.1.13"
|
|
80
|
+
},
|
|
81
|
+
"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",
|
|
84
|
+
"typecheck": "tsgo --noEmit",
|
|
85
|
+
"format": "oxfmt --write src/ tests/",
|
|
86
|
+
"format:check": "oxfmt --check src/ tests/",
|
|
87
|
+
"lint": "oxfmt --check src/ tests/ && oxlint -c ../../oxlint.config.ts src/ tests/",
|
|
88
|
+
"lint:fix": "oxfmt --write src/ tests/ && oxlint -c ../../oxlint.config.ts --fix src/ tests/",
|
|
89
|
+
"test": "vitest run --project unit",
|
|
90
|
+
"test:watch": "vitest --project unit",
|
|
91
|
+
"example:install": "bun link && cd example/tanstack && bun install && bun link @lovable.dev/mcp-js",
|
|
92
|
+
"example:oauth:install": "bun link && cd example/tanstack-supabase-oauth && bun install && bun link @lovable.dev/mcp-js",
|
|
93
|
+
"example:oauth:dev": "cd example/tanstack-supabase-oauth && bun --bun run dev",
|
|
94
|
+
"example:oauth:build": "cd example/tanstack-supabase-oauth && bun --bun run build",
|
|
95
|
+
"example:oauth:smoke": "node tests/integration/oauth-smoke.mjs",
|
|
96
|
+
"test:integration": "npm run build && npm run example:install && vitest run --project integration",
|
|
97
|
+
"test:integration:oauth": "npm run build && npm run example:oauth:install && vitest run --project integration-oauth",
|
|
98
|
+
"clean": "rimraf dist",
|
|
99
|
+
"pack:local": "npm run build && npm pack --pack-destination /tmp"
|
|
100
|
+
}
|
|
101
|
+
}
|