nuxt-ai-ready 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/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=4.0.0"
5
5
  },
6
6
  "configKey": "aiReady",
7
- "version": "0.3.0",
7
+ "version": "0.3.2",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { dirname, join } from 'node:path';
2
- import { useLogger, useNuxt, defineNuxtModule, createResolver, addTypeTemplate, hasNuxtModule, addServerHandler, addPlugin } from '@nuxt/kit';
2
+ import { useLogger, useNuxt, defineNuxtModule, createResolver, addTypeTemplate, hasNuxtModule, addServerHandler, addPlugin, extendPages } from '@nuxt/kit';
3
3
  import defu from 'defu';
4
4
  import { useSiteConfig, installNuxtSiteConfig, withSiteUrl } from 'nuxt-site-config/kit';
5
5
  import { relative } from 'pathe';
@@ -338,6 +338,23 @@ function setupPrerenderHandler(llmsTxtConfig, timestampsConfig) {
338
338
  });
339
339
  }
340
340
 
341
+ function createPagesPromise(nuxt = useNuxt()) {
342
+ return new Promise((resolve) => {
343
+ nuxt.hooks.hook("modules:done", () => {
344
+ if (typeof nuxt.options.pages === "boolean" && !nuxt.options.pages || typeof nuxt.options.pages === "object" && !nuxt.options.pages.enabled) {
345
+ return resolve([]);
346
+ }
347
+ extendPages(resolve);
348
+ });
349
+ });
350
+ }
351
+ function flattenPages(pages, parent = "") {
352
+ return pages.flatMap((page) => {
353
+ const path = parent + page.path;
354
+ const current = { path, name: page.name, meta: page.meta };
355
+ return page.children?.length ? [current, ...flattenPages(page.children, path)] : [current];
356
+ });
357
+ }
341
358
  const module$1 = defineNuxtModule({
342
359
  meta: {
343
360
  name: "nuxt-ai-ready",
@@ -393,9 +410,17 @@ const module$1 = defineNuxtModule({
393
410
  }
394
411
  nuxt.options.nitro.scanDirs = nuxt.options.nitro.scanDirs || [];
395
412
  nuxt.options.nitro.scanDirs.push(
396
- resolve("./runtime/server/utils"),
397
- resolve("./runtime/server/mcp")
413
+ resolve("./runtime/server/utils")
398
414
  );
415
+ const pagesPromise = createPagesPromise(nuxt);
416
+ nuxt.hooks.hook("nitro:config", (nitroConfig) => {
417
+ nitroConfig.virtual = nitroConfig.virtual || {};
418
+ nitroConfig.virtual["#ai-ready/routes.mjs"] = async () => {
419
+ const pages = await pagesPromise;
420
+ const routes = flattenPages(pages);
421
+ return `export default ${JSON.stringify(routes)}`;
422
+ };
423
+ });
399
424
  if (typeof config.contentSignal === "object") {
400
425
  nuxt.options.robots.groups.push({
401
426
  userAgent: "*",
@@ -451,22 +476,12 @@ export {}
451
476
  const hasMCP = hasNuxtModule("@nuxtjs/mcp-toolkit");
452
477
  if (hasMCP) {
453
478
  nuxt.hook("mcp:definitions:paths", (paths) => {
454
- const mcpRuntimeDir = resolve("./runtime/server/mcp");
455
- paths.tools = paths.tools || [];
456
- paths.resources = paths.resources || [];
457
- paths.prompts = paths.prompts || [];
479
+ const mcpRuntimeDir = resolve(`./runtime/server/mcp/${nuxt.options.dev ? "dev" : "prod"}`);
458
480
  const mcpConfig = config.mcp || {};
459
- const toolsConfig = mcpConfig.tools ?? {};
460
- const resourcesConfig = mcpConfig.resources ?? {};
461
- if (toolsConfig.listPages !== false) {
462
- paths.tools.push(`${mcpRuntimeDir}/tools/list-pages.ts`);
463
- }
464
- if (resourcesConfig.pages !== false) {
465
- paths.resources.push(`${mcpRuntimeDir}/resources/pages.ts`);
466
- }
467
- if (resourcesConfig.pagesChunks !== false) {
468
- paths.resources.push(`${mcpRuntimeDir}/resources/pages-chunks.ts`);
469
- }
481
+ if (mcpConfig.tools !== false)
482
+ (paths.tools ||= []).push(`${mcpRuntimeDir}/tools`);
483
+ if (mcpConfig.resources !== false)
484
+ (paths.resources ||= []).push(`${mcpRuntimeDir}/resources`);
470
485
  });
471
486
  const mcpLink = {
472
487
  title: "MCP",
@@ -500,6 +515,7 @@ export {}
500
515
  nuxt.options.runtimeConfig["nuxt-ai-ready"] = {
501
516
  version: version || "0.0.0",
502
517
  debug: config.debug || false,
518
+ hasSitemap: hasNuxtModule("@nuxtjs/sitemap"),
503
519
  mdreamOptions: config.mdreamOptions || {},
504
520
  markdownCacheHeaders: defu(config.markdownCacheHeaders, {
505
521
  maxAge: 3600,
@@ -1,2 +1,2 @@
1
- declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
1
+ declare const _default: import("#app").Plugin<Record<string, unknown>> & import("#app").ObjectPlugin<Record<string, unknown>>;
2
2
  export default _default;
@@ -0,0 +1,19 @@
1
+ import { getDevPages } from "../utils.js";
2
+ export default {
3
+ uri: "resource://nuxt-ai-ready/pages",
4
+ name: "All Pages",
5
+ description: "Page routes from sitemap/routes. In dev mode, returns JSON (TOON format unavailable until build).",
6
+ metadata: {
7
+ mimeType: "application/json"
8
+ },
9
+ async handler(uri) {
10
+ const pages = await getDevPages();
11
+ return {
12
+ contents: [{
13
+ uri: uri.toString(),
14
+ mimeType: "application/json",
15
+ text: JSON.stringify(pages, null, 2)
16
+ }]
17
+ };
18
+ }
19
+ };
@@ -0,0 +1,10 @@
1
+ import { getDevPages, jsonResult } from "../utils.js";
2
+ export default {
3
+ name: "list_pages",
4
+ description: "Lists all available pages with their routes. In dev mode, returns JSON from sitemap/routes (TOON format unavailable until build).",
5
+ inputSchema: {},
6
+ async handler() {
7
+ const pages = await getDevPages();
8
+ return jsonResult(pages);
9
+ }
10
+ };
@@ -0,0 +1,34 @@
1
+ import routes from "#ai-ready/routes.mjs";
2
+ import { useRuntimeConfig } from "nitropack/runtime";
3
+ export { jsonResult } from "../utils.js";
4
+ function routeToRegex(routePath) {
5
+ const pattern = routePath.replace(/:[^/]+\(\.\*\)\*?/g, ".*").replace(/:[^/]+/g, "[^/]+");
6
+ return new RegExp(`^${pattern}$`);
7
+ }
8
+ function matchRoute(path, routeRecords) {
9
+ for (const r of routeRecords) {
10
+ if (routeToRegex(r.path).test(path))
11
+ return r;
12
+ }
13
+ }
14
+ export async function getDevPages() {
15
+ const config = useRuntimeConfig()["nuxt-ai-ready"];
16
+ if (!config.hasSitemap)
17
+ return routes.map((r) => ({ route: r.path, name: r.name, meta: r.meta }));
18
+ const { parseSitemapXml } = await import("@nuxtjs/sitemap/utils");
19
+ const sitemapRes = await fetch("/sitemap.xml");
20
+ if (!sitemapRes.ok)
21
+ return routes.map((r) => ({ route: r.path, name: r.name, meta: r.meta }));
22
+ const xml = await sitemapRes.text();
23
+ const { urls } = await parseSitemapXml(xml);
24
+ return urls.map((entry) => {
25
+ const pathname = typeof entry === "string" ? new URL(entry).pathname : new URL(entry.loc).pathname;
26
+ const matched = matchRoute(pathname, routes);
27
+ return {
28
+ route: pathname,
29
+ ...typeof entry !== "string" && entry.lastmod && { lastmod: entry.lastmod },
30
+ ...matched?.name && { name: matched.name },
31
+ ...matched?.meta && { meta: matched.meta }
32
+ };
33
+ });
34
+ }
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { toonResult } from "../utils.js";
2
+ import { toonResult } from "../../utils.js";
3
3
  const schema = {
4
4
  mode: z.enum(["chunks", "minimal"]).default("minimal").describe("Return individual content chunks (chunks) or page-level metadata (minimal)")
5
5
  };
@@ -63,24 +63,10 @@ export interface ModuleOptions {
63
63
  * @default All enabled when @nuxtjs/mcp-toolkit is installed
64
64
  */
65
65
  mcp?: {
66
- /**
67
- * Enable/disable specific MCP tools
68
- * @default All tools enabled
69
- */
70
- tools?: {
71
- /** Get page by route - fetches markdown content for specific page */
72
- listPages?: boolean;
73
- };
74
- /**
75
- * Enable/disable specific MCP resources
76
- * @default All resources enabled
77
- */
78
- resources?: {
79
- /** pages://list - all pages without markdown content */
80
- pages?: boolean;
81
- /** pages://chunks - individual content chunks from all pages */
82
- pagesChunks?: boolean;
83
- };
66
+ /** Enable MCP tools (list-pages) @default true */
67
+ tools?: boolean;
68
+ /** Enable MCP resources (pages, pages-chunks) @default true */
69
+ resources?: boolean;
84
70
  };
85
71
  /**
86
72
  * Content timestamp tracking configuration
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-ai-ready",
3
3
  "type": "module",
4
- "version": "0.3.0",
4
+ "version": "0.3.2",
5
5
  "description": "Best practice AI & LLM discoverability for Nuxt sites.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -1,17 +0,0 @@
1
- declare const _default: {
2
- uri: string;
3
- name: string;
4
- description: string;
5
- metadata: {
6
- mimeType: string;
7
- };
8
- cache: "1h";
9
- handler(uri: URL): Promise<{
10
- contents: {
11
- uri: string;
12
- mimeType: string;
13
- text: string;
14
- }[];
15
- }>;
16
- };
17
- export default _default;
@@ -1,17 +0,0 @@
1
- declare const _default: {
2
- uri: string;
3
- name: string;
4
- description: string;
5
- metadata: {
6
- mimeType: string;
7
- };
8
- cache: "1h";
9
- handler(uri: URL): Promise<{
10
- contents: {
11
- uri: string;
12
- mimeType: string;
13
- text: string;
14
- }[];
15
- }>;
16
- };
17
- export default _default;
@@ -1,86 +0,0 @@
1
- import { z } from 'zod';
2
- /**
3
- * Lists all pages by fetching and returning TOON-encoded data
4
- * TOON (Token-Oriented Object Notation) is a compact encoding that minimizes tokens for LLM input
5
- * See https://toonformat.dev
6
- */
7
- declare const _default: {
8
- name: string;
9
- description: string;
10
- inputSchema: {
11
- mode: z.ZodDefault<z.ZodEnum<{
12
- minimal: "minimal";
13
- chunks: "chunks";
14
- }>>;
15
- };
16
- cache: "1h";
17
- handler({ mode }: import("@modelcontextprotocol/sdk/server/zod-compat.js").ShapeOutput<Readonly<{
18
- [k: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
19
- }>>): Promise<{
20
- [x: string]: unknown;
21
- content: ({
22
- type: "text";
23
- text: string;
24
- _meta?: {
25
- [x: string]: unknown;
26
- } | undefined;
27
- } | {
28
- type: "image";
29
- data: string;
30
- mimeType: string;
31
- _meta?: {
32
- [x: string]: unknown;
33
- } | undefined;
34
- } | {
35
- type: "audio";
36
- data: string;
37
- mimeType: string;
38
- _meta?: {
39
- [x: string]: unknown;
40
- } | undefined;
41
- } | {
42
- uri: string;
43
- name: string;
44
- type: "resource_link";
45
- description?: string | undefined;
46
- mimeType?: string | undefined;
47
- _meta?: {
48
- [x: string]: unknown;
49
- } | undefined;
50
- icons?: {
51
- src: string;
52
- mimeType?: string | undefined;
53
- sizes?: string[] | undefined;
54
- }[] | undefined;
55
- title?: string | undefined;
56
- } | {
57
- type: "resource";
58
- resource: {
59
- uri: string;
60
- text: string;
61
- mimeType?: string | undefined;
62
- _meta?: {
63
- [x: string]: unknown;
64
- } | undefined;
65
- } | {
66
- uri: string;
67
- blob: string;
68
- mimeType?: string | undefined;
69
- _meta?: {
70
- [x: string]: unknown;
71
- } | undefined;
72
- };
73
- _meta?: {
74
- [x: string]: unknown;
75
- } | undefined;
76
- })[];
77
- _meta?: {
78
- [x: string]: unknown;
79
- } | undefined;
80
- structuredContent?: {
81
- [x: string]: unknown;
82
- } | undefined;
83
- isError?: boolean | undefined;
84
- }>;
85
- };
86
- export default _default;