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

Popular posts from this blog

Recursion

LinkedList - React Show

Train Game