Are you the author? Sign in to claim
Reliability & security proxy for the Model Context Protocol (MCP). Self-healing connections, runtime tool-poisoning/shad
A reliability & security proxy for the Model Context Protocol (MCP).
Self-healing connections, runtime tool-security, and a compliance-mapped audit trail for your MCP servers.
mcp-bastion sits between your MCP client (Claude Code, Cursor, Cline, Windsurf, Zed, Claude
Desktop, or any MCP-compliant agent) and your MCP servers. It is client-agnostic — it works with
any compliant client through configuration alone, with zero client-specific code — and non-invasive:
your servers run unchanged, and removing Bastion is a one-line config revert.
📦 Package: mcp-bastion on npm · 🗂️ Official MCP Registry: io.github.Gowthaman90/mcp-bastion
🔒 Security, measured: on the open, vendor-neutral mcp-defense-bench, Bastion covers 63% of the MCP attack surface (15.0/24 vectors; 11 enforced) at zero false positives — the broadest of the proxies measured.
📖 Launch story: Medium · dev.to
👤 Created & maintained by Gowthaman Arumugam — Independent Researcher. Companion benchmark: mcp-defense-bench.
When an MCP server disconnects mid-session, the agent only sees a generic "No such tool available" error — indistinguishable from a tool that never existed — and it cannot reconnect; only a human can. Long agent sessions silently lose capabilities and fail in confusing ways.
Bastion closes that gap. It health-checks every server, auto-reconnects with backoff, and — crucially — exposes control tools so the agent itself can inspect connection health and recover a dropped server without human intervention.
Bastion now spans three layers: reliability (v0.1), runtime security (v0.2 — tool pinning / rug-pull & poisoning detection), and audit & compliance (v0.3 — pluggable sinks mapped to NIST AI RMF / OWASP LLM Top 10). See the roadmap.
Today your client connects directly to each server. With Bastion, your client connects to Bastion, which connects to those same servers on your behalf — so it sits in the tool-call path and can add reliability (and, later, security) transparently.
Before: Client ─▶ server A / server B / server C
After: Client ─▶ mcp-bastion ─▶ server A
─▶ server B
─▶ server C
Bastion is a standard MCP server to your client and a standard MCP client to each upstream. Because it speaks the protocol faithfully, it works with every compliant client automatically — the only per-client difference is where you put a few lines of config.
bastion__status and bastion__reconnect let the agent detect and fix
drops itself, instead of hitting an opaque "no such tool" wall.Bastion is published on npm as mcp-bastion — the
npx command below fetches it automatically, so there's nothing to install first.
1. Add Bastion to your client, pointing it at a config file:
// your client's mcpServers config
{
"mcpServers": {
"bastion": {
"command": "npx",
"args": ["-y", "mcp-bastion", "--config", "bastion.config.json"],
},
},
}
2. List your real servers in bastion.config.json (moved verbatim from the client):
{
"servers": {
"github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] },
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
},
},
"reconnect": { "auto": true },
"healthCheck": { "enabled": true },
}
3. Restart your client. Your tools now appear namespaced (e.g. github__create_issue) alongside
Bastion's control tools. See bastion.config.example.json for the
full set of options.
🔒 Security is on by default. Out of the box, Bastion runs the
balancedenforcement profile: it blocks high-confidence attacks (rug-pulls, argument/command injection, cross-server exfiltration, server-identity changes) and warns on heuristic ones (description/response poisoning), while redacting leaked secrets from tool results. Setsecurity.enforcementProfiletoobserve(warn-only) orstrict(block-all), or tune any individual control — see Runtime security.
See the whole thing in action — a server crashing mid-session and healing itself:
npm run demo
It boots Bastion in front of a server that crashes on command, shows the agent getting an actionable
"reconnect" message instead of a cryptic error, and then the connection auto-recovering with no human
involved. To record it as a GIF: asciinema rec demo.cast -c "npm run demo" && agg demo.cast assets/demo.gif.
Bastion injects control tools so the agent can manage connections and review security itself, using only standard MCP calls:
| Tool | Purpose |
|---|---|
bastion__status | Health of every proxied server: connected / disconnected / reconnecting / failed, tool counts, last error. |
bastion__reconnect | Reconnect a named server (argument: { "server": "<name>" }) without human intervention. |
bastion__security | Per-tool security report: pin status (approved vs changed), poisoning findings, and shadowing. |
bastion__approve | Re-approve a changed tool (arguments: { "server": "...", "tool": "..." }) to clear a rug-pull block. |
bastion__compliance | Audit summary of recent activity mapped to NIST AI RMF / OWASP LLM Top 10 (requires audit.enabled). |
| Key | Type | Default | Description |
|---|---|---|---|
servers | map | — | Upstream servers to proxy (required, at least one). |
servers.<name>.command | string | — | Executable to launch (e.g. npx, node). |
servers.<name>.args | string[] | [] | Arguments to command. |
servers.<name>.env | map | — | Env overrides merged over the process env. |
servers.<name>.cwd | string | — | Working directory for the spawned process. |
reconnect.auto | boolean | true | Auto-reconnect after an unexpected disconnect. |
reconnect.maxRetries | number | 10 | Max attempts before giving up (-1 = unlimited). |
reconnect.initialBackoffMs | number | 500 | Initial backoff, doubled each attempt. |
reconnect.maxBackoffMs | number | 30000 | Backoff ceiling. |
healthCheck.enabled | boolean | true | Enable periodic liveness probing. |
healthCheck.intervalMs | number | 30000 | Interval between probes. |
healthCheck.timeoutMs | number | 5000 | Per-probe timeout. |
namespace.strategy | prefix | passthrough | prefix | How upstream tool names are exposed. |
namespace.separator | string | __ | Separator used by the prefix strategy. |
security.pinTools | boolean | true | Pin tool definitions and detect later changes. |
security.onRugPull | block | warn | block | Action when a pinned tool's definition changed. |
security.inspectDescriptions | boolean | true | Run poisoning heuristics on tool descriptions. |
security.onPoisoning | block | warn | warn | Action on a high-severity poisoning finding. |
audit.enabled | boolean | false | Record an audit event for every tool call. |
audit.includeArgs | none|redacted|full | none | How tool arguments are recorded. |
audit.tamperEvident | boolean | false | Hash-chain events so tampering is detectable. |
audit.sinks | array | console | Destinations: console, file, webhook, otlp. |
servers.<name>.transport | stdio | http | stdio | Local subprocess or remote endpoint. |
servers.<name>.url | string | — | Remote MCP URL (required for http). |
servers.<name>.headers | map | — | Headers for http upstreams (e.g. Authorization). |
listen.mode | stdio | http | stdio | Serve Bastion over stdio or Streamable HTTP. |
listen.host / listen.port | string / number | 127.0.0.1 / 3000 | Bind address for http mode. |
Bastion speaks two transports on both faces:
servers.<name> with transport: "http",
a url, and optional auth headers), and/or serve Bastion over HTTP to multiple/remote clients
(listen.mode: "http", or --http <port>).HTTP upstreams configured without an authentication header are flagged (authenticated: false) in
bastion__status and warned at connect time.
New in v0.2. Bastion adds a security layer in the tool-call path (an interceptor pipeline), enabled by default:
onRugPull: "block") until you review it and
re-approve with bastion__approve. This catches a server that looks benign at install time and
turns malicious afterward.warn (logged
and reported, not blocked); set onPoisoning: "block" to enforce.Review everything with the bastion__security tool. These checks apply to local stdio servers today;
authentication checks for remote servers arrive with HTTP transport support.
New in v0.3, opt-in. Enable audit to record a structured, versioned event for every tool call —
including calls blocked by the security layer:
"audit": {
"enabled": true,
"includeArgs": "redacted", // none | redacted | full
"tamperEvident": true, // hash-chain events
"sinks": [
{ "type": "file", "path": "./bastion-audit.jsonl" },
{ "type": "webhook", "url": "https://collector.example/v1/audit" }
]
}
console (stderr JSONL), file (JSONL append), webhook (batched POST), and
otlp (native OpenTelemetry logs export — point it at an OTel Collector to fan out to any SIEM/cloud
backend). The sink interface makes new destinations additive.bastion__compliance returns an aggregate report of recent activity.tamperEvident, events are hash-chained; the exported verifyChain helper
detects any retroactive edit or deletion.includeArgs to redacted to keep structure
while masking sensitive keys.Every security check maps to recognized frameworks — the NIST AI Risk Management Framework (a U.S. federal standard), the OWASP Top 10 for LLM (2025) and Agentic (2026) Applications, and STRIDE. The full per-check mapping (with reference links to the MCP-security literature the checks are drawn from) is in docs/CHECKS-MAPPING.md.
Coverage is measured independently — bastion is scored against a 22-vector attack surface by the vendor-neutral mcp-defense-bench benchmark, with a public leaderboard and framework mapping.
The steps are identical for every client — only the config file location differs:
| Client | Where to add the bastion entry |
|---|---|
| Claude Code | project .mcp.json (or claude mcp add) |
| Cursor | ~/.cursor/mcp.json or project .cursor/mcp.json |
| Claude Desktop | claude_desktop_config.json |
| Cline | cline_mcp_settings.json |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
Gradual adoption: you don't have to route every server through Bastion — put only your flaky or untrusted servers behind it and leave the rest connected directly.
Bastion is organized into clear layers with a one-directional dependency flow, so each concern is independently testable and easy to evolve:
src/
├── cli.ts # thin CLI entrypoint (parse → wire → serve)
├── index.ts # public library API
├── errors.ts # error hierarchy (BastionError, …)
├── config/ # schema (Zod) + loader
├── core/ # domain: upstream connection lifecycle, aggregation & routing
├── proxy/ # client-facing MCP server + control tools
├── observability/ # logging (audit sinks in v0.3)
└── internal/ # small cross-cutting utilities
Design details — including the client-agnostic rationale, the interceptor pipeline, and the audit-sink strategy — live in the project's design docs.
npm install
npm run check # format:check + lint + typecheck + test (the full gate)
npm test # unit + end-to-end (in-memory transport) tests
npm run build # bundle to dist/ (CLI + library)
npm run dev -- --config bastion.config.json
| Script | Does |
|---|---|
build | Bundle CLI + library with tsup. |
dev | Run the CLI from source with tsx. |
typecheck | tsc --noEmit (strict). |
lint / lint:fix | ESLint (flat config). |
format / format:check | Prettier. |
test / test:watch | Vitest. |
check | Everything above, as one gate. |
| Version | Theme | Highlights |
|---|---|---|
| v0.1 ✅ | Reliability | Aggregating proxy, auto-reconnect, bastion__status / __reconnect. |
| v0.2 ✅ | Runtime security | Tool-definition pinning (rug-pull detection), poisoning inspection, shadowing detection. |
| v0.3 ✅ | Audit & compliance | Pluggable audit sinks (console / file / webhook), NIST AI RMF / OWASP LLM Top 10 mapping. |
Both stdio and Streamable HTTP transports are supported (see Transports).
Contributions are very welcome — this project is built to be community-owned. Please read CONTRIBUTING.md for the dev setup, project layout, and PR workflow, and our Code of Conduct.
In short: open an issue for non-trivial changes, keep PRs focused with tests, and make sure
npm run check passes (CI runs it on Node 18/20/22). Good first areas: additional client setup
recipes, more upstream test fixtures, and Streamable HTTP transport support.
mcp-bastion is security-adjacent software, so we hold it to a high bar. Please report
vulnerabilities privately — do not open a public issue. See SECURITY.md for
the disclosure process.
Apache-2.0 © Gowthaman Arumugam and mcp-bastion contributors
Run Claude Code as an MCP server so any agent can delegate coding tasks to it
Browser automation using accessibility snapshots instead of screenshots
Google's universal MCP server supporting PostgreSQL, MySQL, MongoDB, Redis, and 10+ databases
Official GitHub integration for repos, issues, PRs, and CI/CD workflows