Skip to main content
An MCP gateway is an endpoint you compose. You bind capabilities to it, and outside MCP clients call them. Each gateway is its own host, its own authorization server, and its own policy boundary.

What a gateway is

  • A host: mcp-{handle}.noorle.com, matched by the handle you chose.
  • An MCP server, speaking Streamable HTTP.
  • A capability surface: whichever built-ins, plugins, and connectors you bound, presented as tools.
  • Its own OAuth authorization server. A token minted for one gateway does not work on another.
There is no platform-wide Noorle MCP server. Every gateway is something a customer composed.

Transport

Streamable HTTP, and only Streamable HTTP. The MCP service is mounted as the router’s fallback, so it answers on the origin root — there is no separate /sse endpoint and no WebSocket endpoint anywhere on the gateway. Practical consequences:
  • A POST must send an Accept header containing both application/json and text/event-stream, or it is rejected with 406.
  • Responses come back as text/event-stream frames on the POST response. SSE is the response encoding, not a separate channel.
  • SSE keep-alive is disabled.
  • For clients on protocol revision 2026-07-28 or later, GET returns 405. That is what the revision prescribes; resumable SSE via Last-Event-ID is not supported.
Older clients still get the session path, including the server-to-client GET stream, for compatibility. The advertised protocol-version fallback is 2025-11-25. A client asking for a version the transport knows gets that version echoed back; only an unknown version falls back.

What the gateway implements

The gateway advertises tools only. There is no resource implementation and no prompt implementation. A gateway serves tools. notifications/tools/list_changed is never sent. When you change a gateway’s bindings, connected clients are not notified.

The tool list is per caller

Two callers hitting the same gateway can legitimately get different lists. Membership differs. Tools that need a workspace to write into — all of Files, all of Sandbox, and Browser’s screenshot / pdf / snapshot — are withheld from a caller with no authenticated identity. They are removed from tools/list and from the discovery corpus, so they cannot be reached by name either. Annotations differ. Each listed tool carries a spec-native readOnlyHint derived from its risk tier, plus a dev.noorle/autonomy entry in _meta carrying the outcome that caller would get. The outcome is caller-specific because the account’s deny ledger is caller-keyed. Denied tools stay listed rather than disappearing. Omitting them would churn the surface on every policy edit and would hide a deliberate “exposed but never autonomous” configuration. The annotation carries an outcome and no reason — tools/list is reachable unauthenticated on a public gateway, and the reason would name the account’s policy internals.

Caching

tools/list responses carry ttlMs: 300000 (five minutes) and cacheScope: "private". cacheScope is always private, on every gateway, precisely because of the two paragraphs above. Neither value is configurable. The TTL is a freshness hint, not a snapshot guarantee. The list can change before it expires, and staleness never grants authority — a stale allow annotation is re-evaluated at dispatch and can still be denied.

Workspace anchoring

When a gateway tool needs storage — a file write, a sandbox, a screenshot — the workspace it writes into is derived server-side from the gateway id and the authenticated user id. Nothing comes off the wire for this. In particular the client-sent Mcp-Session-Id header is never consulted: it is client-chosen and unvalidated, and modern clients do not send one at all. An anonymous caller on a public gateway gets no workspace. Rather than inventing one, the platform withholds the tools that would need it. The gateway id is part of the derivation, so one principal bound to two gateways gets two separate workspaces.

Authentication

Three auth modes per gateway:
  • Public — no authentication. Anonymous callers get the reduced tool list described above.
  • Private — a credential is required.
  • Private with dynamic registration — same, plus /oauth/register accepts dynamic client registration. This is the default.
A bearer credential is disambiguated by prefix: ak- is an API key, eyJ is a JWT, anything else is rejected. A JWT is admitted only if it validates against that gateway’s issuer, carries the mcp scope, matches the gateway’s account, and lists that gateway’s resource URN in its audience. There is no cross-gateway fallback. An API key is admitted if it is valid, belongs to the account, and its principal holds Read on the gateway. After authentication, a billing check runs. No budget returns 402 Payment Required; a failure to check returns 503.

Each gateway is its own authorization server

mcp-{handle}.noorle.com serves /oauth/authorize, /oauth/token, and /oauth/register, plus RFC 9728 protected-resource metadata and RFC 8414 authorization-server metadata. jwks_uri is null on purpose: platform OAuth tokens are HS256-symmetric, and publishing a JWKS would mean publishing the signing secret. There is no device authorization endpoint on a gateway. Human consent for /oauth/authorize is handed to the Portal carrying only an opaque handoff id. All protocol validation — client, registered redirect URI, PKCE, resource indicator — happens at the gateway before that hop.

Rate limits

Two limiters, both fixed 60-second windows, both keyed on (gateway, caller). An anonymous caller keys on peer IP rather than sharing one bucket with every other anonymous caller. Both defaults ship unset in every environment overlay, so these are the live values. Scope caveat worth planning around: the request limiter covers the path that resolves caller context, so tools/call and tools/list share one budget. initialize and ping sit outside it, which is why the handshake cap is a separate middleware. Both limiters fail open if Redis is unreachable. Other hard limits: a 4 MiB maximum POST body and a 240-second server-side request timeout, after which the gateway returns 504.

Autonomy on a gateway

An MCP gateway is an unattended surface. There is no live channel to ask a human on. That means a tool call which would pause for approval is instead denied, with reason non_interactive_surface. Pinning a tool as always-ask on a gateway is a deterministic deny, not a prompt. Per-gateway policy carries a level, an allow_autonomous list, and an always_ask list. Saving a gateway policy is load-bearing: its presence enables gating regardless of the account-level toggle, and it supersedes per-user auto-approve caches on that gateway — Act-tier tools and above deny until they are listed in allow_autonomous.

Which system tools appear

Almost none. Journal tools are agent-only. Skills tools need an agent id, which a gateway call has none of. Form-rendering tools need an interactive surface. Computer is AGENT-scoped and never appears. The workflow tools are the only system tools on a gateway, listing the workflows attached to it.

Troubleshooting


Next: Capabilities — what you bind to a gateway. For the product overview, see the MCP gateway page.