permission-aware-mcp
README.md
# Permission-Aware MCP Security System
A production-grade [Model Context Protocol](https://modelcontextprotocol.io) server and client suite that demonstrates **server-enforced permission management**, **risk assessment**, and **tamper-resistant audit logging**.
It exposes four classic file-system tools — `read_file`, `write_file`, `delete_file`, `execute_command` — each classified by risk and gated by a permission policy. Privileged operations cannot run without explicit, single-use human approval.
## Why this design
The defining principle: **the server is the security boundary.** A server can never trust a client to enforce restrictions on its behalf, so every check — policy, path confinement, approval, audit — happens server-side. Clients only *surface* the server's decisions and relay an explicit human approval back.
| Control | How it works |
|---|---|
| **Sandbox confinement** | All file access passes through `safe_resolve`, which rejects absolute paths and `..` traversal and verifies (after symlink resolution) that the target stays inside `data/`. Enforced at execution — even an *approved* escape attempt is blocked. |
| **Permission policy** | Each tool maps to `allow` / `ask` / `deny`. Stored in `config/permissions.json`, hot-reloaded on change, with risk-derived fail-safe defaults for unknown tools. |
| **Risk assessment** | Every tool has an inherent risk (`low`→`critical`) from a single source of truth, shown at approval time and used to derive default policy. |
| **Human-in-the-loop approval** | An `ask` operation returns a **single-use, args-bound, expiring token** instead of executing. It runs only when a human relays the token back via `approve_operation`. The LLM never receives that tool, so it cannot approve its own calls. |
| **Audit trail** | Append-only JSONL with UTC timestamps, stored *outside* the sandbox so the file tools can't tamper with it. Every decision, approval, and outcome is recorded. |
## Architecture
```
┌──────────────────┐ stdio (MCP) ┌────────────────────────────┐
│ Client / Host │ ────────────────────────► │ server.py (the boundary) │
│ │ │ │
│ gui_client.py │ call_tool ──────────► │ ┌──────────────────────┐ │
│ host_app.py │ │ │ policy → risk → gate │ │
│ cli_demo.py │ ◄─ approval_required ── │ │ allow / ask / deny │ │
│ │ │ └──────────┬───────────┘ │
│ (one background │ approve_operation ──► │ safe_resolve (sandbox) │
│ event loop owns │ │ AuditLog (outside data/) │
│ the session) │ ◄─ ok / denied / error │ ApprovalStore (tokens) │
└──────────────────┘ └────────────────────────────┘
```
Tool results use one uniform JSON envelope:
```json
{"status": "ok", "result": "..."}
{"status": "denied", "reason": "...", "risk": "..."}
{"status": "approval_required", "token": "...", "summary": "...", "risk": "...", "expires_in": 180}
{"status": "error", "message": "..."}
```
## Layout
```
mcp_security/
config.py Settings & resolved paths (env-overridable)
paths.py Sandbox confinement (safe_resolve)
risk.py Risk classification (single source of truth)
policy.py allow/ask/deny engine, hot-reloadable
audit.py Append-only JSONL audit log
approvals.py Single-use, args-bound, expiring tokens
server.py FastMCP server — the enforcement boundary
client.py Background-loop MCP connection (Gradio-safe)
gui_client.py Gradio operator console
host_app.py Gradio AI host (OpenAI), human-in-the-loop
cli_demo.py Headless end-to-end demonstration
tests/ Unit tests for the deterministic core
config/ data/ logs/ Runtime state (generated)
```
## Setup
```bash
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
pip install -r requirements.txt
```
For the AI host, copy `.env.example` to `.env` and set `OPENAI_API_KEY`.
## Running
All commands run from the project root.
**Headless demo** (no API key needed — the fastest way to see the whole gate):
```bash
python -m mcp_security.cli_demo
```
**Operator console** (browse tools/resources/prompts, manage policy, view audit log) at http://127.0.0.1:7863:
```bash
python -m mcp_security.gui_client
```
**AI host** (chat with an LLM that uses the gated tools) at http://127.0.0.1:7864:
```bash
python -m mcp_security.host_app
```
The clients launch the server (`python -m mcp_security.server`) themselves over stdio. To point a client at a different server, pass a module path or script file:
```bash
python -m mcp_security.gui_client path/to/other_server.py
```
## Tests
```bash
python -m pytest -q
```
The suite covers the deterministic security core — path confinement (traversal/absolute/symlink-escape), policy defaults & hot-reload, token binding/single-use/expiry, audit structure, and risk classification. The MCP/Gradio/OpenAI layers are exercised by `cli_demo.py` and the included smoke checks.
## Configuration
Every setting is overridable via environment variable (see `.env.example`): sandbox/log/config directories, max file size, and approval-token TTL.
## Security notes & limits
- `execute_command` is **simulated** — it never runs a real subprocess, matching the original's stance. It is denied by default regardless.
- Approval tokens live in the server's memory; restarting the server clears pending approvals (by design — stale approvals should not survive a restart).
- The bundled clients run on `127.0.0.1` with no authentication; they are operator tools, not multi-tenant services. For shared deployment, add authentication and per-session state.
- The server's policy file (`config/permissions.json`) is the control plane; protect it with filesystem permissions in production.
TDQS
A4.2/5.0
Scored across 5 tools
Disambiguation5/5
Each tool has a clearly distinct purpose: read, write, delete files; execute commands; and approve operations. No overlap or ambiguity between them.
Naming Consistency5/5
All tool names follow a consistent verb_noun pattern, e.g., read_file, delete_file, approve_operation. This makes the tool surface predictable and easy to navigate.
Tool Count5/5
With 5 tools, the server is well-scoped for its purpose of providing permission-aware file and command operations. Each tool earns its place without unnecessary complexity.
Completeness4/5
Core file operations (read, write, delete) and command execution are covered. Minor gaps include the lack of a list/directory tool and no way to view pending approvals, but the essential workflow is complete.
Maintenance
ActivityInactive
ResponsivenessNo issues