gristmill-mcp

gristmill-mcp

An MCP server that inspects AI-generated code and returns a deterministic list of structural and safety violations, so an AI coding agent can fix its own output before the code lands.

Category
访问服务器

README

gristmill-mcp

An MCP server that inspects AI-generated code and returns a deterministic list of structural and safety violations, so an AI coding agent can fix its own output before the code lands.

Grist is grain brought to a mill for grinding. AI output is grist — genuinely valuable raw material, but unprocessed. The mill gives it structure.

AI writes the grist. Gristmill makes it code you can ship.

Why an MCP server, not a skill

A skill is text loaded into a model's context — it changes what the model knows. An MCP server is a program the model executes — it changes what the model can do.

Style guidance ("prefer classes over loose functions") belongs in a skill. Verification ("this file has 7 top-level functions at lines 12, 40, 66…") requires running code against the file. A model reading its own output and reasoning "this looks like it has too many functions" is a guess dressed up as an observation — it has no ground truth for what "too many" means in this file, and no reliable way to count. Gristmill parses the AST and counts. That distinction — instruction versus execution — is why this exists as a server instead of a paragraph of advice.

The server never calls an LLM, never varies between runs on the same input, and never emits a confidence score. Same input → byte-identical output, every time. That determinism is the entire product. The AI layer sits above this server, consuming its findings and deciding what to do about them — the server's job stops at reporting facts with line numbers.

Install

git clone <this repo> gristmill-mcp
cd gristmill-mcp
python3 -m venv .venv
.venv/bin/pip install -e .

Claude Code

Register it with the CLI, pointing at the venv's console script:

claude mcp add gristmill -- /absolute/path/to/gristmill-mcp/.venv/bin/gristmill-mcp

Or add it directly to your MCP config (.mcp.json in a project, or your global Claude Code config):

{
  "mcpServers": {
    "gristmill": {
      "command": "/absolute/path/to/gristmill-mcp/.venv/bin/gristmill-mcp"
    }
  }
}

Other MCP clients

Any stdio-based MCP client can launch the same binary — gristmill-mcp (or python3 -m gristmill.server inside the venv) speaks the standard MCP stdio transport with no client-specific configuration.

Command line (no MCP client)

For local testing, or to reproduce the worked example below, a thin CLI wraps the same engine:

.venv/bin/gristmill-verify path/to/file_or_dir [--checks secrets structure comment_slop] [--severity-floor warning] [--json]

Worked example

demo/billing.py, an unedited first draft of a Stripe billing helper:

import stripe

# I've added this as you requested — sets up the Stripe client
STRIPE_SECRET_KEY = None  # was a literal sk_live_... key — see note below

stripe.api_key = STRIPE_SECRET_KEY


def customer_create(config):
    return stripe.Customer.create(**config)


def customer_delete(config):
    return stripe.Customer.delete(config["id"])


def customer_find(config):
    return stripe.Customer.retrieve(config["id"])


def customer_update(config):
    return stripe.Customer.modify(config["id"], **config)
.venv/bin/gristmill-verify demo/billing.py

Output with a real Stripe-live-key-shaped literal in place of the None above:

gristmill: 1 files scanned, 0 skipped (2 error, 4 warning, 0 info) in 1ms
  [WARNING] STR002  billing.py:1  4 top-level functions share the prefix `customer_` — consider a `Customer` class or module
  [WARNING] STR003  billing.py:1  4 top-level functions take a first parameter named `config` — consider making it instance state
  [WARNING] CMT001  billing.py:3  Comment addresses the reader conversationally ('as you requested')
  [ERROR  ] SEC006  billing.py:4:22  Stripe live key assigned to `STRIPE_SECRET_KEY`
  [ERROR  ] SEC010  billing.py:4:22  String literal assigned to `STRIPE_SECRET_KEY`, which looks credential-shaped
  [WARNING] SEC011  billing.py:4:22  High-entropy string literal (5.1 bits/char) assigned to `STRIPE_SECRET_KEY`

(File paths are shown relative to the nearest .gristmill.toml — demo/ carries its own so this example's output stays stable independent of the top-level project config.)

Note: GitHub's push protection blocks any pushed file containing a real-format secret — including in a comment or a markdown code block, this README included. demo/billing.py currently has the key swapped for None to unblock the initial push; this is a TODO to restore (via an allow-listed secret-scanning exception) so the demo is live again.

The --json flag (or the verify MCP tool, which returns both) gives the full structured form — file, line, column, a static suggestion string, and a redacted evidence field (sk_l… (49 chars), never the key itself).

Tools

verify

Inspect source files for secrets, structural problems, and low-quality comments. Returns deterministic findings with file paths and line numbers. Call this after generating or editing code, before presenting it as finished.

Input: paths (files or directories, required), checks (optional subset of secrets/structure/comment_slop, default all), severity_floor (optional, default info).

Output: a compact human-readable summary, followed by the full structured JSON — file, line, column, message, redacted evidence, and a static suggestion string per rule. Findings are sorted by path, then line, then rule_id, always — that stability is what makes runs byte-identical and lets a model navigate straight to the problem.

explain_rule

Takes a rule_id (e.g. SEC001) and returns its rationale, what it catches, what it misses, and how to suppress it — the same content as docs/RULES.md, served on demand so verify output can stay terse.

Rules

Rule Check Title Default severity
SEC001 secrets AWS access key ID error
SEC002 secrets AWS secret access key error
SEC003 secrets GitHub token error
SEC004 secrets Google API key error
SEC005 secrets Slack token error
SEC006 secrets Stripe live key error
SEC007 secrets Private key block error
SEC008 secrets JWT error
SEC009 secrets Database URI with inline password error
SEC010 secrets Generic credential-shaped assignment error
SEC011 secrets High-entropy string literal warning
STR001 structure Too many top-level functions (default limit 5) warning
STR002 structure Shared function-name prefix (3+ functions) warning
STR003 structure Repeated first-parameter name (3+ functions) warning
STR004 structure Function too long (default limit 60 lines) warning
STR005 structure Mutable module-level state, mutated elsewhere in the file warning
CMT001 comment_slop Conversational address in comment warning
CMT002 comment_slop Comment narrates the obvious info
CMT003 comment_slop Oversized comment block on a short function info
CMT004 comment_slop Placeholder scaffolding left in place warning
CMT005 comment_slop Repeated section-divider banners (4+ per file) info

Full rationale, false-negative notes, and suppression instructions per rule: docs/RULES.md.

Configuration

.gristmill.toml at the project root, all keys optional:

[checks]
enabled = ["secrets", "structure", "comment_slop"]

[structure]
max_top_level_functions = 5
max_function_lines = 60

[secrets]
entropy_threshold = 4.5

[ignore]
paths = ["legacy/**", "vendor/**"]
rules = ["CMT003"]

A .gristmillignore file (gitignore syntax) works alongside [ignore] paths. Inline suppression is also honored on the flagged line or the line above it:

SUPPRESSED = "ghp_" + "..."  # gristmill: ignore SEC003
// gristmill: ignore SEC003
const suppressed = "ghp_" + "...";

Language support

  • Python — full support (stdlib ast and tokenize).
  • JavaScript/TypeScript — full support, via tree-sitter with the tree-sitter-javascript and tree-sitter-typescript compiled grammars, rather than shelling out to a Node-based parser. This trades a compiled Python dependency for independence from the host having Node installed at all — structure and comment_slop work identically whether or not node is on PATH, and it gives a real AST instead of a text-only fallback.
  • Anything else — the secrets check still runs (it's regex-based and language-agnostic); structure and comment_slop are skipped for that file, reported in skipped_paths.

Limitations

Read this before trusting the tool more than it's earned:

  • secrets only catches shaped or high-entropy strings. A low-entropy human password like hunter2 will never be flagged — there is no reliable way to distinguish it from an ordinary short string. Credentials assembled at runtime (string concatenation, os.environ.get(...) or "fallback", base64-decoded pieces) are invisible to a regex/entropy pass over static text.
  • Structural problems that span files are invisible. structure looks at one file at a time; a class that should be split across files, or duplicated logic in two different modules, is out of scope.
  • comment_slop's CMT002 is deliberately narrow. It's the highest false-positive-risk rule in the set, so it's implemented to bias hard toward silence — it will miss real narration far more often than it over-flags. See docs/RULES.md for the exact subset-match rule.
  • Languages outside Python and JS/TS get secrets-only coverage. No structural or comment analysis for Go, Rust, Ruby, etc. in v1.
  • This is not a secrets-in-git-history scanner. It inspects the working tree as given. A key that was committed and later removed from the current file is not this tool's concern (a git-history scanner is a different, complementary tool).
  • No auto-fix. Gristmill reports; the calling model decides what and how to change. That split is intentional (see "Why an MCP server, not a skill" above), but it means a verify call alone never fixes anything.

A tool that oversells its coverage is worse than one that's upfront about its blind spots — silence beats false confidence here as much as it beats noisy findings.

Roadmap

Explicitly out of scope for v1, in rough priority order:

  • Auto-fix / patch generation (the calling model does this today, using verify findings)
  • Dependency freshness and CVE checking (needs network calls to package registries — a natural v2)
  • Language support beyond Python and JavaScript/TypeScript
  • Git history scanning for secrets that were committed and later removed
  • A hosted service, web UI, or dashboard

Development

.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest tests/ -q

Regenerate docs/RULES.md after editing src/gristmill/rules.py:

.venv/bin/python3 scripts/generate_rules_doc.py

Tests cover (tests/): golden-file output for a known-dirty fixture directory, 10x determinism with and without parallelism, a false-positive corpus that must produce zero findings, redaction (no raw secret ever reaches any output field), and resilience (invalid syntax, binary, empty, and oversized files never crash a run).

License

MIT — see LICENSE.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选