> ## Documentation Index
> Fetch the complete documentation index at: https://docs.horizon.prefect.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Gateway

> How Horizon receives MCP client requests, checks access, preserves sessions, and routes traffic to deployed servers.

export const sessionTTL = 24;

The Horizon gateway is the request layer in front of deployed MCP servers. It is
the part of Horizon that receives client traffic, identifies the deployment,
applies the server's access settings, preserves MCP session routing, and sends
the request to the currently promoted deployment artifact.

<Info>
  The gateway runs before your server code. Server compute starts after the
  gateway has accepted and routed a request. For the Python execution contract,
  see
  [Compute model](/platform/compute-model#execution-contract).
</Info>

## Request flow

Every request follows the same broad path.

<Steps>
  <Step title="A client calls the deployment URL">
    The client sends an MCP request to a Horizon server URL. Hosted servers
    usually expose an endpoint ending in `/mcp`.
  </Step>

  <Step title="Horizon identifies the deployment">
    Horizon uses the hostname and path to identify which deployment should
    receive the request. Deployment slugs are stable for a branch or live server,
    so repeated requests route to the same deployment until promotion or
    rollback changes what is live.
  </Step>

  <Step title="Horizon checks access">
    Horizon applies the server's configured authentication and authorization
    mode before your server receives the request. If access is denied, the
    request stops at the gateway.
  </Step>

  <Step title="Horizon preserves MCP session routing">
    For initialized MCP sessions, Horizon keeps enough routing state to send
    follow-up requests in the same session to the right backend path.
  </Step>

  <Step title="Horizon routes to the live artifact">
    Horizon forwards the request to the deployment artifact currently selected
    for that server. Your Python MCP or FastMCP server handles the tool,
    resource, or prompt call.
  </Step>
</Steps>

## What the gateway controls

<CardGroup cols={2}>
  <Card title="Endpoint routing" icon="route">
    Maps incoming deployment URLs to the currently live deployment artifact.
  </Card>

  <Card title="Access enforcement" icon="shield-check">
    Applies server access settings before requests reach server code.
  </Card>

  <Card title="MCP sessions" icon="comments">
    Issues and preserves MCP session IDs for follow-up requests.
  </Card>

  <Card title="Request observability" icon="chart-line">
    Records request-level metadata used for logs, analytics, and debugging.
  </Card>

  <Card title="Limits" icon="gauge" href="/limits">
    Request, session, rate, and payload limits enforced at the gateway.
  </Card>
</CardGroup>

## Deployment routing

Deployments are addressed by deployment slugs. The gateway uses that slug to
decide which deployed artifact should receive a request. For slug stability
semantics and how promotion changes what is live, see
[Deployments](/deployments).

Changing source code does not change gateway routing by itself. A new build must
succeed, and the resulting artifact must be promoted, before the gateway routes
traffic to the new version.

<Tip>
  If a client is still seeing old behavior, check the deployment page to confirm
  which artifact is currently live before debugging server code.
</Tip>

## Access checks

The gateway enforces the server's configured access mode before forwarding the
request. Authentication and authorization decisions happen at the gateway, before
your server code runs.

Your server can still implement its own application-level checks, but gateway
access settings determine whether the request reaches your server at all. When
the gateway authenticates a caller, it attaches the verified caller identity to
the request so your server code and downstream access-aware features can make
their own authorization decisions against the same identity.

For access mode options and how caller identity is verified, see
[Authentication](/platform/authentication) and
[Authorization](/platform/authorization).

## Actor context

When Horizon authentication succeeds, the gateway removes any client-supplied
`horizon-*` identity headers and adds trusted actor context before invoking a
hosted server. Server code can use this context for application behavior that
needs the same identity Horizon used for its access decision.

| Header                 | Value                                                        |
| ---------------------- | ------------------------------------------------------------ |
| `horizon-actor`        | The Horizon ID of the authenticated user or service account. |
| `horizon-actor-type`   | `user` or `service_account`.                                 |
| `horizon-actor-email`  | The user's email address when the actor has one.             |
| `horizon-user-role`    | The actor's organization role.                               |
| `horizon-server-roles` | The actor's resolved server roles as a comma-separated set.  |

A request can resolve more than one server role through explicit and Team
grants, so use `horizon-server-roles` for authorization-aware application code.
Requests to a hosted server with Horizon authentication disabled do not receive
verified Horizon actor context.

FastMCP exposes these headers from the active request through
`get_http_headers()`:

```python theme={null}
from fastmcp import FastMCP
from fastmcp.server.dependencies import get_http_headers

mcp = FastMCP("Identity-aware server")


@mcp.tool
def current_actor() -> dict[str, str | None]:
    headers = get_http_headers()
    return {
        "id": headers.get("horizon-actor"),
        "type": headers.get("horizon-actor-type"),
        "email": headers.get("horizon-actor-email"),
        "organization_role": headers.get("horizon-user-role"),
        "server_roles": headers.get("horizon-server-roles"),
    }


if __name__ == "__main__":
    mcp.run()
```

Keep server and capability access at the gateway. Use actor context inside
server code for application-specific rules and upstream attribution.

## MCP sessions

When a client initializes a session, Horizon returns an `mcp-session-id`. Clients
should send that header on all follow-up requests in the same session. Clients on
MCP protocol versions that have no session handshake do not receive the header,
and each of their requests stands on its own.

Session routing state is retained for {sessionTTL} hours. This is routing state
for MCP traffic, not durable application storage. Store durable application state
outside the local filesystem. For session and other product limits, see
[Limits](/limits).

## Protocol behavior

Horizon hosted servers use
[Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http)
as the MCP transport. Clients send MCP requests as HTTP `POST` requests to the
deployment URL.

| Operation                        | Supported | Gateway behavior                                         |
| -------------------------------- | --------- | -------------------------------------------------------- |
| `POST /mcp`                      | Yes       | Forwarded to your server.                                |
| Long-lived `GET /mcp` SSE stream | No        | Returns method-not-allowed without invoking your server. |
| `DELETE /mcp` session teardown   | No        | Returns method-not-allowed without invoking your server. |

<Warning>
  Horizon does not support long-lived `GET /mcp` server-sent event streams for
  hosted servers. Use the standard `POST`-based request flow for MCP calls, and
  return a job ID quickly for long-running work instead of holding the request
  open.
</Warning>

## Gateway vs compute

The gateway is responsible for getting the request to the right deployed server.
Server compute is responsible for running your Python MCP or FastMCP code.

| Concern                      | Gateway                                                                     | Server compute                                            |
| ---------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------- |
| Identify deployment          | Yes                                                                         | No                                                        |
| Enforce server access mode   | Yes                                                                         | No                                                        |
| Preserve MCP session routing | Issues and routes by `mcp-session-id` for clients that initialize a session | Receives routed requests and returns MCP headers          |
| Run Python server code       | No                                                                          | Yes                                                       |
| Capture stdout and stderr    | No                                                                          | Yes                                                       |
| Enforce request timeout      | Returns an error to the client if the deadline is exceeded                  | Terminates the Python handler if the deadline is exceeded |

## Common gateway outcomes

<AccordionGroup>
  <Accordion title="The request is unauthorized">
    The caller did not provide credentials accepted by the server's access mode,
    or the credentials were expired or malformed.
  </Accordion>

  <Accordion title="The request is forbidden">
    The caller is authenticated, but Horizon access settings do not allow that
    caller to use the server or requested capability.
  </Accordion>

  <Accordion title="The deployment is not found">
    The URL does not map to a known deployment, the deployment was removed, or a
    custom domain is not pointing at the expected server.
  </Accordion>

  <Accordion title="The method is not supported">
    The client attempted a transport operation Horizon does not support for
    hosted servers, such as a long-lived `GET /mcp` stream or `DELETE /mcp`.
  </Accordion>

  <Accordion title="The request reaches the server but fails">
    Gateway routing succeeded. Check your server logs and the
    [Compute model](/platform/compute-model) for errors from your Python
    server.
  </Accordion>
</AccordionGroup>

## Related docs

<CardGroup cols={2}>
  <Card title="Compute model" icon="server" href="/platform/compute-model">
    Learn how Horizon runs deployed Python servers.
  </Card>

  <Card title="Build system" icon="hammer" href="/platform/build-system">
    Learn how deployable server artifacts are created.
  </Card>

  <Card title="Authentication" icon="key" href="/platform/authentication">
    Learn how callers prove identity.
  </Card>

  <Card title="Authorization" icon="shield" href="/platform/authorization">
    Learn how Horizon decides what authenticated callers can do.
  </Card>

  <Card title="Limits" icon="gauge" href="/limits">
    Review request, session, and compute limits.
  </Card>
</CardGroup>
