Skip to main content
ClaudeWave
MCP ServersOfficial Registry0 stars0 forksPythonMITUpdated today
Install in Claude Code / Claude Desktop
Method: pip / Python · rails-mcp
Claude Code CLI
claude mcp add rails-mcp -- python -m rails-mcp
claude_desktop_config.json (Claude Desktop)
{
  "mcpServers": {
    "rails-mcp": {
      "command": "python",
      "args": ["-m", "venv"]
    }
  }
}
1. Run the command above in your terminal (Claude Code), or paste the JSON config into claude_desktop_config.json (Claude Desktop).
2. Replace any <placeholder> values with your API keys or paths.
3. Restart Claude. The MCP server and its tools appear automatically.
💡 Install first: pip install rails-mcp
Use cases

MCP Servers overview

# rails-mcp

[![PyPI](https://img.shields.io/pypi/v/rails-mcp)](https://pypi.org/project/rails-mcp/)
[![MCP Registry](https://img.shields.io/badge/MCP%20Registry-io.github.jaimenbell%2Frails--mcp-blue)](https://registry.modelcontextprotocol.io)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![Tests](https://img.shields.io/badge/tests-106%20passing-brightgreen)](#testing)
[![CI](https://github.com/jaimenbell/rails-mcp/actions/workflows/ci.yml/badge.svg)](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, Y

What 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.

Featured on ClaudeWave: jaimenbell/rails-mcp
[![Featured on ClaudeWave](https://claudewave.com/api/badge/jaimenbell-rails-mcp)](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

rails-mcp alternatives