Metadata-Version: 2.4
Name: mcpcheck-py
Version: 0.1.0
Summary: Lint MCP config files for Claude Desktop, Claude Code, Cursor, Cline, Windsurf, and Zed. Stdlib-only Python port of @mukundakatta/mcpcheck.
Project-URL: Homepage, https://github.com/MukundaKatta/mcpcheck-py
Project-URL: Issues, https://github.com/MukundaKatta/mcpcheck-py/issues
Project-URL: Source, https://github.com/MukundaKatta/mcpcheck-py
Project-URL: JS sibling, https://github.com/MukundaKatta/mcpcheck
Author-email: Mukunda Katta <mukunda.vjcs6@gmail.com>
License: MIT
License-File: LICENSE
Keywords: claude,cline,cursor,linter,mcp,model-context-protocol,secrets,static-analysis,windsurf,zed
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# mcpcheck-py

[![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

**A linter for MCP (Model Context Protocol) config files.** Works on every client that reads `mcp.json` / `.mcp.json` / `claude_desktop_config.json` / Zed's `context_servers`: Claude Desktop, Claude Code, Cursor, Cline, Windsurf, Zed.

Stdlib-only Python port of [@mukundakatta/mcpcheck](https://github.com/MukundaKatta/mcpcheck). Catches hardcoded secrets, dangerous commands, missing transports, malformed env blocks, plaintext HTTP with tokens, and shell-metachar injection.

> Note: there's a separate package called `mcp-config-check` on PyPI by a different author. This package is the user's own port and uses the `-py` suffix to disambiguate.

## Install

```bash
pip install mcpcheck-py
```

## Usage

```python
from mcpcheck import lint

result = lint("/path/to/mcp.json")

result.fatal           # True if the file failed to parse
for issue in result.issues:
    print(issue.line, issue.severity, issue.rule_id, issue.message)
```

You can also lint in-memory configs:

```python
from mcpcheck import lint_source

source = '''
{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": { "GITHUB_TOKEN": "ghp_abcdef1234567890abcdef1234567890" }
    }
  }
}
'''
result = lint_source(source, file="<inline>")
```

## Result shape

```python
@dataclass
class Issue:
    rule_id: str
    severity: str           # "error" | "warning" | "info"
    message: str
    json_path: str          # dotted path, e.g. "mcpServers.github.env.GITHUB_TOKEN"
    line: int = 1

@dataclass
class LintResult:
    file: str
    fatal: bool
    issues: list[Issue]
```

## Rules

| Rule id | Severity | Checks |
|---|---|---|
| `invalid-json` | error | File doesn't parse as JSON / JSONC. |
| `empty-servers` | warning | No `mcpServers` / `servers` / `context_servers` key, or empty. |
| `duplicate-server-name` | warning | Two servers share a case-insensitive name. |
| `unknown-field` | info | Server has a field outside the known set. |
| `missing-transport` | warning | Server has neither `command` (stdio) nor `url` (sse / streamable-http). |
| `dangerous-command` | error | `sudo`, `curl \| sh`, `docker --privileged`, host-root mounts, `rm -rf /`. |
| `hardcoded-secret` | error | Provider-shaped key (OpenAI, Anthropic, GitHub, AWS, Slack, etc.) in `env` values. |
| `plaintext-http-with-token` | warning | `http://` URL combined with a bearer/auth header. |
| `invalid-env-var-name` | warning | env key doesn't match `^[A-Z_][A-Z0-9_]*$`. |
| `empty-env-value` | warning | env value is empty or whitespace. |

## Supported clients

`mcpcheck.CLIENT_PATHS` exposes the same path table the JS sibling uses, so
you can build editor / CI integrations that auto-discover configs:

* `cursor` -- `~/.cursor/mcp.json`
* `claude-desktop` -- `~/Library/Application Support/Claude/claude_desktop_config.json`, ...
* `claude-code` -- `~/.claude.json`, `**/.mcp.json`
* `windsurf` -- `~/.codeium/windsurf/mcp_config.json`
* `zed` -- `~/.config/zed/settings.json`
* `cline` -- `**/.cline/mcp.json`, `**/cline_mcp_settings.json`

## API differences from the JS sibling

* Stdlib only -- JSONC (comments + trailing commas) handled by a small
  hand-rolled tolerant scanner instead of `jsonc-parser`. Strict JSON files
  go through `json.loads` directly.
* Returns a `LintResult` dataclass instead of the JS object literal.
* Spec entrypoint is `lint(config_path)` -- mirror of the spec's
  `lint(config_path) -> LintResult`. `lint_source` covers the in-memory case.
* Fewer rules than the full JS sibling -- the Python port focuses on the
  high-signal subset (secrets, dangerous commands, structure). Run the JS
  CLI for the full surface.

See the JS sibling's [README](https://github.com/MukundaKatta/mcpcheck) for
the broader rationale.
