@wervt/nuxt 0.3.0 → 0.3.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.
- package/README.md +2 -0
- package/dist/module.d.ts +8 -0
- package/dist/module.js +24 -0
- package/dist/preview.d.ts +14 -0
- package/dist/preview.js +39 -0
- package/dist/runtime/server/access.d.ts +34 -0
- package/dist/runtime/server/access.js +87 -0
- package/dist/runtime/server/index.d.ts +2 -1
- package/dist/runtime/server/index.js +3 -2
- package/dist/runtime/server/mcp-auth.d.ts +5 -3
- package/dist/runtime/server/mcp-auth.js +6 -5
- package/dist/runtime/server/mcp-metadata.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,8 @@ export default defineNuxtConfig({
|
|
|
18
18
|
- `useDb()` (server): Drizzle on the app's own schema, with migrations from `drizzle/`.
|
|
19
19
|
- `proxyShape()` (server) and `useShape()` (app): live rows from Postgres via Electric.
|
|
20
20
|
- `useWervtUser()`: the signed-in wervt user, on the server and in the app.
|
|
21
|
+
- `useWervtAccess(event)`: invites on invite-only apps (`wervt access <app> --invite`), as the
|
|
22
|
+
signed-in user.
|
|
21
23
|
- `useValidatedBody()` / `useValidatedQuery()` / `useValidatedParams()`: valibot validation.
|
|
22
24
|
- `useWervtTrack()`: custom events for wervt's cookieless analytics (page views are counted automatically).
|
|
23
25
|
- `wervt: { feedback: { key } }` and `useWervtFeedback()`: the kontor feedback widget, reports go into a kontor project's triage.
|
package/dist/module.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DevOptions } from "./dev.js";
|
|
2
|
+
import { PreviewOptions } from "./preview.js";
|
|
2
3
|
//#region src/module.d.ts
|
|
3
4
|
export interface ModuleOptions {
|
|
4
5
|
/** App name on wervt. Defaults to the package.json name. */
|
|
@@ -37,6 +38,13 @@ export interface ModuleOptions {
|
|
|
37
38
|
* (it can override a configured key the same way).
|
|
38
39
|
*/
|
|
39
40
|
feedback?: FeedbackOptions | boolean;
|
|
41
|
+
/**
|
|
42
|
+
* The link preview of a private app, shown without a login when its link is shared in chats.
|
|
43
|
+
* Defaults to the app's name and icon only; `true` takes title, description, `og:image` and
|
|
44
|
+
* theme color from `app.head`, an object sets them (falling back to `app.head`). `false`
|
|
45
|
+
* sends visitors without a login straight to the login, with no preview of the app.
|
|
46
|
+
*/
|
|
47
|
+
preview?: PreviewOptions | boolean;
|
|
40
48
|
}
|
|
41
49
|
export interface FeedbackOptions {
|
|
42
50
|
/** Public intake key of the kontor project (Project settings → Intake keys). */
|
package/dist/module.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SEED_TASK, unscheduleSeed } from "./seed.js";
|
|
2
2
|
import { resolveAppName, setupDev } from "./dev.js";
|
|
3
3
|
import { findRouteTools } from "./route-tools.js";
|
|
4
|
+
import { localFiles, previewManifest } from "./preview.js";
|
|
4
5
|
import { findShapeRoutes, shapeRoutesTypes, shapesManifest } from "./shapes.js";
|
|
5
6
|
import { addImports, addPlugin, addServerHandler, addServerPlugin, addServerTemplate, addTypeTemplate, createResolver, defineNuxtModule, getNuxtVersion, hasNuxtModule, installModule, updateTemplates, useLogger } from "nuxt/kit";
|
|
6
7
|
import { existsSync } from "node:fs";
|
|
@@ -275,6 +276,10 @@ var module_default = defineNuxtModule({
|
|
|
275
276
|
method: "get",
|
|
276
277
|
handler: resolver.resolve("./runtime/server/user-route")
|
|
277
278
|
});
|
|
279
|
+
if (nitroMajor >= 3) addServerHandler({
|
|
280
|
+
route: "/.well-known/oauth-protected-resource",
|
|
281
|
+
handler: resolver.resolve("./runtime/server/mcp-metadata")
|
|
282
|
+
});
|
|
278
283
|
addPlugin({
|
|
279
284
|
src: resolver.resolve("./runtime/app/plugins/user.client"),
|
|
280
285
|
mode: "client"
|
|
@@ -337,6 +342,20 @@ var module_default = defineNuxtModule({
|
|
|
337
342
|
await writeFile(join(serverDir, "wervt-shapes.json"), `${JSON.stringify(shapesManifest(await findShapeRoutes(shapesDir)), null, 2)}\n`);
|
|
338
343
|
});
|
|
339
344
|
});
|
|
345
|
+
if (!nuxt.options.dev) {
|
|
346
|
+
const app = await resolveAppName(nuxt.options.rootDir, options.app);
|
|
347
|
+
const publicDir = resolve(nuxt.options.rootDir, nuxt.options.dir.public);
|
|
348
|
+
const favicon = FAVICONS.find((file) => existsSync(join(publicDir, file)));
|
|
349
|
+
const manifest = previewManifest(options.preview, nuxt.options.app.head, app, favicon && `/${favicon}`);
|
|
350
|
+
for (const file of localFiles(manifest)) if (!existsSync(join(publicDir, file))) logger.warn(`The link preview names \`${file}\`, which isn't in public/.`);
|
|
351
|
+
nuxt.hook("nitro:init", (instance) => {
|
|
352
|
+
const { serverDir } = instance.options.output;
|
|
353
|
+
instance.hooks.hook("compiled", async () => {
|
|
354
|
+
await mkdir(serverDir, { recursive: true });
|
|
355
|
+
await writeFile(join(serverDir, "wervt-preview.json"), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
}
|
|
340
359
|
nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
|
|
341
360
|
addImports([
|
|
342
361
|
{
|
|
@@ -374,6 +393,11 @@ var module_default = defineNuxtModule({
|
|
|
374
393
|
]);
|
|
375
394
|
}
|
|
376
395
|
});
|
|
396
|
+
const FAVICONS = [
|
|
397
|
+
"favicon.png",
|
|
398
|
+
"favicon.svg",
|
|
399
|
+
"favicon.ico"
|
|
400
|
+
];
|
|
377
401
|
const TOOL_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
378
402
|
".ts",
|
|
379
403
|
".js",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/preview.d.ts
|
|
2
|
+
export interface PreviewOptions {
|
|
3
|
+
/** Defaults to the app's `app.head.title`. */
|
|
4
|
+
title?: string;
|
|
5
|
+
/** Defaults to the app's `description` or `og:description` meta tag. */
|
|
6
|
+
description?: string;
|
|
7
|
+
/** A file in `public/` (`/og.png`, ideally 1200×630) or an https URL. Defaults to `og:image`. */
|
|
8
|
+
image?: string;
|
|
9
|
+
/** A file in `public/` or an https URL. Defaults to the app's `<link rel="icon">` or favicon. */
|
|
10
|
+
icon?: string;
|
|
11
|
+
/** Defaults to the app's `theme-color` meta tag. */
|
|
12
|
+
themeColor?: string;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
package/dist/preview.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/preview.ts
|
|
2
|
+
/**
|
|
3
|
+
* What the runtime shows. Without `preview`, only the app's name and icon: for some private apps
|
|
4
|
+
* the title alone says too much, so the rest of the head is only used once the app opts in.
|
|
5
|
+
*/
|
|
6
|
+
function previewManifest(option, head, app, favicon) {
|
|
7
|
+
if (option === false) return false;
|
|
8
|
+
const icon = headIcon(head) ?? favicon;
|
|
9
|
+
if (option === void 0) return {
|
|
10
|
+
title: app,
|
|
11
|
+
icon
|
|
12
|
+
};
|
|
13
|
+
const fromHead = {
|
|
14
|
+
title: typeof head.title === "string" && head.title ? head.title : app,
|
|
15
|
+
description: headMeta(head, "description") ?? headMeta(head, "og:description"),
|
|
16
|
+
image: headMeta(head, "og:image"),
|
|
17
|
+
icon,
|
|
18
|
+
themeColor: headMeta(head, "theme-color")
|
|
19
|
+
};
|
|
20
|
+
if (option === true) return fromHead;
|
|
21
|
+
const manifest = { ...fromHead };
|
|
22
|
+
for (const [key, value] of Object.entries(option)) if (typeof value === "string" && value) manifest[key] = value;
|
|
23
|
+
return manifest;
|
|
24
|
+
}
|
|
25
|
+
function headMeta(head, key) {
|
|
26
|
+
const tag = head.meta?.find((meta) => meta.name === key || meta.property === key);
|
|
27
|
+
return typeof tag?.content === "string" && tag.content ? tag.content : void 0;
|
|
28
|
+
}
|
|
29
|
+
function headIcon(head) {
|
|
30
|
+
const link = head.link?.find((link) => link.rel === "icon" || link.rel === "shortcut icon");
|
|
31
|
+
return typeof link?.href === "string" && link.href ? link.href : void 0;
|
|
32
|
+
}
|
|
33
|
+
/** Local files the manifest names (not URLs), to check they're in `public/`. */
|
|
34
|
+
function localFiles(manifest) {
|
|
35
|
+
if (!manifest) return [];
|
|
36
|
+
return [manifest.image, manifest.icon].filter((path) => !!path && path.startsWith("/") && !path.startsWith("//"));
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { localFiles, previewManifest };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { RequestEvent } from "nuxt/server";
|
|
2
|
+
//#region src/runtime/server/access.d.ts
|
|
3
|
+
export interface WervtInvite {
|
|
4
|
+
email: string;
|
|
5
|
+
/** Who invited them. */
|
|
6
|
+
by: string;
|
|
7
|
+
/** ISO time. */
|
|
8
|
+
at: string;
|
|
9
|
+
}
|
|
10
|
+
export interface WervtAccess {
|
|
11
|
+
/** Lets `email` into the app. `invited` is false when they could already get in. */
|
|
12
|
+
invite(email: string): Promise<{
|
|
13
|
+
email: string;
|
|
14
|
+
invited: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
/** Takes back an invite (not the owner's allow list). */
|
|
17
|
+
revoke(email: string): Promise<{
|
|
18
|
+
email: string;
|
|
19
|
+
revoked: boolean;
|
|
20
|
+
}>;
|
|
21
|
+
/** Everyone the app invited, with who invited them and when. */
|
|
22
|
+
list(): Promise<WervtInvite[]>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Invites on an invite-only app (`wervt access <app> --invite`), as the user of this request: the
|
|
26
|
+
* runtime vouches for them, and only someone with access can invite. Everyone with access may
|
|
27
|
+
* invite; restrict it in the route if the app has its own roles.
|
|
28
|
+
*
|
|
29
|
+
* Throws with the platform's status: 401 without a signed-in user, 403 when they have no access,
|
|
30
|
+
* 409 when the app isn't invite-only, 429 past the limits (25 invites per person and day, 500 per
|
|
31
|
+
* app).
|
|
32
|
+
*/
|
|
33
|
+
export declare function useWervtAccess(event: RequestEvent): WervtAccess;
|
|
34
|
+
//#endregion
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { DEV_LOGIN_COOKIE, devUser } from "../shared/user.js";
|
|
2
|
+
import { createError, getCookie } from "nuxt/server";
|
|
3
|
+
//#region src/runtime/server/access.ts
|
|
4
|
+
const devInvites = [];
|
|
5
|
+
/**
|
|
6
|
+
* Invites on an invite-only app (`wervt access <app> --invite`), as the user of this request: the
|
|
7
|
+
* runtime vouches for them, and only someone with access can invite. Everyone with access may
|
|
8
|
+
* invite; restrict it in the route if the app has its own roles.
|
|
9
|
+
*
|
|
10
|
+
* Throws with the platform's status: 401 without a signed-in user, 403 when they have no access,
|
|
11
|
+
* 409 when the app isn't invite-only, 429 past the limits (25 invites per person and day, 500 per
|
|
12
|
+
* app).
|
|
13
|
+
*/
|
|
14
|
+
function useWervtAccess(event) {
|
|
15
|
+
const runtime = process.env.WERVT_RUNTIME_URL;
|
|
16
|
+
if (!runtime) return devAccess(event);
|
|
17
|
+
async function call(method, body) {
|
|
18
|
+
const res = await fetch(new URL("/_wervt/access", runtime), {
|
|
19
|
+
method,
|
|
20
|
+
headers: {
|
|
21
|
+
"content-type": "application/json",
|
|
22
|
+
"x-wervt-app": process.env.WERVT_APP ?? "",
|
|
23
|
+
"x-wervt-token": process.env.WERVT_TOKEN ?? "",
|
|
24
|
+
"x-wervt-user": event.req.headers.get("x-wervt-user") ?? "",
|
|
25
|
+
"x-wervt-user-proof": event.req.headers.get("x-wervt-user-proof") ?? ""
|
|
26
|
+
},
|
|
27
|
+
body: body && JSON.stringify(body)
|
|
28
|
+
});
|
|
29
|
+
const data = await res.json().catch(() => ({}));
|
|
30
|
+
if (!res.ok) throw createError({
|
|
31
|
+
status: res.status,
|
|
32
|
+
message: data.error ?? `access ${res.status}`
|
|
33
|
+
});
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
invite: (email) => call("POST", { email }),
|
|
38
|
+
revoke: (email) => call("DELETE", { email }),
|
|
39
|
+
list: async () => (await call("GET")).invited
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function devAccess(event) {
|
|
43
|
+
const user = devUser(getCookie(event, DEV_LOGIN_COOKIE) === "1");
|
|
44
|
+
const by = () => {
|
|
45
|
+
if (!user) throw createError({
|
|
46
|
+
status: 401,
|
|
47
|
+
message: "sign in at /_wervt/login first"
|
|
48
|
+
});
|
|
49
|
+
return user.email;
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
async invite(email) {
|
|
53
|
+
const inviter = by();
|
|
54
|
+
email = email.trim().toLowerCase();
|
|
55
|
+
if (devInvites.some((invite) => invite.email === email)) return {
|
|
56
|
+
email,
|
|
57
|
+
invited: false
|
|
58
|
+
};
|
|
59
|
+
devInvites.push({
|
|
60
|
+
email,
|
|
61
|
+
by: inviter,
|
|
62
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
63
|
+
});
|
|
64
|
+
console.info(`[access] ${inviter} invited ${email} (dev: not stored)`);
|
|
65
|
+
return {
|
|
66
|
+
email,
|
|
67
|
+
invited: true
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
async revoke(email) {
|
|
71
|
+
by();
|
|
72
|
+
email = email.trim().toLowerCase();
|
|
73
|
+
const index = devInvites.findIndex((invite) => invite.email === email);
|
|
74
|
+
if (index >= 0) devInvites.splice(index, 1);
|
|
75
|
+
return {
|
|
76
|
+
email,
|
|
77
|
+
revoked: index >= 0
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
async list() {
|
|
81
|
+
by();
|
|
82
|
+
return [...devInvites];
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { useWervtAccess };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { WervtUser } from "../shared/user.js";
|
|
2
2
|
import { EmailProps, RenderedEmail, renderEmail } from "../email/render.js";
|
|
3
|
+
import { WervtAccess, WervtInvite, useWervtAccess } from "./access.js";
|
|
3
4
|
import { useDb } from "./db.js";
|
|
4
5
|
import { createEvlogError, log, useLogger } from "./logger.js";
|
|
5
6
|
import { SendMailOptions, sendMail } from "./mail.js";
|
|
@@ -11,4 +12,4 @@ import { Task, TaskEvent, TaskResult, defineTask } from "./tasks.js";
|
|
|
11
12
|
import { SafeResult, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, vh } from "./validate.js";
|
|
12
13
|
import { Tool, ToolRoute, defineTool, defineToolRoute } from "./tools.js";
|
|
13
14
|
import { useWervtUser } from "./user.js";
|
|
14
|
-
export { type EmailProps, type McpTokenContext, type RenderedEmail, type SafeResult, type SendMailOptions, type ShapeOptions, type ShapeResponse, type ShapeRow, type Task, type TaskEvent, type TaskResult, type Tool, type ToolRoute, type WervtUser, createEvlogError, defineTask, defineTool, defineToolRoute, log, proxyShape, renderEmail, seedTable, sendMail, useDb, useLogger, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, useWervtUser, vh };
|
|
15
|
+
export { type EmailProps, type McpTokenContext, type RenderedEmail, type SafeResult, type SendMailOptions, type ShapeOptions, type ShapeResponse, type ShapeRow, type Task, type TaskEvent, type TaskResult, type Tool, type ToolRoute, type WervtAccess, type WervtInvite, type WervtUser, createEvlogError, defineTask, defineTool, defineToolRoute, log, proxyShape, renderEmail, seedTable, sendMail, useDb, useLogger, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, useWervtAccess, useWervtUser, vh };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useWervtAccess } from "./access.js";
|
|
2
2
|
import { useDb } from "./db.js";
|
|
3
|
+
import { renderEmail } from "../email/render.js";
|
|
3
4
|
import { createEvlogError, log, useLogger } from "./logger.js";
|
|
4
5
|
import { sendMail } from "./mail.js";
|
|
5
6
|
import { seedTable } from "./seed.js";
|
|
@@ -8,4 +9,4 @@ import { defineTask } from "./tasks.js";
|
|
|
8
9
|
import { useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, vh } from "./validate.js";
|
|
9
10
|
import { defineTool, defineToolRoute } from "./tools.js";
|
|
10
11
|
import { useWervtUser } from "./user.js";
|
|
11
|
-
export { createEvlogError, defineTask, defineTool, defineToolRoute, log, proxyShape, renderEmail, seedTable, sendMail, useDb, useLogger, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, useWervtUser, vh };
|
|
12
|
+
export { createEvlogError, defineTask, defineTool, defineToolRoute, log, proxyShape, renderEmail, seedTable, sendMail, useDb, useLogger, useSafeValidatedBody, useSafeValidatedParams, useSafeValidatedQuery, useValidatedBody, useValidatedParams, useValidatedQuery, useWervtAccess, useWervtUser, vh };
|
|
@@ -17,7 +17,9 @@ declare module 'nuxt/schema' {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
export interface McpEndpoint {
|
|
20
|
-
/** This app's
|
|
20
|
+
/** This app's origin, a resource too: tokens for the whole app (native apps) work on `/mcp`. */
|
|
21
|
+
app: string;
|
|
22
|
+
/** This app's `/mcp`, the audience MCP clients' access tokens carry. */
|
|
21
23
|
resource: string;
|
|
22
24
|
/** wervt's login as OAuth issuer, e.g. `https://auth.wervt.app/api/auth`. */
|
|
23
25
|
issuer: string;
|
|
@@ -36,8 +38,8 @@ export declare const CORS: {
|
|
|
36
38
|
'access-control-allow-headers': string;
|
|
37
39
|
'access-control-max-age': string;
|
|
38
40
|
};
|
|
39
|
-
/** RFC 9728: tells
|
|
40
|
-
export declare function protectedResourceMetadata(endpoint: McpEndpoint): {
|
|
41
|
+
/** RFC 9728: tells clients which login issues tokens for `resource` (`/mcp` or the app). */
|
|
42
|
+
export declare function protectedResourceMetadata(endpoint: McpEndpoint, resource?: string): {
|
|
41
43
|
resource: string;
|
|
42
44
|
authorization_servers: string[];
|
|
43
45
|
bearer_methods_supported: string[];
|
|
@@ -12,6 +12,7 @@ function mcpEndpoint(event) {
|
|
|
12
12
|
if (!authUrl) return void 0;
|
|
13
13
|
const appUrl = trimSlash(process.env.WERVT_APP_URL ?? getRequestURL(event).origin);
|
|
14
14
|
return {
|
|
15
|
+
app: appUrl,
|
|
15
16
|
resource: `${appUrl}/mcp`,
|
|
16
17
|
issuer: `${trimSlash(authUrl)}/api/auth`,
|
|
17
18
|
metadataUrl: `${appUrl}/.well-known/oauth-protected-resource/mcp`
|
|
@@ -24,10 +25,10 @@ const CORS = {
|
|
|
24
25
|
"access-control-allow-headers": "*",
|
|
25
26
|
"access-control-max-age": "86400"
|
|
26
27
|
};
|
|
27
|
-
/** RFC 9728: tells
|
|
28
|
-
function protectedResourceMetadata(endpoint) {
|
|
28
|
+
/** RFC 9728: tells clients which login issues tokens for `resource` (`/mcp` or the app). */
|
|
29
|
+
function protectedResourceMetadata(endpoint, resource = endpoint.resource) {
|
|
29
30
|
return {
|
|
30
|
-
resource
|
|
31
|
+
resource,
|
|
31
32
|
authorization_servers: [endpoint.issuer],
|
|
32
33
|
bearer_methods_supported: ["header"],
|
|
33
34
|
scopes_supported: [
|
|
@@ -36,7 +37,7 @@ function protectedResourceMetadata(endpoint) {
|
|
|
36
37
|
"email",
|
|
37
38
|
"offline_access"
|
|
38
39
|
],
|
|
39
|
-
resource_name: new URL(
|
|
40
|
+
resource_name: new URL(resource).hostname.split(".")[0]
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
/**
|
|
@@ -69,7 +70,7 @@ async function verifyAccessToken(token, endpoint) {
|
|
|
69
70
|
try {
|
|
70
71
|
const { payload } = await jwtVerify(token, keySet(endpoint.issuer), {
|
|
71
72
|
issuer: endpoint.issuer,
|
|
72
|
-
audience: endpoint.resource,
|
|
73
|
+
audience: [endpoint.resource, endpoint.app],
|
|
73
74
|
requiredClaims: ["exp", "sub"],
|
|
74
75
|
clockTolerance: 5
|
|
75
76
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CORS, mcpEndpoint, protectedResourceMetadata } from "./mcp-auth.js";
|
|
2
|
-
import { defineEventHandler } from "nuxt/server";
|
|
2
|
+
import { defineEventHandler, getRequestURL } from "nuxt/server";
|
|
3
3
|
//#region src/runtime/server/mcp-metadata.ts
|
|
4
4
|
var mcp_metadata_default = defineEventHandler((event) => {
|
|
5
5
|
const endpoint = mcpEndpoint(event);
|
|
@@ -8,7 +8,8 @@ var mcp_metadata_default = defineEventHandler((event) => {
|
|
|
8
8
|
headers: CORS
|
|
9
9
|
});
|
|
10
10
|
if (!endpoint) return Response.json({ error: "No login configured." }, { status: 404 });
|
|
11
|
-
|
|
11
|
+
const mcp = getRequestURL(event).pathname.endsWith("/mcp");
|
|
12
|
+
return Response.json(protectedResourceMetadata(endpoint, mcp ? endpoint.resource : endpoint.app), { headers: CORS });
|
|
12
13
|
});
|
|
13
14
|
//#endregion
|
|
14
15
|
export { mcp_metadata_default as default };
|