@yawlabs/ctxlint lints your AI agent context files, MCP server configs and session data against your actual codebase. It reads the files, cross-references them against the code, and catches the drift before your agent does. Built and maintained by Yaw Labs, MIT licensed, published on npm.

npx -y @yawlabs/ctxlint@latest

GitHub · npm · Add to Yaw MCP

Who it is for

Every AI coding tool ships a context file - CLAUDE.md, .cursorrules, AGENTS.md, .mcp.json. They are the most important interface between you and your agent: they say what to build, how to test, where things live. And they rot fast. You rename a file, change a build script, switch test runners - and the context file still says the old thing. The agent follows those instructions faithfully, then fails, and you lose ten minutes debugging what turns out to be a wrong path on line 12 of a markdown file. Multiply that across a team with several context files, a few MCP configs, and two people who touched the build system last week.

ctxlint is a linter purpose-built for that problem. Asking an agent to run it looks like:

Install

1. Try it as a CLI first. No install needed - point it at a project and read the report. It ships as a single self-contained bundle with zero runtime dependencies, so there is no dependency install to wait through.

# lint the current directory npx -y @yawlabs/ctxlint@latest # context files + MCP configs npx -y @yawlabs/ctxlint@latest --mcp # add the cross-project session audit npx -y @yawlabs/ctxlint@latest --session

2. Add the server to your client. The MCP server is the same package started with the serve subcommand. In Claude Code you can add it in one line:

claude mcp add ctxlint -- npx -y @yawlabs/ctxlint@latest serve

Or write it into a .mcp.json in your project root, which also works for Cursor and Windsurf. On macOS, Linux and WSL:

{ "mcpServers": { "ctxlint": { "command": "npx", "args": ["-y", "@yawlabs/ctxlint@latest", "serve"] } } }

On Windows, wrap the call in cmd /c - npx is a .cmd file there, and recent Node releases refuse to spawn one directly. This is, fittingly, one of the things ctxlint itself checks for in your MCP configs:

{ "mcpServers": { "ctxlint": { "command": "cmd", "args": ["/c", "npx", "-y", "@yawlabs/ctxlint@latest", "serve"] } } }

VS Code and GitHub Copilot use .vscode/mcp.json with servers as the root key rather than mcpServers; Claude Desktop takes the same block as above in its own config file. Either file is safe to commit - it contains no secrets.

3. Restart and approve. Restart Claude Code (or your MCP client) and approve the ctxlint server when prompted. Then ask it something: "Audit this project's context files and tell me what is stale."

Running Yaw MCP? One click adds it to your local config, and it is then available in every Yaw Terminal session.

What it covers

Three families of checks, each enabled by its own flag and each with its own set of rules:

CheckWhat it finds
Broken pathsFile references in context that do not exist in your project, with a suggested replacement when git shows a rename or a close fuzzy match
Wrong commandsBuild and test commands that do not match your package.json scripts or Makefile targets
Stale contextContext files not updated after the code they describe kept changing
Token wasteHow much context window each file consumes per session, plus a separate budget for the always-loaded tier
RedundancyContent the agent can already infer - naming a framework that is sitting in your dependency list
ContradictionsConflicting directives across context files, such as one file saying Jest and another saying Vitest
FrontmatterInvalid or missing YAML frontmatter in Cursor rule files, Copilot instructions and Windsurf rules
CI coverage and secretsRelease and deploy workflows that no context file documents, and CI secrets referenced in a workflow but never mentioned in context
Inline secretsReal secrets pasted into a context file - private-key headers and the token shapes used by the major cloud, git-hosting, model and payment providers. Messages never echo the secret, only a short redacted prefix
Dead hooksHook and permission entries in .claude/settings.json pointing at scripts that no longer exist, because a dead gate silently no-ops
MCP config checksSchema errors and the wrong root key, hardcoded keys and Bearer tokens in git-tracked configs, a missing cmd /c wrapper on Windows, deprecated SSE transport, per-client env-var syntax, malformed URLs, and one server configured differently in two clients
Session checksStale memory entries pointing at paths that are gone, near-duplicate memories across projects, an agent stuck repeating commands or cycling between two, a memory index past the documented session-load cap, and secrets, workflows or shared configs that sibling repos have and this one does not

Output comes as text, JSON, or SARIF for GitHub Code Scanning. A watch mode re-lints whenever a context file, an MCP config or package.json changes. A .ctxlintrc in the project root pins which checks run, the token thresholds, extra context-file globs to scan, and globs to exclude from the corpus entirely - the latter being the only way to silence a cross-file contradiction between two files you never wanted linted in the first place.

As an MCP server it exposes ctxlint_audit, ctxlint_mcp_audit, ctxlint_session_audit, ctxlint_skill_audit, ctxlint_validate_path, ctxlint_token_report and ctxlint_fix.

ctxlint is also the reference implementation of four open, tool-agnostic specifications published in the repo: one for AI context file linting, one for MCP config linting, one for agent session linting, and an experimental one for agent-skill linting. Each ships a machine-readable rule catalog alongside the prose, so another linter, IDE extension or CI system can implement the same rules.

Safety and control

Run it in CI, or on every commit

The MCP server is for the interactive case. For the gate, the same package runs headless - as a workflow step, as the published GitHub Action, or as a git pre-commit hook:

- name: Lint context files run: npx @yawlabs/ctxlint@latest --strict # or the action, with options - name: Lint context files uses: yawlabs/ctxlint-action@v1 with: args: '--strict --mcp'

For GitHub Code Scanning, emit SARIF and upload it with the standard CodeQL upload action instead of failing the step - findings then land in the Security tab with file and line annotations. Locally, npx -y @yawlabs/ctxlint@latest init installs a git pre-commit hook that runs the strict lint before each commit, and there is a hook definition for the pre-commit framework if that is what your team already uses.

Frequently asked questions

What does ctxlint check?

Three surfaces. Context files: file references that do not exist, build and test commands that do not match your package.json scripts or Makefile targets, staleness against recent commits, token usage per agent session, content the agent can already infer, contradictions between two files, invalid frontmatter, and real secrets pasted into a context file. MCP configs: schema errors, hardcoded API keys and Bearer tokens in git-tracked files, the missing cmd /c wrapper for npx on Windows, deprecated SSE transport, wrong env-var syntax for the client, and the same server configured differently across clients. Session data: stale and duplicate memory entries, agent loops, a memory index past the session-load cap, and secrets or workflows present on sibling repos but missing here.

Which AI tools and clients does it understand?

On the context side: CLAUDE.md and .claude/rules for Claude Code, AGENTS.md for the multi-agent standard, .cursorrules and .cursor/rules for Cursor, .github/copilot-instructions.md for GitHub Copilot, .windsurfrules for Windsurf, GEMINI.md, .clinerules, .aiderules, plus files for Aide, Amazon Q Developer, Goose, JetBrains Junie and AI Assistant, Continue, Zed and Replit. On the MCP side it reads .mcp.json, .cursor/mcp.json, .vscode/mcp.json, .amazonq/mcp.json and Continue's per-server files, and with a flag it also scans the global Claude Desktop, Cursor, Windsurf and Amazon Q configs.

Is ctxlint a CLI or an MCP server?

Both, from one package. Running it bare lints the current directory; the serve subcommand starts the MCP server, which exposes ctxlint_audit, ctxlint_mcp_audit, ctxlint_session_audit, ctxlint_skill_audit, ctxlint_validate_path, ctxlint_token_report and ctxlint_fix. All the read-only tools declare annotations so MCP clients can skip confirmation dialogs. The same binary also runs as a CI step, a GitHub Action, or a git pre-commit hook.

Will it fail my build?

Only if you ask it to. In non-strict mode ctxlint always exits 0 - it is a reporting tool by default. The strict flag exits 1 on any warning or error, which is the mode to use in CI. Exit code 2 is reserved for a config error, an invalid CLI option or an internal failure, so a broken invocation is distinguishable from a real lint failure.

Can it fix what it finds?

The fix flag rewrites broken paths automatically, using git rename history and fuzzy matching to choose the replacement. There is a dry-run variant that previews the rewrite without writing, and skipping the interactive confirmation in a TTY takes an explicit yes flag. By default a fix will not write through a symlink; allowing that is its own opt-in flag.

Related MCP servers

Further reading

Published by Yaw Labs.