Skip to main content
Glama
README.md
<div align="center">
  <h1>πŸ›‘οΈ Governed Agent MCP Stack</h1>
  <p><strong>Put a fail-closed policy check in front of risky agent toolsβ€”without building a policy service first.</strong></p>

  [![Python 3.10+](https://img.shields.io/badge/Python-3.10%2B-3776AB.svg?style=flat-square&logo=python&logoColor=white)](https://www.python.org/)
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](LICENSE)
  [![CI](https://github.com/IamOumarIbrahim/governed-agent-mcp-stack/actions/workflows/ci.yml/badge.svg)](https://github.com/IamOumarIbrahim/governed-agent-mcp-stack/actions/workflows/ci.yml)
  [![Protocol: MCP](https://img.shields.io/badge/Protocol-MCP-6D28D9.svg?style=flat-square)](https://modelcontextprotocol.io/)
</div>

> [!IMPORTANT]
> **Honest boundary:** this project evaluates and records intended actions. It
> does not magically intercept every MCP call. Enforce decisions with the
> Python wrapper, or make `check_action` a required step in your tool host.

One useful job, end to end: name a capability such as `fs.write_report`, get
`allow`, `approval_required`, or `deny`, and leave a locally verifiable audit
event. No policy daemon, account, or cloud relay is required.

```bash
# Quickstart β€” run the three decisions without cloning (requires uv)
uvx --from git+https://github.com/IamOumarIbrahim/governed-agent-mcp-stack governed-agent demo
```

Expected decisions:

```text
signal.read_file        allow
fs.write_report         approval_required
shell.execute           deny
```

<!-- mcp-name: governed-agent-gate -->
```json
{
  "mcpServers": {
    "governed-agent-gate": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/IamOumarIbrahim/governed-agent-mcp-stack",
        "governed-agent-mcp"
      ]
    }
  }
}
```

<br />

## πŸ“– Table of Contents

- [What is Governed Agent MCP Stack?](#-what-is-governed-agent-mcp-stack)
- [Key Features](#-key-features)
- [System Architecture](#️-system-architecture)
- [Setup & Installation](#-setup--installation)
- [Connecting to AI Clients](#-connecting-to-ai-clients)
- [How to Use](#️-how-to-use)
- [CLI Reference](#-cli-reference)
- [Comparison](#️-comparison)
- [Scope & Limitations](#-scope--limitations)
- [File Structure](#-file-structure)
- [Troubleshooting](#-troubleshooting)
- [Contributing](#-contributing)
- [License](#-license)

---

## πŸ’‘ What is Governed Agent MCP Stack?

Agent tools often jump from harmless reads to file writes, shell execution, or
CAD mutations with scattered `if` statementsβ€”or no check at all. This project
provides a small gate that can be called from Python, a shell pipeline, or an
MCP client before the risky function executes.

Instead of starting with a policy platform, you get:

- **A useful starter policy:** read-only and in-memory work is allowed, writes
  require approval, and destructive or unknown capabilities are denied.
- **One enforcement API:** `Gatekeeper.enforce()` raises before the wrapped
  function body runs.
- **Evidence you can inspect:** every decision is appended to SQLite with a
  SHA-256 hash chained to the previous event.

The best fit is an MCP server author or agent-platform engineer who needs a
reviewable local safety layer today and may graduate to a larger policy system
later.

---

## ✨ Key Features

- πŸ”’ **Fail closed:** unmatched capabilities use `default_action: deny`.
- 🧩 **Exact and glob rules:** express `fs.read_file` or families such as
  `fs.write_*` in plain YAML.
- 🐍 **Real enforcement:** use a Python decorator or `enforce()` before a tool
  body; denied and approval-required calls raise distinct exceptions.
- πŸ”Œ **Real MCP server:** `check_action`, `recent_decisions`, and
  `verify_audit_chain` run over local stdio with FastMCP.
- 🧾 **Tamper-evident audit:** changed or reordered SQLite rows fail chain
  verification. Context stays local and must be JSON serializable.
- πŸ€– **Automation-friendly CLI:** JSON output and stable exit codes let CI,
  shell scripts, and tool hosts gate actions.
- πŸ§ͺ **Useful examples, labeled honestly:** offline DSP helpers and a
  best-effort process scanner remain as optional workloads, not product claims.

---

## βš™οΈ System Architecture

The gate belongs immediately before the code that can cause a side effect.

```mermaid
flowchart LR
    Agent["Agent or MCP client"] --> Intent["Capability + JSON context"]
    Intent --> Gate["Policy gate"]
    Policy["YAML policy"] --> Gate
    Gate -->|"allow"| Tool["Wrapped tool function"]
    Gate -->|"approval_required"| Review["Your approval workflow"]
    Gate -->|"deny"| Stop["Stop"]
    Gate --> Audit[("Local SQLite hash chain")]

    classDef default fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#fff;
    classDef gate fill:#1e1b4b,stroke:#a855f7,stroke-width:2px,color:#fff;
    class Gate gate;
```

> [!NOTE]
> **Approval is a decision, not an approval system.** This repository signals
> `approval_required`; your product must collect and validate human approval
> before retrying the protected action.

---

## πŸš€ Setup & Installation

### Option A: Run from GitHub with uv

```bash
uvx --from git+https://github.com/IamOumarIbrahim/governed-agent-mcp-stack governed-agent demo
```

[`uvx`](https://docs.astral.sh/uv/guides/tools/) runs the command in an
isolated environment. Use this path to evaluate the project without changing
your current Python environment.

### Option B: Clone for development

```bash
git clone https://github.com/IamOumarIbrahim/governed-agent-mcp-stack.git
cd governed-agent-mcp-stack
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
python -m pip install -e ".[all,dev]"
governed-agent doctor
```

Windows users can run `.\setup.ps1` to create `.venv`, install the development
extras, run tests, and print the MCP client config. The script does not edit
client settings.

πŸ” **Verification command:**

```bash
governed-agent doctor
python -m pytest -q
python -m ruff check .
```

Expected test summary for this revision: `22 passed`.

---

## πŸ”Œ Connecting to AI Clients

The zero-edit config near the top uses the packaged starter policy. For a
policy you own:

1. Create a local policy:

   ```bash
   governed-agent init --output ./my-policy.yaml
   ```

2. Print a config containing the current Python executable and absolute paths:

   ```bash
   governed-agent config --policy ./my-policy.yaml
   ```

3. Merge the printed `mcpServers` entry into your client's configuration and
   restart the client.

The server exposes:

| MCP tool | What it does |
| :--- | :--- |
| `check_action` | Evaluates and audits one capability plus optional context |
| `recent_decisions` | Returns recent local decisions, newest first |
| `verify_audit_chain` | Detects modified or reordered audit rows |

FastMCP uses stdio by default for local servers, so the client owns the server
process lifecycle and no HTTP port is opened.

---

## πŸ–₯️ How to Use

### Gate one action from a shell

```bash
governed-agent check fs.write_report \
  --context '{"path":"reports/result.json"}'
```

The JSON response includes the decision, matched rule, reason, policy source,
and audit event hash.

### Enforce before a Python function runs

```python
from interlock import Gatekeeper

gate = Gatekeeper(
    policy_path="interlock.policy.yaml",
    audit_path=".interlock/audit.db",
)

@gate.protect(
    "fs.write_report",
    context_factory=lambda path, content: {"path": path, "bytes": len(content)},
)
def write_report(path: str, content: str) -> None:
    with open(path, "w", encoding="utf-8") as report:
        report.write(content)
```

With the starter policy, `write_report()` raises `ApprovalRequired` before
opening the file. Unknown capabilities raise `ActionDenied`.

### Write a policy

```yaml
version: 1
default_action: deny

capabilities:
  "workspace.read_*":
    mode: allow
    reason: Read-only workspace tools are safe for this host.

  "workspace.write_*":
    mode: approval_required
    reason: A human must review workspace mutations.

  "shell.execute":
    mode: deny
    reason: This host never delegates arbitrary shell execution.
```

Exact rules win over globs. When multiple globs match, the most specific
pattern wins.

---

## πŸ“Š CLI Reference

| Command | Purpose | Exit behavior |
| :--- | :--- | :--- |
| `governed-agent demo` | Run one allow, approval, and deny example | `0` |
| `governed-agent check CAPABILITY` | Evaluate and optionally audit one action | `0` allow, `10` approval, `20` deny |
| `governed-agent doctor` | Validate policy and audit-chain integrity | `0` healthy |
| `governed-agent audit list` | Print recent events as JSON | `0` |
| `governed-agent audit verify` | Verify the event hash chain | `0` valid, `1` invalid |
| `governed-agent init` | Copy the packaged starter policy | `0` |
| `governed-agent config` | Print MCP client JSON; never edits settings | `0` |
| `governed-agent serve` | Run the stdio MCP server | Until client disconnects |

Policy or input errors return `64`.

---

## βš–οΈ Comparison

| Option | Strong fit | Trade-off |
| :--- | :--- | :--- |
| Scattered checks in tool code | One tiny application | Hard to audit and easy to apply inconsistently |
| **This project** | Local Python/MCP prototypes that need a working gate now | Single-process, YAML rules, no identity-aware authorization |
| General policy engines such as OPA or Cedar | Multi-service authorization with a dedicated policy model | More infrastructure and integration work than this starter gate |

This project is a stepping stone, not a claim that a small Python library
replaces a mature organization-wide authorization system.

---

## πŸ”¬ Scope & Limitations

- **No transparent interception:** an MCP client can ignore the gate. Use the
  Python wrapper or enforce the call order in the host you control.
- **No built-in approval UI:** `approval_required` must feed an approval flow
  outside this package.
- **Tamper-evident, not tamper-proof:** the hash chain detects changed or
  reordered rows, but an attacker with filesystem access can delete the whole
  database or truncate its tail. Anchor hashes externally for stronger proof.
- **Local identity only:** rules do not model users, organizations, OAuth
  scopes, or remote multi-tenant authorization.
- **Context is stored:** do not pass secrets in decision context.
- **Optional examples are examples:** `spectramcp/` implements offline numeric
  helpers; `agentpulse/` performs best-effort process-name scanning. Neither is
  a production hardware adapter or transcript telemetry platform.

---

## πŸ“ File Structure

```text
governed-agent-mcp-stack/
β”œβ”€β”€ interlock/
β”‚   β”œβ”€β”€ gate.py                # Enforcement API and decorator
β”‚   β”œβ”€β”€ policy.py              # Validated exact/glob policy engine
β”‚   β”œβ”€β”€ audit.py               # SQLite SHA-256 event chain
β”‚   β”œβ”€β”€ cli.py                 # JSON CLI, doctor, config, audit commands
β”‚   β”œβ”€β”€ server.py              # Three-tool FastMCP stdio server
β”‚   └── default_policy.yaml    # Packaged fail-closed starter policy
β”œβ”€β”€ spectramcp/                # Optional offline DSP example helpers
β”œβ”€β”€ agentpulse/                # Optional local process-scanner example
β”œβ”€β”€ tests/                     # Unit, CLI, and in-process MCP tests
β”œβ”€β”€ interlock.policy.yaml      # Editable repository policy
β”œβ”€β”€ GATES.md                   # Product and release gate checklist
└── pyproject.toml             # Package metadata and console scripts
```

---

## 🩹 Troubleshooting

| Issue | Root Cause | Resolution |
| :--- | :--- | :--- |
| `uvx` is not found | uv is not installed | Use the clone/venv path or install uv from its official documentation |
| Everything returns `deny` | No capability rule matched | Run `governed-agent check ...` and inspect `rule`; add a narrow YAML rule |
| Command exits with `10` | The rule requires approval | Stop; complete your external approval flow before calling the protected tool |
| Client cannot start the server | Client environment cannot find the command or policy | Run `governed-agent config` and use its absolute Python and file paths |
| Audit verification fails | A stored row changed or was reordered | Preserve the DB for investigation; start a new DB only after review |
| DSP imports fail | Optional NumPy extra is absent | Install `.[dsp]` or `.[all]` |

---

## 🧩 Contributing

The highest-value contributions are host adapters that make the gate
unskippable, approval-flow examples, and adversarial tests for policy or audit
edge cases. Start with [CONTRIBUTING.md](CONTRIBUTING.md); every pull request
must pass the gates in [GATES.md](GATES.md).

---

## πŸ“„ License

MIT Β© 2026 [Oumar Ibrahim](https://github.com/IamOumarIbrahim)

## πŸ™ Powered By

[FastMCP](https://gofastmcp.com/) Β·
[PyYAML](https://pyyaml.org/) Β·
[SQLite](https://www.sqlite.org/) Β·
[NumPy](https://numpy.org/)

<div align="center">

If this saved you from rebuilding the same policy gateβ€”or one risky agent
writeβ€”a ⭐ helps other MCP builders find it.

</div>

TDQS

B3/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a distinct aspect of the audit workflow: check_action evaluates an intended capability, recent_decisions lists past decisions, and verify_audit_chain checks log integrity. No two tools overlap in purpose, so an agent can easily select the right one.

Naming Consistency4/5

Two tools follow the verb_noun pattern (check_action, verify_audit_chain), but recent_decisions uses an adjective_noun pattern, which is a minor deviation. The names are still descriptive and predictable.

Tool Count5/5

With only 3 tools, the set is well-scoped for a focused audit and policy-decision utility. Each tool serves a clear and necessary function, and the count is within the ideal 3-15 range.

Completeness4/5

The core audit lifecycle is covered: evaluate an action, list decisions, and verify integrity. Minor gaps exist, such as no way to fetch a single decision by ID or perform detailed historical queries, but these are workable limitations.

Maintenance

ActivityMaintained
ResponsivenessSyncing