Skip to main content
Glama
README.md
# hitl-proxy

> **Human-in-the-Loop MCP Server for LLM Agents**

A Model Context Protocol (MCP) server that **enforces human approval** before any LLM agent can edit files, create files, or execute shell commands.

Works with any MCP-compatible IDE: opencode, Cursor, Windsurf, VS Code Copilot, and others.

---

## The Problem It Solves

LLMs are trained to be helpful and complete tasks — which means they tend to **assume, infer, and act** without checking with the human first. In a coding assistant context, this leads to:

- Unreviewed file edits
- Destructive commands run without warning
- Assumed context that was never verified

`hitl-proxy` inserts a **mandatory human checkpoint** before every write operation.

---

## How It Works

```
LLM wants to edit a file
        │
        ▼
  edit_hitl({ ..., approved: false })     ← First call
        │
        ▼
  HITL Proxy blocks + issues sessionToken
  Returns: "Use question() to ask the user. Token: abc-123"
        │
        ▼
  LLM uses question() → human sees options → human approves
        │
        ▼
  edit_hitl({ ..., approved: true, sessionToken: "abc-123" })  ← Second call
        │
        ▼
  HITL Proxy validates token → executes edit → logs to audit file
```

---

## Enforcement Mechanisms

| Mechanism | Description |
|---|---|
| **Session Tokens** | `approved: true` alone is not enough. LLM must present a valid single-use token issued during the block phase |
| **Violation Counter** | Counts how many times the LLM tried to bypass HITL. Escalates warning messages at 2+ violations |
| **declare_intent_hitl** | Optional tool for the LLM to declare intent *before* asking the user. Best practice flow |
| **Self-check Block** | Every blocked response includes a mandatory self-evaluation prompt for the LLM |
| **Audit Log** | NDJSON log of every action (approved or blocked) with timestamp and metadata |
| **Path Traversal Protection** | File paths are validated against `HITL_PROJECT_ROOT` to prevent access outside the project |
| **Cross-platform Bash** | Uses `cmd /c` on Windows, `sh -c` on Unix. Configurable timeout |

---

## Installation

```bash
# In your project directory
mkdir hitl-proxy
cd hitl-proxy

# Copy src/index.js and package.json from this repo
npm install
```

---

## Configuration

### opencode.json

```json
{
  "mcp": {
    "hitl-proxy": {
      "type": "local",
      "command": ["node", "./hitl-proxy/src/index.js"],
      "enabled": true
    }
  },
  "permission": {
    "edit": "deny",
    "write": "deny"
  }
}
```

> **Critical:** The `"edit": "deny"` and `"write": "deny"` permissions are **mandatory**.
> Without them, the LLM will use the native IDE tools and bypass the proxy entirely.

See `config/opencode.example.json` for a full example.

### Environment Variables

| Variable | Default | Description |
|---|---|---|
| `HITL_PROJECT_ROOT` | `process.cwd()` | Root directory. File paths are validated against this |
| `HITL_AUDIT_LOG` | `./hitl-audit.log` | Path to the NDJSON audit log file |
| `HITL_TOKEN_TTL` | `300000` (5 min) | Session token TTL in milliseconds |
| `HITL_BASH_TIMEOUT` | `30000` (30 sec) | Shell command timeout in milliseconds |
| `HITL_QUESTION_TOOL` | `question` | Name of the IDE's human-input tool |

Set them in your MCP server command:

```json
"command": ["node", "./hitl-proxy/src/index.js"],
"env": {
  "HITL_PROJECT_ROOT": "/path/to/your/project",
  "HITL_TOKEN_TTL": "600000"
}
```

---

## Tools Reference

### `declare_intent_hitl` *(best practice — call before question())*

Declares what the LLM intends to do and why. Returns a `sessionToken`.

```
Parameters:
  action  — "edit" | "write" | "bash"
  target  — file path or command
  reason  — why this action is needed now
```

### `edit_hitl`

Edits an existing file by replacing a text fragment.

```
Parameters:
  filePath     — file to edit
  oldString    — exact text to replace
  newString    — replacement text
  replaceAll   — (optional) replace all occurrences, not just first
  approved     — true only if user approved via question()
  sessionToken — token from block response or declare_intent_hitl
```

### `write_hitl`

Creates or overwrites a file.

```
Parameters:
  filePath     — file to create
  content      — full file content
  approved     — true only if user approved via question()
  sessionToken — token from block response or declare_intent_hitl
```

### `bash_hitl`

Executes a shell command.

```
Parameters:
  command      — shell command to run
  approved     — true only if user approved via question()
  sessionToken — token from block response or declare_intent_hitl
```

---

## Ideal LLM Workflow (Best Practice)

```
1. declare_intent_hitl({ action: "edit", target: "src/app.js", reason: "Fix typo in error message" })
   → receives sessionToken: "uuid-xxx"

2. question("¿Apruebas editar src/app.js para corregir el mensaje de error?", options: ["✅ Sí", "❌ No"])
   → user selects "✅ Sí"

3. edit_hitl({ filePath: "src/app.js", oldString: "...", newString: "...", approved: true, sessionToken: "uuid-xxx" })
   → ✅ Editado: src/app.js
```

---

## Audit Log Format

Each line is a JSON object:

```json
{"ts":"2026-08-29T21:30:00.000Z","action":"edit","target":"src/app.js","approved":true,"violations":0}
{"ts":"2026-08-29T21:31:00.000Z","action":"bash","target":"rm -rf /","approved":false,"violations":1}
```

---

## Compatibility

| IDE/Tool | Compatible | Notes |
|---|---|---|
| **opencode** | ✅ Native | `question()` is built-in |
| **Cursor** | ⚠️ Partial | MCP supported; `question()` depends on implementation |
| **Windsurf** | ✅ Likely | Active MCP support |
| **VS Code Copilot** | ⚠️ Partial | Growing MCP support |
| **Custom agents** | ✅ Adaptable | Replace `question()` with any human-input mechanism |

> If your IDE doesn't have `question()`, the LLM can still ask in plain text — but there's no UI enforcement.
> Set `HITL_QUESTION_TOOL` to match your IDE's tool name.

---

## System Prompt Integration

For maximum HITL enforcement, load `docs/hitl_protocol.md` as the **last** system instruction in your IDE config.

> Tokens at the **end** of the context receive more attention from transformers (recency bias).
> Loading HITL rules last maximizes compliance.

See `docs/llm_enforcement.md` for the full guide on writing effective HITL system prompts.

---

## License

MIT

TDQS

A4.4/5.0

Scored across 4 tools

Disambiguation4/5

Each tool targets a distinct action (declare intent, edit, write, bash), but edit_hitl and write_hitl both handle file changes and the relationship between declare_intent_hitl's sessionToken and the per-action sessionToken is not fully clear. Overall, agents should be able to select the right tool with the guidance provided.

Naming Consistency4/5

All tools share the _hitl suffix and follow a verb prefix pattern. declare_intent_hitl uses a compound verb while the others use single verbs, creating a slight inconsistency, but the pattern remains predictable and readable.

Tool Count5/5

Four tools is well-scoped for a proxy server that adds human-in-the-loop approval to common operations. Each tool has a clear role and no redundant entries inflate the surface.

Completeness4/5

The server covers the core file edit/write and shell execution actions needed for an approval proxy, plus an explicit intent declaration step. Missing session audit/cancel tools are minor gaps that don't block the primary workflow.

Maintenance

ActivityMaintained
ResponsivenessNo issues