Secure Ops Gateway
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Secure Ops Gatewayrestart the api service"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Secure Ops Gateway
Secure Ops Gateway is a small, policy-driven control backend for safely exposing bounded operational capabilities to AI agents, MCP clients, APIs and command-line tools.
It separates authenticated identity, authorization and intent from execution:
authenticated client / agent / API
|
v
transport adapter
derives trusted provider + subject
|
v
Secure Ops Gateway
identity resolution
authorization
tool registry
capability routing
confirmation guard
audit
|
v
isolated executors
|
v
services / hosts / applicationsThe gateway does not need to know how a specific service is implemented. A tool maps a user-facing operation to a capability, permission, resource and bounded request. The executor registry maps that capability to one isolated executor.
Why
Operational automation often ends up exposing a generic shell, SSH session or highly privileged API to an automation client. Secure Ops Gateway takes the opposite approach: expose only explicit operations and evaluate authorization against the concrete resource before execution.
Related MCP server: evav-gateway
v0.1 scope
The initial public core contains:
authenticated-source-to-principal identity resolution;
role- and resource-based authorization;
risk levels (
read,write,privileged);declarative tool and executor registries;
argument validation and bounded request templates;
capability-to-executor routing;
bidirectional HMAC-SHA256 authenticated executor envelopes with mandatory replay protection and request/response binding;
SQLite-backed explicit confirmation with idempotent replay, stale-execution recovery, bounded retention and ownership/symlink/replacement-safe state paths;
authorization-filtered tool discovery;
structured JSONL audit events with fail-closed pre-execution logging and non-duplicating post-execution failure semantics;
in-process rate and concurrency admission controls;
JSON Schema generation helpers for MCP-style tool definitions;
example configuration and tests.
The Python core has no runtime dependencies outside the standard library.
Quick start
Requires Python 3.11+.
python -m venv .venv
source .venv/bin/activate
python -m pip install -e . pytest
python -m pytest
python examples/basic.pyIdentity boundary
A remote client must never be allowed to send its own principal identifier. The transport authenticates the caller and constructs a SourceContext from trusted transport metadata. An identity resolver maps that authenticated source to a gateway principal.
from secure_ops_gateway import SourceContext, StaticIdentityResolver
resolver = StaticIdentityResolver({
"schema": 1,
"bindings": [
{"provider": "ssh", "subject": "uid:1001", "principal": "operator-a"}
],
})
source = SourceContext("ssh", "uid:1001", "request-123")For a real deployment, source_provider and source_subject should come from verified OAuth claims, mutual-TLS identity, SSH/local peer credentials, or equivalent authenticated transport state—not from request JSON supplied by the caller.
Configuration model
A tool declares a bounded public operation:
{
"capability": "demo.restart",
"permission": "demo.restart",
"resource": "demo:{service}",
"risk": "write",
"confirmation": "explicit",
"arguments": {
"service": {
"type": "string",
"required": true,
"enum": ["api", "worker"]
}
},
"request": {
"action": "restart",
"service": "$arg:service"
}
}The executor registry independently decides which isolated component owns the capability. Authorization independently decides which principals may use a permission against which resources and maximum risk level.
Tool discovery filters enumerated resource arguments through authorization. Values a principal cannot access are not returned in the catalog. Dynamic resource templates that cannot be safely enumerated are hidden from discovery and remain callable only if the concrete invocation passes authorization.
See examples/config for a complete minimal configuration.
MCP transport adapter
The project includes a stdio MCP adapter that serves the current stateless 2026-07-28 protocol and the latest handshake-era 2025-11-25 protocol. It implements server/discover, initialize, tools/list, tools/call and legacy ping without adding runtime dependencies.
Transport identity remains outside the MCP request body. An embedding transport supplies an MCPTrustedSource containing the authenticated provider and subject; the adapter creates SourceContext objects from that trusted metadata and generates gateway request IDs server-side. Client-supplied principal fields are never used for gateway identity.
tools/list is authorization-filtered through Gateway.catalog(). tools/call delegates authorization, routing, confirmation and audit semantics to the existing gateway core. For tools requiring explicit confirmation, the adapter exposes the existing confirmed and confirmation_token controls in the MCP input schema and returns a structured confirmation challenge when approval is required. The MCP host is responsible for collecting the intended approval before retrying the operation.
The built-in stdio transport uses newline-delimited JSON-RPC and enforces a bounded request-frame size. It writes protocol messages only to stdout; embedding applications should send diagnostics to stderr.
Executor SDK
The reusable Unix-socket executor server verifies the gateway envelope before dispatching an exact allowlisted capability. Handlers receive an ExecutorInvocation containing authenticated gateway metadata plus the service-specific request object:
from secure_ops_gateway import SQLiteReplayProtector, UnixSocketExecutorServer
KEY = b"replace-with-at-least-32-random-bytes"
def status(invocation):
return {
"ok": True,
"resource": invocation.resource,
"action": invocation.request["action"],
}
replay = SQLiteReplayProtector(
"/var/lib/secure-ops/demo/replay.sqlite3"
)
server = UnixSocketExecutorServer(
"/run/secure-ops/demo/executor.sock",
KEY,
{"demo.status": status},
replay_protector=replay,
)
server.serve_forever()The server accepts one authenticated request per connection, verifies HMAC-SHA256 with replay protection, validates the common executor payload, dispatches only exact registered capability names, and returns a signed response bound to the originating request ID. Request and response sizes and connection time are bounded. Per-connection failures do not terminate serve_forever().
The socket must live below a private directory owned by the executor user. The server refuses to overwrite any pre-existing path, creates the socket with mode 0600, pins its parent-directory identity, and removes the socket on shutdown only if the path still refers to the exact socket inode it created.
The built-in UnixSocketExecutorClient rejects unsigned responses, wrong request IDs, reflected request envelopes and replayed responses. ReplayCache remains a lightweight process-local option. SQLiteReplayProtector provides durable same-host replay protection across restarts and multiple processes using a private SQLite state file with atomic nonce admission. Multi-host deployments still need a distributed replay backend. Handler code remains responsible for validating its capability-specific invocation.request fields and for avoiding generic shell surfaces.
Integration coverage includes the official MCP Python SDK driving a real stdio subprocess through MCPAdapter, Gateway, the authenticated Unix-socket executor channel and an allowlisted executor handler, including the explicit-confirmation retry path.
Example systemd executor
examples/systemd_executor.py is the first concrete executor example. It exposes only service.status and service.restart for public service aliases mapped through a trusted local allowlist to fixed systemd .service unit names. The handler validates the authenticated capability, permission, risk, resource and service-specific request before executing anything.
The example invokes an absolute systemctl binary with an argument vector; it never constructs a shell command and never accepts an arbitrary unit name from the client. service.restart remains a normal write tool with explicit gateway confirmation. Executor-key and service-allowlist files are checked for ownership, regular-file type, symlink avoidance and unsafe write permissions before use.
See examples/systemd for matching tool, executor and authorization configuration plus deployment notes. The executor operating-system account still needs only the external permissions required for the allowlisted systemd units; prefer a dedicated account with narrowly scoped PolicyKit/systemd authorization over unrestricted root.
State-path safety
Security-sensitive state such as the confirmation database and JSONL audit log must live in a directory owned by the effective gateway user and not writable by group or other users. Existing ancestors must be owned by root or the gateway user. Final state files and existing path components may not be symlinks, and the gateway pins the parent directory identity so replacement after initialization is rejected. A file directly under shared /tmp is intentionally rejected; create a private 0700 subdirectory instead.
Admission limits
Gateway enables an in-process rate/concurrency controller by default: 120 calls per authenticated source per 60 seconds, at most 4 concurrent calls per source and 16 globally. Admission happens before identity resolution, so unknown-but-authenticated sources are limited too.
For multiple gateway workers on one host, use SQLiteAdmissionController with one private shared SQLite state file and pass it as admission_controller=. Rate-window events then survive worker restarts, while global and per-source in-flight limits are coordinated transactionally across processes. The controller stores only a SHA-256 digest of the authenticated (provider, subject) source key. On Linux it also records the owning boot identity and process start identity so leases left by crashed workers can be reclaimed without confusing a reused PID or a previous host boot for the original process.
from secure_ops_gateway import SQLiteAdmissionController
admission = SQLiteAdmissionController(
"/var/lib/secure-ops-gateway/admission.sqlite3",
max_inflight_global=16,
max_inflight_per_source=4,
max_calls_per_window=120,
window_seconds=60,
)
gateway = Gateway(
# ... normal gateway configuration ...
admission_controller=admission,
)A failed durable lease release is fail-closed: the lease remains counted instead of turning an already completed operation into a client-visible failure that could encourage a duplicate retry. Use release_failure_handler on SQLiteAdmissionController to surface that degradation out-of-band. Multi-host deployments still require a distributed admission backend for fleet-wide limits.
Structured observability
GatewayMetrics is an optional thread-safe, process-local collector for bounded operational telemetry. Pass it as metrics= to Gateway to record catalog/invoke outcomes, admission denials, identity/authorization denials, confirmation events, executor failures, audit degradation, in-flight request gauges and duration aggregates. The schema uses only fixed operation/outcome/event labels: principal IDs, transport subjects, request IDs, tool names, capabilities and resources are never accepted as metric labels.
from secure_ops_gateway import GatewayMetrics, SQLiteAdmissionController
metrics = GatewayMetrics()
admission = SQLiteAdmissionController(
"/var/lib/secure-ops-gateway/admission.sqlite3",
release_failure_handler=metrics.admission_release_failure,
)
gateway = Gateway(
# ... normal gateway configuration ...
admission_controller=admission,
metrics=metrics,
)
status = metrics.snapshot()
prometheus_text = metrics.render_prometheus()Metrics are deliberately best-effort: collector failures never change authorization, confirmation, audit or executor semantics. snapshot() returns a structured dictionary; render_prometheus() emits bounded Prometheus text without requiring an HTTP server or third-party dependency. Expose either representation only through a separately authenticated monitoring surface. Aggregate per-process collectors externally when multiple gateway workers are used.
Audit failure semantics
The invoke_started audit event is fail-closed: if it cannot be written, the executor is not called. Once an executor has returned success, failure to append invoke_succeeded is treated as an audit-channel degradation rather than an operation failure, avoiding a misleading client error that could trigger a duplicate mutation. Use audit_failure_handler to surface that degradation through an independent monitoring path.
Design principles
No generic shell in the gateway. Add explicit executor capabilities instead.
Authorization is resource-aware. Permission alone is insufficient.
Executor presence does not grant access. Routing and authorization are separate concerns.
Mutations require explicit confirmation by default.
writeandprivilegedtools are rejected unless they use explicit confirmation or deliberately declareallow_unconfirmed_mutation: true. Confirmation tokens bind the full security-relevant operation.Credentials stay behind the gateway. Executor authentication material is not part of client requests.
Identity comes from authenticated transport metadata. Clients do not choose principals.
Service-specific logic stays out of the core. Integrations belong in isolated executors.
Project status
0.1.1 is a security-hardening alpha release of the standalone public project. The API and configuration schemas may change before 1.0.
Near-term work includes deployment documentation, distributed multi-host admission/replay backends and richer MCP confirmation/elicitation integration. See CHANGELOG.md for security changes since 0.1.0.
Security
Read SECURITY.md and THREAT_MODEL.md before using this software for privileged operations. Treat the example policies as demonstrations, not production defaults.
License
Apache License 2.0. See LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
- gatewayOAuthai.sealgate
MCP gateway with runtime security policy, tool-call-level control, and audit of agent actions.
Find, vet, and run MCP tools through a secure audited gateway with prompt-injection risk scoring
Zero-secret MCP gateway for AI agents: risk-scored, audited calls with human-in-the-loop approval.
Zero-setup MCP gateway securely connecting AI to your tools with authentication and workflows
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA secure MCP gateway for enterprise AI tool execution, enabling governed invocation of business tools with authentication, RBAC, audit logging, PII redaction, and async processing.Apache 2.0

evav-gatewayofficial
AlicenseNot gradedqualityBmaintenanceGoverned MCP gateway that lets AI agents call tools with policy enforcement, prompt-injection screening, a kill-switch, and tamper-evident signed audit logs.Apache 2.0- FlicenseNot gradedqualityCmaintenanceEnables AI agents to safely call MCP tools through a security gateway that enforces per-role authorization and least-privilege tool scoping.1-
- AlicenseNot gradedqualityBmaintenanceEnables developers to wrap existing MCP servers with a deterministic policy enforcement gate that intercepts tool discovery and tools/call requests over stdio, allowing only authorized calls to reach the downstream server. It supports dynamic tool exposure or hiding, allow/deny/approval-required decisions, configurable error disclosure modes, and Ed25519-signed retry credentials for approved workflows.Apache 2.0