loading…
Search for a command to run...
loading…
Security gateway for MCP tool calls. Sits between your LLM client and MCP servers, enforcing per-tool policies (allow/block/approve/read-only), logging every ca
Security gateway for MCP tool calls. Sits between your LLM client and MCP servers, enforcing per-tool policies (allow/block/approve/read-only), logging every call, and pausing dangerous operations for human approval in terminal or Slack.
Cordon for MCP
Quickstart • Why Cordon • How It Works • Configuration • Roadmap • Contributing
Every company wants to deploy AI agents. No company is willing to give an agent the keys to their database.
Cordon closes the trust gap.
From the maintainer of Agent Toolbelt — 25+ MCP tools, ~1,600 weekly npm downloads.
https://github.com/user-attachments/assets/153d978f-6303-443a-b49b-b4ec7ebf0452
The Model Context Protocol (MCP) has made it trivially easy to give AI agents access to powerful tools — databases, file systems, APIs, cloud infrastructure.
But MCP has no built-in security model. No audit logs. No approval workflows. No rate limits. Today, an AI agent is either off or full admin. There is nothing in between.
This is the single biggest blocker preventing AI agents from reaching production.
Cordon is the security gateway that sits between the LLM and your MCP servers.
It acts as a firewall, an auditor, and a remote control — giving you complete visibility and authority over what your AI agents can and cannot do.
┌─────────┐ ┌──────────┐ ┌──────────────┐
│ LLM / │ ──▶ │ Cordon │ ──▶ │ MCP Server │
│ Agent │ ◀── │ Gateway │ ◀── │ (database, │
└─────────┘ └──────────┘ │ fs, APIs) │
│ └──────────────┘
├── Policy Engine
├── Audit Logger
└── Approval Workflows
No infrastructure changes. No rewrites. One config file.
Step 1 — Initialize
Run this inside your project (where your claude_desktop_config.json exists):
npx @getcordon/cli init
This reads your existing Claude Desktop MCP config, generates cordon.config.ts, and patches Claude Desktop to route all tool calls through Cordon.
Step 2 — Start
npx @getcordon/cli start
Cordon starts, connects to your MCP servers, and begins intercepting tool calls. Restart Claude Desktop and every tool call now flows through the gateway.
If you prefer to configure manually, install globally and create a config:
npm install -g @getcordon/cli
cordon init
cordon init generates a cordon.config.ts:
import { defineConfig } from '@getcordon/policy';
export default defineConfig({
servers: [
{
name: 'database',
transport: 'stdio',
command: 'npx',
args: ['-y', '@my-org/db-mcp-server'],
policy: 'read-only', // Block all write operations
},
{
name: 'github',
transport: 'stdio',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-github'],
policy: 'approve-writes', // Reads pass; writes require approval
tools: {
delete_branch: 'block', // Never, regardless of approval
},
},
],
audit: {
enabled: true,
output: 'stdout', // or 'file'
},
approvals: {
channel: 'terminal',
timeoutMs: 60_000, // auto-deny after 60s if no response
},
});
| Without Cordon | With Cordon |
|---|---|
| Agent has unrestricted tool access | Granular per-tool policies |
| No visibility into what agents did | Structured audit trail of every call |
| "Did the agent just drop a table?" | Real-time terminal approvals |
| Reads and writes treated the same | approve-writes lets reads through automatically |
| Compliance team says no to AI | Audit logs ready for export |
Define rules per tool, per server, or globally. Tool-level policies override server policies.
// Server-level default
policy: 'approve-writes',
// Per-tool overrides
tools: {
query: 'allow', // reads: pass through
execute: 'approve', // writes: pause for human approval
drop_table: 'block', // catastrophic: always reject
list_tables: 'log-only', // audit but don't interrupt
},
When a tool call requires approval, Cordon pauses the agent and prompts you directly in your terminal:
╔══════════════════════════════════════╗
║ ⚠ APPROVAL REQUIRED ║
╚══════════════════════════════════════╝
Server : database
Tool : execute_sql
Args :
{
"query": "DELETE FROM sessions WHERE expires_at < NOW()"
}
[A]pprove [D]eny
>
The agent waits. You decide.
Every tool call is logged as structured JSON — the request, the policy decision, the response, and timing. Pipe to stdout or write to a file for your compliance team.
{"event":"tool_call_received","callId":"...","serverName":"database","toolName":"execute_sql","timestamp":1773434469641}
{"event":"approval_requested","callId":"...","serverName":"database","toolName":"execute_sql","timestamp":1773434469641}
{"event":"tool_call_approved","callId":"...","serverName":"database","toolName":"execute_sql","timestamp":1773434471203}
{"event":"tool_call_completed","callId":"...","durationMs":34,"isError":false,"timestamp":1773434471237}
One policy setting to block all write operations across a server. Zero guesswork about what counts as a write — Cordon detects it from the tool name.
policy: 'read-only' // any tool starting with write/create/update/delete/drop/execute/... is blocked
For tools the model should never even see — not just rejected on call, but filtered from the tools/list response entirely. Closes a prompt-injection surface: if the model never knows a tool exists, it can't be tricked into calling it.
{
name: 'database',
policy: 'approve-writes',
tools: {
drop_table: 'block', // call attempts are rejected
internal_admin: 'hidden', // not advertised to the client at all
},
}
For database MCP servers where a single tool takes arbitrary SQL (Postgres, SQLite, BigQuery, etc.), tool-name heuristics aren't precise enough — the name query doesn't tell you whether the agent's about to SELECT or DROP TABLE. Cordon ships two policies that parse the SQL itself and decide based on the statement type.
tools: {
// Allow SELECTs (including CTEs that wrap a SELECT). Block everything else.
query: 'sql-read-only',
// Reads pass; writes (INSERT/UPDATE/DELETE/DROP/ALTER/...) pause for human approval.
execute: 'sql-approve-writes',
// When the tool takes SQL in a different arg name:
run: { action: 'sql-read-only', sqlArg: 'statement' },
}
Both policies use the PostgreSQL dialect by default (others coming later) and are fail-closed: unparseable SQL is blocked rather than allowed. Prompt-injection patterns like SELECT 1; DROP TABLE users; and block-comment-wrapped keywords are correctly classified as writes by the AST parser.
Declare the exact tool surface your upstream server is expected to advertise. When the upstream adds a new tool in a future release, Cordon blocks it automatically until you explicitly promote it.
{
name: 'postgres',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-postgres', process.env.POSTGRES_URL!],
policy: 'read-only',
knownTools: ['query', 'list_tables', 'describe_table'], // your approved surface
onUnknownTool: 'block', // default when knownTools is set
}
If the next Postgres MCP release adds truncate_table, Cordon blocks it with a stderr warning — no policy update needed. Leave knownTools undefined for backwards-compatible open-world behavior.

Per-tool policies catch dangerous individual calls. Call-graph rules catch dangerous sequences — combinations of allowed tools used in unintended order. The classic example: an agent reads sensitive data with read_data (allowed) and then writes it to disk with write_file (also allowed). Each call is fine in isolation. The sequence is the problem.
Cordon's call-graph rules apply on top of your per-tool policy and are additive — they can only raise severity, never lower it.
export default defineConfig({
// Optional identity for this Cordon process. Surfaced in audit logs.
agentId: 'et-acquisition-agent',
servers: [{
name: 'demo-db',
transport: 'stdio',
command: 'npx',
args: ['-y', '@my-org/db-mcp'],
policy: 'approve-writes',
}],
callGraph: [
// Reading data and then writing to disk is the exfil shape — block it,
// even though write_file would only require approval on its own.
{ from: 'read_data', to: 'write_file', action: 'block',
reason: 'No file writes after database reads.' },
// Glob matching: any send_* tool right after a database read needs human approval.
{ from: 'read_data', to: 'send_*', action: 'approve' },
// `*` matches anything: after a privileged read, every next call must be approved.
{ from: 'sensitive_read', to: '*', action: 'approve' },
],
});
How rules combine with per-tool policies (additive severity):
Severity ordering: allow < approve < block.
A call-graph rule only takes effect if its action is higher severity than the per-tool decision. That means:
action: 'block' overrides an allow per-tool policy ✓action: 'approve' overrides allow, but doesn't lower a block ✓action: 'allow' never weakens a stricter base — it's a no-opOne-way ratchet — tighten only, never loosen.
Audit log gains chain context. Every tool_call_received event now carries previousTool (what was last successfully executed). Blocked / approval-required events also include callGraphRule: { from, to } when a rule fired:
{"event":"tool_call_received","toolName":"write_file","previousTool":"read_data","..."}
{"event":"tool_call_blocked","toolName":"write_file","reason":"No file writes after database reads.","previousTool":"read_data","callGraphRule":{"from":"read_data","to":"write_file"},"..."}
v1 scoping (read this if you're shipping per-agent policies in production):
In stdio mode (and in the current single-tenant HTTP mode), one Cordon process serves one agent identity — so agentId in config identifies this deployment, not a per-call runtime identity. The multi-tenant HTTP gateway (where one Cordon instance hosts many agents with distinct policies) is still queued — agentId will gain an HTTP-header source then, no config rewrite needed.
lastTool advances only on a clean upstream return. Blocked, denied, and transport-failed calls don't advance the chain. v1 doesn't catch probing — an adversary that attempts fs:delete (blocked) and then http:post will see http:post evaluated against whatever came before the probe, not the probe itself. We chose this on purpose: tracking every blocked attempt false-positives too aggressively on legitimate flows that get blocked once and continue. Revisit when there's real adversarial-misuse data.
Cordon runs as a single aggregating MCP proxy. Instead of Claude Desktop connecting directly to your MCP servers, it connects to Cordon. Cordon then manages your servers internally.
Before: Claude ──▶ MCP Server A (full access)
Claude ──▶ MCP Server B (full access)
After: Claude ──▶ Cordon ──▶ MCP Server A (governed)
──▶ MCP Server B (governed)
Your LLM client and MCP servers don't change at all. cordon init handles the config patching.
For clients that talk MCP over HTTP — n8n's MCP Client Tool node, hosted agents that can't spawn child processes, anything else that wants a network endpoint — Cordon can run as an HTTP listener instead of stdio.
export CORDON_GATEWAY_TOKEN=your-secret-token
npx @getcordon/cli start --http --port 7777
# [cordon] HTTP gateway listening on http://127.0.0.1:7777/mcp
Or set it in your config:
import { defineConfig } from '@getcordon/policy';
export default defineConfig({
servers: [ /* your upstream MCP servers */ ],
gateway: {
transport: 'http',
authToken: process.env.CORDON_GATEWAY_TOKEN!,
port: 7777,
host: '127.0.0.1', // localhost-only by default; set to '0.0.0.0' to expose externally
},
});
Clients connect to http://your-host:7777/mcp with an Authorization: Bearer <your-token> header. The transport implements the MCP Streamable HTTP spec — one endpoint serves both SSE response streams and direct JSON responses, with session multiplexing handled by session IDs in headers.
All policy enforcement, audit logging, and approval workflows work identically across transports — only the wire protocol changes.
Connecting from n8n. Point the MCP Client Tool node at your Cordon URL with the Bearer token as the credential. The deprecated SSE-only endpoint shape (older n8n versions on the legacy MCP SSE spec) is not implemented — Cordon ships only the current Streamable HTTP transport, which n8n is migrating to.
Single-tenant. The current HTTP transport is single-tenant: all sessions share the same upstream MCP servers, policy config, and auth token. A multi-tenant hosted gateway (per-call agentId from headers, per-tenant policy) is on the roadmap.
| Policy | Behavior |
|---|---|
allow |
Pass through immediately |
block |
Reject — agent receives an error |
approve |
Pause pending human approval in terminal |
approve-writes |
Reads pass through; writes require approval |
read-only |
All write operations are blocked |
log-only |
Pass through but flagged in the audit log |
hidden |
Filtered from tools/list — the model never sees it |
sql-read-only |
Parse the SQL arg, allow SELECT/WITH-SELECT, block everything else (fail-closed on unparseable) |
sql-approve-writes |
Parse the SQL arg, allow reads, pause writes for human approval, block unparseable |
Policies can be set at the server level (default for all tools) or per-tool (overrides the server default):
{
name: 'my-server',
policy: 'approve-writes', // server default
tools: {
safe_read: 'allow', // override: always allow
nuke_db: 'block', // override: always block
},
}
| Channel | Status |
|---|---|
terminal |
Available — interactive prompt in your terminal |
slack |
Available — Block Kit messages, HMAC-verified interactions |
web |
Coming in v0.3 |
webhook |
Coming in v0.3 |
| Output | Status |
|---|---|
stdout |
Available |
file |
Available — JSON lines written to a local file |
hosted |
Available — ships events to the Cordon dashboard |
otlp |
Coming in v0.3 |
| Package | Description |
|---|---|
@getcordon/cli |
The CLI — npx @getcordon/cli start |
@getcordon/policy |
TypeScript config SDK — defineConfig() and all types |
@getcordon/core |
Core proxy engine — policy evaluator, audit logger, approval manager |
knownTools + onUnknownTool for future-proof upstream surface controlsql-read-only / sql-approve-writes parse SQL arguments at call time (PostgreSQL dialect)cordon init — auto-reads Claude Desktop config and patches itmain; ships in v0.2.0)agentId from headersSee examples/security-showcase for a working demo of Cordon intercepting an agent that attempts to drop a production database table.
cd examples/security-showcase
npm install
npm run demo
Enterprise — Centralized governance across all AI agent deployments. Policy-as-code, structured logs, and a clear path to SOC2-ready audit trails.
Startup Team — Deploy agents with confidence. Every tool call is logged, writes require approval, and your compliance team has a trail.
Solo Developer — Secure your local Claude/Cursor setup. See exactly what your agent is calling and block anything dangerous before it reaches production.
Agent Toolbelt — a typed toolkit of ready-made MCP tools (web search, fetch, filesystem, and more). Wire it into Claude Desktop, then route those tool calls through Cordon for policy enforcement and audit logging. Agent Toolbelt gives your agents power; Cordon makes sure they ask before using it.
Build & Ship MCP Tools — the companion course that walks through building your own MCP servers end to end. 7 modules, 33 lessons; Module 0 is free, Module 6 covers securing your server with Cordon.
Cordon is open source and we welcome contributions.
git clone https://github.com/marras0914/cordon.git
cd cordon
npm install
npm run build
npm run dev
MIT — see LICENSE for details.
Stop trusting. Start governing.
⭐ Star on GitHub
Privacy ·
Terms ·
Support ·
Partners
© 2026 Elephant Tortoise LLC
Выполни в терминале:
claude mcp add cordon -- npx CSA PROJECT - FZCO © 2026 IFZA Business Park, DDP, Premises Number 31174 - 001
Безопасность
Низкий рискАвтоматическая эвристика по публичным данным — не гарантия безопасности.