|
| 1 | +import { z } from "zod" |
| 2 | +import fs from "node:fs" |
| 3 | +import path from "node:path" |
| 4 | + |
| 5 | +// Next.js started bundling its full documentation inside the npm package |
| 6 | +// (node_modules/next/dist/docs/**/*.md) and generating an AGENTS.md that points |
| 7 | +// there in v16.0.0. At or above this version, agents should read those local, |
| 8 | +// version-accurate docs directly instead of fetching anything over MCP. |
| 9 | +const BUNDLED_DOCS_MIN_MAJOR = 16 |
| 10 | + |
| 11 | +export const inputSchema = { |
| 12 | + topic: z |
| 13 | + .string() |
| 14 | + .optional() |
| 15 | + .describe( |
| 16 | + "Optional: what you're looking for (e.g. 'use cache', 'generateMetadata', 'middleware'). Used only to suggest where to look in the bundled docs." |
| 17 | + ), |
| 18 | + project_path: z |
| 19 | + .string() |
| 20 | + .optional() |
| 21 | + .describe("Path to the Next.js project (defaults to current directory)"), |
| 22 | +} |
| 23 | + |
| 24 | +type NextjsDocsArgs = { |
| 25 | + topic?: string |
| 26 | + project_path?: string |
| 27 | +} |
| 28 | + |
| 29 | +export const metadata = { |
| 30 | + name: "nextjs_docs", |
| 31 | + description: `Find the version-accurate Next.js documentation for THIS project. |
| 32 | +
|
| 33 | +This tool does NOT fetch documentation. Next.js 16+ ships its full docs inside the installed package at \`node_modules/next/dist/docs/\` (markdown), kept in sync with the exact version you have installed. This tool tells you where those docs are and how to read them — so you read the docs that match this project, not a generic or outdated copy. |
| 34 | +
|
| 35 | +Call this before answering Next.js questions or writing Next.js code. Then read the relevant guide from the path it returns. If the project is on an older Next.js, it will tell you how to upgrade.`, |
| 36 | +} |
| 37 | + |
| 38 | +// Extract the major version from an installed version ("16.3.0-canary.49") or a |
| 39 | +// declared range ("^16.0.0", "~15.2"). Returns null when it can't be determined |
| 40 | +// (e.g. "latest", "canary", a git/file specifier). |
| 41 | +function parseMajor(versionish: string | null | undefined): number | null { |
| 42 | + if (!versionish) return null |
| 43 | + const match = versionish.match(/(\d+)\./) |
| 44 | + if (!match) { |
| 45 | + // Bare integer like "16" |
| 46 | + const bare = versionish.match(/^\D*(\d+)\D*$/) |
| 47 | + return bare ? parseInt(bare[1], 10) : null |
| 48 | + } |
| 49 | + return parseInt(match[1], 10) |
| 50 | +} |
| 51 | + |
| 52 | +// Resolve the Next.js version for a project, preferring the actually-installed |
| 53 | +// version (most accurate) over the declared dependency range. |
| 54 | +function resolveNextVersion(projectPath: string): { |
| 55 | + version: string | null |
| 56 | + source: "installed" | "declared" | null |
| 57 | +} { |
| 58 | + try { |
| 59 | + const installedPkg = path.join( |
| 60 | + projectPath, |
| 61 | + "node_modules", |
| 62 | + "next", |
| 63 | + "package.json" |
| 64 | + ) |
| 65 | + if (fs.existsSync(installedPkg)) { |
| 66 | + const { version } = JSON.parse(fs.readFileSync(installedPkg, "utf8")) |
| 67 | + if (typeof version === "string") return { version, source: "installed" } |
| 68 | + } |
| 69 | + } catch { |
| 70 | + // fall through to declared |
| 71 | + } |
| 72 | + |
| 73 | + try { |
| 74 | + const pkgPath = path.join(projectPath, "package.json") |
| 75 | + if (fs.existsSync(pkgPath)) { |
| 76 | + const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")) |
| 77 | + const declared = pkg.dependencies?.next ?? pkg.devDependencies?.next |
| 78 | + if (typeof declared === "string") return { version: declared, source: "declared" } |
| 79 | + } |
| 80 | + } catch { |
| 81 | + // fall through to unknown |
| 82 | + } |
| 83 | + |
| 84 | + return { version: null, source: null } |
| 85 | +} |
| 86 | + |
| 87 | +export async function handler({ topic, project_path }: NextjsDocsArgs): Promise<string> { |
| 88 | + const projectPath = project_path || process.cwd() |
| 89 | + const { version, source } = resolveNextVersion(projectPath) |
| 90 | + const major = parseMajor(version) |
| 91 | + |
| 92 | + // Treat unknown declared versions like "latest"/"canary" as modern. |
| 93 | + const isModern = |
| 94 | + major !== null |
| 95 | + ? major >= BUNDLED_DOCS_MIN_MAJOR |
| 96 | + : /latest|canary|rc|beta/i.test(version ?? "") |
| 97 | + |
| 98 | + if (isModern) { |
| 99 | + const docsDir = path.join(projectPath, "node_modules", "next", "dist", "docs") |
| 100 | + const docsExist = fs.existsSync(docsDir) |
| 101 | + return JSON.stringify({ |
| 102 | + status: "use_bundled_docs", |
| 103 | + nextVersion: version, |
| 104 | + versionSource: source, |
| 105 | + docsPath: "node_modules/next/dist/docs/", |
| 106 | + docsAvailable: docsExist, |
| 107 | + instructions: [ |
| 108 | + "Next.js ships its full documentation with the installed package, matching your exact version.", |
| 109 | + `Read the relevant guide directly from \`${docsDir}\` (markdown files mirroring the nextjs.org/docs structure).`, |
| 110 | + topic |
| 111 | + ? `For "${topic}", search those files, e.g.: grep -ril "${topic.replace(/"/g, "")}" node_modules/next/dist/docs` |
| 112 | + : "Browse the directory or grep it for the API/topic you need.", |
| 113 | + "Do not rely on training-data knowledge of Next.js APIs — this version may differ. Prefer the bundled docs.", |
| 114 | + ...(docsExist |
| 115 | + ? [] |
| 116 | + : [ |
| 117 | + "Note: the docs directory was not found. Make sure dependencies are installed (the docs ship inside the `next` package).", |
| 118 | + ]), |
| 119 | + ], |
| 120 | + }) |
| 121 | + } |
| 122 | + |
| 123 | + // Older Next.js (or no Next.js found): point to the upgrade path. |
| 124 | + return JSON.stringify({ |
| 125 | + status: "upgrade_required", |
| 126 | + nextVersion: version, |
| 127 | + versionSource: source, |
| 128 | + message: |
| 129 | + version |
| 130 | + ? `This project is on Next.js ${version}. Version-accurate documentation is bundled with Next.js ${BUNDLED_DOCS_MIN_MAJOR}+ (at node_modules/next/dist/docs/) and surfaced to agents via AGENTS.md.` |
| 131 | + : `No installed Next.js was detected in ${projectPath}. Next.js ${BUNDLED_DOCS_MIN_MAJOR}+ bundles version-accurate documentation at node_modules/next/dist/docs/.`, |
| 132 | + instructions: [ |
| 133 | + `Upgrade to the latest Next.js by running: npx @next/codemod@latest upgrade latest`, |
| 134 | + "After upgrading, this project will ship version-accurate docs locally and `next dev` will generate/update an AGENTS.md pointing agents to them.", |
| 135 | + "Until then, refer to https://nextjs.org/docs and avoid guessing version-specific APIs.", |
| 136 | + ], |
| 137 | + }) |
| 138 | +} |
0 commit comments