Skip to main content

MCP Gateway

@vengtoo/mcp-gateway is a drop-in proxy that sits between an MCP client (Claude Code, Cursor, VS Code, GitHub Copilot) and any Model Context Protocol server. It intercepts every tool call and checks authorization before forwarding it to the downstream server.

Because it runs as a proxy, you don't modify the MCP server: point your client at the gateway instead of the server directly. This is the right choice when you're connecting an agent to servers you don't own or can't change.

Why

An agent connected to an MCP server can call any tool it's exposed: read your database, delete files, execute arbitrary SQL. The gateway puts a policy enforcement point in front of those tools, so every call is authorized before it executes and every invocation is logged.

How it works

The gateway runs as an MCP server: over stdio by default (a local subprocess), or over HTTP for a remote, shared deployment (see Transports). Either way, your client talks to it exactly as it would talk to the real server; the gateway spawns the downstream server, forwards the tools it exposes, and gates each call.

Every decision is fail-closed: if the authorization backend is unreachable or returns an error, the call is denied, not allowed through.

Two modes

Configure either cloudUrl or agentUrl in your gateway.config.json: the gateway connects to whichever is set.

ModeDecision pointWhen to use
CloudVengtoo Cloud (api.vengtoo.com) evaluating your managed policiesCentrally managed policies, shared across deployments, full Decision Log
LocalVengtoo Agent on localhost:8181Offline, self-contained, no cloud account needed

Transports

The two modes above decide where authorization is evaluated. Transports decide how callers reach the gateway: the two are independent, so any transport pairs with either mode.

TransportShapeWhen to use
stdio (default)Local subprocess speaking MCP over stdin/stdoutA single desktop agent (Claude Code, Cursor, Claude Desktop). No network surface.
HTTPStreamable HTTP server at a URLA remote agent, or many concurrent agents, sharing one governed gateway.

stdio

The default. Nothing to configure: the client launches the gateway as a child process. All the examples below use it unless noted.

HTTP (remote gateway)

Serve the gateway over HTTP so agents can reach it at a URL. Enable it with --http (or "transport": "http" in the config):

npx -y @vengtoo/mcp-gateway --config gateway.config.json --http --port 8808
{
"vengtoo": { "cloudUrl": "https://pdp.vengtoo.com/access/v1/evaluation", "apiKey": "vgt_..." },
"subject": "agent:prod-assistant",
"transport": "http",
"http": {
"port": 8808,
"host": "0.0.0.0",
"path": "/mcp",
"authTokens": ["<caller-token>"],
"allowedHosts": ["gateway.example.com"]
},
"servers": { "database": { "command": "node", "args": ["./db-server.js"] } }
}

The gateway runs the MCP Streamable HTTP transport in stateful mode: the client's first request (initialize) mints a session, returned in the mcp-session-id header, that every follow-up request carries. Many agents can hold sessions concurrently against one gateway, all governed by the same policies and sharing one pool of downstream servers.

An unauthenticated GET /healthz liveness endpoint is always served (returns {"status":"ok"}) for load balancers and readiness probes.

Securing a public endpoint

The HTTP listener is hardened for public exposure: bounded request bodies, connection timeouts (slowloris protection), and a bounded, idle-evicted session table.

The most important guard: the gateway refuses to bind a non-loopback interface (e.g. 0.0.0.0) unless you configure caller auth (http.callers or http.authTokens); otherwise it exits at startup rather than silently exposing an open MCP endpoint. Callers present Authorization: Bearer <token>; requests without a valid token get 401. To knowingly run an open endpoint (e.g. behind your own auth proxy), set VENGTOO_GATEWAY_ALLOW_UNAUTHENTICATED=true.

Set http.allowedHosts / http.allowedOrigins to enable DNS-rebinding protection: the transport then rejects requests whose Host/Origin header isn't listed.

Per-caller identity

One HTTP gateway can serve many agents, each authorized as itself. Map each bearer token to a subject with http.callers:

"http": {
"port": 8808,
"host": "0.0.0.0",
"callers": [
{ "token": "<token-a>", "subject": "agent:claude" },
{ "token": "<token-b>", "subject": "agent:cursor" }
]
}

Each call and audit record is then attributed to that caller's subject and evaluated against its own policies, not one shared identity. A session is bound to the token that opened it, so a different caller presenting another caller's mcp-session-id gets a 401. Also settable via VENGTOO_GATEWAY_HTTP_CALLERS="<token>=agent:claude,<token>=agent:cursor".

http.authTokens remains for the simpler case: anonymous tokens that grant access but run as the config's global subject. In open mode (no tokens configured, loopback or ALLOW_UNAUTHENTICATED), calls also run as the global subject.

note

Per-caller rate-limiting at the gateway is still on the roadmap; today rate limits are enforced cloud-side, per subject and action.

Quick start (cloud mode)

1. Create a gateway.config.json

{
"vengtoo": {
"cloudUrl": "https://pdp.vengtoo.com/access/v1/evaluation",
"apiKey": "vgt_..."
},
"subject": "agent:ai-assistant",
"servers": {
"database": {
"command": "node",
"args": ["./my-database-mcp-server.js"]
}
}
}

The API key can also be supplied via VENGTOO_API_KEY.

2. Point your MCP client at the gateway

claude mcp add --transport stdio vengtoo-gateway -- \
npx -y @vengtoo/mcp-gateway --config /path/to/gateway.config.json

Your agent now calls tools through the gateway, and every call is authorized against your Vengtoo policies before it executes. Tool inventory, decisions, and subjects are visible in the Vengtoo console.

Local mode (no cloud account)

Install and start the Vengtoo Agent locally, then scaffold a policy for your tools:

npx -y @vengtoo/mcp-gateway --config ./gateway.config.json --generate-policy ./policy.rego

The gateway connects to each downstream server, discovers all tools, and generates a starter policy file classified by trust level: read-only tools allowed, write operations requiring explicit approval, destructive operations blocked by default:

package vengtoo.mcp

default allow := false

# LOW trust — allowed for all approved agents
allow if { input.resource.name == "database__query" }
allow if { input.resource.name == "database__list_tables" }

# MEDIUM trust — allowed for specific agents
allow if {
input.resource.name == "database__execute"
input.subject.id in {"agent:cursor", "agent:claude"}
}

# HIGH trust — uncomment to allow (destructive operations)
# allow if {
# input.resource.name == "database__drop_table"
# input.subject.id == "agent:claude"
# }

Review the file, adjust the rules, and start the agent:

vengtoo-agent --policy ./policy.rego

Then use the same gateway.config.json with agentUrl instead of cloudUrl:

{
"vengtoo": {
"agentUrl": "http://localhost:8181"
},
"subject": "agent:dev-assistant",
"servers": {
"database": {
"command": "node",
"args": ["./my-database-mcp-server.js"]
}
}
}

For centralized policy management, audit logs, and multi-agent visibility, point the gateway at Vengtoo Cloud instead: swap agentUrl for cloudUrl and add your API key.

Tool governance

Beyond per-call policy checks, the gateway enforces the governance state each tool holds in the Vengtoo console. Every call is checked against this state before any policy evaluation, and the gateway fails closed.

StateWhat it meansGateway behavior
ApprovedReviewed; current schema matches the approved baselineProceeds to policy evaluation
Pending reviewDiscovered but never approved: a new tool is not trusted on first sightDenied with tool_pending_review until an admin approves it
DriftedSchema changed from the approved baselineDenied (drift block) until re-approved
BlockedExplicitly blocked by an adminDenied

State is synced from the cloud as tools change and re-seeded on startup, so a restart never silently makes a held or blocked tool callable again. Approve or block tools from the MCP Governance section of the console; the change reaches the gateway on the next sync.

The gateway reports only facts about tools: names, schemas, and what changed. It never scores trust or drift severity: those judgments are computed in the Vengtoo cloud from the reported facts. This keeps the gateway, the most exposed component, unable to influence a security decision even if a downstream server it proxies is compromised.

Human-in-the-loop approvals

A policy can require a human to approve an action before it runs (set on the policy in the console). When an agent calls such a tool, the gateway blocks the call and waits: it polls Vengtoo until the request is approved (the call then proceeds and returns the tool's result), denied, or the approval window lapses.

To keep the call from outliving your MCP client's request timeout, the gateway blocks for at most vengtoo.approvalMaxWaitMs (default 50s). If approval hasn't arrived by then, the tool returns a "still awaiting approval" message and the agent can re-invoke to resume waiting on the same pending request: no duplicate approval is created.

Configuration

FieldTypeRequiredDescription
vengtoo.cloudUrlstring*Vengtoo Cloud evaluation URL (cloud mode). Env: VENGTOO_CLOUD_URL
vengtoo.agentUrlstring*URL of a local Vengtoo Agent (local mode). Env: VENGTOO_AGENT_URL
vengtoo.apiKeystringAPI key from the Vengtoo console. Env: VENGTOO_API_KEY
vengtoo.clientIdstringOAuth2 client ID: alternative to apiKey when using client credentials
vengtoo.clientSecretstringOAuth2 client secret. Use together with clientId
vengtoo.timeoutMsnumberAuthorization request timeout in ms (default: 10000)
vengtoo.blockOnDriftbooleanLocal mode only: block any tool whose schema drifts from the approved baseline (default: false)
vengtoo.approvalMaxWaitMsnumberMax time a call blocks awaiting human approval before returning "still pending" (default: 50000)
subjectstringyesIdentity of the agent making tool calls. Env: VENGTOO_SUBJECT
subjectTypestringSubject type (default: "agent")
resourceTypestringResource type for authorization checks (default: "mcp_tool")
serversobjectyesMap of downstream MCP servers to proxy
transportstringCaller transport: "stdio" (default) or "http". Env: VENGTOO_GATEWAY_TRANSPORT
httpobjectHTTP transport settings (used when transport is "http"), see below

* Provide either cloudUrl (cloud mode) or agentUrl (local mode).

Each entry under servers:

FieldTypeRequiredDescription
commandstringyesCommand to spawn the MCP server
argsstring[]Command arguments
envobjectAdditional environment variables

The http block (used when transport is "http"):

FieldTypeDefaultDescription
portnumber8808TCP port to listen on. Env: VENGTOO_GATEWAY_PORT / PORT
hoststring127.0.0.1Interface to bind; 0.0.0.0 accepts remote connections. Env: VENGTOO_GATEWAY_HOST
pathstring/mcpURL path of the MCP endpoint. Env: VENGTOO_GATEWAY_PATH
callersobject[]Per-caller identity: { token, subject } pairs: each token authorizes as its own subject. Env: VENGTOO_GATEWAY_HTTP_CALLERS (token=subject,…)
authTokensstring[]Anonymous bearer tokens (grant access, run as the global subject). Env: VENGTOO_GATEWAY_HTTP_TOKENS (comma-separated)
allowedHostsstring[]Enables DNS-rebinding protection; rejects requests with an unlisted Host header
allowedOriginsstring[]Enables DNS-rebinding protection; rejects requests with an unlisted Origin header

Set VENGTOO_GATEWAY_ALLOW_UNAUTHENTICATED=true to bind a non-loopback interface without any caller auth (callers or authTokens); otherwise the gateway refuses to start (see Securing a public endpoint).

Audit forwarding

Every tool call, allowed or denied, is recorded. Records are always written as structured JSON to stderr, and forwarded to Vengtoo by default: in cloud mode to <cloudUrl>/v1/agent-logs/ingest, in local mode to <agentUrl>/v1/agent-logs/ingest. Forwarding activates automatically whenever the gateway holds credentials (the ingest endpoint authenticates the batch and derives the tenant from your API key). Events are batched and flushed on an interval.

FieldTypeDefaultDescription
audit.forwardbooleantrueSet false to suppress network forwarding and keep audit stderr-only
audit.forwardUrlstringOverride the ingest endpoint. Defaults to the cloud/agent ingest URL for the configured mode
audit.tenantIdstringOptional. Attribution comes from the API key; a matching value is fine, a mismatched one is rejected

CLI flags

FlagDescription
--config <path>Path to the gateway config file (default: ./gateway.config.json)
--httpServe over HTTP instead of stdio
--port <n>HTTP listen port (default: 8808)
--host <h>HTTP bind interface (default: 127.0.0.1)
--path <p>HTTP endpoint path (default: /mcp)
--list-toolsList all tools from the configured downstream servers and exit
--generate-policy [path]Discover tools and generate a trust-classified starter policy (default: policy.rego)

MCP client setup

Point your client at the gateway instead of the downstream server. The examples below use the default stdio transport, where the client launches the gateway as a subprocess. If you run the gateway over HTTP instead, point your client at its URL (e.g. http://gateway.example.com:8808/mcp) using the client's HTTP/streamable transport, and supply the bearer token if one is configured.

Claude Code

claude mcp add --transport stdio vengtoo-gateway -- \
npx -y @vengtoo/mcp-gateway --config /path/to/gateway.config.json

Cursor

Add to .cursor/mcp.json:

{
"mcpServers": {
"vengtoo-gateway": {
"command": "npx",
"args": [
"-y",
"@vengtoo/mcp-gateway",
"--config",
"/path/to/gateway.config.json"
]
}
}
}

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
"mcpServers": {
"vengtoo-gateway": {
"command": "npx",
"args": [
"-y",
"@vengtoo/mcp-gateway",
"--config",
"/path/to/gateway.config.json"
]
}
}
}

VS Code / GitHub Copilot

Add to .vscode/mcp.json:

{
"servers": {
"vengtoo-gateway": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@vengtoo/mcp-gateway",
"--config",
"/path/to/gateway.config.json"
]
}
}
}

Source

github.com/vengtoo/mcp-gateway: Apache-2.0 licensed.