# SentinelMCP

> The Open-Source MCP Firewall & Security Gateway for AI Agents. Inspect, redact, and control tool calls. Proxy mode (universal) and Inline SDK mode (Go).

- **Type:** MCP server
- **Install:** `agentstack add mcp-technosiveuk-ui-sentinelmcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [technosiveuk-ui](https://agentstack.voostack.com/s/technosiveuk-ui)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [technosiveuk-ui](https://github.com/technosiveuk-ui)
- **Source:** https://github.com/technosiveuk-ui/SentinelMCP
- **Website:** https://technosive.co.uk/sentinelmcp

## Install

```sh
agentstack add mcp-technosiveuk-ui-sentinelmcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# SentinelMCP

**The Open-Source MCP Security Gateway for AI Agents**

Built by [Technosive Ltd.](https://technosive.co.uk)

[](LICENSE)
[](go.mod)
[](https://github.com/technosiveuk-ui/SentinelMCP/actions/workflows/ci.yml)

---

> ⚠️ **Alpha Software — v0.1**
>
> SentinelMCP is currently in **Alpha**. The project is under active development and APIs, configuration formats, and graph behaviors **may change** in future releases without advance notice.
>
> **Production warning:** This software is provided as-is. While we strive for security, an Alpha-stage proxy should **not** be your sole defense in a highly regulated production environment without thorough testing. Use at your own risk.
>
> We actively seek early adopters and feedback. If you encounter issues, have suggestions, or want to contribute — please [open an issue](https://github.com/technosiveuk-ui/SentinelMCP/issues). Your input directly shapes the roadmap.

---

## What is SentinelMCP?

SentinelMCP is a security enforcement engine for the **Model Context Protocol (MCP)** that secures AI agent tool calls at runtime. It provides inspection, policy enforcement, PII/secret redaction, and audit logging — sitting between your AI agents and the tools they invoke.

Built in high-performance Go with runtime-native graph orchestration, interrupt/resume capabilities, and sub-millisecond enforcement — enabling human-in-the-loop approval workflows without custom plumbing.

---

## Two Deployment Modes

SentinelMCP can be deployed in two ways, depending on your architecture:

### 1. Proxy Mode (Universal)

A standalone sidecar binary that intercepts HTTP/SSE MCP traffic. Works with **any language** (Python, TypeScript, Go, etc.) and any agent framework. Zero code changes required — just route your MCP traffic through the proxy.

```
AI Agent (any language) → SentinelMCP Proxy → MCP Server
```

### 2. Inline SDK Mode (Go Native)

A Go library imported directly into your application. Wraps MCP tool calls **in-memory** with zero-copy graph orchestration. Provides sub-millisecond latency and deep context awareness — no network hop, no separate process.

```
Go AI Application → SentinelMCP SDK (in-process) → MCP Server
```

This dual-mode architecture is SentinelMCP's key differentiator: competitors like Permit or Envoy only offer network proxies. With the Inline SDK, Go applications get the same security enforcement at a fraction of the latency.

---

## Key Features

- **MCP Proxy (Sidecar)** — Drop-in HTTP/SSE proxy for any MCP client. Transparent to existing agent frameworks.
- **Inline SDK (Go)** — Native Go module for in-process enforcement. Sub-millisecond overhead on the Allow path (19μs p99).
- **Policy Engine** — Local YAML-based policy definitions with hot-reloading. Risk levels (low/medium/high) map to enforcement actions (allow/redact/block/interrupt).
- **Data Loss Prevention (DLP)** — Regex-based PII and secret redaction in tool arguments and responses. 6 built-in patterns (private keys, passwords, API keys, credit cards, SSNs, emails) plus custom regex support.
- **Transport & Auth Security** — A security gateway must harden its own transport too. Loopback-plaintext-default with fail-closed hardening: opt-in inbound TLS, API-key authentication, an admin token gating approval-resume, strict mode that rejects plaintext/IP-literal upstreams, upstream cert pinning, outbound credential injection, and an egress allowlist. See [Transport Security](docs/transport-security.md).
- **Human-in-the-Loop (HITL)** — Interrupt high-risk tool calls via generic Webhooks and resume via a local API endpoint. BoltDB-backed checkpoints for durable interrupt/resume.
- **Audit Logging** — Structured JSON logging to `stdout` and native OpenTelemetry (OTel) integration for SIEM pipelines.
- **State Management** — BoltDB-backed checkpoints for durable interrupt/resume across process restarts.

---

## 🏗️ Architecture & Extensibility

SentinelMCP is built with a strict separation of concerns to ensure maintainability, testability, and a seamless Open-Core experience.

The `gateway/` package defines all core interfaces and domain types (Policy, DLP, Risk, Audit) with **zero dependencies** on the underlying Eino framework or MCP transport libraries. The `adapter/eino/` package is the *only* package that imports Eino types.

This architectural boundary provides three major benefits:

1. **Framework Agnostic:** If the underlying orchestration framework ever needs to change, only a new adapter is required; the core security engine remains untouched.
2. **Independently Testable:** Core domain logic (policy evaluation, DLP redaction, risk analysis) can be unit-tested cleanly without spinning up MCP servers or LLM graphs.
3. **Architecturally Enforced Open-Core:** Enterprise implementations (Teams/Slack HITL, Nightfall DLP, Control Plane APIs) simply swap into the `GatewayConfig` via interfaces. Zero changes to this OSS codebase are required.

---

## Quickstart

### Proxy Mode

**1. Start the sidecar.** The fastest path is the bundled three-service demo — an upstream MCP server with demo tools, the sidecar, and a test client that exercises every enforcement flow:

```bash
docker compose up --build --abort-on-container-exit
```

For a persistent sidecar against your own upstream, run a single container (or locally: `go run ./cmd/sentinelmcp -config policies.yaml`):

```bash
docker run --name sentinelmcp \
  -p 8080:8080 -p 9090:9090 \
  -v ./policies.yaml:/etc/sentinelmcp/config.yaml \
  ghcr.io/technosiveuk-ui/sentinelmcp:latest
```

Port `8080` is the MCP proxy; `9090` is the admin API (health probes and the approval-resume endpoint used by high-risk interrupts). Add `-d` to run detached, then follow logs with `docker logs -f sentinelmcp`. The example config below binds loopback, which suits a local run; a non-loopback bind (e.g. `0.0.0.0` inside a container) requires TLS or the `--insecure-dev-mode` flag — see [Transport Security](docs/transport-security.md).

**2. Define your policy (`policies.yaml`):**

```yaml
schema_version: "1.0"

global:
  default_risk: low                 # risk for tools with no explicit entry (set medium for a stricter default)
  redaction_mask: "***REDACTED***"

sidecar:
  listen_addr: "localhost:8080"     # loopback bind → plaintext HTTP is safe here
  health_addr: "localhost:9090"     # admin server (health + approval-resume); loopback enforced
  transport: streamable_http
  strict: false                     # DEMO ONLY — the local upstream below is plaintext. Production
                                    # MUST set strict: true and dial https:// upstreams.
  admin_token: ""                   # gates /api/v1/approval/resume — set via SENTINELMCP_ADMIN_TOKEN
  # tls:                            # opt-in inbound TLS; required when listen_addr is non-loopback
  #   cert_file: "/etc/sentinelmcp/tls.crt"
  #   key_file:  "/etc/sentinelmcp/tls.key"
  upstream_servers:                 # your MCP tool servers — at least one is required
    - name: my-tools
      url: "http://localhost:3001/mcp"

auth:
  api_keys: {}                      # inbound key → principal; enforced on every call when non-empty
                                    # e.g. { "key-abc": "agent-prod" } via Authorization: Bearer / X-API-Key

tools:
  read_file:
    risk: low                       # allow — args and response are still DLP-scanned
    redact_patterns: [PASSWORD, API_KEY]

  write_file:
    risk: medium                    # redact — sensitive argument fields are masked before the tool runs

  exec_command:
    risk: high                      # interrupt — pauses for human approval, then runs or blocks
    require_approval: true
    approval_reason: "Shell commands can modify system state"

  "db_*":                           # glob patterns are supported
    risk: high
    require_approval: true

dlp_patterns:                       # six patterns are built in (PRIVATE_KEY, PASSWORD, API_KEY,
  INTERNAL_HOSTNAME:                # CREDIT_CARD, SSN, EMAIL); define your own here as needed
    regex: '\b[a-z]+\.internal\.company\.com\b'
    type: pii
```

**3. Point your MCP client at the proxy:**

```text
http://localhost:8080/mcp
```

The proxy speaks standard Streamable HTTP MCP, so any client — Python, TypeScript, or Go — connects to that URL with no SDK changes. Point `upstream_servers` at your own MCP tool server(s) and the proxy enforces the policy above on every call.

### Inline SDK Mode

Secure tool calls **in-process** with a small builder API — no sidecar, no network hop, sub-millisecond overhead. Full guide: [`docs/INLINE-SDK.md`](docs/INLINE-SDK.md).

> **Requires v0.2.0+.** The Inline SDK (`sdk/` package) shipped in v0.2.0.

```go
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/technosiveuk-ui/sentinelmcp/gateway"
    "github.com/technosiveuk-ui/sentinelmcp/sdk"
)

func main() {
    // Register plain Go functions as secured tools.
    invoker := sdk.NewFuncInvoker().
        Register("read_file", func(_ context.Context, args map[string]any) (string, error) {
            return fmt.Sprintf("contents of %v", args["path"]), nil
        }).
        Register("exec_command", func(_ context.Context, args map[string]any) (string, error) {
            return fmt.Sprintf("ran %v", args["cmd"]), nil
        })

    // Build the pipeline. StrictDefaults routes unknown tools to redact.
    pipeline, err := sdk.New(invoker).
        WithRisk("exec_command", gateway.RiskHigh). // interrupt for approval
        StrictDefaults().
        Build()
    if err != nil {
        log.Fatal(err)
    }

    // Every call is inspected, DLP-scanned, policy-checked, and audited.
    result, err := pipeline.Run(context.Background(), "read_file", map[string]any{
        "path": "/etc/config.yaml",
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Result:", result)
}
```

Run the complete allow / redact / interrupt example:

```bash
go run ./examples/inline-sdk
```

---

## How It Works

```mermaid
flowchart LR
    A[AI Agent] --> S[SentinelMCP]
    S --> I[Inspect Tool Call]
    I --> D{Policy Decision}
    D -->|Allow| E[Execute Tool]
    D -->|Redact| R[Redact PII] --> E
    D -->|Block| X[Return Error]
    D -->|Interrupt| H[Human Approval]
    H -->|Approved| E
    H -->|Denied| X
    E --> O[Inspect Response]
    O --> A
    E --> MCP[MCP Server]
    MCP --> O
```

**Pipeline flow:**
1. **Inspect** — Serialize tool arguments, run DLP scanning, look up risk level in policy DB
2. **Policy Decision** — Route by risk: low→allow, medium→redact, high→interrupt for approval, or block
3. **Execute** — Call the upstream MCP tool (with redacted arguments if applicable)
4. **Inspect Response** — DLP-scan the tool response, redact findings, emit structured audit event

---

## Transport & Auth Security

SentinelMCP is a *security* gateway, so its own transport surface is hardened, not left plaintext. It follows a **loopback-plaintext-default** model: on a loopback address (`127.0.0.1`, `::1`, `localhost`) the sidecar may run plaintext HTTP with no auth — the host boundary carries trust, like `redis` or `postgres` on a local socket. The moment it binds a non-loopback interface or dials a network upstream, it **fail-closes** to TLS and authentication instead of degrading to plaintext.

- **Strict mode (default `true`)** rejects plaintext `http://` and IP-literal upstream URLs at config load.
- **Inbound TLS** is opt-in on loopback, **required** off-loopback (`sidecar.tls`).
- **API-key auth** (`auth.api_keys`) validates every inbound call in constant time; the resolved principal reaches the policy and audit layers.
- **Admin token** (`sidecar.admin_token` / `SENTINELMCP_ADMIN_TOKEN`) always gates the approval-resume endpoint, even on loopback.
- **Upstream identity** can be pinned per-server (`ca_bundle`, `server_name`, `pinned_sha256`); `InsecureSkipVerify` is never set.
- **Outbound credentials** are injected from a mode-`0600` `secrets.yaml` or env, and never logged.
- **Egress allowlist** (`sidecar.egress_allowlist`) bounds which upstream hosts may be dialed.
- **Secrets at rest**: a group/world-readable `config.yaml` containing secrets, or a `secrets.yaml`, is refused at load.

Full rationale, the bind-policy matrix, and the complete fail-closed contract live in [`docs/transport-security.md`](docs/transport-security.md). The two open-core seams — `gateway/auth.Authenticator` and `gateway/secrets.Provider` — are where Enterprise implementations (mTLS, OAuth2/OIDC, HashiCorp Vault) attach behind the same pure interfaces.

---

## Architecture

```
sentinelmcp/
├── gateway/               # Core domain (ZERO Eino imports)
│   ├── types.go           # GatewayContext, AuditEvent, DLPFinding, RiskLevel, Decision
│   ├── graph.go           # Pipeline, ToolInvoker, GatewayConfig, MetricsRecorder
│   ├── policy.go          # Policy, RiskDB interfaces + DefaultPolicy, YAMLRiskDB
│   ├── policy_action.go   # Action-based PolicySet (allow/redact/block/interrupt by rule)
│   ├── policy_reload.go   # Hot policy reload (atomic swap on config change)
│   ├── redact.go          # DLPScanner, Redactor, ScanArgs + RegexDLPScanner, DefaultRedactor
│   ├── dlp_multi.go       # MultiDLPScanner (compose regex + external)
│   ├── dlp_external.go    # DLPEndpoint interface + ExternalDLPScanner adapter
│   ├── audit.go           # AuditEmitter interface + Stdout/FileAuditEmitter
│   ├── audit_sink.go      # AuditSink + WriterAuditSink + CompositeAuditEmitter
│   ├── metrics.go         # MetricsRecorder interface + NOPMetricsRecorder
│   ├── webhook_approval.go # WebhookApprovalProvider + CLIApprovalProvider
│   ├── auth/              # Authenticator interface + APIKeyAuthenticator (open-core seam)
│   │   └── auth.go        #   pure — no net/http; key→principal, constant-time
│   └── secrets/           # secrets.Provider interface + FileEnvProvider (open-core seam)
│       └── secrets.go     #   pure — resolves credentials_ref; secrets.yaml (0600) or env
├── sdk/                   # Inline SDK: ergonomic builder over gateway + adapter
│   ├── builder.go         # Builder API (New, With*, StrictDefaults, Build)
│   ├── func_invoker.go    # FuncInvoker — secure plain Go functions
│   └── defaults.go        # Convenience constructors (BuiltinDLP, StdoutAudit, …)
├── adapter/eino/          # Eino framework adapter (ONLY package with Eino imports)
│   ├── graph.go           # BuildGraph() → 3-node graph with interrupt/resume
│   ├── otel_metrics.go    # OTelMetricsRecorder (counters + histograms)
│   ├── otel_audit_emitter.go # OTelAuditEmitter (audit → span events)
│   └── bolt_checkpoint.go # BoltCheckPointStore
├── adapter/sidecar/       # Sidecar proxy adapters
│   ├── invoker.go         # SidecarInvoker (routes to upstream MCP servers)
│   └── discovery.go       # DiscoverTools (upstream MCP server discovery)
├── adapter/siem/          # SIEM audit sink implementations
│   ├── splunk_hec.go      # SplunkHECSink (batching + retry)
│   └── file_rotation.go   # FileRotationSink (size-based + gzip)
├── config/                # YAML config loading, validation, hot-reload
│   ├── config.go          # Load, Validate, ToRiskDB, ToDLPPatterns
│   └── watcher.go         # ConfigWatcher (fsnotify + debounce)
├── cmd/
│   ├── sentinelmcp/       # Sidecar proxy binary
│   │   ├── main.go        # Bootstrap: config → discover → pipeline → proxy → admin
│   │   ├── proxy.go       # Proxy MCP server wrapping the pipeline
│   │   ├── health.go      # Admin server: /healthz, /readyz, /api/v1/approval/resume
│   │   └── config.go      # GatewayConfig wiring (audit sinks, approval, OTel)
│   ├── upstream/          # Demo upstream MCP server (3 tools)
│   ├── testclient/        # Demo test client (3 enforcement flows)
│   └── demo/              # In-process demo (4 MCP servers)
├── examples/
│   └── inline-sdk/        # Runnable allow/redact/interrupt SDK demo
├── Dockerfile

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [technosiveuk-ui](https://github.com/technosiveuk-ui)
- **Source:** [technosiveuk-ui/SentinelMCP](https://github.com/technosiveuk-ui/SentinelMCP)
- **License:** Apache-2.0
- **Homepage:** https://technosive.co.uk/sentinelmcp

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-technosiveuk-ui-sentinelmcp
- Seller: https://agentstack.voostack.com/s/technosiveuk-ui
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
