@farming-labs/next 0.2.59 → 0.2.61

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/config.d.mts CHANGED
@@ -1,6 +1,7 @@
1
+ import { DocsConfig } from "@farming-labs/docs";
1
2
  import { NextConfig } from "next";
2
3
 
3
4
  //#region src/config.d.ts
4
- declare function withDocs(nextConfig?: NextConfig): NextConfig;
5
+ declare function withDocs(nextConfig?: NextConfig, docsConfig?: Pick<DocsConfig, "mcp">): NextConfig;
5
6
  //#endregion
6
7
  export { withDocs };
package/dist/config.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
2
- import { dirname, isAbsolute, join, relative } from "node:path";
2
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { DEFAULT_AGENT_SKILLS_INDEX_ROUTE, DEFAULT_AGENT_SKILLS_ROUTE_PREFIX, DEFAULT_API_CATALOG_ROUTE, DOCS_AI_AGENT_USER_AGENT_HEADER_PATTERN, DOCS_BOT_LIKE_USER_AGENT_HEADER_PATTERN, DOCS_TRADITIONAL_BOT_USER_AGENT_HEADER_PATTERN } from "@farming-labs/docs";
4
+ import { DEFAULT_A2A_AGENT_CARD_ROUTE, DEFAULT_AGENT_SKILLS_INDEX_ROUTE, DEFAULT_AGENT_SKILLS_ROUTE_PREFIX, DEFAULT_AGENT_SKILL_ARCHIVE_FORMAT, DEFAULT_API_CATALOG_ROUTE, DEFAULT_LEGACY_SKILLS_INDEX_ROUTE, DEFAULT_LEGACY_SKILLS_ROUTE_PREFIX, DOCS_AI_AGENT_USER_AGENT_HEADER_PATTERN, DOCS_BOT_LIKE_USER_AGENT_HEADER_PATTERN, DOCS_TRADITIONAL_BOT_USER_AGENT_HEADER_PATTERN } from "@farming-labs/docs";
5
5
  import { ensureDocsReviewWorkflow } from "@farming-labs/docs/server";
6
6
  import matter from "gray-matter";
7
7
  import createMDX from "@next/mdx";
@@ -152,6 +152,7 @@ const DEFAULT_AGENT_FEEDBACK_ROUTE = "/api/docs/agent/feedback";
152
152
  const DEFAULT_MCP_ROUTE = "/api/docs/mcp";
153
153
  const DEFAULT_MCP_PUBLIC_ROUTE = "/mcp";
154
154
  const DEFAULT_MCP_WELL_KNOWN_ROUTE = "/.well-known/mcp";
155
+ const DEFAULT_MCP_PROTECTED_RESOURCE_METADATA_ROUTE = "/.well-known/oauth-protected-resource";
155
156
  const DEFAULT_LLMS_TXT_ROUTE = "/llms.txt";
156
157
  const DEFAULT_LLMS_FULL_TXT_ROUTE = "/llms-full.txt";
157
158
  const DEFAULT_LLMS_TXT_WELL_KNOWN_ROUTE = "/.well-known/llms.txt";
@@ -544,6 +545,44 @@ function readDocsConfigPath(root) {
544
545
  }
545
546
  return "docs.config.ts";
546
547
  }
548
+ function readLiteralStringList(block, key) {
549
+ const cursor = findTopLevelPropertyValueIndex(block, key);
550
+ if (cursor === void 0) return null;
551
+ const value = block.slice(cursor);
552
+ const quoted = value.match(/^(["'])(.*?)\1/s);
553
+ if (quoted) return [quoted[2]];
554
+ if (!value.startsWith("[")) return null;
555
+ const end = value.indexOf("]");
556
+ if (end === -1) return null;
557
+ return [...value.slice(1, end).matchAll(/(["'])(.*?)\1/g)].map((match) => match[2]);
558
+ }
559
+ function readAgentSkillTraceGlobs(root, configPath) {
560
+ const absoluteConfigPath = join(root, configPath);
561
+ if (!existsSync(absoluteConfigPath)) return [];
562
+ const agent = extractTopLevelObjectLiteral(readFileSync(absoluteConfigPath, "utf8"), "agent");
563
+ if (!agent) return [];
564
+ const skillsCursor = findTopLevelPropertyValueIndex(agent, "skills");
565
+ if (skillsCursor === void 0) return [];
566
+ const skillsObject = agent[skillsCursor] === "{" ? extractObjectLiteral(agent, "skills") : void 0;
567
+ const configured = skillsObject ? readLiteralStringList(skillsObject, "paths") : readLiteralStringList(agent, "skills");
568
+ if (!configured) throw new Error("withDocs could not statically trace agent.skills. Use a literal path, a literal path array, or { paths: [...] } so configured skills are included in production.");
569
+ return configured.map((configuredPath) => {
570
+ const normalized = configuredPath.replace(/\\/g, "/").replace(/\/$/, "");
571
+ return normalized.endsWith("/SKILL.md") || normalized === "SKILL.md" ? normalized : `${normalized}/**/*`;
572
+ });
573
+ }
574
+ function commonTracingRoot(root, globs) {
575
+ let common = resolve(root);
576
+ for (const glob of globs) {
577
+ const candidate = resolve(root, glob.replace(/\/\*\*\/\*$/, ""));
578
+ while (relative(common, candidate) === ".." || relative(common, candidate).startsWith(`..${sep}`)) {
579
+ const parent = dirname(common);
580
+ if (parent === common) break;
581
+ common = parent;
582
+ }
583
+ }
584
+ return common;
585
+ }
547
586
  /** Read the OG endpoint from docs.config.ts[x] (returns undefined if not set). */
548
587
  function readOgEndpoint(root) {
549
588
  for (const ext of FILE_EXTS) {
@@ -1025,6 +1064,23 @@ function resolveManagedChangelogSourceLayoutPath(root, appDir, entry, changelogC
1025
1064
  function removeManagedFile(filePath) {
1026
1065
  if (isManagedGeneratedFile(filePath)) rmSync(filePath, { force: true });
1027
1066
  }
1067
+ function resolveMcpConfig(mcp) {
1068
+ if (mcp === false) return {
1069
+ enabled: false,
1070
+ route: DEFAULT_MCP_ROUTE,
1071
+ protectedResource: false
1072
+ };
1073
+ if (!mcp || mcp === true) return {
1074
+ enabled: true,
1075
+ route: DEFAULT_MCP_ROUTE,
1076
+ protectedResource: false
1077
+ };
1078
+ return {
1079
+ enabled: mcp.enabled ?? true,
1080
+ route: normalizeRoutePath(mcp.route ?? DEFAULT_MCP_ROUTE),
1081
+ protectedResource: Boolean(mcp.security?.authenticate && mcp.security.protectedResource)
1082
+ };
1083
+ }
1028
1084
  function readMcpConfig(root) {
1029
1085
  for (const ext of FILE_EXTS) {
1030
1086
  const configPath = join(root, `docs.config.${ext}`);
@@ -1033,36 +1089,50 @@ function readMcpConfig(root) {
1033
1089
  const content = readFileSync(configPath, "utf-8");
1034
1090
  if (content.match(/mcp\s*:\s*false/)) return {
1035
1091
  enabled: false,
1036
- route: DEFAULT_MCP_ROUTE
1092
+ route: DEFAULT_MCP_ROUTE,
1093
+ protectedResource: false
1037
1094
  };
1038
1095
  if (content.match(/mcp\s*:\s*true/)) return {
1039
1096
  enabled: true,
1040
- route: DEFAULT_MCP_ROUTE
1097
+ route: DEFAULT_MCP_ROUTE,
1098
+ protectedResource: false
1041
1099
  };
1042
1100
  const block = extractObjectLiteral(content, "mcp");
1043
1101
  if (!block) continue;
1044
1102
  const enabledMatch = block.match(/enabled\s*:\s*(true|false)/);
1045
1103
  const routeMatch = block.match(/route\s*:\s*["']([^"']+)["']/);
1104
+ const securityBlock = extractObjectLiteral(block, "security");
1105
+ const protectedResource = Boolean(securityBlock && /\bauthenticate\b/u.test(securityBlock) && extractObjectLiteral(securityBlock, "protectedResource"));
1046
1106
  return {
1047
1107
  enabled: enabledMatch ? enabledMatch[1] !== "false" : true,
1048
- route: normalizeRoutePath(routeMatch?.[1] ?? DEFAULT_MCP_ROUTE)
1108
+ route: normalizeRoutePath(routeMatch?.[1] ?? DEFAULT_MCP_ROUTE),
1109
+ protectedResource
1049
1110
  };
1050
1111
  } catch {
1051
1112
  return {
1052
1113
  enabled: true,
1053
- route: DEFAULT_MCP_ROUTE
1114
+ route: DEFAULT_MCP_ROUTE,
1115
+ protectedResource: false
1054
1116
  };
1055
1117
  }
1056
1118
  }
1057
1119
  return {
1058
1120
  enabled: true,
1059
- route: DEFAULT_MCP_ROUTE
1121
+ route: DEFAULT_MCP_ROUTE,
1122
+ protectedResource: false
1060
1123
  };
1061
1124
  }
1062
1125
  function normalizeRoutePath(route) {
1063
1126
  const normalized = `/${route}`.replace(/\/+/g, "/");
1064
1127
  return normalized !== "/" ? normalized.replace(/\/+$/, "") : DEFAULT_MCP_ROUTE;
1065
1128
  }
1129
+ function normalizeMcpResourcePath(route) {
1130
+ return route.trim() === "/" ? "/" : normalizeRoutePath(route);
1131
+ }
1132
+ function buildMcpProtectedResourceMetadataRoute(resourcePath) {
1133
+ const normalized = normalizeMcpResourcePath(resourcePath);
1134
+ return normalized === "/" ? DEFAULT_MCP_PROTECTED_RESOURCE_METADATA_ROUTE : `${DEFAULT_MCP_PROTECTED_RESOURCE_METADATA_ROUTE}${normalized}`;
1135
+ }
1066
1136
  function normalizeRoutePrefix(prefix) {
1067
1137
  if (!prefix) return "";
1068
1138
  const normalized = `/${prefix.replace(/^\/+|\/+$/g, "")}`.replace(/\/+/g, "/");
@@ -1422,7 +1492,35 @@ function buildStandardsDiscoveryRewrites() {
1422
1492
  },
1423
1493
  {
1424
1494
  source: `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/:name/SKILL.md`,
1425
- destination: "/api/docs?format=agent-skill&name=:name"
1495
+ destination: "/api/docs?format=agent-skill-file&name=:name&path=SKILL.md"
1496
+ },
1497
+ {
1498
+ source: `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/:name/skill.md`,
1499
+ destination: "/api/docs?format=agent-skill-file&name=:name&path=SKILL.md"
1500
+ },
1501
+ {
1502
+ source: `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/:name.tar.gz`,
1503
+ destination: `/api/docs?format=${DEFAULT_AGENT_SKILL_ARCHIVE_FORMAT}&name=:name`
1504
+ },
1505
+ {
1506
+ source: `${DEFAULT_AGENT_SKILLS_ROUTE_PREFIX}/:name/:path*`,
1507
+ destination: "/api/docs?format=agent-skill-file&name=:name&path=:path*"
1508
+ },
1509
+ {
1510
+ source: DEFAULT_LEGACY_SKILLS_INDEX_ROUTE,
1511
+ destination: "/api/docs?format=legacy-skills"
1512
+ },
1513
+ {
1514
+ source: `${DEFAULT_LEGACY_SKILLS_ROUTE_PREFIX}/:name/skill.md`,
1515
+ destination: "/api/docs?format=agent-skill-file&name=:name&path=SKILL.md"
1516
+ },
1517
+ {
1518
+ source: `${DEFAULT_LEGACY_SKILLS_ROUTE_PREFIX}/:name/:path*`,
1519
+ destination: "/api/docs?format=agent-skill-file&name=:name&path=:path*"
1520
+ },
1521
+ {
1522
+ source: DEFAULT_A2A_AGENT_CARD_ROUTE,
1523
+ destination: "/api/docs?format=agent-card"
1426
1524
  }
1427
1525
  ];
1428
1526
  }
@@ -1527,10 +1625,19 @@ function buildRobotsRewrites(config) {
1527
1625
  }
1528
1626
  function buildMcpRewrites(config) {
1529
1627
  if (!config.enabled) return [];
1530
- return [DEFAULT_MCP_PUBLIC_ROUTE, DEFAULT_MCP_WELL_KNOWN_ROUTE].filter((source) => source !== config.route).map((source) => ({
1628
+ const endpointRewrites = [DEFAULT_MCP_PUBLIC_ROUTE, DEFAULT_MCP_WELL_KNOWN_ROUTE].filter((source) => source !== config.route).map((source) => ({
1531
1629
  source,
1532
1630
  destination: config.route
1533
1631
  }));
1632
+ const metadataRewrites = config.protectedResource ? Array.from(new Set([
1633
+ config.route,
1634
+ DEFAULT_MCP_PUBLIC_ROUTE,
1635
+ DEFAULT_MCP_WELL_KNOWN_ROUTE
1636
+ ])).map(buildMcpProtectedResourceMetadataRoute).map((source) => ({
1637
+ source,
1638
+ destination: config.route
1639
+ })) : [];
1640
+ return [...endpointRewrites, ...metadataRewrites];
1534
1641
  }
1535
1642
  function buildAgentFeedbackRewrites(config) {
1536
1643
  if (!config.enabled) return [];
@@ -1694,11 +1801,16 @@ function mergeDocsMarkdownRewrites(entry, docsPath, mcp, sitemap, agentFeedback,
1694
1801
  function mergeDocsRedirects(autoRedirects, redirects) {
1695
1802
  return dedupeRedirects([...redirects ?? [], ...autoRedirects]);
1696
1803
  }
1697
- function withDocs(nextConfig = {}) {
1804
+ function withDocs(nextConfig = {}, docsConfig) {
1698
1805
  const root = process.cwd();
1699
1806
  const workspaceRoot = findDocsWorkspaceRoot(root);
1700
1807
  const docsConfigPath = readDocsConfigPath(root);
1701
1808
  const docsConfigAbsolutePath = join(root, docsConfigPath);
1809
+ const mcp = docsConfig ? resolveMcpConfig(docsConfig.mcp) : readMcpConfig(root);
1810
+ const nextBasePath = normalizeRoutePrefix(nextConfig.basePath);
1811
+ if (mcp.enabled && mcp.protectedResource && nextBasePath) throw new Error(`@farming-labs/next: mcp.security.protectedResource cannot be combined with Next.js basePath ${JSON.stringify(nextBasePath)} because RFC 9728 metadata must be hosted at origin-root well-known URLs. Host the protected docs app at the origin root or publish the metadata and MCP proxy at the edge.`);
1812
+ const agentSkillTraceGlobs = readAgentSkillTraceGlobs(root, docsConfigPath);
1813
+ if (agentSkillTraceGlobs.some((glob) => glob.startsWith("../"))) nextConfig.outputFileTracingRoot ??= workspaceRoot ?? commonTracingRoot(root, agentSkillTraceGlobs);
1702
1814
  const agentFeedback = readAgentFeedbackConfig(root);
1703
1815
  const docsConfigRelativeAlias = docsConfigPath.startsWith("./") || docsConfigPath.startsWith("../") ? docsConfigPath : `./${docsConfigPath}`;
1704
1816
  ensureDocsReviewWorkflow({
@@ -1725,7 +1837,6 @@ function withDocs(nextConfig = {}) {
1725
1837
  mkdirSync(docsApiRouteDir, { recursive: true });
1726
1838
  writeFileSync(docsApiRoutePath, DOCS_API_ROUTE_TEMPLATE);
1727
1839
  }
1728
- const mcp = readMcpConfig(root);
1729
1840
  const sitemap = readSitemapConfig(root);
1730
1841
  const robots = readRobotsConfig(root);
1731
1842
  const docsMcpRouteDir = join(root, appDir, "api", "docs", "mcp");
@@ -1917,9 +2028,15 @@ function withDocs(nextConfig = {}) {
1917
2028
  skillTraceFile,
1918
2029
  agentsTraceFile,
1919
2030
  agentTraceFile,
1920
- sitemapManifestTraceFile
2031
+ sitemapManifestTraceFile,
2032
+ ...agentSkillTraceGlobs
1921
2033
  ])],
1922
- [DEFAULT_MCP_ROUTE]: [...new Set([...existingTracingIncludes[DEFAULT_MCP_ROUTE] ?? [], docsTraceGlob])]
2034
+ [DEFAULT_MCP_ROUTE]: [...new Set([
2035
+ ...existingTracingIncludes[DEFAULT_MCP_ROUTE] ?? [],
2036
+ docsTraceGlob,
2037
+ skillTraceFile,
2038
+ ...agentSkillTraceGlobs
2039
+ ])]
1923
2040
  };
1924
2041
  return withMDX(nextConfig);
1925
2042
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/next",
3
- "version": "0.2.59",
3
+ "version": "0.2.61",
4
4
  "description": "Next.js adapter for @farming-labs/docs — MDX config wrapper",
5
5
  "keywords": [
6
6
  "docs",
@@ -106,8 +106,8 @@
106
106
  "tsdown": "^0.20.3",
107
107
  "typescript": "^5.9.3",
108
108
  "vitest": "^4.1.8",
109
- "@farming-labs/docs": "0.2.59",
110
- "@farming-labs/theme": "0.2.59"
109
+ "@farming-labs/theme": "0.2.61",
110
+ "@farming-labs/docs": "0.2.61"
111
111
  },
112
112
  "peerDependencies": {
113
113
  "@farming-labs/docs": ">=0.0.1",