Skip to main content
Glama
shanewin

codebase-lens

by shanewin

nextjs-lens

An MCP server that gives Claude Code (or any MCP client) deep insight into Next.js projects.

Claude can read a page.tsx file on its own. What it can't easily do is hold the whole app in its head: which layout wraps which page, where 'use client' pulls a subtree into the browser bundle, which route handlers skip auth, or which exports nothing imports. nextjs-lens parses your project with the TypeScript compiler API and answers those questions directly.

Why

We tested the same security audit question on a 90-file Next.js app across different models and configurations:

Config

Correct findings

Hallucinations

Cost

Haiku alone

~5 of 27

7 false positives

$0.19

Haiku + nextjs-lens

~23 of 27

0

$0.10

Opus alone

~20 of 27

2 false positives

$0.47

Opus + nextjs-lens

~24 of 27

0

$0.65

Haiku with nextjs-lens outperformed Opus without it — at one-fifth the cost, in a quarter of the time, with zero hallucinations. Without tools, Haiku invented security issues that don't exist (fake CSRF problems, nonexistent password handling). With tools, it reported only what the code actually shows.

Related MCP server: vibealive MCP Server

How it works

Your Next.js project
    ↓ PROJECT_PATH
nextjs-lens (MCP server over stdio)
    ├── Next.js tools (AST-based; PROJECT_PATH must be a Next.js app or a monorepo containing one)
    ├── Generic scanners (files, search, imports, styles)
    └── Knowledge resources (official docs + community gotchas)

Quick Start

1. Add to your project

Requires Node.js 20.11 or later. Create .mcp.json in your project root:

{
  "mcpServers": {
    "nextjs-lens": {
      "command": "npx",
      "args": ["-y", "nextjs-lens"],
      "env": {
        "PROJECT_PATH": "/absolute/path/to/your/project"
      }
    }
  }
}

To run from source instead (for development, or to refresh the bundled Next.js docs):

git clone https://github.com/shanewin/nextjs-lens.git
cd nextjs-lens
npm install
npm run fetch-docs   # pull the latest Next.js docs (optional)
npm run build

Then use "command": "node" and "args": ["/absolute/path/to/nextjs-lens/dist/server.js"] in .mcp.json.

In a monorepo, point PROJECT_PATH at the repo root: nextjs-lens analyzes the Next.js app with the most routes. To choose a different app, set "NEXTJS_LENS_APP": "apps/admin" (a path relative to PROJECT_PATH) in env. If no Next.js app is found, the server exits with an error explaining why.

To tune findings for your project (exempt public routes, raise severities, ignore legacy files), add a .nextjs-lens.json file.

This project was previously called codebase-lens. Existing .codebase-lens.json files and the CODEBASE_LENS_APP variable still work.

2. Use it

Open Claude Code in your project. The tools are available automatically. Try:

  • "Show me the route tree with which layouts and error boundaries apply to each page"

  • "Where does 'use client' pull server code into the client bundle?"

  • "Which API route handlers don't check auth?"

  • "Find exports nothing imports"

  • "Audit my next.config and middleware for security issues"

Tools Reference

Next.js (loaded when next.config.* exists or next is in package.json)

Every tool parses source with the TypeScript compiler API (ts.createSourceFile), not regex. That means it handles multi-line exports, export const GET = withAuth(...), export { handler as POST }, re-export barrels, and tsconfig path aliases.

Results are compact by default so they fit comfortably in Claude's context on large apps: counts, every finding, and one-line lists. Pass detail: "full" to any Next.js tool for complete per-item data (layout chains, file lists, auth evidence, fetch options).

Whole-app analysis

Tool

What it does

get_route_tree

App Router segment tree with inheritance resolved: the layout chain, templates, and the loading / error / not-found boundary that actually applies to each page, plus merged route segment config. Flags page+route conflicts, error boundaries without 'use client', parallel slots without default, missing root layouts, and route groups that collide on the same URL.

map_client_boundaries

Walks the real import graph from every page and layout to find where 'use client' starts the client tree. Reports which files ship to the browser, which stay on the server, and which run in both. Flags server-only code (server-only, Node builtins, DB/secret SDKs) in the client bundle, listing every client import chain that reaches them, private process.env reads in client code, and hooks used in Server Components. Pass file to see why one file runs where it does.

audit_route_auth

Per-method auth coverage for every route handler and Pages API route: auth calls, auth wrappers, credential header checks, shared-secret comparisons, and webhook signature checks. Follows auth helpers in the same file or imported from other modules, and handlers defined in other modules (re-exports, imported functions passed to wrappers). Handlers built with tRPC, GraphQL, or Auth.js are marked delegated rather than unprotected, and routes that are usually public by design (health checks, CSRF tokens, sign-in flows, OG images) are reported as info. Evaluates the middleware/proxy matcher against real routes to separate endpoints protected in the handler, protected only by middleware, and unprotected.

find_unused_exports

Dead exports and unimported files. Follows barrel re-exports and dynamic imports, and ignores the exports Next.js consumes by convention (default exports, metadata, generateStaticParams, HTTP handlers, segment config, …).

Focused audits

Tool

What it does

list_routes

Flat list of App Router + Pages Router routes with HTTP methods

find_server_actions

Every server action (module-level and inline 'use server') with auth checks, input validation, and importers. Unauthenticated actions are graded: destroying data is critical, exporting data is high, cache-only revalidation is low

analyze_middleware

Parsed matcher config, auth logic, and exactly which routes middleware/proxy runs on and which it skips. On Next.js 16, migration advice that accounts for the Edge runtime (proxy only runs on Node.js)

analyze_data_fetching

Per-route segment config, fetch cache options, 'use cache', cacheLife/cacheTag, dynamic APIs (followed into imported data helpers, with the file each came from), and the inferred rendering mode. On Next.js 16, flags the deprecated single-argument revalidateTag

audit_next_config

Statically evaluates next.config (unwrapping plugin wrappers) and flags secrets in env, wildcard image hosts, ignored build errors, source maps, and missing security headers

audit_env_files

Secret-looking NEXT_PUBLIC_ vars, env files not covered by .gitignore (high when they contain secrets), .env.example templates and monorepo-root env files, and public vars used in code but defined nowhere

Generic (always available)

Tool

What it does

list_project_files

List files matching extensions with sizes

read_file

Read any file (100KB limit)

search_content

Regex search across the codebase

trace_imports

Build a dependency graph from any file

search_styles

Find hardcoded colors/spacing escaping the design system

Project Rules

Add .nextjs-lens.json to PROJECT_PATH (or to the analyzed app's directory) to adapt findings to your project:

{
  "authFunctions": ["makeSureLoggedIn", "requireOrgMember"],
  "exempt": ["/api/public/*", "/api/search"],
  "severity": {
    "src/app/api/cron/*": "critical",
    "src/app/api/billing/webhook": "critical"
  },
  "ignore": ["src/lib/legacy/*", "src/components/Unused.tsx"]
}

Key

Effect

exempt

Drops findings whose file or route matches. A finding that lists many routes (such as "route handlers not matched by middleware") loses only the exempt routes.

severity

Reports matching findings at critical, high, medium, low, or info. The original level is kept in original_severity.

ignore

Removes matching files from find_unused_exports results.

authFunctions

Names of your own auth check functions. audit_route_auth, find_server_actions, and analyze_middleware treat calls to them as auth checks, alongside the built-in list (auth(), getServerSession, currentUser, …) and any helper that calls one of those.

Patterns match file paths (relative to the app directory) or URL routes:

  • src/app/api/cron/* or /api/public/*: everything under that prefix

  • * matches within one path segment, ** across segments

  • A plain path matches itself and anything inside it, so src/app/api/billing/webhook covers its route.ts

Rules match a finding's file and route fields, never its message text. Results that rules changed include a rules_applied count. Problems in the file (invalid JSON, unknown keys, unsupported severities) are logged to stderr and shown in the lens://status resource.

Policy Checks

Write down which code may import what, and check it in CI. Add nextjs-lens.policy.json to the project root (or the app directory):

{
  "version": 1,
  "mode": "warn",
  "rules": {
    "client-bundle": [
      { "name": "database stays on the server", "module": ["@prisma/client", "@acme/db"], "message": "Load data in a server component and pass it down" },
      { "import": "src/server/**" }
    ],
    "forbidden-imports": [
      { "module": "stripe", "allowedIn": ["src/server/billing/**"] },
      { "module": "next/router", "from": "src/app/**", "message": "Use next/navigation in the App Router" }
    ]
  }
}
npx nextjs-lens check /path/to/project          # readable report
npx nextjs-lens check /path/to/project --json   # machine-readable, includes exit_code

From a source checkout, npm run check -- /path/to/project does the same (add --silent before check when piping --json).

Rule

Checks

client-bundle

Nothing it names reaches the browser through any chain of imports from a 'use client' module. Follows the same import walk as map_client_boundaries: type-only imports, 'use server' action references, and unused barrel re-exports don't count. Each violation points at the import to fix and lists every chain to it.

forbidden-imports

Only allowed files import something: importers matching from are violations, or importers outside allowedIn are (an empty allowedIn means nowhere). Covers imports, re-exports, and dynamic import().

Each rule entry needs a target, either module (package names) or import (file globs, matched after resolving path aliases). The other fields are optional:

  • module names match exactly; "pkg/*" matches the package's subpaths, so list both to cover either.

  • except lists importer globs the rule never applies to, such as data-loading files next to UI code.

  • severity is error (default) or warn, and message is shown with each violation.

  • Files inside an import glob may import each other.

  • forbidden-imports only: includeTypeOnly also checks type-only imports, and includeTests also checks test, story, and mock files.

Globs are relative to the app directory and use the same patterns as project rules.

mode decides the exit code, and it can only be set in the policy file, so protect that file with CODEOWNERS:

Exit code

When

0

No error-severity violations outside the baseline, or mode is warn or off

1

mode is enforce and there is at least one error-severity violation that isn't in the baseline

2

The check could not run: no policy file, an invalid policy or baseline (every problem is listed), or no Next.js app

The policy file fails closed: an unknown key, a misspelled rule name, or a bad value makes the whole policy invalid instead of quietly skipping that rule.

Baselines

An existing app usually breaks a new policy in a few places already. A baseline records those, so you can switch to enforce right away and fail only on new violations:

npx nextjs-lens check /path/to/project --update-baseline   # record every current violation in nextjs-lens.baseline.json
npx nextjs-lens check /path/to/project --prune-baseline    # remove violations that have been fixed; never adds new ones

Commit nextjs-lens.baseline.json next to the policy file and protect it with CODEOWNERS too, since adding an entry allows a violation. Violations are matched by rule, file, and what they import, not by line number, so unrelated edits don't make known violations look new. A second offending import of the same thing in the same file is still new.

Each report lists new violations in full, known ones in a short list, and baseline entries that no longer occur, so the baseline can shrink as code is fixed. An invalid baseline file stops the check (exit code 2) instead of being ignored; --update-baseline regenerates it. Renaming a rule makes its baselined violations new, since the rule name is part of the match.

Rollout: start in warn mode to see what the policy reports, fix or except what's wrong, record the rest with --update-baseline, then switch to enforce.

Inline exceptions

To allow one specific violation, say why in a comment on the line above the import, or at the end of the import's first line:

// lens-allow client-bundle: only imports an error message constant, no server code
import { INVALID_TOKEN_ERROR } from '@acme/lib/server/turnstile'

import { db } from '@/server/db' // lens-allow "No server code in routes": removed in #1234

Name the rule by type (forbidden-imports, client-bundle) or by its name in quotes. Other // comment lines may sit between the exception and the import.

  • The reason is required. A comment without one doesn't apply, and the report says so.

  • Every applied exception is listed in the report with its reason, so reviewers see them.

  • Comments that no longer match a violation are listed as unused, so they don't pile up.

  • Allowed violations are never written to the baseline. Removing the comment makes the violation fail again.

Use exceptions for decisions about a single import, and except in the policy for whole groups of files.

Pull request annotations (SARIF)

--sarif <file> also writes the violations that count (not baselined, not allowed by an exception) in SARIF, which GitHub code scanning shows on pull requests at the offending line. Run the check against the repository root so the paths line up.

# .github/workflows/nextjs-lens.yml
name: nextjs-lens
on: [pull_request]
permissions:
  contents: read
  security-events: write
jobs:
  policy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with:
          node-version: 22
      - run: npx -y nextjs-lens@0.4 check . --sarif nextjs-lens.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always() && hashFiles('nextjs-lens.sarif') != ''
        with:
          sarif_file: nextjs-lens.sarif

The check step fails the job in enforce mode; the upload runs either way. Code scanning is free for public repositories; private repositories need GitHub Advanced Security. Without it, the check step's log still shows every violation.

Knowledge Resources

Markdown knowledge files are exposed as MCP resources that Claude can read:

  • knowledge/nextjs/docs/: selected official Next.js docs pages (routing, Server and Client Components, route handlers, proxy, data security, caching, environment variables, the version 16 upgrade guide), one resource per page, plus docs/index.md listing them. npm run fetch-docs refreshes them from nextjs.org's Markdown versions; don't edit them by hand.

  • knowledge/nextjs/community.md: security checklist, Next.js 16 changes, gotchas, and patterns the official docs don't cover well. This is where contributors add the most value. PRs welcome.

Architecture

src/
├── cli.ts                 # The nextjs-lens command: starts the MCP server, or runs `check`
├── server.ts              # MCP entry point, app resolution, rules, tool registration
├── core/
│   ├── types.ts           # ToolRegistration, ToolCollector interfaces
│   ├── helpers.ts         # safePath, walkFiles, file utilities
│   ├── rules.ts           # .nextjs-lens.json loading and matching
│   ├── policy.ts          # nextjs-lens.policy.json loading and validation
│   ├── check.ts           # Runs policy rules and builds the check report
│   ├── checkCli.ts        # `nextjs-lens check` arguments and output
│   ├── baseline.ts        # nextjs-lens.baseline.json: known violations
│   ├── exceptions.ts      # // lens-allow inline exceptions
│   ├── sarif.ts           # SARIF output for GitHub code scanning
│   ├── runner.ts          # Runs every Next.js tool with timings (snapshot script)
│   └── workspace.ts       # Finds the Next.js app (PROJECT_PATH, NEXTJS_LENS_APP, or monorepo workspaces)
├── scanners/              # Generic tools
│   ├── files.ts           # File listing, reading, searching
│   ├── imports.ts         # Import/dependency tracing
│   └── styles.ts          # Design system compliance checking
└── stacks/
    ├── nextjs.ts          # Registers the Next.js tools; config, middleware, and env audits
    └── nextjs/
        ├── ast.ts         # Parsing, exports/imports, module resolution (tsconfig paths and extends, workspaces)
        ├── graph.ts       # Shared, cached import graph used by every tool
        ├── routes.ts      # Route tree and route list
        ├── boundaries.ts  # Server/client boundary map
        ├── auth.ts        # Route handler auth and server actions
        ├── unused.ts      # Unused exports
        ├── fetching.ts    # Data fetching and caching
        ├── forbidden.ts   # forbidden-imports policy rule
        ├── clientBundle.ts # client-bundle policy rule
        └── snapshot.ts    # Snapshot normalization and diffing
test/                      # node --test suites and fixture apps
knowledge/nextjs/          # Docs + community knowledge (MCP resources)
scripts/                   # check.mjs (policy check), snapshot.mjs (real-app regression check), fetch-docs.ts

Development

npm test   # compiles, then runs node --test against the fixture apps in test/fixtures

test/fixtures/app (a single Next.js app) and test/fixtures/mono (a workspace monorepo) contain planted issues. The tests assert what each tool must find there and what it must not flag. CI runs the suite on Node 20 and 22.

Fixtures only cover the cases someone thought of, so also check a large real app before a release:

npm run snapshot -- /path/to/a/real/nextjs/app            # first run saves a snapshot; later runs print what changed
npm run snapshot -- /path/to/a/real/nextjs/app --update   # accept the current results

The report lists endpoint auth status flips, added and removed findings, changed counts, and tools that got much slower. It exits with 1 when anything changed. Snapshots are saved in .lens-snapshots/ (gitignored), since they depend on your local checkout.

License

MIT

Related MCP Connectors

Related MCP Servers

  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    Automatically generates typed React hooks for Next.js projects by crawling API routes, GraphQL queries, and components. Analyzes pages to suggest optimal render modes (SSR/CSR/ISR) and produces documentation with performance guidance.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides code analysis for Next.js projects through the Model Context Protocol, enabling IDE and LLM integration to identify unused files, dead code, and redundant API endpoints.
    9 npm
    8
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP-compatible clients to inspect Next.js codebases, analyze App Router and Pages Router structure, discover API routes, and audit build performance through controlled tools.
    7 npm
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    MCP server that scans Next.js projects and returns a compact summary of routes, API endpoints, schema, and security issues.
    1
    28 npm
    1
    MIT