nuxt-ai-ready 1.5.8 β 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/cli.mjs +26 -36
- package/dist/module.d.mts +14 -2
- package/dist/module.json +2 -2
- package/dist/module.mjs +222 -33
- package/dist/runtime/app/composables/webmcp.d.ts +24 -0
- package/dist/runtime/app/composables/webmcp.js +103 -0
- package/dist/runtime/app/plugins/webmcp.client.d.ts +2 -0
- package/dist/runtime/app/plugins/webmcp.client.js +59 -0
- package/dist/runtime/app/webmcp-config.d.ts +3 -0
- package/dist/runtime/app/webmcp-config.js +7 -0
- package/dist/runtime/mcp.d.ts +11 -5
- package/dist/runtime/mcp.js +2 -1
- package/dist/runtime/server/db/queries.d.ts +4 -4
- package/dist/runtime/server/mcp/tools/get-page-markdown.js +35 -0
- package/dist/runtime/server/mcp/tools/list-pages.js +11 -4
- package/dist/runtime/server/mcp/tools/search-pages.js +11 -4
- package/dist/runtime/server/middleware/markdown.js +35 -8
- package/dist/runtime/server/middleware/markdown.prerender.js +1 -1
- package/dist/runtime/server/plugins/mcp-data.d.ts +2 -0
- package/dist/runtime/server/plugins/mcp-data.js +22 -0
- package/dist/runtime/server/routes/__ai-ready/pages.get.d.ts +41 -0
- package/dist/runtime/server/routes/__ai-ready/pages.get.js +48 -0
- package/dist/runtime/server/utils/cloudflare.js +9 -6
- package/dist/runtime/server/utils/content-negotiation.d.ts +16 -0
- package/dist/runtime/server/utils/content-negotiation.js +23 -0
- package/dist/runtime/server/utils/llms-full.js +18 -25
- package/dist/runtime/server/utils/sitemap.d.ts +2 -2
- package/dist/runtime/server/utils/sitemap.js +269 -79
- package/dist/runtime/server/utils.d.ts +7 -1
- package/dist/runtime/server/utils.js +4 -3
- package/dist/runtime/site-tool-catalog.d.ts +31 -0
- package/dist/runtime/site-tool-catalog.js +52 -0
- package/dist/runtime/site-tool-config.d.ts +75 -0
- package/dist/runtime/site-tool-config.js +0 -0
- package/dist/runtime/types.d.ts +37 -1
- package/dist/runtime/webmcp-public.d.ts +4 -0
- package/dist/runtime/webmcp-public.js +3 -0
- package/dist/runtime/webmcp-site-tools.d.ts +26 -0
- package/dist/runtime/webmcp-site-tools.js +282 -0
- package/dist/runtime/webmcp.d.ts +163 -0
- package/dist/runtime/webmcp.js +59 -0
- package/dist/types.d.mts +3 -1
- package/mcp.d.ts +1 -1
- package/package.json +33 -27
- package/webmcp.d.ts +1 -0
package/README.md
CHANGED
|
@@ -21,7 +21,8 @@ Nuxt AI Ready implements both. It converts your pages to markdown, generates llm
|
|
|
21
21
|
- π **[Nuxt Content](https://content.nuxt.com) Integration**: Routes backed by a page collection serve source markdown directly, skipping HTMLβmarkdown conversion
|
|
22
22
|
- π‘ **Content Signals**: Configure AI training/search/input permissions via [Nuxt Robots](https://nuxtseo.com/robots)
|
|
23
23
|
- π **Sitemap Integration**: Index AI-allowed pages via [Nuxt Sitemap](https://nuxtseo.com/sitemap)
|
|
24
|
-
- β‘ **MCP Server**:
|
|
24
|
+
- β‘ **MCP Server**: List, search and read indexed pages through `@nuxtjs/mcp-toolkit`
|
|
25
|
+
- π§© **[WebMCP](https://nuxtseo.com/ai-ready/guides/webmcp)**: Give browser agents access to built-in site tools or your own tools with `useWebMcpTool()`
|
|
25
26
|
- ποΈ **Runtime Indexing**: Index pages on-demand without prerendering, with SQLite/D1/LibSQL support
|
|
26
27
|
- π **[IndexNow](https://nuxtseo.com/ai-ready/guides/indexnow)**: Instantly notify Bing, Yandex, and other search engines when pages change
|
|
27
28
|
- π **[i18n Aware](https://nuxtseo.com/ai-ready/guides/i18n)**: Auto-detects [`@nuxtjs/i18n`](https://i18n.nuxtjs.org/) for hreflang `Link` headers, locale-tagged frontmatter, and an Available Languages section in `llms.txt`
|
package/dist/cli.mjs
CHANGED
|
@@ -17,6 +17,19 @@ async function getSecret(cwd) {
|
|
|
17
17
|
function authHeaders(secret) {
|
|
18
18
|
return { Authorization: `Bearer ${secret}` };
|
|
19
19
|
}
|
|
20
|
+
async function requireSecret(cwd, hint = "") {
|
|
21
|
+
const secret = await getSecret(cwd);
|
|
22
|
+
if (!secret) {
|
|
23
|
+
consola.error(`No secret found. Run \`nuxi dev\` or \`nuxi build\` first${hint}.`);
|
|
24
|
+
}
|
|
25
|
+
return secret;
|
|
26
|
+
}
|
|
27
|
+
async function fetchJson(url, init, errorLabel = "Failed") {
|
|
28
|
+
return fetch(url, init).then((r) => r.json()).catch((err) => {
|
|
29
|
+
consola.error(`${errorLabel}: ${err.message}`);
|
|
30
|
+
return null;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
20
33
|
const main = defineCommand({
|
|
21
34
|
meta: {
|
|
22
35
|
name: "nuxt-ai-ready",
|
|
@@ -44,17 +57,12 @@ const main = defineCommand({
|
|
|
44
57
|
},
|
|
45
58
|
async run({ args }) {
|
|
46
59
|
const cwd = resolve(args.cwd || ".");
|
|
47
|
-
const secret = await
|
|
48
|
-
if (!secret)
|
|
49
|
-
consola.error("No secret found. Run `nuxi dev` or `nuxi build` first to generate one.");
|
|
60
|
+
const secret = await requireSecret(cwd, " to generate one");
|
|
61
|
+
if (!secret)
|
|
50
62
|
return;
|
|
51
|
-
}
|
|
52
63
|
const url = `${args.url}/__ai-ready/status`;
|
|
53
64
|
consola.info(`Fetching status from ${args.url}...`);
|
|
54
|
-
const res = await
|
|
55
|
-
consola.error(`Failed to connect: ${err.message}`);
|
|
56
|
-
return null;
|
|
57
|
-
});
|
|
65
|
+
const res = await fetchJson(url, { headers: authHeaders(secret) }, "Failed to connect");
|
|
58
66
|
if (!res)
|
|
59
67
|
return;
|
|
60
68
|
consola.box("AI Ready Status");
|
|
@@ -146,11 +154,9 @@ const main = defineCommand({
|
|
|
146
154
|
},
|
|
147
155
|
async run({ args }) {
|
|
148
156
|
const cwd = resolve(args.cwd || ".");
|
|
149
|
-
const secret = await
|
|
150
|
-
if (!secret)
|
|
151
|
-
consola.error("No secret found. Run `nuxi dev` or `nuxi build` first.");
|
|
157
|
+
const secret = await requireSecret(cwd);
|
|
158
|
+
if (!secret)
|
|
152
159
|
return;
|
|
153
|
-
}
|
|
154
160
|
const params = new URLSearchParams();
|
|
155
161
|
if (args.all) {
|
|
156
162
|
params.set("all", "true");
|
|
@@ -159,10 +165,7 @@ const main = defineCommand({
|
|
|
159
165
|
}
|
|
160
166
|
const url = `${args.url}/__ai-ready/poll?${params}`;
|
|
161
167
|
consola.info(`Triggering poll at ${args.url}...`);
|
|
162
|
-
const res = await
|
|
163
|
-
consola.error(`Failed: ${err.message}`);
|
|
164
|
-
return null;
|
|
165
|
-
});
|
|
168
|
+
const res = await fetchJson(url, { method: "POST", headers: authHeaders(secret) });
|
|
166
169
|
if (!res)
|
|
167
170
|
return;
|
|
168
171
|
consola.success(`Indexed: ${colors.green(res.indexed?.toString() || "0")} pages`);
|
|
@@ -201,21 +204,16 @@ const main = defineCommand({
|
|
|
201
204
|
},
|
|
202
205
|
async run({ args }) {
|
|
203
206
|
const cwd = resolve(args.cwd || ".");
|
|
204
|
-
const secret = await
|
|
205
|
-
if (!secret)
|
|
206
|
-
consola.error("No secret found. Run `nuxi dev` or `nuxi build` first.");
|
|
207
|
+
const secret = await requireSecret(cwd);
|
|
208
|
+
if (!secret)
|
|
207
209
|
return;
|
|
208
|
-
}
|
|
209
210
|
const params = new URLSearchParams();
|
|
210
211
|
if (!args.clear) {
|
|
211
212
|
params.set("clear", "false");
|
|
212
213
|
}
|
|
213
214
|
const url = `${args.url}/__ai-ready/restore?${params}`;
|
|
214
215
|
consola.info(`Restoring database at ${args.url}...`);
|
|
215
|
-
const res = await
|
|
216
|
-
consola.error(`Failed: ${err.message}`);
|
|
217
|
-
return null;
|
|
218
|
-
});
|
|
216
|
+
const res = await fetchJson(url, { method: "POST", headers: authHeaders(secret) });
|
|
219
217
|
if (!res)
|
|
220
218
|
return;
|
|
221
219
|
consola.success(`Restored: ${colors.green(res.restored?.toString() || "0")} pages`);
|
|
@@ -265,12 +263,9 @@ const main = defineCommand({
|
|
|
265
263
|
params.set("ttl", args.ttl);
|
|
266
264
|
const url = `${args.url}/__ai-ready/prune?${params}`;
|
|
267
265
|
consola.info(`${args.dry ? "Previewing" : "Pruning"} stale routes at ${args.url}...`);
|
|
268
|
-
const res = await
|
|
266
|
+
const res = await fetchJson(url, {
|
|
269
267
|
method: "POST",
|
|
270
268
|
headers: secret ? authHeaders(secret) : void 0
|
|
271
|
-
}).then((r) => r.json()).catch((err) => {
|
|
272
|
-
consola.error(`Failed: ${err.message}`);
|
|
273
|
-
return null;
|
|
274
269
|
});
|
|
275
270
|
if (!res)
|
|
276
271
|
return;
|
|
@@ -315,20 +310,15 @@ const main = defineCommand({
|
|
|
315
310
|
},
|
|
316
311
|
async run({ args }) {
|
|
317
312
|
const cwd = resolve(args.cwd || ".");
|
|
318
|
-
const secret = await
|
|
319
|
-
if (!secret)
|
|
320
|
-
consola.error("No secret found. Run `nuxi dev` or `nuxi build` first.");
|
|
313
|
+
const secret = await requireSecret(cwd);
|
|
314
|
+
if (!secret)
|
|
321
315
|
return;
|
|
322
|
-
}
|
|
323
316
|
const params = new URLSearchParams({
|
|
324
317
|
limit: args.limit || "100"
|
|
325
318
|
});
|
|
326
319
|
const url = `${args.url}/__ai-ready/indexnow?${params}`;
|
|
327
320
|
consola.info(`Triggering IndexNow sync at ${args.url}...`);
|
|
328
|
-
const res = await
|
|
329
|
-
consola.error(`Failed: ${err.message}`);
|
|
330
|
-
return null;
|
|
331
|
-
});
|
|
321
|
+
const res = await fetchJson(url, { method: "POST", headers: authHeaders(secret) });
|
|
332
322
|
if (!res)
|
|
333
323
|
return;
|
|
334
324
|
if (res.success) {
|
package/dist/module.d.mts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
import { LlmsTxtConfig, ModuleOptions } from '../dist/runtime/types.js';
|
|
2
|
+
import { LlmsTxtConfig, ContentNegotiationPolicy, ModuleOptions } from '../dist/runtime/types.js';
|
|
3
3
|
export { ModuleOptions } from '../dist/runtime/types.js';
|
|
4
|
+
import { ResolvedWebMcpToolsConfig } from '../dist/runtime/site-tool-config.js';
|
|
5
|
+
export { GetPageMarkdownToolOptions, ListPagesToolOptions, McpSiteToolAttachmentOptions, SearchPagesToolOptions, SiteToolOptions, SiteToolsConfig, WebMcpSiteToolAttachmentOptions } from '../dist/runtime/site-tool-config.js';
|
|
4
6
|
|
|
5
7
|
interface ParsedMarkdownResult {
|
|
6
8
|
markdown: string;
|
|
@@ -11,6 +13,11 @@ interface ParsedMarkdownResult {
|
|
|
11
13
|
updatedAt?: string;
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
interface ResolvedWebMcpConfig {
|
|
17
|
+
tools: ResolvedWebMcpToolsConfig;
|
|
18
|
+
exposedTo?: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
interface ModuleHooks {
|
|
15
22
|
/**
|
|
16
23
|
* Hook called when page markdown is generated during prerendering.
|
|
@@ -36,6 +43,7 @@ declare module '@nuxt/schema' {
|
|
|
36
43
|
interface ModulePublicRuntimeConfig {
|
|
37
44
|
debug: boolean;
|
|
38
45
|
debugCron: boolean;
|
|
46
|
+
contentNegotiation: ContentNegotiationPolicy;
|
|
39
47
|
version: string;
|
|
40
48
|
mdreamOptions: ModuleOptions['mdreamOptions'];
|
|
41
49
|
markdownCacheHeaders: Required<NonNullable<ModuleOptions['markdownCacheHeaders']>>;
|
|
@@ -67,7 +75,11 @@ interface ModulePublicRuntimeConfig {
|
|
|
67
75
|
} | null;
|
|
68
76
|
ftsTokenizer?: string;
|
|
69
77
|
}
|
|
78
|
+
/** Runtime config exposed to the browser, only set when WebMCP is enabled. */
|
|
79
|
+
interface ModuleAppRuntimeConfig {
|
|
80
|
+
webmcp: ResolvedWebMcpConfig;
|
|
81
|
+
}
|
|
70
82
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
71
83
|
|
|
72
84
|
export { _default as default };
|
|
73
|
-
export type { ModuleHooks, ModulePublicRuntimeConfig };
|
|
85
|
+
export type { ModuleAppRuntimeConfig, ModuleHooks, ModulePublicRuntimeConfig };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { mkdir, writeFile, appendFile, stat, readdir, access, readFile } from 'node:fs/promises';
|
|
3
3
|
import { join, dirname, relative, resolve } from 'node:path';
|
|
4
|
-
import { useLogger, useNuxt, resolveFiles, addTypeTemplate, addTemplate, defineNuxtModule, createResolver,
|
|
4
|
+
import { useLogger, useNuxt, hasNuxtModule, resolveFiles, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, addServerPlugin, addServerHandler, addPlugin, addImports, extendRouteRules } from '@nuxt/kit';
|
|
5
5
|
import defu from 'defu';
|
|
6
6
|
import { installNuxtSiteConfig, useSiteConfig, withSiteUrl } from 'nuxt-site-config/kit';
|
|
7
7
|
import { setupDevToolsUI } from 'nuxtseo-shared/devtools';
|
|
8
8
|
import { resolveNuxtContentVersion } from 'nuxtseo-shared/kit';
|
|
9
9
|
import { readPackageJSON, resolvePackageJSON } from 'pkg-types';
|
|
10
|
-
import { parseSitemapXml } from '@nuxtjs/sitemap/utils';
|
|
11
10
|
import { colorize } from 'consola/utils';
|
|
11
|
+
import { collectSitemap } from 'sitemapd/parse';
|
|
12
12
|
import { withLeadingSlash, withBase, joinURL } from 'ufo';
|
|
13
13
|
import { normalizePagePath, toMarkdownPath } from '../dist/runtime/markdown-path.js';
|
|
14
14
|
import { toLogicalRoute, toDeployedRoute } from '../dist/runtime/route-path.js';
|
|
@@ -249,11 +249,13 @@ async function crawlSitemapEntries(state, nuxt, nitro, entries) {
|
|
|
249
249
|
}
|
|
250
250
|
async function crawlSitemapContent(state, nuxt, nitro, sitemapContent) {
|
|
251
251
|
logger.debug(`Parsing sitemap XML (${sitemapContent.length} bytes)`);
|
|
252
|
-
const result = await
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
252
|
+
const result = await collectSitemap(sitemapContent);
|
|
253
|
+
if (result._tag !== "document" || result.document._tag !== "urlset") {
|
|
254
|
+
const issues = result.issues.map((issue) => issue.message).join("; ");
|
|
255
|
+
logger.debug(`Skipping sitemap: ${issues || "document is not a URL set"}`);
|
|
256
|
+
return 0;
|
|
257
|
+
}
|
|
258
|
+
const urls = result.document.entries;
|
|
257
259
|
logger.debug(`Found ${urls.length} URLs in sitemap`);
|
|
258
260
|
return crawlSitemapEntries(state, nuxt, nitro, urls);
|
|
259
261
|
}
|
|
@@ -269,10 +271,8 @@ function includesSitemapRoot(sitemapName, routes) {
|
|
|
269
271
|
function detectSitemapPrerender(sitemapName = "sitemap.xml") {
|
|
270
272
|
const nuxt = useNuxt();
|
|
271
273
|
const prerenderedRoutes = nuxt.options.nitro.prerender?.routes || [];
|
|
272
|
-
const hasSitemapModule = nuxt
|
|
273
|
-
|
|
274
|
-
);
|
|
275
|
-
let prerenderSitemap = hasSitemapModule || isNuxtGenerate() || includesSitemapRoot(sitemapName, prerenderedRoutes);
|
|
274
|
+
const hasSitemapModule = hasNuxtModule("@nuxtjs/sitemap", nuxt);
|
|
275
|
+
let prerenderSitemap = isNuxtGenerate() || includesSitemapRoot(sitemapName, prerenderedRoutes);
|
|
276
276
|
if (resolveNitroPreset() === "vercel-edge")
|
|
277
277
|
prerenderSitemap = true;
|
|
278
278
|
const hasPrerender = !!(nuxt.options.nitro.prerender?.routes?.length || nuxt.options.nitro.prerender?.crawlLinks);
|
|
@@ -491,7 +491,48 @@ function setupPrerenderHandler(options, dbPath, siteInfo, llmsTxtConfig, indexNo
|
|
|
491
491
|
});
|
|
492
492
|
}
|
|
493
493
|
|
|
494
|
-
|
|
494
|
+
const declarativeWebMcpTypes = `
|
|
495
|
+
import type { WebMcpToolsContext } from 'nuxt-ai-ready/webmcp'
|
|
496
|
+
|
|
497
|
+
declare module '#app' {
|
|
498
|
+
interface RuntimeNuxtHooks {
|
|
499
|
+
/** Mutate built-in browser tools and their registration options before registration. */
|
|
500
|
+
'ai-ready:webmcp:tools': (context: WebMcpToolsContext) => void | Promise<void>
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
declare module '@vue/runtime-dom' {
|
|
505
|
+
interface HTMLAttributes {
|
|
506
|
+
/** Names the tool this form exposes to agents. */
|
|
507
|
+
toolname?: string
|
|
508
|
+
/** Describes what submitting this form does. */
|
|
509
|
+
tooldescription?: string
|
|
510
|
+
/** Describes this field as a tool parameter. */
|
|
511
|
+
toolparamdescription?: string
|
|
512
|
+
/** Submit the form as soon as an agent fills it in. */
|
|
513
|
+
toolautosubmit?: boolean | ''
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
declare global {
|
|
518
|
+
interface SubmitEvent {
|
|
519
|
+
/** Whether an agent triggered this submit through a tool call. */
|
|
520
|
+
readonly agentInvoked?: boolean
|
|
521
|
+
/** Return a result to the agent. Call preventDefault() first. */
|
|
522
|
+
respondWith?: (result: Promise<unknown>) => void
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
interface WebMcpToolEvent extends Event {
|
|
526
|
+
readonly toolName: string
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
interface WindowEventMap {
|
|
530
|
+
toolactivated: WebMcpToolEvent
|
|
531
|
+
toolcancel: WebMcpToolEvent
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
`;
|
|
535
|
+
function registerTypeTemplates(ctx) {
|
|
495
536
|
addTypeTemplate({
|
|
496
537
|
filename: "types/nuxt-ai-ready-augments.d.ts",
|
|
497
538
|
getContents: () => `// Generated by nuxt-ai-ready
|
|
@@ -506,7 +547,7 @@ declare module 'nitropack/types' {
|
|
|
506
547
|
'ai-ready:page:indexed': (context: PageIndexedContext) => void | Promise<void>
|
|
507
548
|
}
|
|
508
549
|
}
|
|
509
|
-
|
|
550
|
+
${ctx.config.webmcp ? declarativeWebMcpTypes : ""}
|
|
510
551
|
export {}
|
|
511
552
|
`
|
|
512
553
|
});
|
|
@@ -625,6 +666,26 @@ async function detectI18n(opts = {}) {
|
|
|
625
666
|
return toRuntimeI18nConfig(auto);
|
|
626
667
|
}
|
|
627
668
|
|
|
669
|
+
function hasConfiguredNuxtModule(modules, name) {
|
|
670
|
+
return modules.some((entry) => {
|
|
671
|
+
const module = Array.isArray(entry) ? entry[0] : entry;
|
|
672
|
+
if (typeof module === "string")
|
|
673
|
+
return module === name;
|
|
674
|
+
if ((typeof module === "function" || typeof module === "object" && module !== null) && "meta" in module)
|
|
675
|
+
return module.meta?.name === name;
|
|
676
|
+
return false;
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
function resolveMcpToolkitState(input) {
|
|
680
|
+
if (!input.installed)
|
|
681
|
+
return { _tag: "Absent" };
|
|
682
|
+
if (input.options === false || input.options?.enabled === false)
|
|
683
|
+
return { _tag: "Disabled" };
|
|
684
|
+
if (input.static || input.generating)
|
|
685
|
+
return { _tag: "Static" };
|
|
686
|
+
return { _tag: "Enabled", route: input.options?.route || "/mcp" };
|
|
687
|
+
}
|
|
688
|
+
|
|
628
689
|
function escapeRegExp(value) {
|
|
629
690
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
630
691
|
}
|
|
@@ -656,6 +717,110 @@ function ensureStaticHeader(contents, route, name, value) {
|
|
|
656
717
|
return `${contents.slice(0, blockStart)}${prefix} ${name}: ${value}${eol}${contents.slice(blockStart)}`;
|
|
657
718
|
}
|
|
658
719
|
|
|
720
|
+
const DEFAULT_MAX_OUTPUT_CHARS = 1500;
|
|
721
|
+
const DEFAULT_LIST_LIMIT = 20;
|
|
722
|
+
const DEFAULT_SEARCH_LIMIT = 10;
|
|
723
|
+
const MAX_TOOL_LIMIT = 50;
|
|
724
|
+
function parsePositiveInteger(value, fallback, name, warnings, max) {
|
|
725
|
+
if (value === void 0)
|
|
726
|
+
return fallback;
|
|
727
|
+
const integer = Math.trunc(value);
|
|
728
|
+
if (!Number.isFinite(value) || integer < 1) {
|
|
729
|
+
warnings.push(`\`aiReady.${name}\` must be a positive finite integer. Using ${fallback}.`);
|
|
730
|
+
return fallback;
|
|
731
|
+
}
|
|
732
|
+
if (max && integer > max) {
|
|
733
|
+
warnings.push(`\`aiReady.${name}\` cannot exceed ${max}. Using ${max}.`);
|
|
734
|
+
return max;
|
|
735
|
+
}
|
|
736
|
+
if (integer !== value)
|
|
737
|
+
warnings.push(`\`aiReady.${name}\` must be an integer. Using ${integer}.`);
|
|
738
|
+
return integer;
|
|
739
|
+
}
|
|
740
|
+
function resolveWebMcpAttachment(options, path, warnings) {
|
|
741
|
+
if (options?.enabled === false)
|
|
742
|
+
return { enabled: false };
|
|
743
|
+
return {
|
|
744
|
+
enabled: true,
|
|
745
|
+
maxOutputChars: parsePositiveInteger(
|
|
746
|
+
options?.maxOutputChars,
|
|
747
|
+
DEFAULT_MAX_OUTPUT_CHARS,
|
|
748
|
+
`${path}.webmcp.maxOutputChars`,
|
|
749
|
+
warnings
|
|
750
|
+
),
|
|
751
|
+
exposedTo: options?.exposedTo === void 0 ? void 0 : [...options.exposedTo]
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function resolveSiteToolsConfig(input) {
|
|
755
|
+
const warnings = [];
|
|
756
|
+
return {
|
|
757
|
+
config: {
|
|
758
|
+
listPages: {
|
|
759
|
+
defaultLimit: parsePositiveInteger(
|
|
760
|
+
input?.listPages?.defaultLimit,
|
|
761
|
+
DEFAULT_LIST_LIMIT,
|
|
762
|
+
"tools.listPages.defaultLimit",
|
|
763
|
+
warnings,
|
|
764
|
+
MAX_TOOL_LIMIT
|
|
765
|
+
),
|
|
766
|
+
mcp: { enabled: input?.listPages?.mcp?.enabled !== false },
|
|
767
|
+
webmcp: resolveWebMcpAttachment(input?.listPages?.webmcp, "tools.listPages", warnings)
|
|
768
|
+
},
|
|
769
|
+
searchPages: {
|
|
770
|
+
defaultLimit: parsePositiveInteger(
|
|
771
|
+
input?.searchPages?.defaultLimit,
|
|
772
|
+
DEFAULT_SEARCH_LIMIT,
|
|
773
|
+
"tools.searchPages.defaultLimit",
|
|
774
|
+
warnings,
|
|
775
|
+
MAX_TOOL_LIMIT
|
|
776
|
+
),
|
|
777
|
+
mcp: { enabled: input?.searchPages?.mcp?.enabled !== false },
|
|
778
|
+
webmcp: resolveWebMcpAttachment(input?.searchPages?.webmcp, "tools.searchPages", warnings)
|
|
779
|
+
},
|
|
780
|
+
getPageMarkdown: {
|
|
781
|
+
mcp: { enabled: input?.getPageMarkdown?.mcp?.enabled !== false },
|
|
782
|
+
webmcp: resolveWebMcpAttachment(input?.getPageMarkdown?.webmcp, "tools.getPageMarkdown", warnings)
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
warnings
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
function resolveWebMcpConfig(input, toolsConfig) {
|
|
789
|
+
if (!input)
|
|
790
|
+
return { _tag: "Disabled" };
|
|
791
|
+
const options = input === true ? {} : input;
|
|
792
|
+
const tools = {};
|
|
793
|
+
if (options.tools !== false) {
|
|
794
|
+
if (toolsConfig.listPages.webmcp.enabled) {
|
|
795
|
+
tools.listPages = {
|
|
796
|
+
defaultLimit: toolsConfig.listPages.defaultLimit,
|
|
797
|
+
maxOutputChars: toolsConfig.listPages.webmcp.maxOutputChars,
|
|
798
|
+
exposedTo: toolsConfig.listPages.webmcp.exposedTo
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
if (toolsConfig.searchPages.webmcp.enabled) {
|
|
802
|
+
tools.searchPages = {
|
|
803
|
+
defaultLimit: toolsConfig.searchPages.defaultLimit,
|
|
804
|
+
maxOutputChars: toolsConfig.searchPages.webmcp.maxOutputChars,
|
|
805
|
+
exposedTo: toolsConfig.searchPages.webmcp.exposedTo
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
if (toolsConfig.getPageMarkdown.webmcp.enabled) {
|
|
809
|
+
tools.getPageMarkdown = {
|
|
810
|
+
maxOutputChars: toolsConfig.getPageMarkdown.webmcp.maxOutputChars,
|
|
811
|
+
exposedTo: toolsConfig.getPageMarkdown.webmcp.exposedTo
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return {
|
|
816
|
+
_tag: "Enabled",
|
|
817
|
+
config: {
|
|
818
|
+
tools,
|
|
819
|
+
exposedTo: options.exposedTo?.length ? [...options.exposedTo] : void 0
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
|
|
659
824
|
const module$1 = defineNuxtModule({
|
|
660
825
|
meta: {
|
|
661
826
|
name: "nuxt-ai-ready",
|
|
@@ -715,13 +880,17 @@ const module$1 = defineNuxtModule({
|
|
|
715
880
|
if (rawConfig.mdreamOptions?.preset) {
|
|
716
881
|
logger.warn("`mdreamOptions.preset` is deprecated. Use `mdreamOptions: { minimal: true }` instead. See https://github.com/harlan-zw/nuxt-ai-ready/releases/tag/v1.0.0");
|
|
717
882
|
}
|
|
883
|
+
const siteToolsResult = resolveSiteToolsConfig(config.tools);
|
|
884
|
+
for (const warning of siteToolsResult.warnings)
|
|
885
|
+
logger.warn(warning);
|
|
886
|
+
const siteToolsConfig = siteToolsResult.config;
|
|
887
|
+
const hasMcpSiteTools = Object.values(siteToolsConfig).some((tool) => tool.mcp.enabled);
|
|
718
888
|
await installNuxtSiteConfig();
|
|
719
889
|
const i18nConfig = await detectI18n({ autoI18n: config.autoI18n });
|
|
720
890
|
if (i18nConfig) {
|
|
721
891
|
logger.info(`i18n detected: ${i18nConfig.locales.length} locales (default: ${i18nConfig.defaultLocale}, strategy: ${i18nConfig.strategy})`);
|
|
722
892
|
}
|
|
723
893
|
const ftsTokenizer = i18nConfig && hasCjkLocale(i18nConfig) ? "trigram" : "unicode61 remove_diacritics 2";
|
|
724
|
-
nuxt.options.nitro.alias = nuxt.options.nitro.alias || {};
|
|
725
894
|
nuxt.options.alias["#ai-ready"] = resolve("./runtime");
|
|
726
895
|
const preset = String(nuxt.options.nitro.preset || "");
|
|
727
896
|
const isCloudflare = preset.startsWith("cloudflare");
|
|
@@ -755,7 +924,14 @@ const module$1 = defineNuxtModule({
|
|
|
755
924
|
);
|
|
756
925
|
}
|
|
757
926
|
}
|
|
758
|
-
|
|
927
|
+
const mcpToolkitState = resolveMcpToolkitState({
|
|
928
|
+
installed: hasNuxtModule("@nuxtjs/mcp-toolkit") || hasConfiguredNuxtModule(nuxt.options.modules, "@nuxtjs/mcp-toolkit"),
|
|
929
|
+
options: nuxt.options.mcp,
|
|
930
|
+
static: nuxt.options.nitro.static === true,
|
|
931
|
+
generating: nuxt.options._generate === true
|
|
932
|
+
});
|
|
933
|
+
const mcpAvailable = mcpToolkitState._tag === "Enabled";
|
|
934
|
+
if (mcpAvailable && nuxt.options.mcp !== false && !nuxt.options.mcp?.name) {
|
|
759
935
|
nuxt.options.mcp = nuxt.options.mcp || {};
|
|
760
936
|
nuxt.options.mcp.name = useSiteConfig().name;
|
|
761
937
|
}
|
|
@@ -780,7 +956,7 @@ const module$1 = defineNuxtModule({
|
|
|
780
956
|
group.contentUsage = [`train-ai=${config.contentSignal.aiTrain ? "y" : "n"}`];
|
|
781
957
|
groups.push(group);
|
|
782
958
|
}
|
|
783
|
-
registerTypeTemplates();
|
|
959
|
+
registerTypeTemplates({ config });
|
|
784
960
|
const defaultLlmsTxtSections = [];
|
|
785
961
|
const llmsFullRoute = withSiteUrl("llms-full.txt", { withBase: true });
|
|
786
962
|
defaultLlmsTxtSections.push({
|
|
@@ -793,19 +969,19 @@ const module$1 = defineNuxtModule({
|
|
|
793
969
|
}
|
|
794
970
|
]
|
|
795
971
|
});
|
|
796
|
-
|
|
797
|
-
|
|
972
|
+
if (mcpAvailable) {
|
|
973
|
+
addServerPlugin(resolve("./runtime/server/plugins/mcp-data"));
|
|
798
974
|
nuxt.hook("mcp:definitions:paths", (paths) => {
|
|
799
975
|
const mcpRuntimeDir = resolve(`./runtime/server/mcp`);
|
|
800
976
|
const mcpConfig = config.mcp || {};
|
|
801
|
-
if (mcpConfig.tools !== false)
|
|
977
|
+
if (mcpConfig.tools !== false && hasMcpSiteTools)
|
|
802
978
|
(paths.tools ||= []).push(`${mcpRuntimeDir}/tools`);
|
|
803
979
|
if (mcpConfig.resources !== false)
|
|
804
980
|
(paths.resources ||= []).push(`${mcpRuntimeDir}/resources`);
|
|
805
981
|
});
|
|
806
982
|
const mcpLink = {
|
|
807
983
|
title: "MCP",
|
|
808
|
-
href: withSiteUrl(
|
|
984
|
+
href: withSiteUrl(mcpToolkitState.route, { withBase: true }),
|
|
809
985
|
description: "Model Context Protocol server endpoint for AI agent integration."
|
|
810
986
|
};
|
|
811
987
|
if (defaultLlmsTxtSections[0]) {
|
|
@@ -836,12 +1012,15 @@ const module$1 = defineNuxtModule({
|
|
|
836
1012
|
const buildDbPath = join(nuxt.options.buildDir, ".data/ai-ready/build.db");
|
|
837
1013
|
const runtimeSyncConfig = typeof config.runtimeSync === "object" ? config.runtimeSync : {};
|
|
838
1014
|
const runtimeSyncEnabled = !!config.runtimeSync || !!config.cron;
|
|
1015
|
+
const webmcpResult = resolveWebMcpConfig(config.webmcp, siteToolsConfig);
|
|
1016
|
+
const webmcpConfig = webmcpResult._tag === "Enabled" ? webmcpResult.config : null;
|
|
839
1017
|
const indexNow = config.indexNow === true ? createHash("sha256").update(useSiteConfig().url || "nuxt-ai-ready").digest("hex").slice(0, 32) : config.indexNow || process.env.NUXT_AI_READY_INDEX_NOW_KEY;
|
|
840
1018
|
nuxt.hooks.hook("nuxt-seo-pro:modules", (modules) => {
|
|
841
1019
|
const mod = modules.find((m) => m.name === "nuxt-ai-ready");
|
|
842
1020
|
if (mod) {
|
|
843
1021
|
mod.features = {
|
|
844
|
-
mcp:
|
|
1022
|
+
mcp: mcpAvailable,
|
|
1023
|
+
webmcp: !!webmcpConfig,
|
|
845
1024
|
runtimeSync: runtimeSyncEnabled,
|
|
846
1025
|
cron: !!config.cron,
|
|
847
1026
|
indexNow: !!indexNow,
|
|
@@ -868,10 +1047,12 @@ const module$1 = defineNuxtModule({
|
|
|
868
1047
|
nuxt.hooks.hook("nitro:config", (nitroConfig) => {
|
|
869
1048
|
nitroConfig.experimental = nitroConfig.experimental || {};
|
|
870
1049
|
nitroConfig.experimental.asyncContext = true;
|
|
1050
|
+
nitroConfig.externals = nitroConfig.externals || {};
|
|
1051
|
+
nitroConfig.externals.inline = nitroConfig.externals.inline || [];
|
|
1052
|
+
nitroConfig.externals.inline.push("sitemapd");
|
|
871
1053
|
const preset2 = String(nitroConfig.preset || "");
|
|
872
1054
|
const isEdgePreset = ["cloudflare", "vercel-edge", "netlify-edge", "deno"].some((p) => preset2.startsWith(p));
|
|
873
1055
|
if (!isEdgePreset) {
|
|
874
|
-
nitroConfig.externals = nitroConfig.externals || {};
|
|
875
1056
|
nitroConfig.externals.external = nitroConfig.externals.external || [];
|
|
876
1057
|
nitroConfig.externals.external.push("mdream");
|
|
877
1058
|
}
|
|
@@ -913,6 +1094,7 @@ const module$1 = defineNuxtModule({
|
|
|
913
1094
|
}
|
|
914
1095
|
}
|
|
915
1096
|
nitroConfig.virtual = nitroConfig.virtual || {};
|
|
1097
|
+
nitroConfig.virtual["#ai-ready-virtual/site-tools.mjs"] = `export default ${JSON.stringify(siteToolsConfig)}`;
|
|
916
1098
|
const markdownLinkAvailabilityPath = join(dirname(buildDbPath), MARKDOWN_LINK_AVAILABILITY_FILE);
|
|
917
1099
|
nitroConfig.virtual["#ai-ready-virtual/read-page-data.mjs"] = nuxt.options.dev ? `
|
|
918
1100
|
export async function readPageDataFromFilesystem() { return { pages: [], errorRoutes: [] } }
|
|
@@ -983,11 +1165,8 @@ export async function readPageDataFromFilesystem() {
|
|
|
983
1165
|
nitroConfig.virtual["#ai-ready-virtual/page-data.mjs"] = `export const pages = []
|
|
984
1166
|
export const errorRoutes = []`;
|
|
985
1167
|
nitroConfig.virtual["#ai-ready-virtual/logger.mjs"] = `
|
|
986
|
-
import {
|
|
987
|
-
export const logger =
|
|
988
|
-
defaults: { tag: 'nuxt-ai-ready' },
|
|
989
|
-
level: ${config.debug ? 4 : 3},
|
|
990
|
-
})
|
|
1168
|
+
import { createModuleLogger } from 'nuxtseo-shared/utils'
|
|
1169
|
+
export const logger = createModuleLogger('nuxt-ai-ready', ${!!config.debug})
|
|
991
1170
|
`;
|
|
992
1171
|
const providerMap = {
|
|
993
1172
|
sqlite: "#ai-ready/server/db/drizzle/providers/sqlite",
|
|
@@ -1002,7 +1181,7 @@ export const logger = createConsola({
|
|
|
1002
1181
|
nitroConfig.virtual["#ai-ready-virtual/db-schema.mjs"] = `export * from '${schemaPath}'`;
|
|
1003
1182
|
nitroConfig.virtual["#ai-ready-virtual/devtools-meta.mjs"] = `export const devtoolsMeta = ${JSON.stringify({
|
|
1004
1183
|
contentSignal: config.contentSignal || false,
|
|
1005
|
-
mcp: { enabled:
|
|
1184
|
+
mcp: { enabled: mcpAvailable, tools: mcpAvailable && config.mcp?.tools !== false && hasMcpSiteTools, resources: mcpAvailable && config.mcp?.resources !== false },
|
|
1006
1185
|
cron: !!config.cron
|
|
1007
1186
|
})}`;
|
|
1008
1187
|
nitroConfig.virtual["#ai-ready-virtual/content-lookup.mjs"] = hasNuxtContentV3 ? `
|
|
@@ -1039,6 +1218,7 @@ export async function lookupContentPage(event, path) {
|
|
|
1039
1218
|
version: version || "0.0.0",
|
|
1040
1219
|
debug: config.debug || false,
|
|
1041
1220
|
debugCron: config.debugCron || false,
|
|
1221
|
+
contentNegotiation: config.contentNegotiation === void 0 ? "auto" : config.contentNegotiation ? "enabled" : "disabled",
|
|
1042
1222
|
mdreamOptions: config.mdreamOptions || {},
|
|
1043
1223
|
markdownCacheHeaders: defu(config.markdownCacheHeaders, {
|
|
1044
1224
|
maxAge: 3600,
|
|
@@ -1081,6 +1261,19 @@ export async function lookupContentPage(event, path) {
|
|
|
1081
1261
|
});
|
|
1082
1262
|
addServerHandler({ route: "/llms.txt", handler: resolve("./runtime/server/routes/llms.txt.get") });
|
|
1083
1263
|
addServerHandler({ route: "/llms-full.txt", handler: resolve("./runtime/server/routes/llms-full.txt.get") });
|
|
1264
|
+
if (webmcpConfig) {
|
|
1265
|
+
addImports(["useWebMcpSupported", "useWebMcpTool"].map((name) => ({
|
|
1266
|
+
name,
|
|
1267
|
+
from: resolve("./runtime/app/composables/webmcp")
|
|
1268
|
+
})));
|
|
1269
|
+
nuxt.options.runtimeConfig.public["nuxt-ai-ready"] = {
|
|
1270
|
+
webmcp: webmcpConfig
|
|
1271
|
+
};
|
|
1272
|
+
addPlugin({ mode: "client", src: resolve("./runtime/app/plugins/webmcp.client") });
|
|
1273
|
+
if (Object.keys(webmcpConfig.tools).length) {
|
|
1274
|
+
addServerHandler({ route: "/__ai-ready/pages", handler: resolve("./runtime/server/routes/__ai-ready/pages.get") });
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1084
1277
|
addServerHandler({ route: "/__ai-ready__/debug.json", handler: resolve("./runtime/server/routes/__ai-ready/devtools.get") });
|
|
1085
1278
|
if (config.debug) {
|
|
1086
1279
|
addServerHandler({ route: "/__ai-ready-debug", handler: resolve("./runtime/server/routes/__ai-ready-debug.get") });
|
|
@@ -1128,12 +1321,8 @@ export async function lookupContentPage(event, path) {
|
|
|
1128
1321
|
title: "AI Ready",
|
|
1129
1322
|
icon: "carbon:ai-label"
|
|
1130
1323
|
}, resolve, nuxt);
|
|
1131
|
-
nuxt.options.nitro.routeRules = nuxt.options.nitro.routeRules || {};
|
|
1132
1324
|
for (const route of ["/llms.txt", "/llms-full.txt"]) {
|
|
1133
|
-
|
|
1134
|
-
nuxt.options.nitro.routeRules[route],
|
|
1135
|
-
{ headers: { "Content-Type": "text/plain; charset=utf-8" } }
|
|
1136
|
-
);
|
|
1325
|
+
extendRouteRules(route, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
|
1137
1326
|
}
|
|
1138
1327
|
nuxt.hooks.hook("nitro:build:before", (nitro) => {
|
|
1139
1328
|
nitro.hooks.hook("compiled", async () => {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ComputedRef, MaybeRefOrGetter, ShallowRef } from 'vue';
|
|
2
|
+
import type { InferWebMcpInput, WebMcpInputSchema, WebMcpRegisterOptions, WebMcpTool, WebMcpToolRegistrationState } from '../../webmcp.js';
|
|
3
|
+
export interface UseWebMcpToolOptions extends WebMcpRegisterOptions {
|
|
4
|
+
/** Register only while this reactive value is true. */
|
|
5
|
+
enabled?: MaybeRefOrGetter<boolean>;
|
|
6
|
+
}
|
|
7
|
+
export interface UseWebMcpToolReturn {
|
|
8
|
+
/** Whether the mounted client supports `document.modelContext`. */
|
|
9
|
+
supported: ComputedRef<boolean>;
|
|
10
|
+
/** Registration lifecycle, including browser rejections. */
|
|
11
|
+
state: Readonly<ShallowRef<WebMcpToolRegistrationState>>;
|
|
12
|
+
/** Remove the tool permanently for this composable instance. */
|
|
13
|
+
unregister: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Hydration-safe WebMCP support. It stays false through SSR and the first client
|
|
17
|
+
* render, then updates after mount.
|
|
18
|
+
*/
|
|
19
|
+
export declare function useWebMcpSupported(): ComputedRef<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Register a WebMCP tool while its component is mounted, active and enabled.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useWebMcpTool<const Schema extends WebMcpInputSchema, Output>(tool: WebMcpTool<InferWebMcpInput<NoInfer<Schema>>, Output, Schema>, options?: UseWebMcpToolOptions): UseWebMcpToolReturn;
|
|
24
|
+
export declare function useWebMcpTool<Input extends Record<string, unknown>, Output>(tool: WebMcpTool<Input, Output>, options?: UseWebMcpToolOptions): UseWebMcpToolReturn;
|