# SentinelMCP
**面向 AI Agent 的开源 MCP 安全网关**
由 [Technosive Ltd.](https://technosive.co.uk) 构建
[](LICENSE)
[](go.mod)
[](https://github.com/technosiveuk-ui/SentinelMCP/actions/workflows/ci.yml)
## 什么是 SentinelMCP?
SentinelMCP 是一个用于 **Model Context Protocol (MCP)** 的安全执行引擎,可在 runtime 保护 AI agent 的工具调用。它提供检查、策略执行、PII/secret 脱敏和审计日志功能——部署在您的 AI agent 与它们调用的工具之间。
它采用高性能 Go 语言构建,具备原生的 runtime 图编排、中断/恢复功能以及亚毫秒级的执行延迟——无需额外开发自定义管道即可实现 human-in-the-loop(人工介入)审批工作流。
## 两种部署模式
根据您的架构需求,SentinelMCP 可以通过两种方式进行部署:
### 1. Proxy 模式(通用)
一个独立的 sidecar 二进制程序,用于拦截 HTTP/SSE MCP 流量。兼容**任何语言**(Python、TypeScript、Go 等)和任何 agent 框架。无需修改任何代码——只需将您的 MCP 流量路由通过该 proxy 即可。
```
AI Agent (any language) → SentinelMCP Proxy → MCP Server
```
### 2. Inline SDK 模式(Go 原生)
一个直接导入到您应用中的 Go 库。通过零拷贝图编排**在内存中**封装 MCP 工具调用。提供亚毫秒级的延迟和深度的上下文感知能力——无需网络跳转,也无需独立进程。
```
Go AI Application → SentinelMCP SDK (in-process) → MCP Server
```
这种双模式架构是 SentinelMCP 的关键差异化优势:像 Permit 或 Envoy 等竞争产品仅提供网络 proxy。借助 Inline SDK,Go 应用程序能以极低的延迟获得相同的安全执行能力。
## 核心功能
- **MCP Proxy (Sidecar)** — 适用于任何 MCP 客户端的即插即用 HTTP/SSE proxy。对现有的 agent 框架透明。
- **Inline SDK (Go)** — 用于进程内执行的原生 Go 模块。在 Allow(允许)路径上具有亚毫秒级开销(19μs p99)。
- **Policy 引擎** — 基于本地 YAML 的策略定义,支持热重载。风险等级(低/中/高)映射到相应的执行动作(允许/脱敏/拦截/中断)。
- **数据防泄漏 (DLP)** — 基于正则表达式的 PII 和 secret 脱敏,覆盖工具参数和响应。内置 6 种匹配模式(私钥、密码、API key、信用卡、SSN、电子邮件)并支持自定义正则表达式。
- **传输与认证安全** — 安全网关也必须强化其自身的传输通道。默认采用回环明文(loopback-plaintext)并进行故障关闭(fail-closed)强化:可选的入站 TLS、API key 认证、用于控制审批-恢复流程的管理员 token、拒绝明文/IP字面量上游的严格模式、上游证书锁定(cert pinning)、出站凭证注入以及出站白名单。详见[传输安全](docs/transport-security.md)。
- **Human-in-the-Loop (HITL)** — 通过通用的 Webhook 中断高风险工具调用,并通过本地 API endpoint 恢复执行。由 BoltDB 支持的检查点机制确保持久化的中断/恢复。
- **审计日志** — 将结构化 JSON 日志输出到 `stdout`,并原生集成 OpenTelemetry (OTel) 以对接 SIEM pipeline。
- **状态管理** — 基于 BoltDB 的检查点机制,可在进程重启时持久的执行中断/恢复。
## 🏗️ 架构与可扩展性
SentinelMCP 的构建严格遵循关注点分离原则,以确保可维护性、可测试性以及无缝的 Open-Core 体验。
`gateway/` 包定义了所有的核心接口和领域类型(Policy、DLP、Risk、Audit),并且对底层的 Eino 框架或 MCP 传输库保持**零依赖**。`adapter/eino/` 包是*唯一*导入 Eino 类型的包。
这种架构边界带来了三大主要优势:
1. **框架无关:** 如果底层编排框架需要更改,只需提供一个新的 adapter;核心安全引擎完全不受影响。
2. **独立可测试:** 核心领域逻辑(策略评估、DLP 脱敏、风险分析)可以进行纯净的单元测试,无需启动 MCP 服务器或 LLM 图。
3. **架构层面强制的 Open-Core:** 企业版实现(Teams/Slack HITL、Nightfall DLP、Control Plane API)只需通过接口替换到 `GatewayConfig` 中。无需对此开源代码库进行任何更改。
## 快速开始
### Proxy 模式
**1. 启动 sidecar。** 最快的方式是运行内置的三服务演示——它包含一个带有演示工具的 MCP 上游服务器、一个 sidecar,以及一个测试客户端,用于触发所有的执行流程:
```
docker compose up --build --abort-on-container-exit
```
要针对您自己的上游服务运行一个持久化的 sidecar,请运行单个容器(或在本地运行:`go run ./cmd/sentinelmcp -config policies.yaml`):
```
docker run --name sentinelmcp \
-p 8080:8080 -p 9090:9090 \
-v ./policies.yaml:/etc/sentinelmcp/config.yaml \
ghcr.io/technosiveuk-ui/sentinelmcp:latest
```
端口 `8080` 用于 MCP proxy;端口 `9090` 用于管理 API(健康探针以及供高风险中断使用的审批-恢复 endpoint)。添加 `-d` 以后台分离模式运行,然后使用 `docker logs -f sentinelmcp` 查看日志。下面的示例配置绑定了回环地址,适合本地运行;如果绑定非回环地址(例如容器内的 `0.0.0.0`),则必须使用 TLS 或加上 `--insecure-dev-mode` 标志——请参阅[传输安全](docs/transport-security.md)。
**2. 定义您的策略 (`policies.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. 将您的 MCP 客户端指向该 proxy:**
```
http://localhost:8080/mcp
```
该 proxy 支持标准的 Streamable HTTP MCP 协议,因此任何客户端(无论是 Python、TypeScript 还是 Go)都可以无需修改 SDK 直接连接到该 URL。将 `upstream_servers` 指向您自己的 MCP 工具服务器,proxy 就会在每次调用时执行上述策略。
### Inline SDK 模式
使用简单的构建器 API **在进程内**保护工具调用——无需 sidecar,无需网络跳转,开销为亚毫秒级。完整指南请见:[`docs/INLINE-SDK.md`](docs/INLINE-SDK.md)。
```
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)
}
```
运行完整的允许 / 脱敏 / 中断示例:
```
go run ./examples/inline-sdk
```
## 工作原理
```
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 流程:**
1. **检查** — 序列化工具参数,运行 DLP 扫描,在策略数据库中查找风险等级
2. **策略决策** — 根据风险进行路由:低→允许,中→脱敏,高→中断等待审批,或者直接拦截
3. **执行** — 调用上游 MCP 工具(如适用,将传入脱敏后的参数)
4. **检查响应** — 对工具响应进行 DLP 扫描,对敏感内容进行脱敏,发出结构化的审计事件
## 传输与认证安全
SentinelMCP 是一个*安全*网关,因此它自身的传输接口也经过了强化,不会保留为明文。它遵循**回环明文默认**模型:在回环地址(`127.0.0.1`、`::1`、`localhost`)上,sidecar 可以在无认证的情况下运行明文 HTTP——由主机边界承载信任,就像本地 socket 上的 `redis` 或 `postgres` 一样。当它绑定到非回环接口或连接到网络上游的那一刻,它会立即**故障关闭**并强制要求 TLS 和身份认证,而不是降级为明文。
- **严格模式(默认为 `true`)** 在加载配置时会拒绝明文 `http://` 和 IP 字面量上游 URL。
- **入站 TLS** 在回环地址上为可选,但在非回环地址上为**必需**(`sidecar.tls`)。
- **API key 认证**(`auth.api_keys`)以常数时间验证每一个入站调用;解析出的主体信息会传递给策略层和审计层。
- **管理员 token**(`sidecar.admin_token` / `SENTINELMCP_ADMIN_TOKEN`)始终控制着审批-恢复 endpoint,即使在回环地址上也是如此。
- **上游身份** 可以按服务器进行锁定(`ca_bundle`、`server_name`、`pinned_sha256`);绝不设置 `InsecureSkipVerify`。
- **出站凭证** 从权限为 `0600` 的 `secrets.yaml` 或环境变量中注入,且绝不记录到日志中。
- **出站白名单**(`sidecar.egress_allowlist`)限制可连接的上游主机范围。
- **静态机密**:如果 `config.yaml` 或 `secrets.yaml` 包含机密信息,但权限设置为组/全局可读,在加载时将被拒绝。
完整的原理解释、绑定策略矩阵以及完整的故障关闭契约位于 [`docs/transport-security.md`](docs/transport-security.md)。这两个开放核心的扩展切面——`gateway/auth.Authenticator` 和 `gateway/secrets.Provider`——是企业版实现(mTLS、OAuth2/OIDC、HashiCorp Vault)挂载的位置,它们同样基于这些纯净的接口。
## 架构
```
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 # Multi-stage distroless build
├── docker-compose.yml # One-command demo
└── config/
├── config.yaml # Default config
└── docker-config.yaml # Docker-specific config
```
## 开源版 vs. 企业版
| 功能 | 开源版 (本代码库 — Apache 2.0) | 企业版 (商业版) |
|:--------|:------------------------------|:-------------------------|
| **部署方式** | Sidecar Proxy + Inline Go SDK | 集中式 Control Plane |
| **策略管理** | 本地 YAML,支持热重载 | 集中式 API/DB,Web UI |
| **DLP** | 基于正则表达式(6种内置 + 自定义) | 企业级 DLP 连接器 (基于 API) |
| **HITL 审批** | 通用 Webhook + CLI | 企业通讯渠道集成 |
| **审计** | JSON stdout + OTel | SIEM 聚合,合规性 PDF 报告 |
| **认证** | API keys (常数时间比较) | SSO, RBAC, 多租户 |
| **传输安全** | 回环明文默认;可选 TLS、严格模式、证书锁定、出站白名单 | mTLS, OAuth2/OIDC, Mesh egress |
| **机密管理** | 文件 (0600) / 环境变量 | HashiCorp Vault, AWS/GCP Secret Manager |
| **状态存储** | BoltDB (本地) | 分布式 (Postgres) |
## 性能
在 Apple M1 Pro (Go 1.26) 上的基准测试:
| 路径 | 延迟 | 内存分配 |
|------|---------|-------------|
| Allow (关键路径) | **19μs** | 182 |
| Redact | 19μs | 185 |
| Interrupt | 33μs | 303 |
| DLP 扫描 | 9.3μs | 0 |
| RiskDB 查找 | 71ns | 0 |
| Policy 决策 | 193ns | 4 |
### NFR 验证
| NFR | 目标 | 验证方式 |
|-----|--------|-------------|
| p99 延迟 | ≤500μs Allow | `BenchmarkPipeline_Allow` — 19μs (低于预算 26 倍) |
| 内存 | <10MB heap / 1000次调用 | `TestMemory_*` — 0.73MB 并发 |
| 并发 | 100+ 并发,无死锁 | `TestLoad_*` — 1000 并发,0 死锁 |
| 默认拒绝 | 发生任何故障均拦截 | `TestDegradation_*` — 所有错误路径均拦截 |
## 测试覆盖率
跨越 6 个包的 90 多项测试,7 项基准测试,在开启 `-race` 的情况下全部通过:
```
# 运行所有测试
go test ./... -race -count=1
# NFR 验证
go test ./adapter/eino/ -run "TestLoad_|TestMemory_|TestDegradation_" -race -v
# Sidecar E2E
go test ./cmd/sentinelmcp/ -race -v
# Benchmarks
go test ./adapter/eino/ -bench=. -benchmem
```
### 端到端验证
完整的执行接口也会在真实的 MCP 上游环境中进行实时验证,
而不仅仅是在单元测试中。在演示上游服务运行且配置好 sidecar
对其进行代理(参见[快速开始](#quickstart);明文 `localhost` 上游
需要设置 `sidecar.strict: false`)的情况下,内置客户端将遍历每一条执行路径:
```
go run ./cmd/upstream # demo upstream (echo_message, filesystem_read, exec_command)
go run ./cmd/sentinelmcp -config ... # sidecar proxy (point upstream_servers at :3001)
go run ./cmd/testclient # allow / redact / interrupt+resume — exits non-zero on any failure
```
实时运行验证了以下内容:**允许**(低风险直通)、**脱敏**(中风险下对
响应中的 secret 进行 DLP 掩码)、**中断 + 恢复**(高风险调用
暂停等待审批,随后通过管理员恢复 API 完成)、**故障关闭认证**
(恢复 endpoint 会以 `401` 拒绝缺少/无效管理员 token 的请求),以及**策略热重载**
(配置保存时即应用更改,无需重启)。
## 许可证、商标与可扩展性
**开源代码:**
本代码库中的代码,包括 `gateway/` 接口和 `adapter/` 实现,均基于 [Apache 2.0 License](LICENSE) 授权。您可以根据该许可证自由使用、修改和分发此代码。
**企业版扩展与接口实现:**
本代码库在 `gateway/` 包中定义了可扩展接口(例如 `Policy`、`DLPScanner`、`ApprovalProvider`、`AuditEmitter`),以允许进行自定义集成。虽然这些接口本身是 Apache 2.0 授权的,但**由 Technosive Ltd. 提供的这些接口的专有实现**(例如 SentinelMCP 企业版 Control Plane、企业通讯 HITL 集成、高级 DLP 连接器)是另外基于商业许可证授权的,并且不包含在本代码库中。
在您自己的代码中实现这些接口并不要求您将实现基于 Apache 2.0 授权,也不强制您开源您的自定义集成代码。
**商标:**
“SentinelMCP”以及 SentinelMCP 标识是 Technosive Ltd. 的商标。Apache 2.0 许可证并未授予您在未经 Technosive Ltd. 事先书面许可的情况下,使用 SentinelMCP 商标来认可或推广衍生自本软件的产品或服务的权利。