mcp-hitl-wrapper
v1.0.0
Published
Universal MCP proxy with Human-in-the-Loop approval flow via Telegram
Maintainers
Readme
mcp-hitl-wrapper
Human-in-the-loop approval for AI agent tool calls.
AI agents with MCP access can post messages, create tickets, delete resources — all autonomously. This proxy sits between your agent and upstream MCP servers, intercepting dangerous tool calls and requiring your explicit approval via Telegram before execution.
┌─────────────┐ ┌───────────────────┐ ┌─────────────────┐
│ Agent │────>│ mcp-hitl-wrapper │────>│ MCP: Slack │
│ (Claude, │ │ │────>│ MCP: GitHub │
│ Cursor) │<────│ proxy + approve │────>│ MCP: Jira │
└─────────────┘ └────────┬──────────┘ └─────────────────┘
│
┌────────v──────────┐
│ Telegram Bot │
│ [✅] [❌] │
└───────────────────┘Agent calls a tool → wrapper checks access rules → if HITL required, you get a Telegram message with Approve/Reject buttons → result forwarded to agent or error on reject/timeout. Everything is logged.
Quick start
Claude Desktop / Cursor
Add to your MCP client config (claude_desktop_config.json or Cursor settings):
{
"mcpServers": {
"safe": {
"command": "npx",
"args": ["-y", "mcp-hitl-wrapper", "serve", "--config", "./config.json"],
"env": {
"TG_BOT_TOKEN": "your-bot-token",
"TG_CHAT_ID": "your-chat-id",
"SLACK_BOT_TOKEN": "xoxb-..."
}
}
}
}This launches the wrapper and passes environment variables to it. The wrapper reads its own config.json where ${ENV_VAR} references are substituted from these values. All variables used in config.json must be listed here.
Create config.json — see config example below.
Docker
docker pull ghcr.io/underwear/mcp-hitl-wrapper:latest
docker run -v ./config.json:/app/config/config.json:ro \
-e TG_BOT_TOKEN=... -e TG_CHAT_ID=... \
ghcr.io/underwear/mcp-hitl-wrappernpm
npm install -g mcp-hitl-wrapper
mcp-hitl serve --config config.jsonConfig at a glance
{
"destinations": {
"tg": {
"driver": "telegram",
"botToken": "${TG_BOT_TOKEN}",
"chatId": "${TG_CHAT_ID}"
}
},
"mcps": {
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": { "SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}" },
"tools": { "block": ["users_deletePhoto"] }
}
},
"hitl": {
"defaultDestination": "tg",
"tools": {
"slack": {
"chat_postMessage": {},
"chat_delete": { "timeout": "1m" }
}
}
}
}${ENV_VAR} values are substituted from environment at load time. Full reference: docs/configuration.md
Features
- Universal proxy — wrap any MCP server: stdio, SSE, Streamable HTTP
- Telegram approval — approve/reject with one tap, auto-reject on timeout (requires a dedicated bot)
- Access control — allow-all, whitelist, or blocklist per upstream MCP
- Tool namespacing — automatic
{mcp}__{tool}prefix prevents collisions - Audit trail — SQLite log of every call with decision, latency, user
- Discovery — detect new tools from upstream MCPs, auto-block in whitelist mode
- CLI toolkit — validate, discover, diff, audit query/export
- Docker ready — multi-stage image on ghcr.io, compose included
What approval looks like
When a HITL-configured tool is called:
🔔 HITL Approval Request
Agent: claude-code
MCP: slack → chat_postMessage
Parameters:
{"channel": "#general", "text": "Hello team!"}
⏱ Auto-reject in 3:00
[✅ Approve] [❌ Reject]Approve → tool executes, result goes back to agent. Reject or timeout → agent gets an error, nothing happens.
CLI
mcp-hitl serve --config config.json # start proxy
mcp-hitl validate config.json # validate config
mcp-hitl discover --config config.json # list upstream tools
mcp-hitl diff --config config.json # tool access status
mcp-hitl audit list --last 20 # query audit log
mcp-hitl audit export --format csv # export audit dataDocumentation
- Configuration — full config reference, transports, access control, HITL rules
- CLI Reference — all commands and options
- Docker — ghcr.io, local build, docker-compose
Development
npm install && npm run build
npm test # vitest
npm run lint # eslint
npm run typecheck # tsc