claude mcp add rails-mcp -- python -m rails-mcp{
"mcpServers": {
"rails-mcp": {
"command": "python",
"args": ["-m", "venv"]
}
}
}MCP Servers overview
# rails-mcp
[](https://pypi.org/project/rails-mcp/)
[](https://registry.modelcontextprotocol.io)
[](LICENSE)
[](#testing)
[](https://github.com/jaimenbell/rails-mcp/actions/workflows/ci.yml)
A self-hosted, caller-configured **default-deny action registry** +
append-only **spend ledger** + CLI-only **sign-off audit trail**, exposed
as MCP tools. Different category from this author's other six MCP servers
(mcp-factory, rag-mcp, bus-mcp, desktop-mcp, github-mcp, discord-mcp) --
those are devtools ("connect an agent to X"); this one is **governance and
safety**: "stop an agent from doing something irreversible without a human
noticing."
Ported and generalized from a live internal registry (`shared/rails/`,
31 tests, running against a real multi-bot fleet since 2026-07-06) --
this pattern shipped internally before it shipped publicly.
## What this is / is not
**Is:**
- A schema + pure logic for classifying `action_type` strings as
unconditionally **GATED** (default-deny), registered or not.
- A place to **register** what you know about an action-type's current
enforcement (`enforcement_layer`, `enforcement_pointer`, `ceremony`) --
informational, never permissive.
- An append-only **spend-intent ledger** + rolling-window budget check.
- An append-only **human sign-off ledger**, recording who blessed the
registry's current hash and when.
**Is NOT:**
- **Not an enforcer.** `classify_action` returning `GATED` does not block
anything by itself. You still wire it into your own PreToolUse hook,
permission deny-list, or CI gate -- rails-mcp gives you the schema and
the audit trail, not the interceptor. `record_spend_intent` records
intent to spend; it never calls a vendor, a paid API, or a broker, and
nothing here stops an over-budget spend from happening.
- **Not pre-loaded with any action-type data.** Every adopter supplies
their own registry (a YAML/JSON config file, or a plain dict). No
fleet's specific action-types ship with this package.
- **Not multi-tenant.** The sign-off ledger assumes one human operator
string per registry; fine for v1, a known limitation for later.
## The one invariant that is never configurable
`classify_action(action_type)` **always** returns `"GATED"` -- registered
or not, whatever config was loaded, no argument or config field can change
it. This is the whole product. A config-driven fail-open knob would defeat
the entire pitch, so `classify()` (`rails_mcp/registry.py`) takes only an
`action_type` argument: there is no parameter through which a caller could
ever make it return anything permissive. `is_action_registered` answers a
separate, purely informational question -- "do I know something about this
action-type's enforcement?" -- and never feeds back into the GATED verdict.
## Quickstart (60 seconds)
```bash
pip install rails-mcp
```
Add to your Claude Desktop/Code MCP config:
```json
{
"mcpServers": {
"rails-mcp": {
"command": "rails-mcp"
}
}
}
```
No console script on PATH? Fall back to `"command": "python", "args": ["-m", "rails_mcp"]`.
By default the registry loads **empty** (honest-empty, not fail-open --
`classify_action` is still unconditionally `GATED` for everything). Point
it at your own action-type config:
```json
{
"mcpServers": {
"rails-mcp": {
"command": "rails-mcp",
"env": { "RAILS_MCP_CONFIG_PATH": "C:\\path\\to\\rails.config.yaml" }
}
}
}
```
See `examples/rails.config.example.yaml` (or `.example.json`) for the
config shape.
## Tools
All six are **read-mostly** -- none of them can write to the sign-off
ledger.
| Tool | Purpose |
|---|---|
| `classify_action(action_type)` | Default-deny verdict: always `"GATED"`. Implemented in `rails_mcp/registry.py::classify`, tested in `tests/test_registry.py` + `tests/test_server.py`. |
| `is_action_registered(action_type)` | Whether the loaded registry has an entry, plus `enforcement_layer`/`enforcement_pointer`/`ceremony` when present. `rails_mcp/routes.py::is_action_registered`, tested in `tests/test_routes.py`. |
| `get_rails_hash()` | 12-hex sha256 digest of the loaded registry + entry count -- the value a human sign-off records. `rails_mcp/registry.py::rails_hash`, tested in `tests/test_registry.py`. |
| `get_signoff_state()` | Current active human sign-off, or `null`. Read-only. `rails_mcp/registry.py::load_signoff_state`, tested in `tests/test_registry.py` + `tests/test_routes.py`. |
| `record_spend_intent(amount_usd, vendor, purpose, actor)` | Append one spend-intent record. Never calls a vendor or paid API. `rails_mcp/spend_ledger.py::record_spend_intent`, tested in `tests/test_spend_ledger.py`. |
| `evaluate_budget(limit_usd, window_days=30.0)` | Rolling-window spend total vs. limit. Never raises. `rails_mcp/spend_ledger.py::evaluate_budget`, tested in `tests/test_spend_ledger.py`. |
## The CLI-only sign-off boundary -- and why it exists
`append_signoff` -- the function that records a human blessing the
registry's current hash -- is **deliberately not an MCP tool**, and never
will be. It is exposed only as a CLI command a human runs by hand:
```bash
rails-mcp sign --operator "jaime" --note "reviewed 2026-07-16 config"
```
**Why:** the boundary exists to prevent an agent holding only this
server's MCP tool connection from self-approving an irreversible action.
If `append_signoff` were reachable as an MCP tool, any agent holding this
server's connection could sign its own registry -- silently defeating the
one thing the boundary exists to enforce. This mirrors the internal design
rule the original `shared/rails/` implementation was built around: **the
lane that builds the auditor never signs the registry it ships.** An
auditor that can also sign isn't an auditor.
**What this boundary does *not* prove:** the sign-off ledger has no
cryptographic tamper-evidence and no binding to a real human identity --
its integrity rests entirely on filesystem ACLs and the self-hosted
deployment model, not on cryptography. An agent (or anyone) with shell or
file-write access to the ledger's path can run `rails-mcp sign` itself, or
hand-append a forged `{"type": "signoff", ...}` JSONL line straight into
the file -- the ledger has no way to tell that apart from a real CLI
invocation. The MCP-only boundary stops the narrower case of an agent that
has *only* this server's MCP tool connection; it is not proof that a human
reviewed anything, and shouldn't be read as one.
This boundary is enforced structurally, not just by convention:
- `rails_mcp/server.py` and `rails_mcp/routes.py` never import or call
`append_signoff`, anywhere -- proven by an AST-based check (not a naive
string grep, which would false-positive on this very explanation
appearing in their docstrings) in
`tests/test_server.py::test_append_signoff_unreachable_via_any_mcp_tool`.
- The registered MCP tool set is exactly the 6 read-mostly tools above --
no `sign`/`append_signoff`/`revoke_signoff` tool exists, checked in
`tests/test_server.py::test_all_six_rails_tools_registered`.
- A behavioral test drives every registered tool and confirms the sign-off
ledger file is never created
(`test_no_registered_tool_can_create_a_signoff_record`).
- `run_server.py` (the entrypoint `~/.claude.json` invokes) imports only
`rails_mcp.server`, never `rails_mcp.cli` -- so even the process that
serves MCP tools has no code path to the `sign` subcommand.
## Env vars
| Var | Default | Purpose |
|---|---|---|
| `RAILS_MCP_CONFIG_PATH` | unset | Path to your `rails.config.{yaml,yml,json}`. Unset = honest-empty registry (nothing registered, `classify_action` still unconditionally `GATED`). |
| `RAILS_MCP_SIGNOFF_LEDGER_PATH` | `./rails_data/signoff.jsonl` | Where the append-only sign-off ledger lives. |
| `RAILS_MCP_SPEND_LEDGER_PATH` | `./rails_data/spend.jsonl` | Where the append-only spend-intent ledger lives. |
## Config file shape
```yaml
actions:
deploy_prod:
enforcement_layer: "L1"
enforcement_pointer: "CI gate requires a passing e2e suite + a manual approve step"
ceremony: "operator hand"
```
Or the more compact 3-element form (matches the internal registry's native
shape):
```yaml
actions:
deploy_prod: ["L1", "CI gate requires a passing e2e suite + a manual approve step", "operator hand"]
```
JSON works identically (`{"actions": {"deploy_prod": [...]}}`). See
`examples/` for full examples of both.
`enforcement_layer` should be **honest, not aspirational** -- `"prose"`
(no structural rail exists yet, just a doc) is a legitimate, correct value.
Rounding a `"prose"` entry up to `"L1"` because it feels better defeats the
entire point of an honest registry.
## Testing
```bash
.venv/Scripts/python.exe -m pytest -q
```
CI (`.github/workflows/ci.yml`) runs this suite on every push/PR and fails
the build if the Tests badge above drifts from what the suite actually
reports -- see `scripts/check_readme_counts.py`.
106 tests, all hermetic (every ledger/config path goes through `tmp_path` +
an autouse env-isolation fixture in `tests/conftest.py`; nothing touches a
real `./rails_data/`). No network, no live-smoke gate needed -- this
server has no external API to fake.
- `tests/test_registry.py` (24) -- the ported + generalized registry logic:
default-deny property tests, immutability, hash determinism, sign-off
ledger fold/append/load, structural no-shell-out proof.
- `tests/test_spend_ledger.py` (14) -- ported near-verbatim from the
internal suite: append/load roundtrips, budget window math, naive-
datetime honest-degrade, structural no-effector proof.
- `tests/test_config.py` (18) -- new: env-var resolution, YWhat people ask about rails-mcp
What is jaimenbell/rails-mcp?
+
jaimenbell/rails-mcp is mcp servers for the Claude AI ecosystem with 0 GitHub stars.
How do I install rails-mcp?
+
You can install rails-mcp by cloning the repository (https://github.com/jaimenbell/rails-mcp) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.
Is jaimenbell/rails-mcp safe to use?
+
jaimenbell/rails-mcp has not been audited yet by our security agent. Review the original repository on GitHub before using it in production.
Who maintains jaimenbell/rails-mcp?
+
jaimenbell/rails-mcp is maintained by jaimenbell. The last recorded GitHub activity is from today, with 0 open issues.
Are there alternatives to rails-mcp?
+
Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.
Deploy rails-mcp to your cloud
Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.
Maintain this repo? Add a badge to your README
Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.
[](https://claudewave.com/repo/jaimenbell-rails-mcp)<a href="https://claudewave.com/repo/jaimenbell-rails-mcp"><img src="https://claudewave.com/api/badge/jaimenbell-rails-mcp" alt="Featured on ClaudeWave: jaimenbell/rails-mcp" width="320" height="64" /></a>More MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
The fastest path to AI-powered full stack observability, even for lean teams.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!