{"slug": "show-hn-sentinelmcp-stateless-policy-firewall-and-token-cost-proxy-for-mcp", "title": "Show HN: SentinelMCP – Stateless policy firewall and token-cost proxy for MCP", "summary": "A developer released SentinelMCP, a stateless policy firewall and token-cost proxy that sits in front of any Model Context Protocol (MCP) server and checks every tool call for identity, policy and budget before it reaches backend infrastructure. SentinelMCP enforces Origin validation for DNS-rebinding mitigation, JWKS-backed JWT verification with asymmetric algorithms only and resource-claim binding against confused-deputy replay, structural token estimation over the JSON-RPC payload, and 7-day sliding-window budgets held in a fixed-memory circular buffer of 168 hourly buckets. A budget breach returns an InputRequiredResult JSON-RPC success carrying the projected overrun, run-rate spike and an HMAC-signed, request-bound resumeToken that a human clears via POST /mcp/resume with APPROVE or DENY.", "body_md": "**Stateless enterprise policy firewall & token-cost proxy for MCP.**\n\nDrop it in front of any Model Context Protocol server. Every tool call gets checked for identity, policy, and budget before it reaches your infrastructure — no database, no sticky sessions, nothing new to operate at 3am.\n\n`docker run --rm -it ghcr.io/asctttt/sentinel-mcp:demo`\n\nMCP made it trivial to wire an LLM agent up to real tools — databases, payment APIs, internal services. It did **not** ship a policy layer. If you've run an MCP server in production, you've probably already hit one of these:\n\n**Runaway loops.** An agent gets stuck retrying a failing call, or a bad prompt sends it into a call-and-recall spiral. Nothing in the MCP spec stops it. You find out from the bill, not an alert.\n\n**Token budgets that don't exist until they explode.** Nothing in a typical agent loop stops it from calling the same expensive tool hundreds of times in an hour. The common failure mode: a $40/day budget quietly becomes a $40k/month bill, and the first signal anyone gets is the invoice.\n\n**DNS-rebinding and confused-deputy risk.** The Streamable HTTP transport spec requires Origin validation for exactly this reason — a malicious page can rebind DNS to reach a local MCP server your browser would otherwise block. Most self-hosted servers don't implement it. Stack a compromised or injected tool result on top, and \"just add an MCP server\" is a bigger attack surface than most teams have budgeted time to secure.\n\nSentinelMCP won't stop a model from being tricked — no proxy can. What it does is make sure a tricked or runaway agent can't reach a tool it isn't allowed to call, blow through a budget nobody approved, or keep running once the numbers look wrong — without a human getting a say.\n\n- **Origin validation** on every request — the DNS-rebinding mitigation the Streamable HTTP spec requires and most self-hosted servers skip. Loopback-only by default; explicit allowlist for anything else, override not extend.\n- **ID-JAG identity enforcement** — real JWKS-backed JWT verification, asymmetric algorithms only (no HMAC-downgrade path), including the`resource` claim binding that closes the confused-deputy hole: a token issued for one MCP server can't be replayed against another.\n\n- **Structural token estimation** — walks the actual JSON-RPC payload instead of`length / 4` , pricing natural-language content and JSON structure at different, calibrated rates. Input measured exactly; output projected from a per-tool rolling average and trued up against the real response after every call.\n- **7-day sliding-window budgets** per team, enforced against a fixed-memory circular buffer (168 hourly buckets) — bounded, no per-request log to prune, no unbounded growth under load.\n- **Spike detection independent of the hard cap** — a sudden burst against a team's own recent baseline gets caught even while the cumulative total is nowhere near the limit. Catches the exponential-growth pattern before the raw budget does.\n\n- A budget breach doesn't 500 the call. It returns `InputRequiredResult` — a normal JSON-RPC*success* — carrying the projected overrun, the run-rate spike, and an HMAC-signed, request-bound`resumeToken` .\n- `POST /mcp/resume` with`{ resumeToken, decision: \"APPROVE\" | \"DENY\", originalRequest }` lets a human clear it. The token is bound to the exact call via a canonical-JSON hash of the request — approve*this* wire transfer, not whatever an agent decides to substitute next.\n- Delivery is transport-aware: the pause notice reaches the client over whichever channel the original call used.\n\n- **Streamable HTTP** — the current MCP standard (spec 2025-03-26+), and the primary path.\n- **Legacy HTTP+SSE** (spec 2024-11-05) for backward compatibility with clients that haven't migrated. Not the default; don't build new integrations against it.\n\n```\ngit clone https://github.com/your-org/sentinelmcp.git\ncd sentinelmcp\nnpm install\ncp .env.example .env\n```\n\nSix required values in `.env` — everything else ships with a sane default:\n\n| Variable | What it is | \n|---|---|\n| `MCP_UPSTREAM_URL` | The real MCP server SentinelMCP forwards allowed calls to | \n| `JWKS_URI` | Your IdP's JWKS endpoint (Okta, Auth0, Azure AD, …) | \n| `ID_JAG_ISSUER` | Expected `iss` claim on incoming ID-JAG tokens | \n| `ID_JAG_AUDIENCE` | Expected `aud` claim | \n| `ID_JAG_RESOURCE` | This gateway's resource identifier — the anti-confused-deputy binding | \n| `HMAC_SECRET` | 32+ random chars signing resume tokens — `openssl rand -base64 32` .**Must be identical across every replica** , or resume verification breaks depending on which one handles a given call. | \n\n```\nnpm run build && npm start   # production\n# or, for local iteration:\nnpm run dev                  # tsx watch\n```\n\nConfirm it's alive:\n\n```\ncurl http://localhost:8080/healthz\nflowchart LR\n    Client([\"MCP Client<br/>Claude Desktop · Cursor · custom agent\"])\n\n    subgraph Sentinel[\"SentinelMCP — stateless gateway\"]\n        direction TB\n        Origin[\"Origin Guard<br/>DNS-rebinding check\"]\n        Policy[\"Policy Gate<br/>tool denylist\"]\n        Identity[\"Identity Gate<br/>ID-JAG · JWKS verify\"]\n        Budget[\"Budget Gate<br/>sliding window + spike detect\"]\n        Origin --> Policy --> Identity --> Budget\n    end\n\n    Upstream[\"Upstream MCP Server(s)\"]\n    Resume[\"POST /mcp/resume<br/>HMAC-signed resumeToken\"]\n\n    Client -->|\"POST /mcp\"| Origin\n    Budget -->|\"allowed → forward\"| Upstream\n    Upstream -->|\"result\"| Client\n    Budget -.->|\"denied → paused<br/>InputRequiredResult\"| Client\n    Client -.->|\"human decides,<br/>out of band\"| Resume\n    Resume -->|\"APPROVE<br/>budget bypassed only —<br/>identity + policy still enforced\"| Upstream\n```\n\nSentinelMCP sits in front of one or more upstream MCP servers as a reverse proxy. Every gate runs on every request; a denial short-circuits before the call ever reaches your tools. Only the budget gate can be bypassed, and only by an explicit, signed, single-call human approval — identity and policy are re-checked even on resume.\n\nTwo of the five subsystems keep state in memory (SSE session tracking and\nthe budget ledger) — the request/response path itself is stateless and\nhorizontally scalable behind a plain load balancer. See\n[`docs/ENGINEERING.md`](https://github.com/asctttt/sentinelmcp/blob/main/docs/ENGINEERING.md) for exactly which pieces, and\nwhat that means for running more than one replica.\n\n- **63 tests, 19 suites** —`npm test` . Unit coverage for the token\nestimator, the sliding-window budget algorithm (spike detection included,\nverified with an injectable clock rather than waiting real days), and the\nHMAC token service (round-trip, tampering, forged secrets, canonical-hash\nbinding). Integration coverage runs the full request pipeline through`fastify.inject()` against a real, ephemeral-port mock identity provider\nand mock upstream — no stubbed JWT verification, no mocked crypto.\n- **Strict TypeScript** —`strict` ,`noUncheckedIndexedAccess` ,`noImplicitOverride` . No`any` in the source tree.\n- **Zero test-framework dependencies** — built on Node's own`node:test` .\n- CI runs typecheck, the full suite, and a production build on every push, against Node 20 and 22.\n\nThe full engineering log — what's verified, every bug found during\ndevelopment and how it was fixed, and every known limitation stated\nplainly rather than glossed over — lives in\n[`docs/ENGINEERING.md`](https://github.com/asctttt/sentinelmcp/blob/main/docs/ENGINEERING.md).\n\nSentinelMCP's core gateway is free and MIT-licensed. For teams running multiple replicas in production or requiring compliance audit trails, **SentinelMCP Pro** unlocks enterprise capabilities via a single `SENTINEL_LICENSE_KEY` environment variable:\n\n| Feature | Community (MIT) | SentinelMCP Pro ($199/mo) | \n|---|---|---|\n| Origin Guard, Policy Gate & ID-JAG Verification | Included | Included | \n| 168-Bucket Sliding Window & Spike Detection | Single Instance | Multi-Replica / Cluster Ready | \n| Structured SIEM / Datadog Audit Log Export | — | Included | \n| Slack / PagerDuty Budget Breach Webhooks | — | Included | \n| Priority Security Patches & Architecture Support | Community Issues | Direct Engineering Support | \n\n👉 [Purchase a SentinelMCP Pro License Key via Polar.sh](https://buy.polar.sh/YOUR_CHECKOUT_LINK)", "url": "https://wpnews.pro/news/show-hn-sentinelmcp-stateless-policy-firewall-and-token-cost-proxy-for-mcp", "canonical_source": "https://github.com/asctttt/sentinelmcp", "published_at": "2026-10-22 12:37:04+00:00", "updated_at": "2026-10-22 12:40:09.213471+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-safety", "ai-tools", "developer-tools"], "entities": ["SentinelMCP", "Model Context Protocol", "Streamable HTTP", "ID-JAG", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-sentinelmcp-stateless-policy-firewall-and-token-cost-proxy-for-mcp", "markdown": "https://wpnews.pro/news/show-hn-sentinelmcp-stateless-policy-firewall-and-token-cost-proxy-for-mcp.md", "text": "https://wpnews.pro/news/show-hn-sentinelmcp-stateless-policy-firewall-and-token-cost-proxy-for-mcp.txt", "jsonld": "https://wpnews.pro/news/show-hn-sentinelmcp-stateless-policy-firewall-and-token-cost-proxy-for-mcp.jsonld"}}