@frontier-collective/whetstone-mcp
v0.6.0
Published
An MCP server that captures and encodes human rejections of AI output into durable, queryable constraints.
Downloads
1,167
Maintainers
Readme
Whetstone MCP
A whetstone is a flat stone used to sharpen blades. The blade does the cutting, but without the stone it dulls. The sharpener's skill is knowing the right angle and pressure — judgment that can't be automated, only practiced.
Whetstone MCP applies this metaphor to AI-assisted development. The AI is the blade. Your judgment is the stone. Every time you reject AI output and explain why, you're sharpening the edge. Whetstone captures those moments and turns them into durable, queryable constraints — so the blade stays sharp across conversations, projects, and teams.
Features
- Capture rejections — log what was wrong and why, as structured data, inside your agent conversation
- Encode constraints — distill recurring rejections into durable, queryable rules with severity and domain
- Proactive application — agents fetch constraints before generating output, so you don't reject the same thing twice
- Pattern detection — surface clusters of similar rejections that haven't been encoded yet
- Web dashboard — real-time overview of constraint health, domain gaps, graduation candidates, and trends
- Per-project storage — each project has its own SQLite database, committed to git so constraints travel with the code
- Git integration — pre-push hook exports human-readable constraint snapshots, making taste changes visible in diffs
- Agent-agnostic — works with any MCP-compatible agent (Claude Code, Cursor, Codex, Kiro, or anything else)
- CLI + MCP — every tool is available both as an MCP tool for agents and as a CLI subcommand for humans
How It Works
- You reject AI output during a conversation and explain why
- Whetstone captures the rejection as structured data
- You (or your agent) distill rejections into constraints — encoded rules
- Constraints are proactively applied before generating future output
- Your taste sharpens over time, tracked and versioned alongside your code
Quick Start
1. Install Whetstone
npm install -g @frontier-collective/whetstone-mcpTo update: run the same command again. To uninstall: npm uninstall -g @frontier-collective/whetstone-mcp
Development install
For contributing or running from source:
git clone [email protected]:frontier-collective/whetstone-mcp.git ~/tools/whetstone
cd ~/tools/whetstone
make setupTo update: git pull && make build — the symlink picks up the new build automatically.
2. Initialize a project
In any project where you want to use Whetstone:
cd ~/projects/my-app
whetstone initThis creates:
my-app/
.whetstone/
whetstone.db # SQLite database for this project's constraints
exports/ # human-readable snapshots (generated on git push)Commit the .whetstone/ directory to your repo so constraints travel with the code.
Then install the pre-push git hook:
whetstone hookThis installs .git/hooks/pre-push-whetstone (the export script) and a .git/hooks/pre-push dispatcher that calls it. If you already have a pre-push hook, you'll be prompted before overwriting.
3. Configure your AI agent
Whetstone runs as an MCP server — it starts automatically when your agent starts. Add it to your project's agent config:
Claude Code
Create .claude/settings.json in your project:
{
"mcpServers": {
"whetstone": {
"command": "whetstone-mcp",
"env": {
"WHETSTONE_DB": ".whetstone/whetstone.db"
}
}
}
}If you didn't run npm link, reference the build directly:
{
"mcpServers": {
"whetstone": {
"command": "node",
"args": ["/absolute/path/to/whetstone/dist/index.js"],
"env": {
"WHETSTONE_DB": ".whetstone/whetstone.db"
}
}
}
}Cursor
In .cursor/mcp.json:
{
"mcpServers": {
"whetstone": {
"command": "whetstone-mcp",
"env": {
"WHETSTONE_DB": ".whetstone/whetstone.db"
}
}
}
}Other MCP-compatible agents
Any agent that supports MCP can connect — point it at whetstone (or node /path/to/dist/index.js) with the WHETSTONE_DB environment variable set. The server uses stdio transport.
4. Use it
The following tools are now available in your agent conversations:
| Tool | What it does |
|---|---|
| reject | Log a rejection — capture what was wrong and why |
| constrain | Create a durable constraint from rejections (accepts rejection_ids to link them) |
| get_constraints | Fetch active constraints before generating output |
| search | Free-text search across constraints and rejections |
| applied | Mark a constraint as applied (usage tracking) |
| link | Link existing rejections to a constraint — closes the flywheel after the fact |
| update_constraint | Refine, supersede, or deprecate a constraint |
| export | Export constraints as markdown or JSON |
| patterns | Surface recurring rejection themes not yet encoded |
| list | Browse rejections filtered by domain and encoded/unencoded status |
| stats | Get rejection and constraint statistics |
| db_path | Returns the resolved database file path (diagnostics) |
No special syntax required — your agent sees these as available tools and can use them naturally in conversation.
Constraint Lifecycle
Rejection (raw event — "this is wrong because...")
→ Constraint (encoded rule, status: active)
→ Graduated (exported to CLAUDE.md, cursor rules, etc.)
→ Deprecated in Whetstone (avoid duplication)Rejections are the raw material. Constraints are the refined product. When a constraint has proven durable, export it to your project's permanent docs and deprecate it in Whetstone.
Dashboard
Whetstone includes a web dashboard for visualizing your project's constraint health at a glance.
whetstone dashboard # launch on localhost:1337
whetstone dashboard --port 3000 # use a different portThe dashboard auto-refreshes every 5 seconds and is designed to run alongside your agent — open it in a browser while you work.
What you'll see
Summary cards — total rejections, constraints, and the percentage of rejections that have been encoded into constraints. Each card includes a weekly delta showing the trend over the last 7 days.
Unencoded patterns — clusters of similar rejections that haven't been encoded into constraints yet. These are the "you keep saying the same no" signals. Each cluster shows a suggested action to encode it, closing the flywheel.
Domain gaps — domains ranked by their encoding coverage (lowest first). A domain with many rejections but few constraints is a gap in your encoded taste.
Graduation candidates — constraints that have been applied 8 or more times, signaling they're durable enough to graduate to your project's permanent docs (CLAUDE.md, cursor rules, etc.).
Fading constraints — active constraints that were once used but haven't been applied in over 30 days. These may need to be refreshed, refined, or deprecated.
Recent activity — the latest rejections and constraints with domain badges, timestamps, and status indicators.
The dashboard reads from the same SQLite database as the MCP server, so everything stays in sync. After running clear-db, the dashboard detects the file change and reconnects automatically.
Try It: A Complete Walkthrough
This walkthrough uses make targets to exercise every tool from the command line. Each make tool-* command sends a JSON-RPC call to the MCP server — the same protocol your AI agent uses. By the end you'll have experienced the full rejection-to-constraint flywheel.
Prerequisites: Run make setup once to install, build, and link.
Step 1: Log some rejections
Rejections are the raw material. Each one captures a moment where AI output wasn't good enough. Start by logging a few in the frontend domain:
make tool-reject DOMAIN=frontend DESC="Used useEffect to compute derived state"
make tool-reject DOMAIN=frontend DESC="Computed derived values inside useEffect instead of inline"
make tool-reject DOMAIN=frontend DESC="Put a simple string concatenation in a useEffect hook"Each response includes the rejection ID and a suggestion to encode it as a constraint. Notice these three rejections are all about the same underlying issue — useEffect misuse for derived state.
Now log a couple in a different domain:
make tool-reject DOMAIN=backend DESC="Leaked raw Prisma error message to API response"
make tool-reject DOMAIN=backend DESC="Exposed internal database error in REST endpoint"And one unrelated frontend rejection:
make tool-reject DOMAIN=frontend DESC="Used a modal dialog for a simple yes/no confirmation"Step 2: Detect patterns
The patterns tool clusters similar rejections by textual similarity within each domain. It's the "you keep saying the same no" detector:
make tool-patternsYou'll see two clusters — the three useEffect rejections grouped together with a shared theme like "derived, useeffect, state", and the two database-error rejections grouped with a theme like "database, error". The modal dialog rejection stands alone (no cluster) because it's about a different issue.
This is the signal that these rejections should become constraints.
Step 3: Encode a constraint
Pick one of the clusters and articulate a constraint — a durable rule in imperative voice. This is the encoding step, where a vague "I don't like this" becomes a precise instruction:
make tool-constrain \
DOMAIN=frontend \
TITLE="No useEffect for derived state" \
RULE="Compute derived values inline. Never use useEffect or useMemo for values that can be calculated directly from props or state."The response includes the constraint ID (e.g., 01ABC123). Copy it for the next step.
Step 4: Link rejections to the constraint
Now close the flywheel — mark those rejections as "encoded" by linking them to the constraint. Use the rejection IDs from Step 1 (printed when each rejection was logged):
make tool-link ID=<constraint-id> RID=<first-rejection-id>
make tool-link ID=<constraint-id> RID=<second-rejection-id>
make tool-link ID=<constraint-id> RID=<third-rejection-id>You can also pass rejection_ids directly when creating a constraint in Step 3 to link them in one shot — the constrain tool accepts an optional rejection_ids parameter.
Step 5: Check the dashboard
make tool-statsYou'll see total rejections, how many are still unencoded (the ones you linked are no longer unencoded), rejections by domain, and most-applied constraints. Run patterns again:
make tool-patternsThe useEffect cluster is gone — those rejections are encoded now. Only the unlinked backend cluster remains, telling you there's still a pattern waiting to be encoded.
Step 6: Fetch constraints before generating output
This is the highest-value tool. An agent calls get_constraints before generating code to pre-apply your taste:
make tool-get-constraints DOMAIN=frontendReturns your active constraints for the frontend domain, sorted by usage — the most-applied constraints appear first. An agent reads these and applies them proactively, so you don't have to reject the same thing twice.
Step 7: Track which constraints are actually used
When an agent applies a constraint during generation, it calls applied to record that:
make tool-applied ID=<constraint-id>
make tool-applied ID=<constraint-id>
make tool-applied ID=<constraint-id>Run make tool-stats again — the constraint now shows up in "Most applied constraints" with a count of 3. Over time, this data reveals which constraints are actually valuable and which are never used (stale).
Step 8: Search across everything
Find constraints and rejections by keyword:
make tool-search QUERY=useEffect
make tool-search QUERY=errorSearches across titles, rules, descriptions, reasoning, and tags.
Step 9: Export for graduation
When a constraint has proven durable, export it to your project's permanent docs:
make tool-export FORMAT=markdown
make tool-export FORMAT=json
make tool-export DOMAIN=frontend FORMAT=markdownPaste the markdown into your CLAUDE.md, cursor rules, or Codex instructions. Then deprecate the constraint in Whetstone to avoid duplication:
make tool-update-constraint ID=<constraint-id> TITLE="No useEffect for derived state"What just happened
You experienced the full flywheel:
Reject ("this is wrong") x 3
-> Patterns detected ("you keep saying the same no")
-> Constraint encoded ("here's the rule")
-> Rejections linked (flywheel closed)
-> Constraint applied (proactive taste)
-> Usage tracked (what's actually valuable)
-> Exported (graduated to project docs)In practice, your AI agent does all of this inside the conversation. You reject output naturally, the agent logs it, surfaces patterns, and helps you encode constraints. The Makefile targets are just a way to see the machinery directly.
Git Integration
The pre-push hook installed by whetstone hook automatically:
- Exports all active constraints to a temp file
- Compares against the most recent export — skips if nothing changed
- If changed, saves to
.whetstone/exports/<timestamp>.mdand commits - Aborts the push so the snapshot commit is included — push again and it goes through instantly
This means constraint changes are always visible in diffs alongside the code they govern. The timestamped files create a history of how your project's taste evolves.
Versioning
Whetstone uses semver with automated git-flow releases:
make version # show current version
make release patch # 0.1.0 → 0.1.1
make release minor # 0.1.0 → 0.2.0
make release major # 0.1.0 → 1.0.0You must be on the develop branch with a clean working tree. The release target handles the full git-flow automatically:
- Builds and runs tests (aborts on failure)
- Bumps
package.jsonand updatesCHANGELOG.md - Creates
release/<version>branch with a tagged commit - Merges to
masterand back todevelop - Cleans up the release branch
When it's done, push and create the GitHub release:
git push origin master develop --tags
make gh-releaseGitHub Releases
make gh-release creates a GitHub Release with notes pulled from CHANGELOG.md. It only shows tags that don't already have a GitHub Release — if everything is released, it exits cleanly.
make gh-release # pick from unreleased tags
make gh-release TAG=v0.0.3 # skip the prompt, release a specific tagThe full release workflow:
make release patch # bump, changelog, git-flow merge, tag
git push origin master develop --tags
make gh-release # create GitHub Release with changelog notes
make npm-publish # publish to npm registryCLI Commands
Both whetstone and whetstone-mcp work as the CLI command — they're identical. Every MCP tool is also available as a CLI subcommand.
Setup
whetstone init # set up .whetstone/ directory and database
whetstone hook # install pre-push git hook (dispatcher pattern)
whetstone dashboard # launch web dashboard on localhost:1337
whetstone dashboard --port 3000 # use a different port
whetstone --help # show all commands and optionsCapture
# Log a rejection
whetstone reject --domain frontend --desc "Used useEffect for derived state" \
--reasoning "Derived values should be computed inline"
# Encode a constraint (link rejections by ID)
whetstone constrain --domain frontend --category pattern \
--title "No useEffect for derived state" \
--rule "Compute derived values inline, never in useEffect" \
--severity critical --rejection-ids "01ABC123,01DEF456"Query
# Fetch constraints before generating code
whetstone get-constraints --domain frontend
whetstone get-constraints --severity critical
# Search across everything
whetstone search --query useEffect
whetstone search --query error --type constraints
# Surface unencoded rejection patterns
whetstone patterns
whetstone patterns --domain backend
# Browse rejections
whetstone list --domain frontend
whetstone list --status unencoded --limit 20
# View statistics
whetstone stats
# Export for graduation to project docs
whetstone export --format markdown
whetstone export --domain backend --format json --output constraints.jsonManagement
# Track constraint usage
whetstone applied --id 01ABC123
# Link rejections to a constraint
whetstone link --id 01ABC123 --rejection-ids 01DEF456,01GHI789
# Evolve a constraint
whetstone update-constraint --id 01ABC123 --severity critical
whetstone update-constraint --id 01ABC123 --status deprecatedDiagnostics
# Show which database file is being used
whetstone db-path
# Wipe all data and recreate empty database (prompts for confirmation)
whetstone clear-db
whetstone clear-db --force # skip confirmation (for scripting)All commands accept --db <path> to override the database location.
Development
Use make targets for all common operations. Run make help for the full list.
make setup # install, build, and link globally (first time)
make install # install npm dependencies
make build # compile TypeScript
make dev # watch mode
make test # run tests
make clean # remove dist/ and test database
make init # set up .whetstone/ directory and database
make help # show all targets including MCP tool runnersMCP tools can also be exercised directly from the command line via Make:
make tool-reject DOMAIN=backend DESC="Leaked internal error to client"
make tool-constrain DOMAIN=backend TITLE="No raw errors" RULE="Wrap all API errors"
make tool-get-constraints DOMAIN=backend
make tool-search QUERY=error
make tool-statsTech Stack
| Technology | Purpose | |---|---| | TypeScript | Server language — widest MCP SDK support | | better-sqlite3 | Native SQLite bindings — fast, atomic writes, WAL mode | | @modelcontextprotocol/sdk | Standard MCP server implementation | | ulid | Sortable unique identifiers |
