Metadata-Version: 2.4
Name: flask-production-mcp
Version: 0.1.0
Summary: Static production-readiness auditor for Flask apps: architecture, templates, deployment, testing, security, database, dependencies and code quality. CLI, pre-commit hook, GitHub Action and MCP server.
Keywords: flask,mcp,static-analysis,security,linter,production-readiness,audit,pre-commit
Author: Yemi Balogun
Author-email: Yemi Balogun <slickact2006@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Security
Classifier: Typing :: Typed
Requires-Dist: fastmcp>=2.14,<3
Requires-Dist: pydantic>=2.13.4
Requires-Dist: bandit>=1.9.4 ; extra == 'scanners'
Requires-Dist: pip-audit>=2.10.1 ; extra == 'scanners'
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/yemibalogun/flask-production-mcp
Project-URL: Repository, https://github.com/yemibalogun/flask-production-mcp
Project-URL: Issues, https://github.com/yemibalogun/flask-production-mcp/issues
Provides-Extra: scanners
Description-Content-Type: text/markdown

# Flask Production MCP

A static **production-readiness auditor for Flask applications**, usable
three ways:

- a **CLI** (`flask-production-mcp audit`) for local runs and scripts
- a **pre-commit hook** and a **GitHub Action** so it runs at the right moments
- an **MCP server** so an AI coding agent can call it while building

It answers *"is this Flask app ready to deploy?"* — not just *"does it have
security bugs?"* — across nine categories:

| Category | What it checks |
| --- | --- |
| **flask** | app-factory / blueprint discovery, route inventory, duplicate routes, debug mode enabled |
| **architecture** | extensions bound at import time, module-level `Flask()` beside a factory, hardcoded `SECRET_KEY` fallback, `db.create_all()` instead of migrations, unguarded `app.run()`, unregistered blueprints |
| **templates** | POST forms with no CSRF field, `\| safe` / `{% autoescape false %}` on dynamic data, `url_for()` to an endpoint that doesn't exist |
| **deployment** | Dockerfile running as root, dev server as the container command, base image on `:latest`, debug mode via env, secrets baked into an image/compose file, DB port published to the host, Gunicorn `reload = True`, dev server in a Procfile/entrypoint, committed `.env`, Nginx (`server_tokens`, missing forwarded headers, `client_max_body_size`, HTTP-only edge) |
| **testing** | no test suite at all, a low line-rate in an existing `coverage.xml`, a test count that looks thin next to the number of routes |
| **security** | `eval`/`exec`, `pickle` loads, hardcoded secrets, missing auth on sensitive routes, **missing rate limiting**, debug config — plus **Bandit**, folded in and de-duplicated |
| **database** | SQLAlchemy models / relationships / indexes, raw SQL, missing indexes on filtered columns, likely N+1 access |
| **dependencies** | known CVEs via **pip-audit** (PyPI + OSV) — `requirements*.txt`, or `uv.lock` / `poetry.lock` / `Pipfile.lock`, or the exact-pinned deps in a bare `pyproject.toml` |
| **code_quality** | bare/broad `except`, `print()`, `breakpoint()`, `assert` in app code, `TODO`/`FIXME` (test modules excluded) |

All analysis is **static**. The target application is never imported or
executed; virtual-environments, caches and build dirs are skipped.

## Findings: blockers vs. advisories vs. notes

Every finding has a **severity** and a **confidence**. The audit sorts them:

| Tier | Rule | Gates a release? |
| --- | --- | --- |
| **blockers** | high-confidence critical/high | **yes** |
| **advisories** | other critical/high/medium — needs a judgement call | no (configurable) |
| **notes** | low / info cleanups | no |

The `overall_score` is **confidence-weighted**: a confirmed critical bites
hard, a long tail of "consider an index" guesses barely moves it. A project
is `production_ready` when it has **no blockers** and no single category has
collapsed below the score floor (default 50).

## Install

```bash
pip install flask-production-mcp

# with the dependency-CVE and Bandit scanners:
pip install "flask-production-mcp[scanners]"

# or, from a checkout:
uv sync --extra scanners
```

Python 3.11+. The base install is MCP + the static analyzers. The
`scanners` extra adds `pip-audit` (needs network) and `bandit`; without
it the audit reports those scans as skipped rather than failing.

## CLI

```bash
flask-production-mcp audit path/to/your/flask/app
```

```
Flask Production Audit  --  /path/to/app
------------------------------------------------------------
Overall score  71/100          NOT READY

  flask          100   clean
  architecture    96   1 finding
  templates       55   1 finding (1 blocker)
  deployment     100   clean
  testing         92   1 finding
  security       100   clean
  database        76   6 findings
  dependencies   100   clean
  code_quality    79   7 findings

BLOCKERS (1)
  x [TMPL-CSRF-001] templates  app/templates/admin/products.html:242
      POST form has no CSRF token field
      -> Add a hidden CSRF field inside the form ...

Result: NOT production ready
  - 1 blocker(s) in templates
```

| Flag | Effect |
| --- | --- |
| `--json` | emit the full JSON result |
| `--github` | emit GitHub Actions `::error` / `::warning` annotations + job summary |
| `--fail-on {blockers,advisories,any,never}` | what makes exit code 1 |
| `--skip-deps` | skip the CVE scan (no network) |
| `--skip-bandit` | skip the Bandit scan |
| `--config FILE` | use a specific config TOML |
| `-q` | print blockers only |

Exit codes: `0` pass · `1` fail (per `--fail-on`) · `2` bad usage.

## Configuration

`flask-production.toml` in the project root, or `[tool.flask-production]`
in `pyproject.toml`. See [`flask-production.toml.example`](flask-production.toml.example).

```toml
fail_on = "blockers"
category_floor = 50
scan_dependencies = true
run_bandit = true
ignore = ["DB-PERF-001"]        # drop rules by id
select = []                      # if set, run ONLY these

[severity]
"ARCH-SEC-001" = "high"          # re-grade a rule for this project
```

## pre-commit

```yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/yemibalogun/flask-production-mcp
    rev: v0.1.0
    hooks:
      - id: flask-production-audit
```

The hook runs offline (`--skip-deps --skip-bandit -q`) and fails the commit
on **blockers only**. Override `args:` to change that.

## GitHub Action

```yaml
# .github/workflows/audit.yml
jobs:
  flask-production-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: yemibalogun/flask-production-mcp@v0.1.0
        with:
          path: .
          fail-on: blockers      # or advisories / any / never
```

Findings appear as inline annotations on the PR and in the job summary.

## MCP server

```bash
flask-production-mcp serve      # stdio
```

Register with an MCP client (Claude Code / Desktop):

```jsonc
{
  "mcpServers": {
    "flask-production": {
      "command": "flask-production-mcp",
      "args": ["serve"]
    }
  }
}
```

Then: *"Run a production audit on /path/to/my_flask_app and list the blockers."*

### Tools

| Tool | Purpose |
| --- | --- |
| `audit_flask_production` | **Start here.** Unified audit: `overall_score`, per-category `score`, `production_ready` + `blocking_reasons`, `blockers` / `advisories` / `notes`. |
| `inspect_flask_project` | Structure only: file counts, blueprints, routes, indicators. |
| `audit_flask` | Flask route/debug findings. |
| `audit_architecture` | Flask-architecture rules. |
| `audit_templates` | Jinja/HTML template findings. |
| `audit_deployment` | Dockerfile / compose / Gunicorn / Nginx / Procfile findings. |
| `audit_testing` | Test-suite presence and rough adequacy. |
| `audit_security` | Security audit (incl. Bandit). |
| `audit_database` | Database architecture + performance findings. |
| `audit_dependencies` | Dependency CVE scan. |
| `audit_code_quality` | Production code-quality findings. |

Every `audit_*` tool returns the same envelope (`success`, `project_path`,
`score`, `summary`, `findings`, `recommendations`, `errors`).

## Development

```bash
uv run pytest
uv run ruff check src tests
uv run mypy src
```

```
src/flask_production_mcp/
├── cli.py                  # audit / serve subcommands
├── config.py               # flask-production.toml loader + finding policy
├── server.py               # MCP server, registers the tools
├── models/findings.py      # Finding / AuditSummary / AuditResult
├── analyzers/
│   ├── base.py             # scoring (weighted), classification
│   ├── exclusions.py       # shared file-walk + exclusion rules
│   ├── flask.py            # discovery + analyze_flask
│   ├── architecture.py     # Flask-architecture rules
│   ├── templates.py        # Jinja/HTML rules
│   ├── deployment.py       # Dockerfile / compose / Gunicorn / Nginx rules
│   ├── testing.py          # test-suite health
│   ├── security.py         # analyze_security
│   ├── bandit_scan.py      # Bandit integration
│   ├── database.py         # analyze_database
│   ├── dependencies.py     # pip-audit (requirements / lock / pyproject pins)
│   ├── code_quality.py     # analyze_code_quality_file
│   └── production.py       # analyze_production (unified, parallel)
└── tools/                  # one thin MCP wrapper per analyzer
```

## Scope

Everything is **static** — the app is never run. So it does not do
performance profiling or dynamic/runtime security testing (both need a
live app + database), and the `testing` category reads an existing
`coverage.xml` rather than producing one. Unpinned dependencies in a bare
`pyproject.toml` can't be resolved to exact versions without installing —
add a lock file for full CVE coverage.
