Skip to main content
Glama
Gowthaman90

mcp-bastion

by Gowthaman90

🛡️ mcp-bastion

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.

npm version npm downloads MCP Registry Measured coverage CI License Node TypeScript PRs welcome


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.

Contents

Related MCP server: McpVanguard

Why

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.

How it works

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.

Features

  • 🔌 Client-agnostic — one binary, config-only integration; no per-client plugins.

  • ♻️ Self-healing — health checks + capped exponential-backoff auto-reconnect for stdio servers.

  • 🧭 Agent-recoverablebastion__status and bastion__reconnect let the agent detect and fix drops itself, instead of hitting an opaque "no such tool" wall.

  • 🧩 Transparent aggregation — merges many servers into one, with per-server tool namespacing to prevent collisions and tool-shadowing.

  • 💬 Legible failures — a dropped server yields an actionable message, not a crash.

  • 🛡️ Runtime security (new in v0.2) — pins each tool's definition and blocks "rug pulls" (a server changing a tool after approval); heuristically inspects descriptions for poisoning; detects cross-server shadowing. See Runtime security.

  • 📝 Audit & compliance (new in v0.3, opt-in) — structured, integrity-hash-chained audit events to pluggable sinks (console / file / webhook), mapped to NIST AI RMF & OWASP LLM Top 10. See Audit & compliance.

  • 🪶 Non-invasive & reversible — your servers run unchanged; uninstall is a config revert.

  • 🧱 Enterprise-grade codebase — strict TypeScript, layered architecture, ESLint + Prettier, and unit + end-to-end tests.

Quick start

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 balanced enforcement profile: it blocks high-confidence attacks (rug-pulls, argument/command injection, cross-server exfiltration) and warns on heuristic ones (description/response poisoning), while redacting leaked secrets from tool results. Set security.enforcementProfile to observe (warn-only) or strict (block-all), or tune any individual control — see Runtime security.

Demo

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.

Control tools

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__compliance

Audit summary of recent activity mapped to NIST AI RMF / OWASP LLM Top 10 (requires audit.enabled).

Re-approval is operator-only. Clearing a rug-pull block is a security authority, so it is not an agent-callable tool — a prompt-injected agent must not be able to re-approve the very tool it was blocked from. A changed tool stays blocked until an operator clears it out-of-band; bastion__approve is not advertised and a client call to it is refused.

Configuration

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.

listen.validateRoutingHeaders

boolean

true

Reject Mcp-* routing headers that disagree with the body (-32020, MCP 2026-07-28).

security.maxCacheTtlMs

number (ms)

3600000

Ceiling on upstream ttlMs cache hints forwarded downstream (MCP 2026-07-28).

servers.<name>.protocol

auto|legacy|2026-07-28

auto

Era negotiation per upstream (probe 2026-07-28, fall back; or pin).

listen.legacy

stateless | reject

stateless

Serve pre-2026-07-28 clients statelessly, or refuse them (-32022).

security.inspectInputRequests

boolean

true

Gate input_required rounds (credential phishing, model steering).

security.onInputRequired

block | warn

block

Action on a high-severity MRTR finding (warn strips the request).

security.requestStateKey

string (≥16)

random per process

HMAC key sealing requestState envelopes; or MCP_BASTION_REQUEST_STATE_KEY.

security.requestStateTtlSeconds

number

300

Lifetime of a sealed requestState envelope.

Transports

Bastion runs on the MCP TypeScript SDK 2.0 and speaks both protocol eras — the stateless 2026-07-28 revision and the pre-2026 initialize handshake — on both faces, per upstream and per client. When the protocol is stateless, the gateway is the only component that can still hold security state, which is why v1.0 adds requestState custody and an input_required consent gate (see CHANGELOG.md).

Bastion speaks two transports on both faces:

  • stdio (default) — the client spawns Bastion, and Bastion spawns local servers.

  • Streamable HTTP — connect to remote MCP servers (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.

Runtime security

New in v0.2. Bastion adds a security layer in the tool-call path (an interceptor pipeline), enabled by default:

  • Rug-pull detection (tool pinning). Each tool's definition is pinned on first use. If a server later changes that definition, the tool is blocked (onRugPull: "block") until an operator reviews it and re-approves it out-of-band (operator-only — not an agent-callable tool). This catches a server that looks benign at install time and turns malicious afterward.

  • Poisoning inspection. Tool names and descriptions are scanned for manipulation heuristics (instruction override, secret access, data exfiltration, covert instructions, embedded directives, hidden/zero-width characters). Because heuristics can false-positive, the default is warn (logged and reported, not blocked); set onPoisoning: "block" to enforce.

  • Shadowing. When two servers expose a tool with the same name, it's surfaced in the report.

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.

Audit & compliance

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,          // integrity hash-chain (detects naive edits; unkeyed, not signed)
  "sinks": [
    { "type": "file", "path": "./bastion-audit.jsonl" },
    { "type": "webhook", "url": "https://collector.example/v1/audit" }
  ]
}
  • Pluggable sinks. 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.

  • Compliance mapping. Each event is mapped to NIST AI RMF functions and OWASP LLM Top 10 categories; bastion__compliance returns an aggregate report of recent activity.

  • Integrity hash chain. With tamperEvident, events are linked by a SHA-256 hash chain, and the exported verifyChain helper detects naive or partial edits within an intact log. The chain is unkeyed: a party who can rewrite the log file can recompute a consistent chain, and truncating the most-recent events links cleanly — so treat this as corruption-detection, not cryptographic tamper-proofing. For stronger guarantees, ship events to an append-only external sink (webhook / OTLP → a WORM store or SIEM). Keyed/signed attestation is on the roadmap.

  • Redaction (best-effort). Arguments are omitted by default; includeArgs: "redacted" keeps structure while masking values under known credential key-names and common secret patterns. It is a heuristic, not a guarantee — a secret under an unrecognized key can still be recorded, so prefer none for high-sensitivity deployments.

Standards alignment

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.

Client setup

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.

Architecture

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.

Development

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.

Roadmap

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).

Contributing

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.

Security

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.

License

Apache-2.0 © Gowthaman Arumugam and mcp-bastion contributors

Available Tools

2 tools
bastion__approveA

Re-approve a tool whose definition changed, clearing a rug-pull block so it can be called again. Only do this after reviewing and trusting the change.

ParametersJSON Schema
NameRequiredDescriptionDefault
toolYesThe tool's original (un-namespaced) name.
serverYesServer that owns the tool.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description bears full burden. It clearly explains the effect (clears a block to allow calling the tool again). Could mention if approval requires specific permissions, but sufficiently transparent for the action.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two succinct sentences that front-load the purpose and condition. No extraneous words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given low complexity (2 required params, no output schema, single sibling), the description covers what the tool does, when to use it, and its effect completely.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline 3. Description does not add extra meaning to parameters beyond schema definitions (tool name, server name). No enrichment needed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states the tool re-approves a tool after a definition change, clearing a rug-pull block. It distinguishes from the sibling 'bastion__compliance' by its specific action (re-approve vs. compliance).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit condition 'Only do this after reviewing and trusting the change' guides when to use. Does not explicitly mention when not to use, but context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

bastion__complianceA

Report an audit/compliance summary of recent tool activity, mapped to governance frameworks (NIST AI RMF functions and OWASP LLM Top 10 categories). Requires audit to be enabled.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description bears full burden. It mentions the output is a summary mapped to frameworks, but does not disclose whether the tool is read-only, any side effects, authentication needs, or rate limits. The behavioral transparency is minimal.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence with no wasted words. It conveys the purpose, mapping, and prerequisite efficiently, achieving maximum conciseness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter tool with no output schema, the description is fairly complete. It states what the tool does and a key requirement. However, it lacks detail on what 'recent tool activity' means or the format of the summary, leaving some ambiguity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, and the schema coverage is 100% (empty schema). Per guidelines, baseline is 4. The description adds no parameter info, which is acceptable given no parameters exist.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it reports an audit/compliance summary of recent tool activity, mapped to specific governance frameworks (NIST AI RMF and OWASP LLM Top 10). It distinguishes from the sibling 'bastion__approve' by focusing on compliance reporting rather than approval actions.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly notes that audit must be enabled, providing a clear prerequisite. It implies when to use (when an audit summary is needed), but does not explicitly state when not to use or list alternatives beyond the sibling tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.6.3
    • First observedbastion__approve
    • First observedbastion__compliance

TDQS

A4.2/5.0

Scored across 2 tools

Disambiguation5/5

The two tools serve completely distinct purposes: one for re-approving changed tools to prevent rug-pull, the other for reporting audit/compliance summaries. There is no overlap or ambiguity.

Naming Consistency5/5

Both tools follow a consistent 'bastion__<verb>' naming pattern (bastion__approve, bastion__compliance), making the action clear and predictable.

Tool Count4/5

With only 2 tools, the server feels slightly thin, but for a focused governance purpose (rug-pull prevention and compliance reporting), the count is reasonable and not inappropriate.

Completeness4/5

The tools cover the core functions: approving changed tools and reporting compliance. Minor gaps exist, such as listing pending approvals or configuring audit settings, but the surface is adequate for the stated purpose.

Maintenance

ActivityMaintained
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    An enterprise infrastructure layer for the Model Context Protocol that provides authentication, RBAC, audit logging, and rate limiting for tool calls. It acts as a secure proxy between AI agents and MCP servers to ensure security and compliance in production environments.
    13 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    A security proxy and active firewall for the Model Context Protocol that protects host systems from malicious intent, prompt injection, and data exfiltration. It acts as an interception layer between AI agents and tools, providing real-time verification and multi-layered defense mechanisms.
    12
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides a secure gRPC transport layer for the Model Context Protocol (MCP) with mutual TLS, token-based authentication, and fine-grained authorization. Includes comprehensive telemetry and a real-time visualization dashboard for monitoring AI model interactions and security events.
    1
    Apache 2.0
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables unified access to multiple MCP backend servers through one local daemon, with curated per-client tool profiles, allow/deny policies, audit logging, and drift protection.
    13 npm
    1
    -