nuxt-ai-ready 1.7.0 → 1.7.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.d.mts +12 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +77 -16
- package/dist/runtime/app/webmcp-config.d.ts +6 -3
- package/dist/runtime/server/middleware/markdown.js +1 -1
- package/dist/runtime/server/middleware/markdown.prerender.js +1 -1
- package/dist/runtime/server/routes/agent-skills-artifact.d.ts +1 -1
- package/dist/runtime/server/routes/agent-skills-artifact.js +2 -2
- package/dist/runtime/server/routes/agent-skills-index.d.ts +1 -1
- package/dist/runtime/server/routes/agent-skills-index.js +1 -1
- package/dist/runtime/server/routes/ai-catalog.d.ts +2 -0
- package/dist/runtime/server/routes/ai-catalog.js +24 -0
- package/dist/runtime/server/routes/api-catalog.d.ts +1 -3
- package/dist/runtime/server/routes/api-catalog.js +2 -2
- package/dist/runtime/server/routes/mcp-server-card.d.ts +1 -2
- package/dist/runtime/server/routes/mcp-server-card.js +8 -4
- package/dist/runtime/server/utils/discovery-response.d.ts +11 -0
- package/dist/runtime/server/utils/discovery-response.js +10 -0
- package/dist/runtime/server/utils/i18n.d.ts +11 -2
- package/dist/runtime/server/utils/link-header.d.ts +8 -2
- package/dist/runtime/server/utils.d.ts +2 -2
- package/package.json +4 -3
package/dist/module.d.mts
CHANGED
|
@@ -3,6 +3,7 @@ import { LlmsTxtConfig, ContentNegotiationPolicy, ModuleOptions } from '../dist/
|
|
|
3
3
|
export { AgentSkillConfig, AgentSkillsConfig, AgentSkillsIndex, AgentSkillsIndexEntry, ApiCatalogConfig, ApiCatalogEntry, ApiCatalogLinkTarget, ApiCatalogLinks, ExternalAgentSkillConfig, LocalAgentSkillConfig, McpServerCardConfig, ModuleOptions } from '../dist/runtime/types.js';
|
|
4
4
|
import { ResolvedWebMcpToolsConfig } from '../dist/runtime/site-tool-config.js';
|
|
5
5
|
export { GetPageMarkdownToolOptions, ListPagesToolOptions, McpSiteToolAttachmentOptions, SearchPagesToolOptions, SiteToolOptions, SiteToolsConfig, WebMcpSiteToolAttachmentOptions } from '../dist/runtime/site-tool-config.js';
|
|
6
|
+
import { AiCatalog } from '../dist/runtime/server/utils/discovery-response.js';
|
|
6
7
|
|
|
7
8
|
interface ParsedMarkdownResult {
|
|
8
9
|
markdown: string;
|
|
@@ -34,6 +35,12 @@ interface ResolvedWebMcpConfig {
|
|
|
34
35
|
exposedTo?: string[];
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
declare function resolveAiCatalog(input: {
|
|
39
|
+
siteUrl: string;
|
|
40
|
+
serverCardName: string;
|
|
41
|
+
serverCardUrl: string;
|
|
42
|
+
}): AiCatalog;
|
|
43
|
+
|
|
37
44
|
interface ModuleHooks {
|
|
38
45
|
/**
|
|
39
46
|
* Hook called when page markdown is generated during prerendering.
|
|
@@ -90,6 +97,11 @@ interface ModulePublicRuntimeConfig {
|
|
|
90
97
|
}>;
|
|
91
98
|
} | null;
|
|
92
99
|
ftsTokenizer?: string;
|
|
100
|
+
aiCatalog?: {
|
|
101
|
+
cacheMaxAge: number;
|
|
102
|
+
document: ReturnType<typeof resolveAiCatalog>;
|
|
103
|
+
etag: string;
|
|
104
|
+
};
|
|
93
105
|
apiCatalog?: ResolvedApiCatalogConfig;
|
|
94
106
|
}
|
|
95
107
|
/** Runtime config exposed to the browser, only set when WebMCP is enabled. */
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { mkdir, writeFile, appendFile, stat, readdir, realpath, readFile, access } from 'node:fs/promises';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
3
4
|
import { join, dirname, relative, resolve, isAbsolute, sep } from 'node:path';
|
|
4
5
|
import { useLogger, useNuxt, hasNuxtModule, resolveFiles, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, addServerPlugin, addServerHandler, extendRouteRules, addPlugin, addImports } from '@nuxt/kit';
|
|
5
6
|
import defu from 'defu';
|
|
@@ -16,9 +17,9 @@ import { initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump
|
|
|
16
17
|
import { comparePageHashes, submitToIndexNowShared } from '../dist/runtime/server/utils/indexnow-shared.js';
|
|
17
18
|
import { buildLlmsFullTxtHeader, formatPageForLlmsFullTxt } from '../dist/runtime/server/utils/llms-full.js';
|
|
18
19
|
import { parseDocument } from 'yaml';
|
|
20
|
+
import { MCP_SERVER_CARD_MEDIA_TYPE, AI_CATALOG_MEDIA_TYPE } from '../dist/runtime/server/utils/discovery-response.js';
|
|
19
21
|
import { isAbsolute as isAbsolute$1, join as join$1 } from 'pathe';
|
|
20
22
|
import { resolveI18nConfig } from 'nuxtseo-shared/i18n';
|
|
21
|
-
import { createRequire } from 'node:module';
|
|
22
23
|
|
|
23
24
|
const logger = useLogger("nuxt-ai-ready");
|
|
24
25
|
|
|
@@ -842,6 +843,23 @@ async function resolveAgentSkillsConfig(config, rootDir) {
|
|
|
842
843
|
};
|
|
843
844
|
}
|
|
844
845
|
|
|
846
|
+
const AI_CATALOG_PATH = "/.well-known/ai-catalog.json";
|
|
847
|
+
function resolveAiCatalog(input) {
|
|
848
|
+
const hostname = new URL(input.siteUrl).hostname;
|
|
849
|
+
const serverName = input.serverCardName.split("/").at(-1);
|
|
850
|
+
return {
|
|
851
|
+
specVersion: "1.0",
|
|
852
|
+
entries: [{
|
|
853
|
+
identifier: `urn:air:${hostname}:mcp:${serverName}`,
|
|
854
|
+
type: MCP_SERVER_CARD_MEDIA_TYPE,
|
|
855
|
+
url: input.serverCardUrl
|
|
856
|
+
}]
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
function createAiCatalogEtag(catalog) {
|
|
860
|
+
return `"${createHash("sha256").update(JSON.stringify(catalog)).digest("hex")}"`;
|
|
861
|
+
}
|
|
862
|
+
|
|
845
863
|
const API_CATALOG_PATH = "/.well-known/api-catalog";
|
|
846
864
|
const API_CATALOG_PROFILE = "https://www.rfc-editor.org/info/rfc9727";
|
|
847
865
|
const API_CATALOG_MEDIA_TYPE = `application/linkset+json; profile="${API_CATALOG_PROFILE}"`;
|
|
@@ -1125,7 +1143,6 @@ function resolveMcpToolkitState(input) {
|
|
|
1125
1143
|
}
|
|
1126
1144
|
|
|
1127
1145
|
const MCP_SERVER_CARD_SCHEMA = "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json";
|
|
1128
|
-
const MCP_SERVER_CARD_MEDIA_TYPE = "application/mcp-server-card+json";
|
|
1129
1146
|
const MCP_SERVER_CARD_NAME_PATTERN = /^[a-z\d.-]+\/[a-z\d._-]+$/i;
|
|
1130
1147
|
const allowedConfigKeys = /* @__PURE__ */ new Set([
|
|
1131
1148
|
"cacheMaxAge",
|
|
@@ -1213,17 +1230,28 @@ function resolveMcpServerCardName(input) {
|
|
|
1213
1230
|
message: "Could not derive a valid MCP Server Card name; configure `aiReady.mcpServerCard.name`."
|
|
1214
1231
|
};
|
|
1215
1232
|
}
|
|
1216
|
-
function
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1233
|
+
function parseSupportedMcpProtocolVersions(source) {
|
|
1234
|
+
const latestMatch = source.match(/LATEST_PROTOCOL_VERSION\s*=\s*['"]([^'"]+)['"]/);
|
|
1235
|
+
const supportedMatch = source.match(/SUPPORTED_PROTOCOL_VERSIONS\s*=\s*\[([\s\S]*?)\]/);
|
|
1236
|
+
if (!latestMatch?.[1] || !supportedMatch?.[1]) {
|
|
1219
1237
|
return {
|
|
1220
1238
|
_tag: "Invalid",
|
|
1221
|
-
message: "Could not read
|
|
1239
|
+
message: "Could not read SUPPORTED_PROTOCOL_VERSIONS from the installed MCP SDK."
|
|
1222
1240
|
};
|
|
1223
1241
|
}
|
|
1224
|
-
|
|
1242
|
+
const versionEntryPattern = /(?:exports\.)?LATEST_PROTOCOL_VERSION|(['"])([^'"]+)\1/g;
|
|
1243
|
+
const entriesSource = supportedMatch[1];
|
|
1244
|
+
const protocolVersions = [...entriesSource.matchAll(versionEntryPattern)].map((match) => match[2] || latestMatch[1]).filter((version) => isNonEmptyString(version));
|
|
1245
|
+
const unsupportedSyntax = entriesSource.replace(versionEntryPattern, "").replace(/[\s,]/g, "");
|
|
1246
|
+
if (!protocolVersions.length || unsupportedSyntax) {
|
|
1247
|
+
return {
|
|
1248
|
+
_tag: "Invalid",
|
|
1249
|
+
message: unsupportedSyntax ? "The installed MCP SDK uses an unsupported protocol versions format." : "The installed MCP SDK does not declare any supported protocol versions."
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
return { _tag: "Resolved", protocolVersions: [...new Set(protocolVersions)] };
|
|
1225
1253
|
}
|
|
1226
|
-
async function
|
|
1254
|
+
async function resolveInstalledMcpProtocolVersions(input) {
|
|
1227
1255
|
const resolutionBases = [.../* @__PURE__ */ new Set([...input.modulesDir, input.rootDir])];
|
|
1228
1256
|
const sdkResolutionAttempts = resolutionBases.map(
|
|
1229
1257
|
(base) => resolvePackageJSON("@nuxtjs/mcp-toolkit", { from: base }).then((toolkitPackagePath) => createRequire(toolkitPackagePath).resolve("@modelcontextprotocol/sdk/types.js"))
|
|
@@ -1233,9 +1261,9 @@ async function resolveInstalledMcpProtocolVersion(input) {
|
|
|
1233
1261
|
if (resolved)
|
|
1234
1262
|
return resolved.value;
|
|
1235
1263
|
throw new AggregateError(results.map((result) => result.status === "rejected" ? result.reason : void 0), "Could not resolve the MCP Toolkit SDK.");
|
|
1236
|
-
}).then((sdkTypesPath) => readFile(sdkTypesPath, "utf8")).then(
|
|
1264
|
+
}).then((sdkTypesPath) => readFile(sdkTypesPath, "utf8")).then(parseSupportedMcpProtocolVersions).catch((error) => ({
|
|
1237
1265
|
_tag: "Invalid",
|
|
1238
|
-
message: `Could not resolve the MCP SDK protocol
|
|
1266
|
+
message: `Could not resolve the MCP SDK protocol versions: ${error instanceof Error ? error.message : String(error)}`
|
|
1239
1267
|
}));
|
|
1240
1268
|
}
|
|
1241
1269
|
function resolveDescription(input) {
|
|
@@ -1262,7 +1290,7 @@ function resolveMcpServerCard(input) {
|
|
|
1262
1290
|
card.remotes = [{
|
|
1263
1291
|
type: "streamable-http",
|
|
1264
1292
|
url: input.endpoint,
|
|
1265
|
-
supportedProtocolVersions:
|
|
1293
|
+
supportedProtocolVersions: input.protocolVersions
|
|
1266
1294
|
}];
|
|
1267
1295
|
}
|
|
1268
1296
|
if (title)
|
|
@@ -1457,6 +1485,8 @@ const module$1 = defineNuxtModule({
|
|
|
1457
1485
|
};
|
|
1458
1486
|
},
|
|
1459
1487
|
async setup(config, nuxt) {
|
|
1488
|
+
const resolveFromModule = createRequire(import.meta.url);
|
|
1489
|
+
const nuxtSeoSharedUtilsPath = resolveFromModule.resolve("nuxtseo-shared/utils");
|
|
1460
1490
|
const { resolve } = createResolver(import.meta.url);
|
|
1461
1491
|
const { version } = await readPackageJSON(resolve("../package.json"));
|
|
1462
1492
|
logger.level = config.debug || nuxt.options.debug ? 4 : 3;
|
|
@@ -1752,14 +1782,14 @@ ${details}`);
|
|
|
1752
1782
|
});
|
|
1753
1783
|
if (finalMcpServerCardNameResult._tag === "Invalid")
|
|
1754
1784
|
throw new Error(`[nuxt-ai-ready] ${finalMcpServerCardNameResult.message}`);
|
|
1755
|
-
const
|
|
1785
|
+
const protocolVersionsResult = await resolveInstalledMcpProtocolVersions({
|
|
1756
1786
|
rootDir: nuxt.options.rootDir,
|
|
1757
1787
|
modulesDir: nuxt.options.modulesDir
|
|
1758
1788
|
});
|
|
1759
|
-
if (
|
|
1760
|
-
throw new Error(`[nuxt-ai-ready] ${
|
|
1789
|
+
if (protocolVersionsResult._tag === "Invalid")
|
|
1790
|
+
throw new Error(`[nuxt-ai-ready] ${protocolVersionsResult.message}`);
|
|
1761
1791
|
const card = resolveMcpServerCard({
|
|
1762
|
-
|
|
1792
|
+
protocolVersions: protocolVersionsResult.protocolVersions,
|
|
1763
1793
|
endpoint: siteConfig.url ? withSiteUrl(finalMcpToolkitState.route, { withBase: true }) : finalMcpToolkitState.route,
|
|
1764
1794
|
name: finalMcpServerCardNameResult.name,
|
|
1765
1795
|
toolkitTitle: configuredMcpTitle,
|
|
@@ -1780,6 +1810,7 @@ ${details}`);
|
|
|
1780
1810
|
const handler = resolve("./runtime/server/routes/mcp-server-card");
|
|
1781
1811
|
addServerHandler({ route: mcpServerCardRoute, method: "get", handler });
|
|
1782
1812
|
addServerHandler({ route: mcpServerCardRoute, method: "head", handler });
|
|
1813
|
+
addServerHandler({ route: mcpServerCardRoute, method: "options", handler });
|
|
1783
1814
|
extendRouteRules(mcpServerCardRoute, {
|
|
1784
1815
|
sitemap: false,
|
|
1785
1816
|
headers: {
|
|
@@ -1792,6 +1823,36 @@ ${details}`);
|
|
|
1792
1823
|
"ETag": etag
|
|
1793
1824
|
}
|
|
1794
1825
|
});
|
|
1826
|
+
if (siteConfig.url) {
|
|
1827
|
+
const document = resolveAiCatalog({
|
|
1828
|
+
siteUrl: siteConfig.url,
|
|
1829
|
+
serverCardName: card.name,
|
|
1830
|
+
serverCardUrl: withSiteUrl(mcpServerCardRoute, { withBase: true })
|
|
1831
|
+
});
|
|
1832
|
+
const aiCatalogEtag = createAiCatalogEtag(document);
|
|
1833
|
+
const aiCatalogCacheMaxAge = mcpServerCardResult.config.cacheMaxAge;
|
|
1834
|
+
runtimeConfig.aiCatalog = {
|
|
1835
|
+
cacheMaxAge: aiCatalogCacheMaxAge,
|
|
1836
|
+
document,
|
|
1837
|
+
etag: aiCatalogEtag
|
|
1838
|
+
};
|
|
1839
|
+
const aiCatalogHandler = resolve("./runtime/server/routes/ai-catalog");
|
|
1840
|
+
addServerHandler({ route: AI_CATALOG_PATH, method: "get", handler: aiCatalogHandler });
|
|
1841
|
+
addServerHandler({ route: AI_CATALOG_PATH, method: "head", handler: aiCatalogHandler });
|
|
1842
|
+
addServerHandler({ route: AI_CATALOG_PATH, method: "options", handler: aiCatalogHandler });
|
|
1843
|
+
extendRouteRules(AI_CATALOG_PATH, {
|
|
1844
|
+
sitemap: false,
|
|
1845
|
+
headers: {
|
|
1846
|
+
"Access-Control-Allow-Headers": "Content-Type, If-None-Match",
|
|
1847
|
+
"Access-Control-Allow-Methods": "GET, HEAD",
|
|
1848
|
+
"Access-Control-Allow-Origin": "*",
|
|
1849
|
+
"Access-Control-Expose-Headers": "ETag",
|
|
1850
|
+
"Cache-Control": `public, max-age=${aiCatalogCacheMaxAge}`,
|
|
1851
|
+
"Content-Type": AI_CATALOG_MEDIA_TYPE,
|
|
1852
|
+
"ETag": aiCatalogEtag
|
|
1853
|
+
}
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1795
1856
|
});
|
|
1796
1857
|
const sitemapConfig = nuxt.options.sitemap;
|
|
1797
1858
|
const sitemapRouteRule = nuxt.options.nitro?.routeRules?.["/sitemap.xml"];
|
|
@@ -1952,7 +2013,7 @@ export async function readPageDataFromFilesystem() {
|
|
|
1952
2013
|
nitroConfig.virtual["#ai-ready-virtual/page-data.mjs"] = `export const pages = []
|
|
1953
2014
|
export const errorRoutes = []`;
|
|
1954
2015
|
nitroConfig.virtual["#ai-ready-virtual/logger.mjs"] = `
|
|
1955
|
-
import { createModuleLogger } from
|
|
2016
|
+
import { createModuleLogger } from ${JSON.stringify(nuxtSeoSharedUtilsPath)}
|
|
1956
2017
|
export const logger = createModuleLogger('nuxt-ai-ready', ${!!config.debug})
|
|
1957
2018
|
`;
|
|
1958
2019
|
const providerMap = {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
interface WebMcpDefaults {
|
|
2
|
+
exposedTo?: string[];
|
|
3
|
+
}
|
|
4
|
+
export declare function setWebMcpDefaults(config: WebMcpDefaults): void;
|
|
5
|
+
export declare function getWebMcpDefaults(): Readonly<WebMcpDefaults>;
|
|
6
|
+
export {};
|
|
@@ -67,7 +67,7 @@ function notFoundMarkdown(canonicalUrl, path, config, resolveUrl) {
|
|
|
67
67
|
${body}`;
|
|
68
68
|
}
|
|
69
69
|
export default defineEventHandler(async (event) => {
|
|
70
|
-
if (event.path.startsWith("/.well-known/
|
|
70
|
+
if (event.path.startsWith("/.well-known/"))
|
|
71
71
|
return;
|
|
72
72
|
if (getHeader(event, INTERNAL_HEADER))
|
|
73
73
|
return;
|
|
@@ -21,7 +21,7 @@ function extractHeadingsFromMarkdown(markdown) {
|
|
|
21
21
|
return headings;
|
|
22
22
|
}
|
|
23
23
|
export default defineEventHandler(async (event) => {
|
|
24
|
-
if (event.path.startsWith("/.well-known/
|
|
24
|
+
if (event.path.startsWith("/.well-known/"))
|
|
25
25
|
return;
|
|
26
26
|
if (!import.meta.prerender) {
|
|
27
27
|
return;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default:
|
|
1
|
+
declare const _default: any;
|
|
2
2
|
export default _default;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { assertMethod, createError, eventHandler, getRequestURL, setHeader } from "h3";
|
|
2
|
-
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
1
|
import { localAgentSkillArtifacts } from "#ai-ready-virtual/agent-skills.mjs";
|
|
2
|
+
import { assertMethod, createError, eventHandler, getRequestURL, setHeader } from "#nuxtseo/h3";
|
|
3
|
+
import { useRuntimeConfig } from "#nuxtseo/nitro";
|
|
4
4
|
import { toLogicalRoute } from "../../route-path.js";
|
|
5
5
|
export default eventHandler((event) => {
|
|
6
6
|
assertMethod(event, ["GET", "HEAD"]);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default:
|
|
1
|
+
declare const _default: any;
|
|
2
2
|
export default _default;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { assertMethod, eventHandler, setHeader } from "h3";
|
|
2
1
|
import { agentSkillsIndex } from "#ai-ready-virtual/agent-skills.mjs";
|
|
2
|
+
import { assertMethod, eventHandler, setHeader } from "#nuxtseo/h3";
|
|
3
3
|
export default eventHandler((event) => {
|
|
4
4
|
assertMethod(event, ["GET", "HEAD"]);
|
|
5
5
|
setHeader(event, "Content-Type", "application/json; charset=utf-8");
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { eventHandler, getHeader, setHeaders, setResponseStatus } from "#nuxtseo/h3";
|
|
2
|
+
import { useRuntimeConfig } from "#nuxtseo/nitro";
|
|
3
|
+
import { AI_CATALOG_MEDIA_TYPE, matchesDiscoveryEtag } from "../utils/discovery-response.js";
|
|
4
|
+
export default eventHandler((event) => {
|
|
5
|
+
const config = useRuntimeConfig(event)["nuxt-ai-ready"];
|
|
6
|
+
setHeaders(event, {
|
|
7
|
+
"Access-Control-Allow-Headers": "Content-Type, If-None-Match",
|
|
8
|
+
"Access-Control-Allow-Methods": "GET, HEAD",
|
|
9
|
+
"Access-Control-Allow-Origin": "*",
|
|
10
|
+
"Access-Control-Expose-Headers": "ETag",
|
|
11
|
+
"Cache-Control": `public, max-age=${config.aiCatalog.cacheMaxAge}`,
|
|
12
|
+
"Content-Type": AI_CATALOG_MEDIA_TYPE,
|
|
13
|
+
"ETag": config.aiCatalog.etag
|
|
14
|
+
});
|
|
15
|
+
if (event.method === "OPTIONS") {
|
|
16
|
+
setResponseStatus(event, 204);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (matchesDiscoveryEtag(getHeader(event, "if-none-match"), config.aiCatalog.etag)) {
|
|
20
|
+
setResponseStatus(event, 304);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return config.aiCatalog.document;
|
|
24
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { assertMethod, defineEventHandler, setHeader } from "h3";
|
|
2
|
-
import { useRuntimeConfig } from "
|
|
1
|
+
import { assertMethod, defineEventHandler, setHeader } from "#nuxtseo/h3";
|
|
2
|
+
import { useRuntimeConfig } from "#nuxtseo/nitro";
|
|
3
3
|
export default defineEventHandler((event) => {
|
|
4
4
|
assertMethod(event, ["GET", "HEAD"]);
|
|
5
5
|
const config = useRuntimeConfig(event)["nuxt-ai-ready"].apiCatalog;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { eventHandler, getHeader, setHeaders, setResponseStatus } from "h3";
|
|
2
|
-
import { useRuntimeConfig } from "
|
|
3
|
-
import {
|
|
1
|
+
import { eventHandler, getHeader, setHeaders, setResponseStatus } from "#nuxtseo/h3";
|
|
2
|
+
import { useRuntimeConfig } from "#nuxtseo/nitro";
|
|
3
|
+
import { matchesDiscoveryEtag, MCP_SERVER_CARD_MEDIA_TYPE } from "../utils/discovery-response.js";
|
|
4
4
|
export default eventHandler((event) => {
|
|
5
5
|
const config = useRuntimeConfig(event)["nuxt-ai-ready"];
|
|
6
6
|
setHeaders(event, {
|
|
@@ -12,7 +12,11 @@ export default eventHandler((event) => {
|
|
|
12
12
|
"Content-Type": MCP_SERVER_CARD_MEDIA_TYPE,
|
|
13
13
|
"ETag": config.mcpServerCard.etag
|
|
14
14
|
});
|
|
15
|
-
if (
|
|
15
|
+
if (event.method === "OPTIONS") {
|
|
16
|
+
setResponseStatus(event, 204);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (matchesDiscoveryEtag(getHeader(event, "if-none-match"), config.mcpServerCard.etag)) {
|
|
16
20
|
setResponseStatus(event, 304);
|
|
17
21
|
return null;
|
|
18
22
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const AI_CATALOG_MEDIA_TYPE = "application/ai-catalog+json";
|
|
2
|
+
export declare const MCP_SERVER_CARD_MEDIA_TYPE = "application/mcp-server-card+json";
|
|
3
|
+
export interface AiCatalog {
|
|
4
|
+
specVersion: '1.0';
|
|
5
|
+
entries: Array<{
|
|
6
|
+
identifier: string;
|
|
7
|
+
type: typeof MCP_SERVER_CARD_MEDIA_TYPE;
|
|
8
|
+
url: string;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
export declare function matchesDiscoveryEtag(requestHeader: string | undefined, etag: string): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const AI_CATALOG_MEDIA_TYPE = "application/ai-catalog+json";
|
|
2
|
+
export const MCP_SERVER_CARD_MEDIA_TYPE = "application/mcp-server-card+json";
|
|
3
|
+
export function matchesDiscoveryEtag(requestHeader, etag) {
|
|
4
|
+
if (!requestHeader)
|
|
5
|
+
return false;
|
|
6
|
+
return requestHeader.split(",").some((candidate) => {
|
|
7
|
+
const normalized = candidate.trim().replace(/^W\//, "");
|
|
8
|
+
return normalized === "*" || normalized === etag;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
/** Runtime-safe subset used for route locale resolution. */
|
|
2
|
+
export interface RuntimeI18nConfig {
|
|
3
|
+
defaultLocale: string;
|
|
4
|
+
strategy: 'no_prefix' | 'prefix_except_default' | 'prefix' | 'prefix_and_default';
|
|
5
|
+
locales: Array<{
|
|
6
|
+
code: string;
|
|
7
|
+
hreflang: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
nativeName?: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
3
12
|
export interface LocaleAlternate {
|
|
4
13
|
code: string;
|
|
5
14
|
hreflang: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RuntimeI18nConfig } from './i18n.js';
|
|
2
2
|
/**
|
|
3
3
|
* Encode a URL path for safe inclusion in an HTTP header value.
|
|
4
4
|
* HTTP header values must be ASCII-only per RFC 9110 §5.5, so paths containing
|
|
@@ -8,8 +8,14 @@ import type { ModulePublicRuntimeConfig } from '../../../module.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function encodePathForHeader(path: string): string;
|
|
10
10
|
type LinkUrlResolver = (path: string) => string;
|
|
11
|
+
interface LinkHeaderConfig {
|
|
12
|
+
apiCatalog?: {
|
|
13
|
+
href: string;
|
|
14
|
+
};
|
|
15
|
+
i18n?: RuntimeI18nConfig | null;
|
|
16
|
+
}
|
|
11
17
|
/**
|
|
12
18
|
* Build a comma-joined Link header value with the standard alternates plus i18n hreflang variants.
|
|
13
19
|
*/
|
|
14
|
-
export declare function buildLinkHeader(path: string, variant: 'html' | 'markdown', config:
|
|
20
|
+
export declare function buildLinkHeader(path: string, variant: 'html' | 'markdown', config: LinkHeaderConfig, resolveUrl?: LinkUrlResolver): string;
|
|
15
21
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ContentNegotiationResult } from '@mdream/js/negotiate';
|
|
2
2
|
import type { H3Event } from '#nuxtseo/h3';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ModuleOptions } from '../types.js';
|
|
4
4
|
export { toMarkdownPath } from '../markdown-path.js';
|
|
5
5
|
export declare function negotiateRepresentation(event: H3Event): ContentNegotiationResult;
|
|
6
6
|
export type MarkdownRequestMode = {
|
|
@@ -29,7 +29,7 @@ interface ConvertHtmlOptions {
|
|
|
29
29
|
/** Extra fields to inject at the root of mdream's emitted YAML frontmatter */
|
|
30
30
|
additionalFrontmatter?: Record<string, string>;
|
|
31
31
|
}
|
|
32
|
-
export declare function convertHtmlToMarkdown(html: string, url: string, mdreamOptions:
|
|
32
|
+
export declare function convertHtmlToMarkdown(html: string, url: string, mdreamOptions: ModuleOptions['mdreamOptions'], opts?: ConvertHtmlOptions): Promise<{
|
|
33
33
|
updatedAt?: string | undefined;
|
|
34
34
|
markdown: string;
|
|
35
35
|
title: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ai-ready",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.2",
|
|
5
5
|
"description": "Best practice AI & LLM discoverability for Nuxt sites.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@libsql/client": "^0.14.0",
|
|
49
49
|
"@neondatabase/serverless": "^1.0.0",
|
|
50
50
|
"@nuxtjs/sitemap": ">=8.3.1",
|
|
51
|
-
"better-sqlite3": "^11.0.0 || ^12.0.0"
|
|
51
|
+
"better-sqlite3": "^11.0.0 || ^12.0.0 || ^13.0.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"@libsql/client": {
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"scripts": {
|
|
121
121
|
"lint": "eslint .",
|
|
122
122
|
"lint:fix": "eslint . --fix",
|
|
123
|
-
"build": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build && pnpm run build:devtools",
|
|
123
|
+
"build": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build && pnpm run build:devtools && pnpm run test:dist",
|
|
124
124
|
"build:devtools": "node -e \"require('fs').cpSync('devtools','dist/devtools',{recursive:true})\"",
|
|
125
125
|
"dev": "nuxt dev playground",
|
|
126
126
|
"dev:minimal": "nuxt dev playground",
|
|
@@ -134,6 +134,7 @@
|
|
|
134
134
|
"test:run": "pnpm run prepare:fixtures && vitest run && pnpm test:nuxt5",
|
|
135
135
|
"test:unit": "vitest run --project=unit",
|
|
136
136
|
"test:e2e": "pnpm run prepare:fixtures && vitest run --project=e2e",
|
|
137
|
+
"test:dist": "node scripts/check-dist-imports.mjs",
|
|
137
138
|
"typecheck": "nuxt typecheck && tsc -p test/types/tsconfig.json",
|
|
138
139
|
"test:attw": "attw --pack",
|
|
139
140
|
"test:nuxt5": "pnpm pack --out test/fixtures/nuxt5/module.tgz && pnpm install --dir test/fixtures/nuxt5 --no-frozen-lockfile --force --update-checksums && pnpm --dir test/fixtures/nuxt5 test"
|