gristmill-mcp
Provides tools for scanning generated code for security issues, including exposed GitHub tokens, before it is committed or pushed.
Provides tools for detecting exposed Google API keys in generated code during the verification process.
Provides tools for detecting exposed Slack tokens in generated code during the verification process.
Provides tools for inspecting generated code that interacts with Stripe, catching structural issues and secrets (like live API keys) before the code is shipped.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@gristmill-mcpverify src/app.py for structural issues and secrets"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.
Related MCP server: code-verify-mcp
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-mcpOr 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.pyOutput 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.pycurrently has the key swapped forNoneto 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 |
| secrets | AWS access key ID | error |
| secrets | AWS secret access key | error |
| secrets | GitHub token | error |
| secrets | Google API key | error |
| secrets | Slack token | error |
| secrets | Stripe live key | error |
| secrets | Private key block | error |
| secrets | JWT | error |
| secrets | Database URI with inline password | error |
| secrets | Generic credential-shaped assignment | error |
| secrets | High-entropy string literal | warning |
| structure | Too many top-level functions (default limit 5) | warning |
| structure | Shared function-name prefix (3+ functions) | warning |
| structure | Repeated first-parameter name (3+ functions) | warning |
| structure | Function too long (default limit 60 lines) | warning |
| structure | Mutable module-level state, mutated elsewhere in the file | warning |
| comment_slop | Conversational address in comment | warning |
| comment_slop | Comment narrates the obvious | info |
| comment_slop | Oversized comment block on a short function | info |
| comment_slop | Placeholder scaffolding left in place | warning |
| 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
astandtokenize).JavaScript/TypeScript — full support, via
tree-sitterwith thetree-sitter-javascriptandtree-sitter-typescriptcompiled 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 —structureandcomment_slopwork identically whether or notnodeis onPATH, and it gives a real AST instead of a text-only fallback.Anything else — the
secretscheck still runs (it's regex-based and language-agnostic);structureandcomment_slopare skipped for that file, reported inskipped_paths.
Limitations
Read this before trusting the tool more than it's earned:
secretsonly catches shaped or high-entropy strings. A low-entropy human password likehunter2will 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.
structurelooks 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. Seedocs/RULES.mdfor 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
verifycall 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
verifyfindings)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/ -qRegenerate docs/RULES.md after editing src/gristmill/rules.py:
.venv/bin/python3 scripts/generate_rules_doc.pyTests 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.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Tools
Related MCP Servers
- Alicense-qualityAmaintenanceAn MCP server that provides AI coding agents with AST-accurate, context-budget-aware codebase querying, safety gates, and team policy integration via structured tools and a local plugin layer.5724MIT
- Alicense-qualityDmaintenanceAn MCP server for verifying AI-generated code quality, security, and performance, addressing trust gaps in AI coding assistants.MIT
- Flicense-qualityCmaintenanceMCP server that helps AI agents inspect Minecraft project evidence (crash logs, mod files, datapacks) before writing development code.3
- Alicense-qualityCmaintenanceAn MCP server that gives AI coding agents structured access to a project's architecture, rules, modules, and technical decisions.MIT
Related MCP Connectors
Hosted MCP server for structured code review passes on human- and AI-written code. Free tier.
An MCP server that gives your AI access to the source code and docs of all public github repos
MCP server teaching AI agents to implement TideCloak: auth, E2EE, IGA, security analysis
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/mattshuttle/gristmill-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server