Skip to main content
Glama
mattshuttle

gristmill-mcp

by mattshuttle

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-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.tomldemo/ 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.

Available Tools

2 tools
explain_ruleA

Look up a gristmill rule by id (e.g. SEC001, STR002, CMT004): its rationale, what it catches, what it misses, and how to suppress it.

ParametersJSON Schema
NameRequiredDescriptionDefault
rule_idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It correctly implies a read-only operation ('Look up') and lists the content returned. However, it does not explicitly state that the tool has no side effects, nor does it mention authorization requirements, rate limits, or error handling for invalid rule IDs. A 3 is adequate but leaves gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence that is front-loaded with the action and resource, includes concrete examples in parentheses, and conveys the full return intent. There is no wasted text; every part earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers the tool's purpose, the parameter, and the core return values. An output schema exists to handle return type details, so the description does not need to reiterate those. However, it omits mention of what happens if the rule ID is invalid or missing (e.g., error or null response), which would make it more complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% (no parameter descriptions in the input schema), so the description must compensate. It adds value by specifying the parameter is a 'rule id' and provides examples (SEC001, STR002, CMT004), hinting at a consistent format. However, it does not fully specify the pattern or acceptable formats, leaving ambiguity for the agent.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Look up', identifies the resource as a 'gristmill rule', and specifies exactly what information is returned: rationale, what it catches, what it misses, and how to suppress it. This distinguishes it from the sibling tool 'verify', which likely performs a different function.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage when you need details about a specific rule (e.g., by its ID), but it does not explicitly state when to use this tool versus the sibling 'verify', nor does it provide guidance on when not to use it or any prerequisites. More specific exclusions or comparisons would improve this dimension.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

verifyA

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathsYes
checksNo
severity_floorNoinfo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Without any annotations, the description must disclose behavioral traits fully. It states that findings are 'deterministic' and include 'file paths and line numbers,' which adds value. However, it does not address permissions, side effects (though likely read-only), rate limits, or what happens when no issues are found.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three efficiently structured sentences: purpose, output nature, and usage timing. No redundant or extraneous content. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The presence of an output schema partially offsets the need to describe return values. However, the description does not explain how the three parameters interact or provide examples for common use cases, leaving gaps for a tool invoked after code generation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It only implicitly refers to 'paths' via 'source files' and does not explain 'checks' (the enum options) or 'severity_floor' at all. This forces the agent to rely solely on parameter names, which are insufficient.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('inspect') and resource ('source files') and enumerates three concrete issue types (secrets, structural problems, low-quality comments). With only one sibling tool 'explain_rule', the purpose is clearly distinct.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly tells when to call this tool: 'after generating or editing code, before presenting it as finished.' It provides clear context but does not mention when not to use it or compare to alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 2 tool updatesv0.1.0
    • First observedexplain_rule
    • First observedverify

TDQS

A3.5/5.0
Disambiguation4/5

The two tools have clearly distinct purposes: one is for static analysis of code, the other for documentation of rules. An agent would not confuse them.

Naming Consistency3/5

The naming is inconsistent: 'verify' uses a plain verb while 'explain_rule' uses verb_noun pattern. Both are clear in isolation, but the lack of a unified pattern (e.g., 'verify_code' vs 'explain_rule') makes the set feel ad-hoc.

Tool Count2/5

With only 2 tools, the server feels thin for a tool suite called 'gristmill-mcp'. A code analysis server typically needs more tools like listing rules or scanning for specific categories to feel properly scoped.

Completeness2/5

The server only provides a scan tool and a rule lookup tool, but is missing operations like listing all rules, skipping specific rules, or generating reports. Users cannot discover available rules without knowing their IDs, creating a dead end.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

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