Skip to main content

Validate and security-check claude_desktop_config.json — CI-friendly CLI

Project description

mcp-config-lint

PyPI version Python versions CI License: MIT GitHub Action

Validate and security-check your claude_desktop_config.json before it bites you. Zero dependencies.

MCP configs wire Claude to real tools — filesystems, GitHub, databases, payment APIs. A single misconfiguration can silently break a server, expose credentials in a process list, or grant Claude write access to your entire home directory. mcp-config-lint catches these issues locally and in CI, before they reach production.


Install

pip install mcp-config-lint

Requires Python 3.9+. No third-party dependencies.


Quickstart

mcp-config-lint claude_desktop_config.json

Sample output:

  mcp-lint  claude_desktop_config.json

  (global)
    [WARN ]  High context tax: ~82,000 tokens (41% of 200k window) consumed by tool
              definitions before any conversation.
              Fix: Remove servers you rarely use, or replace heavy multi-purpose servers
              with focused single-purpose ones.

  filesystem
    [WARN ]  Filesystem path "projects/" appears to be relative.
              Fix: Use an absolute path (e.g. /Users/you/projects). Relative paths break
              when Claude spawns the server from a different working directory.
    [SEC:HIGH]  filesystem gives Claude READ + WRITE access to all listed paths.
              Fix: Review each path. Only include directories you are comfortable with
              Claude modifying.

  github
    [SEC:MED]  GITHUB_PERSONAL_ACCESS_TOKEN - classic PAT with repo scope grants
               read+write to all repos. Consider fine-grained PATs scoped to specific
               repositories.

  my-custom-server
    [SEC:HIGH]  Possible GitHub classic PAT found directly in args array.
               Fix: Move credentials to the env object. Args are visible in process
               lists (ps aux) and shell history.

  3 error(s), 2 warning(s)

Exit code 0 if clean. Exit code 1 if errors are found (or warnings in --strict mode).


Flags

mcp-config-lint [config] [--strict] [--json] [--security-only] [--max-tokens N]
Flag Description
--strict Treat warnings as failures (exit 1)
--json Machine-readable JSON output
--security-only Only run security checks
--max-tokens N Fail if estimated token cost exceeds N
--version Print version and exit

Use in CI

Add this to .github/workflows/mcp-lint.yml in any repo that ships a claude_desktop_config.json:

name: MCP Config Lint

on:
  push:
    branches: [main]
    paths: ['claude_desktop_config.json']
  pull_request:
    paths: ['claude_desktop_config.json']

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: basilalshukaili/mcp-config-lint@v1
        with:
          config-path: claude_desktop_config.json

The action fails the job (exit 1) when errors are found, blocking PR merges until the config is fixed.

Action inputs

Input Description Default
config-path Path to the MCP config file claude_desktop_config.json
strict Treat warnings as failures false
max-tokens Fail if token budget exceeded (unset)
security-only Security checks only false
python-version Python version to use 3.12

Advanced action usage

- uses: basilalshukaili/mcp-config-lint@v1
  with:
    config-path: configs/claude_desktop_config.json
    strict: 'true'
    max-tokens: '50000'
    security-only: 'false'

Full rule reference

Structural

Code Level What it catches
structure:not-object ERROR Config root is not a JSON object
structure:missing-mcp-servers ERROR Top-level mcpServers key is absent
structure:mcp-servers-not-object ERROR mcpServers is an array or scalar
structure:empty WARN mcpServers has no entries
json:parse-error ERROR File contains invalid JSON

Per-server

Code Level What it catches
server:not-object ERROR Server config is not a JSON object
server:missing-command ERROR command field is absent
server:missing-args ERROR args field is absent or empty
server:cmd-mismatch-uvx-for-npm ERROR uvx used with an npm package
server:cmd-mismatch-npx-for-uv ERROR npx used with a Python/uv package
server:missing-env-object ERROR No env object; required vars are absent
server:missing-env-var ERROR Required env var is missing
server:placeholder-env-var WARN Required env var still has a placeholder value
server:placeholder-extra-env WARN Extra env var has an unfilled placeholder
server:placeholder-arg WARN Arg looks like an unfilled path placeholder

Per-server gotchas

Code Level What it catches
gotcha:filesystem-relative-path WARN Filesystem path is relative (breaks when server spawns from different cwd)
gotcha:filesystem-write-access INFO Reminder that filesystem gives read + write access
gotcha:filesystem-docker-socket WARN /var/run/docker.sock exposed — allows container escape
gotcha:github-fine-grained-pat WARN Fine-grained PAT needs explicit per-repo permission grants
gotcha:github-write INFO Reminder that GitHub server can push commits and merge PRs
gotcha:postgres-prefix WARN postgres:// prefix — some drivers require postgresql://
gotcha:postgres-creds-in-args WARN Database credentials visible in args
gotcha:brave-search-quota INFO Brave Search free tier: 2,000 queries/month
gotcha:slack-scopes INFO Reminder to keep Slack bot scopes minimal
gotcha:redis-creds-in-url WARN Redis password visible in REDIS_URL
gotcha:gdrive-relative-path WARN GDRIVE_CREDENTIALS_PATH is a relative path
gotcha:sentry-scopes INFO Sentry token needs org:read and project:read
gotcha:notion-oauth-token WARN Notion OAuth token (ntn_...) expires; integration tokens are more reliable
gotcha:notion-invalid-token-format WARN NOTION_API_KEY doesn't match expected secret_ format
gotcha:browser-no-sandbox WARN --no-sandbox disables Chromium security sandboxing
gotcha:stripe-live-key WARN Stripe live key (sk_live_...) can create real charges

Security

Code Level What it catches
sec:write-access SEC:HIGH Server can modify files, data, or send messages
sec:broad-scope SEC:MED Server uses a broad-scoped token
sec:classic-github-pat SEC:MED Classic GitHub PAT (ghp_...) grants repo-wide read+write
sec:aws-key-in-config SEC:HIGH AWS access key present in config
sec:db-creds-in-env SEC:MED DB connection string with credentials in env var
sec:db-creds-in-args SEC:HIGH DB credentials visible in args (exposed in process lists)
sec:bind-all-interfaces SEC:HIGH Server bound to 0.0.0.0
sec:wildcard-cors SEC:MED Wildcard CORS enabled
sec:network-capable SEC:LOW Server can make outbound requests (prompt-injection risk)
sec:filesystem-root-path SEC:HIGH Filesystem path covers /, ~, or drive root
sec:filesystem-home-path SEC:MED Filesystem path covers an entire home directory
sec:secret-in-args SEC:HIGH Known secret pattern (PAT, API key, token) found directly in args
sec:private-key-in-env SEC:HIGH Raw PEM private key or certificate content in env var

Patterns detected by sec:secret-in-args: GitHub PATs (ghp_, github_pat_, ghs_), OpenAI keys (sk-), Slack tokens (xoxb-, xoxp-), AWS key IDs (AKIA...), Google API keys (AIza...), Google OAuth tokens (ya29.), PyPI tokens (pypi-), GitLab PATs (glpat-), Linear keys (lin_api_), Notion tokens (secret_), DigitalOcean PATs (dp.pt.).

Token cost

Code Level What it catches
token:high-context-tax WARN Total token cost exceeds 50k tokens
token:moderate-context-tax INFO Total token cost between 20k–50k tokens
token:estimate:<tier> INFO Per-server token estimate
token:exceeds-max ERROR Total cost exceeds --max-tokens limit

Token estimates are available for 30+ servers. Unknown servers default to 5k tokens.


JSON output schema

mcp-config-lint --json claude_desktop_config.json
{
  "source": "claude_desktop_config.json",
  "exit_code": 1,
  "findings": [
    {
      "level": "security:high",
      "server": "my-custom-server",
      "code": "sec:secret-in-args",
      "message": "Possible GitHub classic PAT found directly in args array.",
      "fix": "Move credentials to the env object. Args are visible in process lists (ps aux) and shell history."
    }
  ],
  "summary": {
    "errors": 1,
    "warnings": 0,
    "info": 2
  }
}

FAQ

Why does sec:write-access fire on servers I trust?

The write-access check is informational about capability, not a bug. It fires on any server that can modify data (filesystem, GitHub, Slack, databases, etc.) to ensure you've made a conscious decision to include it. It will always fire for these servers; review the included paths/repos/channels to confirm the scope is intentional.

How do I suppress a false positive?

There is no per-server suppression yet. If a check fires on a valid config, please open an issue with a minimal reproducer and we'll either fix the check or add a suppression mechanism.

Can I lint configs other than claude_desktop_config.json?

Yes — pass any JSON file that matches the {"mcpServers": {...}} schema.

What does --max-tokens do?

It fails the run if the estimated total token cost of all configured servers exceeds the given number. Useful for enforcing a budget in CI. The default threshold warnings (20k / 50k) still apply regardless of this flag.

Does this send my config anywhere?

No. The linter runs entirely locally and makes no network requests.

Where do the token estimates come from?

Community benchmarks, MCP server READMEs, and direct measurement. GitHub Copilot's public announcement that cutting their toolset from 40 to 13 tools improved benchmark scores is one calibration point. Estimates are conservative; the actual cost depends on the server version and runtime.


Why this exists

MCP configs wire AI assistants to real tools. A misconfigured server silently fails. An insecure one exposes credentials or grants write access you did not intend.

The checks in this CLI are ported from the Hatchloop MCP Config Auditor (battle-tested in the browser) — now available as a zero-dependency CLI you can drop into CI.

GitHub Copilot cut their MCP toolset from 40 tools down to 13 and benchmark scores went up. Fewer, well-scoped tools consistently outperform a sprawling toolset. Use --max-tokens to stay disciplined.


Contributing

Bug reports and new server checks are welcome. See CONTRIBUTING.md for the full guide, or open an issue at github.com/basilalshukaili/mcp-config-lint/issues.


License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp_config_lint-0.2.0.tar.gz (25.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_config_lint-0.2.0-py3-none-any.whl (20.3 kB view details)

Uploaded Python 3

File details

Details for the file mcp_config_lint-0.2.0.tar.gz.

File metadata

  • Download URL: mcp_config_lint-0.2.0.tar.gz
  • Upload date:
  • Size: 25.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for mcp_config_lint-0.2.0.tar.gz
Algorithm Hash digest
SHA256 6e82b189827878e16c42a339fdf0ca9b552574f31e13187fcd8f7e21c80750e7
MD5 3c68753d4af90c6713011cb95f861a9e
BLAKE2b-256 551808c1f5cf8ee76a602dda460ba5fd5601f58c6b9b3267e0f47cd6114041d4

See more details on using hashes here.

File details

Details for the file mcp_config_lint-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for mcp_config_lint-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f6290b722b03c261a1232ca5f682df502f30f6dd0442a6ce6eefabd88d1da421
MD5 a6cbd3032c16897b1ca6ef8e4db8e3b0
BLAKE2b-256 4a4b206fcb326e80a62fc6f5db54bc0d34a585cc93a61acfe6d732541c400179

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page