Skip to content

MCP Security Gateway

Shield can sit between any MCP client and its MCP servers, evaluating every tool call before it reaches the server. This turns Shield into a transparent security gateway -- your MCP client connects to Shield, Shield connects to the actual MCP servers, and every tool call passes through the 4-tier pipeline.

The Problem

MCP servers are powerful. A filesystem server can read and write any file. A shell server can execute arbitrary commands. A database server can drop tables. When an LLM proposes a tool call, the MCP client typically executes it immediately with no security evaluation.

         Without Shield
┌────────────┐    tool call    ┌────────────────┐
│ MCP Client │ ──────────────▶ │   MCP Server   │
│ (Claude,   │                 │ (filesystem,   │
│  Cursor)   │ ◀────────────── │  shell, DB)    │
└────────────┘    result       └────────────────┘


                              No security check.
                              LLM says "rm -rf /"?
                              Server executes it.

The Solution

Insert Shield as a proxy between the client and server:

         With Shield
┌────────────┐    tool call    ┌─────────────┐    tool call    ┌────────────────┐
│ MCP Client │ ──────────────▶ │   Shield    │ ──────────────▶ │   MCP Server   │
│ (Claude,   │                 │   Gateway   │                 │ (filesystem,   │
│  Cursor)   │ ◀────────────── │  4-tier     │ ◀────────────── │  shell, DB)    │
└────────────┘    result       │  pipeline   │    result       └────────────────┘
                               └─────────────┘

                              Every tool call
                              evaluated before
                              forwarding.

Shield is transparent to both the client and server. The client sees the same tools, the same schemas, the same responses. But every tool call passes through policy matching, ML classification, and (when needed) LLM evaluation before it reaches the server.

Configuration

shield.yaml for MCP Proxy

yaml
listen: localhost:9090

policy:
  file: security/shield/default.yaml

classifier:
  model_dir: ~/.openparallax/models/prompt-injection/
  threshold: 0.85

heuristic:
  enabled: true

evaluator:
  provider: anthropic
  model: claude-sonnet-4-6
  api_key_env: ANTHROPIC_API_KEY

fail_closed: true
daily_budget: 100

# MCP upstream servers
mcp:
  servers:
    - name: filesystem
      transport: stdio
      command: npx
      args:
        - "@modelcontextprotocol/server-filesystem"
        - "/home/user/workspace"

    - name: github
      transport: streamable-http
      url: https://api.github.com/mcp

    - name: database
      transport: stdio
      command: npx
      args:
        - "@modelcontextprotocol/server-postgres"
        - "postgresql://localhost/mydb"

# Audit logging
audit:
  enabled: true
  file: shield-mcp-audit.jsonl

Upstream Server Types

Shield supports two MCP transport types for upstream servers:

stdio Transport

The server runs as a child process. Shield spawns it, communicates over stdin/stdout:

yaml
mcp:
  servers:
    - name: filesystem
      transport: stdio
      command: npx
      args: ["@modelcontextprotocol/server-filesystem", "/home/user"]
      env:
        NODE_ENV: production

Streamable HTTP Transport

The server runs as a remote HTTP service:

yaml
mcp:
  servers:
    - name: remote-tools
      transport: streamable-http
      url: https://mcp-server.example.com
      headers:
        Authorization: "Bearer ${MCP_AUTH_TOKEN}"

Running the Proxy

bash
# Start Shield as MCP proxy.
openparallax-shield mcp-proxy --config shield.yaml

# The proxy listens at http://localhost:9090/mcp
# Point your MCP client here instead of directly at the servers.

Shield starts all upstream servers, discovers their tools, and presents a unified tool list to the client. When a tool call arrives, Shield:

  1. Identifies which upstream server owns the tool
  2. Maps the MCP tool name to a Shield action type
  3. Runs the action through the 4-tier pipeline
  4. If ALLOW: forwards the call to the upstream server and returns the result
  5. If BLOCK: returns an error to the client with the reason

Deployment with MCP Clients

Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

Before (direct connection):

json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["@modelcontextprotocol/server-filesystem", "/home/user"]
    },
    "github": {
      "command": "npx",
      "args": ["@modelcontextprotocol/server-github"],
      "env": { "GITHUB_TOKEN": "..." }
    }
  }
}

After (through Shield):

json
{
  "mcpServers": {
    "shield": {
      "url": "http://localhost:9090/mcp"
    }
  }
}

All tools from all upstream servers are available through the single Shield endpoint. Shield handles server management, tool routing, and security evaluation.

Cursor

In Cursor settings, replace individual MCP server configurations with the Shield proxy URL:

json
{
  "mcp.servers": {
    "shield": {
      "url": "http://localhost:9090/mcp"
    }
  }
}

Custom Agents

Any MCP client library can connect to Shield. Point the MCP client URL at Shield instead of the upstream server:

python
# Python (mcp SDK)
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async with streamablehttp_client("http://localhost:9090/mcp") as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()
        tools = await session.list_tools()
        # All upstream tools available, all calls go through Shield
typescript
// TypeScript (mcp SDK)
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'

const transport = new StreamableHTTPClientTransport(
  new URL('http://localhost:9090/mcp'),
)
const client = new Client({ name: 'my-agent', version: '1.0' })
await client.connect(transport)

const tools = await client.listTools()
// All upstream tools, secured by Shield

Tool-to-Action Mapping

Shield maps MCP tool names to Shield action types for policy evaluation:

MCP Tool PatternShield Action Type
read_file, get_file_contentsread_file
write_file, create_filewrite_file
run_command, execute, bashexecute_command
list_directory, lslist_directory
search, grep, findsearch_files
git_*Corresponding git_* action
Other tool namesUsed as-is for action type

The mapping is configurable in shield.yaml:

yaml
mcp:
  tool_mapping:
    # Custom mappings: MCP tool name → Shield action type
    "my_custom_tool": "execute_command"
    "query_database": "http_request"

Audit Logging

Every MCP tool call is logged to the audit file with full details:

jsonl
{"timestamp":"2026-04-03T10:30:00Z","tool":"read_file","server":"filesystem","action_type":"read_file","payload":{"path":"/home/user/workspace/main.go"},"verdict":"ALLOW","tier":0,"confidence":1.0,"reasoning":"policy allow [allow_workspace_reads]: read_file on \"/home/user/workspace/main.go\" matched a policy pattern","hash":"sha256:...","prev_hash":"sha256:..."}
{"timestamp":"2026-04-03T10:30:01Z","tool":"bash","server":"shell","action_type":"execute_command","payload":{"command":"rm -rf /"},"verdict":"BLOCK","tier":1,"confidence":0.95,"reasoning":"heuristic [rm_rf_root, critical]: Recursive root delete detected","hash":"sha256:...","prev_hash":"sha256:..."}

Each audit entry includes:

  • The MCP tool name and upstream server
  • The mapped Shield action type
  • The full payload
  • The verdict, tier, confidence, and reasoning
  • SHA-256 hash chain for tamper detection

Reviewing the Audit Log

bash
# View recent decisions
tail -20 shield-mcp-audit.jsonl | jq '.'

# Find all blocked actions
grep '"verdict":"BLOCK"' shield-mcp-audit.jsonl | jq '.'

# Count decisions by verdict
jq -r '.verdict' shield-mcp-audit.jsonl | sort | uniq -c

# Verify hash chain integrity
openparallax-shield audit verify --file shield-mcp-audit.jsonl

Before and After

Before: Unprotected MCP

User: "Delete all files in /home/user"

  Claude Desktop ──→ filesystem server
                     rm -rf /home/user/*
                     ✓ Executed immediately
                     Files deleted. No undo.

After: Shield-Protected MCP

User: "Delete all files in /home/user"

  Claude Desktop ──→ Shield proxy ──→ filesystem server

                     Tier 0: deny rule matches
                     delete_file on /home/user/*
                     BLOCK (confidence 1.0)

                     ✗ Blocked. Files safe.
                     Error returned to Claude:
                     "Action blocked by security policy"

Multiple Upstream Servers

Shield aggregates tools from all configured upstream servers into a single endpoint. Tool names are namespaced to avoid collisions:

yaml
mcp:
  servers:
    - name: fs
      transport: stdio
      command: npx
      args: ["@modelcontextprotocol/server-filesystem", "/home/user"]

    - name: github
      transport: streamable-http
      url: https://api.github.com/mcp

    - name: postgres
      transport: stdio
      command: npx
      args: ["@modelcontextprotocol/server-postgres", "postgresql://localhost/db"]

The client sees tools from all three servers through the single Shield proxy.

Performance

Shield adds minimal latency to MCP tool calls:

TierAdded LatencyWhen Used
Tier 0 only< 1msAction matches a policy rule
Tier 1 (heuristic only)~2msNo policy match, heuristic-only mode
Tier 1 (ONNX + heuristic)~50msNo policy match, ONNX model installed
Tier 2 (LLM evaluator)1-3sEscalated from Tier 1

Most tool calls are resolved at Tier 0 (policy match) or Tier 1 (classifier). Tier 2 evaluations are rare -- they only occur when the classifier is uncertain about a potentially dangerous action.

Next Steps