SpectraMCP
<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>
[](https://www.python.org/)
[](LICENSE)
[](https://github.com/IamOumarIbrahim/governed-agent-mcp-stack/actions/workflows/ci.yml)
[](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
Scored across 3 tools
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.
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.
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.
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.