mcp-config-lint
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-tokensto 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.
Metadata
Release files for mcp-config-lint 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| mcp_config_lint-0.2.0.tar.gz | 25.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcp_config_lint-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 45.4 kB
Release files / mcp_config_lint-0.2.0.tar.gz
| Download URL | mcp_config_lint-0.2.0.tar.gz |
|---|---|
| Size | 25.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6e82b189827878e16c42a339fdf0ca9b552574f31e13187fcd8f7e21c80750e7
|
|
BLAKE2b-256 checksum How to use checksums |
551808c1f5cf8ee76a602dda460ba5fd5601f58c6b9b3267e0f47cd6114041d4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / mcp_config_lint-0.2.0-py3-none-any.whl
| Download URL | mcp_config_lint-0.2.0-py3-none-any.whl |
|---|---|
| Size | 20.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f6290b722b03c261a1232ca5f682df502f30f6dd0442a6ce6eefabd88d1da421
|
|
BLAKE2b-256 checksum How to use checksums |
4a4b206fcb326e80a62fc6f5db54bc0d34a585cc93a61acfe6d732541c400179
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Security for mcp-config-lint 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.