Show HN: mcp-gate – Ephemeral capability token proxy for LLM tool execution in Go
I built mcp-gate, a small Go reverse proxy for a problem I keep coming back to with LLM agents:
How much authority should we actually give a model when it calls a tool?
Repo: https://github.com/ananthaprakashb/mcp-gate
A typical agent integration eventually ends up holding something powerful: an API key, service credential, OAuth token, or access to an MCP/tool server that can perform multiple operations.
Even when the model is supposed to perform one very specific action, the credential it indirectly controls may authorize far more.
I wanted the authorization boundary to look more like this:
The model never receives the upstream API credential.
Instead, the trusted orchestrator exchanges its gate credential for an ephemeral capability token authorizing one specific operation.
For example:
{
"route": "tickets",
"method": "POST",
"path": "/v1/tickets",
"ttl_seconds": 15
}
The returned bearer token is HMAC-signed and bound to:
a configured route
an HTTP method
an exact path
an expiration time
a unique token ID
It can then be used for that operation and, by default, only once.
Why single-use tokens?
Short expiration helps, but I don't think expiration alone is enough for agent tool calls.
Imagine issuing a 30-second token to create a ticket.
The intended authority is really:
Create this ticket once.
It isn't:
Create as many tickets as you can during the next 30 seconds.
So mcp-gate maintains replay state for the token ID. Once a valid request consumes the capability, replaying the same token fails.
The built-in store is process-local. There is also a ReplayStore interface intended for distributed implementations such as Redis/Valkey using an atomic SET ... NX ... EX ... style operation.
If the replay store fails, the proxy fails closed rather than silently disabling replay protection.
The arguments are part of the security boundary
Restricting the endpoint isn't sufficient if the model can invent additional JSON fields.
Suppose a tool is intended to expose:
{
"title": "Investigate alert",
"priority": "high"
}
but the underlying API also understands:
{
"title": "Investigate alert",
"priority": "high",
"admin": true
}
Depending entirely on prompt instructions to prevent that doesn't seem like a great security boundary.
mcp-gate therefore validates the body before forwarding it.
I deliberately implemented a small subset of JSON Schema rather than pulling in a large schema system. It currently supports things such as:
type
properties
required
items
enum
pattern
minLength / maxLength
minimum / maximum
minItems / maxItems
Objects are closed by default.
If a property wasn't declared in the policy, it isn't forwarded.
Importantly, schema validation happens before the capability is consumed. An agent can therefore correct malformed arguments without losing the one legitimate execution it was authorized to perform.
Upstream secrets stay upstream
Route configuration lives on mcp-gate, not inside the capability.
A route can inject things such as:
Bearer credentials
Basic Auth
fixed headers
fixed query parameters
So an agent might see:
POST /proxy/tickets/v1/tickets
Authorization: Bearer <ephemeral-capability>
while mcp-gate sends something like:
POST https://internal-api/v1/tickets
X-API-Key: <actual-secret>
The actual upstream secret never needs to enter the model context.
I think this distinction becomes increasingly useful as agents interact with infrastructure, CI/CD, databases, ticketing systems, cloud APIs, internal admin tools, and eventually more destructive operations.
I also didn't want the proxy to become an information leak
Upstream failures are deliberately sanitized.
If the backend responds with something like:
database connection failed at postgres.internal.example
token=...
stack trace...
mcp-gate does not pass that body back to the model.
The caller gets a stable error instead.
Request and response sizes are bounded, caller-provided authentication headers aren't forwarded to the upstream service, and sensitive hop-by-hop headers are restricted.
Why Go?
There isn't much framework machinery here.
The project is intentionally small and uses Go's HTTP and crypto primitives. My goal is for the authorization path to remain understandable enough that someone can read it rather than trusting a large opaque security layer.
You can run it with Go 1.22+:
export GATE_SIGNING_KEY='replace-with-at-least-32-random-characters'
export GATE_ADMIN_KEY='orchestrator-to-gate-secret'
export GATE_ROUTES='[...]'
go run ./cmd/mcp-gate
There is also a Docker image setup and an end-to-end Docker Compose example with a mock ticket API and a small agent:
cd examples
docker compose up --build \
--abort-on-container-exit \
--exit-code-from agent
CI is set up to run tests with the race detector, go vet, and govulncheck.
What mcp-gate is not
This isn't an identity provider, and it isn't trying to replace OAuth.
It also isn't a complete policy engine or a full implementation of JSON Schema.
The idea is narrower:
Turn broad application authority into a tiny, short-lived capability for a particular LLM tool execution.
The current implementation still has obvious areas to explore:
distributed replay-store adapters
asymmetric signing / key rotation
richer policy constraints
argument-bound capabilities
rate/budget limits
audit events
MCP-native integration examples
OpenTelemetry support
Kubernetes deployment examples
I've intentionally kept the first version small enough to reason about before expanding it.
The question I'm interested in
As agents become more autonomous, I think we need to distinguish between:
"The agent is authenticated"
and:
"The agent has authority to perform exactly this operation,
with these arguments, once, for the next few seconds."
mcp-gate is an experiment around that second model.
I'd particularly appreciate feedback from people building MCP servers, coding agents, internal automation, agent gateways, or security infrastructure.
Where would this capability model break down in a real agent architecture?
And what would you need before putting something like this between an LLM agent and a production API?
GitHub:
https://github.com/ananthaprakashb/mcp-gate
Comments
Post a Comment