# Directory Sync Source: https://docs.horizon.prefect.io/administration/directory-sync Provision and deprovision Horizon organization members from an identity provider through WorkOS. Directory Sync connects a Horizon organization to an identity provider through WorkOS. The identity provider remains the source of directory users and group membership, while WorkOS carries lifecycle changes into Horizon organization membership. Directory Sync is available on and is currently rolling out to selected organizations. The setup flow and Team access behavior may change during the rollout. You must be an organization Admin to configure it. You also need permission to configure applications in your identity provider. If the Directory Sync card is not visible, contact Horizon support to confirm that it is enabled for your organization. ## Provisioning model Directory Sync separates identity configuration from Horizon access management. Your identity provider decides which people and groups belong in the directory. WorkOS hosts the provider-specific SCIM connection and sends user and membership changes to Horizon. Horizon reflects the WorkOS organization membership and shows the connection state. | System | Responsibility | | ------------------- | -------------------------------------------------------------------------------- | | Identity provider | Controls directory users, their active state, and group membership. | | WorkOS Admin Portal | Configures and validates the provider's SCIM connection. | | Horizon | Reflects organization membership, directory-backed Teams, and connection status. | Directory groups and their membership synchronize into directory-backed Horizon Teams. A group member can appear as pending until WorkOS has also provisioned that person's Horizon account. When Teams is enabled, you can review these groups and their members under Govern > Teams. ## Connect directory Horizon generates the setup link, while the identity provider connection is configured in the WorkOS Admin Portal. Each setup action creates a one-time portal link. Open it immediately and complete the task in that browser tab. In the Horizon organization sidebar, find Govern and select Single Sign-on. The Directory Sync card appears below the SSO settings. Select Configure directory sync. Horizon generates a one-time link and opens the WorkOS Admin Portal in a new tab. Choose your identity provider and follow its instructions in the linked portal. The portal supplies the SCIM values and configuration steps for Okta, Microsoft Entra ID, and other supported providers. Complete the connection validation in WorkOS. Return to Horizon and reload the page if needed. The Directory Sync card shows Connected when WorkOS reports that the directory is connected. ## Verify provisioning Test the connection with a person whose membership you can safely change in the identity provider. Assign the person to the Horizon application in your identity provider using the provider's normal provisioning workflow. Return to the Directory Sync card. Its Members, Active, and Inactive values are live counts from the WorkOS directory. Reload the page if the new directory user has not appeared yet. Ask the person to sign in to Horizon with their work email address. WorkOS activates the pending organization membership during the first sign-in. Horizon grants organization access only after that membership is active. Open Govern > Members and confirm that the person belongs to the organization. If Teams is enabled, open the corresponding directory-backed Team to verify its group membership. The card's Connected time records when the directory connection was created. It is not the time of the latest successful synchronization. ## Roles and access Connecting a directory provisions organization membership. It does not by itself grant access to a server. WorkOS IdP Role Assignment can map identity provider groups to the Horizon organization roles Admin and Member. That organization-role configuration lives in WorkOS and is separate from the Directory Sync connection. Server roles are assigned separately in Horizon. Explicit server grants and server default roles remain available for provisioned members. Directory groups also become Teams, and Team grants are enforced where team access is enabled. Directory Sync never creates, changes, or removes those grants. Admins manage Team grants from a server's **Access** > **Members** page and review a Team's saved grants from its **Servers** tab. See [Roles](/roles) for how organization roles, explicit grants, team grants, and server default roles combine. ## Deprovisioning Deprovision a person through the identity provider so that WorkOS remains the source of their lifecycle. When Horizon receives an inactive or deleted organization membership from WorkOS, it removes the person's organization membership and their explicit server grants in that organization. Their personal API keys no longer provide access to that organization, although the keys are not globally revoked. If the same person is provisioned again later, prior explicit server grants are not restored automatically. Assign any required server access again after confirming the new membership on the [Members](/members) page. In Okta, suspending a person does not deprovision them through SCIM. Deactivate or delete the directory user instead, then verify the inactive directory count and Horizon membership removal. A temporary disconnection freezes provisioning until the directory is reconnected. Deleting a directory marks its directory-backed Teams as missing and removes their memberships. Existing team grants remain saved, but they stop granting server access because those Teams no longer have members. Use the provider's deprovisioning action while the connection is active rather than disconnecting or deleting the directory to remove an individual member. ## Connection states Horizon reads directory state and provider metadata from WorkOS whenever the settings page loads. | Horizon status | Meaning | Required action | | -------------- | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | | Not configured | WorkOS has no directory for the organization. | Configure a directory through the one-time portal link. | | Connecting | WorkOS is establishing or validating the connection. | Finish the provider setup and wait for validation. | | Connected | The directory is connected and provisioning changes can flow to Horizon. | Verify user and membership changes. | | Disconnected | The directory is disconnected, being deleted, or has invalid credentials. Provisioning is frozen. | Select **Reconnect** and repair the connection in WorkOS. | For a connected directory, select Manage to generate a new one-time WorkOS portal link. Use it to review or update the provider configuration. # Single sign-on Source: https://docs.horizon.prefect.io/administration/sso Configure SAML or OIDC single sign-on for a Horizon organization through the WorkOS Admin Portal. Single sign-on (SSO) connects a Horizon organization to an identity provider. After setup, members with email addresses on the verified domain sign in through the organization's identity provider. SSO is available on . You must be an organization Admin to view or change SSO settings. You also need access to the domain's DNS records and permission to configure applications in your identity provider. Horizon delegates domain verification and identity provider configuration to the WorkOS Admin Portal. Each setup action in Horizon opens a one-time WorkOS portal link. Open the link immediately and complete the task in that browser tab. ## Configuration boundary Horizon controls access to the setup workflow and shows its current state. WorkOS handles the provider-specific configuration. | Horizon | WorkOS Admin Portal | | ------------------------------------------------------------------ | -------------------------------------------------------- | | Checks the organization's plan and the administrator's permissions | Verifies ownership of the sign-in domain | | Generates one-time portal links | Collects the SAML or OIDC configuration | | Shows domain and connection status | Validates and activates the identity provider connection | | Deletes configured domains and connections | Stores the provider configuration | You enter identity provider certificates, metadata, client credentials, and secrets in WorkOS. Horizon requests portal links, reads non-secret domain and connection status, and sends deletion requests to WorkOS. The provider configuration does not pass through the Horizon API. ## Verify domain Domain verification proves that the organization controls the email domain that will use SSO. Horizon requires at least one verified domain before it lets you configure an identity provider connection. In the Horizon organization sidebar, find Govern and select Single Sign-on. The page initially shows Domain verification required. Select Verify domain. Horizon generates a one-time link and opens the WorkOS Admin Portal in a new tab. Enter the domain your members use to sign in. Follow the WorkOS instructions to add the required record to that domain's DNS configuration. DNS changes can take time to propagate, so wait for WorkOS to report that verification succeeded before closing the portal. Return to Horizon and reload the Single Sign-on page if its status has not updated. The domain should show Verified, and the page should move to SSO not configured. ## Connect provider An SSO connection tells WorkOS how to authenticate members with your identity provider. The WorkOS portal supplies the provider-specific values and prompts. Horizon does not collect them. Supported providers follow the [WorkOS integration catalog](https://workos.com/integrations). The catalog includes provider-specific SAML and OIDC integrations as well as generic protocol options for compatible identity providers. Select Configure SSO. Horizon generates another one-time link and opens the WorkOS Admin Portal. Select your identity provider and configure the connection as SAML or OIDC. For SAML, follow the portal prompts for metadata, sign-in URLs, identifiers, and certificates. For OIDC, follow its prompts for issuer or discovery details and client credentials. Complete the provider's setup and validation in WorkOS. Keep the Horizon administrator session open while you test the connection so you can return to the settings page if the test fails. Return to Horizon and reload the page if needed. The page shows SSO enabled when it finds both a verified domain and an Active connection. ## Verify sign-in Test with a member whose email address uses the verified domain. Open a private browser window, go to the Horizon sign-in page, and enter that member's work email address. WorkOS identifies the domain and sends the member to the organization's identity provider. After the identity provider authenticates the member, the browser returns to Horizon. SSO changes authentication. Use [Directory Sync](/administration/directory-sync) for identity-provider-managed provisioning and deprovisioning. SSO does not change organization or server access. The member keeps the permissions assigned through their [organization and server roles](/roles). ## SSO states After SSO is available on the organization's plan, Horizon reads the current domain and connection status from WorkOS and reduces it to one setup state. | Horizon status | Meaning | Required action | | ---------------------------- | ----------------------------------------------------- | -------------------------------------------------- | | Domain verification required | No configured domain is verified. | Complete domain verification in WorkOS. | | SSO not configured | A domain is verified, but no connection is active. | Configure and activate SAML or OIDC in WorkOS. | | SSO enabled | A domain is verified and an SSO connection is active. | Test sign-in with a member on the verified domain. | ## Manage SSO When SSO is enabled, select Manage SSO to generate a new one-time WorkOS portal link. Use it to review or update the provider configuration. Organization admins can also delete domains and connections from Horizon. Deleting the last active connection moves the organization back to SSO not configured. Deleting its last verified domain moves it back to Domain verification required. Deletion cannot be undone, so keep the existing configuration until the replacement domain or connection is ready. # AI-friendly docs Source: https://docs.horizon.prefect.io/ai-friendly-docs Use Markdown pages, llms.txt, and llms-full.txt to give AI tools current Horizon documentation. Horizon publishes its documentation in formats that AI assistants, coding agents, and automated tools can read without parsing the website layout. Choose the narrowest source that covers the task: one Markdown page for a focused question, the documentation index for discovery, or the complete documentation for broad context. ## Individual pages Append `.md` to any Horizon documentation URL to retrieve a Markdown version of that page. This gives an AI tool the page's structured content without the navigation and other website elements. For example, the Markdown version of the Quickstart is available at: ```text theme={null} https://docs.horizon.prefect.io/quickstart.md ``` The page action menu provides the same content for people browsing the site. Use it to copy the page as Markdown, view the Markdown version, or open the page as context in ChatGPT or Claude. ## Documentation index Use [llms.txt](https://docs.horizon.prefect.io/llms.txt) when an AI tool needs to find the relevant Horizon pages first. The index lists every public page with a short description and a link to its Markdown version. A tool can read the index, select the pages related to the task, and fetch only those pages. ## Complete documentation Use [llms-full.txt](https://docs.horizon.prefect.io/llms-full.txt) when a task needs context from across the documentation or when a tool indexes the whole site. This file combines all public Horizon documentation into one response. For focused questions, the index and selected Markdown pages provide a smaller, more relevant context. # API keys Source: https://docs.horizon.prefect.io/api-keys Choose, use, rotate, and revoke personal and service-account credentials for Horizon. API keys are bearer credentials for non-interactive access to Horizon. Use them when a script, CI job, Terraform run, service account, or MCP client needs to authenticate without completing an interactive sign-in flow. An API key proves the identity of the actor that owns it. That owner can be a user or a service account. The key does not carry a separate permission scope: Horizon evaluates the owner's current organization role, server access, and capability access on each request. Horizon API keys start with `fmcp_`. The full key is shown only once, when you create it. Store it before closing the creation dialog. ## Key types Choose the owner based on who should appear as the actor and how long the workflow should outlive a person. | Key type | Identity scope | Best for | | ------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | Personal API key | Acts as one user across every organization where that user is a member. | Personal scripts, local testing, and MCP clients that cannot open an interactive sign-in flow. | | Service-account key | Acts as one service account in the organization that owns it. | CI/CD, Terraform, scheduled jobs, and production integrations that must survive employee offboarding. | For production automation, prefer service accounts. A service account makes ownership explicit and avoids depending on a person's membership or personal key lifecycle. ## Permissions A key can perform only the actions its owner can perform. For the REST API, organization and server permissions control which resources the actor can read or change. For an MCP endpoint, organization membership, server access, and capability policies control which tools, resources, and prompts the actor can use. Keys do not elevate access or preserve permissions that their owner has lost. See [Roles](/roles) for the organization and server role contracts, and [Authorization](/platform/authorization) for how Horizon resolves them on each request. ## Supported surfaces Both key types use the same header on the two customer-facing surfaces that accept Horizon API keys: ```http theme={null} Authorization: Bearer fmcp_... ``` The target URL decides whether Horizon applies management permissions or MCP server access. ### REST API The Horizon REST API accepts personal and service-account keys. Each operation checks whether the owning actor has the required access. Use the interactive [API reference](https://horizon.prefect.io/api/v0/docs) for the current routes, request schemas, and response schemas. This request lists the organizations available to the key owner. For a one-off test, read the key without saving it in shell history. In automation, load the same environment variable from your secret manager. ```bash theme={null} read -rsp "Horizon API key: " HORIZON_API_KEY echo curl --fail-with-body \ https://horizon.prefect.io/api/v0/me/organizations \ --header "Authorization: Bearer ${HORIZON_API_KEY}" unset HORIZON_API_KEY ``` ### MCP endpoints A protected hosted, external, or remix server accepts a Horizon API key at its serving URL. The gateway resolves the key owner, checks organization and server access, applies capability permissions, and then forwards an accepted MCP request. Use the stable `/mcp` URL from the server's **Connect** page. This example sends an MCP `initialize` request directly. Most users configure the same URL and header in an [MCP client](/connect-a-client) instead. ```bash theme={null} read -rsp "Horizon API key: " HORIZON_API_KEY echo HORIZON_MCP_URL="https://weather-mcp.fastmcp.app/mcp" curl --fail-with-body "${HORIZON_MCP_URL}" \ --header "Authorization: Bearer ${HORIZON_API_KEY}" \ --header "Content-Type: application/json" \ --header "Accept: application/json, text/event-stream" \ --data '{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-03-26", "capabilities": {}, "clientInfo": {"name": "curl", "version": "1.0"} } }' unset HORIZON_API_KEY ``` ## Credential boundaries A Horizon API key authenticates only at a Horizon API or gateway boundary. Adjacent surfaces use different credentials. | Surface | Authentication behavior | | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Dashboard and browser authorization flows | Dashboard sign-in and consent pages require an interactive user session. An API key cannot create a browser session. | | Downstream services | A Horizon key identifies the caller to Horizon. A remote MCP server or an API called by hosted server code uses the OAuth token or API key configured for that service. See [External authentication](/platform/external-authentication) for this second credential boundary. | | Hosted servers with Horizon authentication disabled | Horizon does not validate an `fmcp_` key for these endpoints. The hosted server decides which credentials, if any, it accepts. | A service-account key can authenticate to a protected hosted server, but per-actor delegated authorization is not supported for service accounts. Browser authorization saves the downstream credential for the signed-in user, not the service account. Automation must provide its upstream credential through the hosted server, such as with an [environment variable](/environment-variables), until service-account delegated authorization is supported. ## Personal keys Create a personal API key for work that should act as your user identity. In Horizon, open the user menu and select API Keys. Select Create API Key. Add a descriptor that identifies where the key will be used, such as `local-client` or `staging-ci`. Copy the `fmcp_` value from the reveal dialog and store it in the client, CI secret store, or secret manager that will send requests. Confirm the user has access to every organization and server the key needs. The key cannot exceed the user's current access. ## Create a service-account API key Create service-account keys from an organization's service account settings. Service accounts are designed for automation, so their keys can be rotated without involving a human user's account. Go to organization settings and select Service Accounts. Create a new service account for the automation, or open an existing service account that already owns the right access. New service accounts receive an API key when created. Existing service accounts can have additional keys added from the service account details panel. Give the service account the organization role, explicit server grants, and capability access it needs. Service accounts can have up to two active API keys at a time. This supports zero-downtime rotation: add a replacement key, move traffic, then revoke the old key. ## Rotation workflow Rotate keys by introducing a replacement before revoking the old key. Create a new key for the same user or service account. For service accounts, keep the two-key limit in mind. Change the secret value used by the MCP client, CI job, Terraform run, or integration. Confirm the client succeeds with the new key. Check request logs or the client response before removing the old key. Delete or revoke the old key. Revocation stops that key from authenticating immediately. ## Lifecycle and access API key behavior follows the owner and the active key record. Removing the member deletes their membership and explicit server grants in that organization. Their personal API key remains active, but it loses access to the organization they left. The same key can still access other organizations where the user remains a member. Move shared automation to a service account before offboarding the user. Reinviting the user later does not restore their previous explicit server grants. Horizon stops accepting the key immediately. Existing clients must be updated to use another key or sign-in method. Horizon cannot show the full key again. Create a new key, update the client, and revoke the old key if it might still be in use. Requests made with that service account's keys can no longer use the suspended or deleted identity. Replace the key with one owned by an active service account that has the required access. ## Security Store keys in a secret manager or the encrypted secret store provided by your CI system. Keep them out of source control, client-side application code, shell history, issue text, screenshots, logs, and traces. Treat an exposed key as compromised and rotate it. Give each key a descriptor that identifies its integration, and use a separate key per integration so each credential can be rotated without disrupting unrelated clients. Keys owned by the same actor still share one identity and permission set. For least privilege and clearer audit attribution, create a separate service account for each production workflow and grant only the access it needs. [Request logs and usage views](/observability) attribute protected MCP traffic to the user or service account that owns the key. They do not distinguish between multiple keys owned by that same actor, and they never display the raw key. Use separate service accounts when audit records must distinguish one automation from another. ## Common failures Horizon did not accept the credential. Check that the request uses `Authorization: Bearer`, that the key starts with `fmcp_`, that the full value was copied, and that the key has not been revoked. The key authenticated, but the owning actor does not have permission for the organization, server, or action. Check the owner's organization role, server grants, default server role, and capability policy. The deployment URL may not map to a live server, or Horizon may be concealing a server from an authenticated actor that lacks discovery access. Confirm the URL and the owner's server access. Revoke an unused service-account key first. Service accounts support up to two active keys at a time. ## Related docs Learn how API keys, user tokens, and browser sessions authenticate. Learn how Horizon decides what an authenticated actor can do. # Billing contact Source: https://docs.horizon.prefect.io/billing-contact Choose who manages an organization's billing and receives invoice emails. An organization's billing contact is the person responsible for its payment methods, [subscription plan](/plans), and add-ons. Horizon sends invoice emails to this person. Billing contact status is separate from the person's [organization role](/roles#organization-roles). The contact can be an Admin or Member, and assigning the contact does not change their organization role or server access. Organization admins can reassign the billing contact without becoming the contact themselves. ## Change the contact In Horizon, select the organization and open Billing. In the Billing contact card, select Change billing contact. Search for and select the organization member who should manage billing. Select Confirm change. The selected member becomes the billing contact. The previous contact can no longer manage payment methods, plans, or add-ons as the billing contact and stops receiving invoice emails. Horizon records the new contact immediately and synchronizes the invoice email address with its billing providers in the background. The updated email address may take a short time to appear in external billing systems. ## Eligible members The new billing contact must be a current human member of the organization with an email address. Both organization Admins and Members are eligible. Service accounts cannot be billing contacts. If the person does not appear in the selection list, confirm that they have accepted their organization invitation and have an email address in Horizon. See [Members](/members) for the membership lifecycle. ## Member removal Horizon prevents an organization admin from removing the current billing contact through the Members page. Reassign the billing contact first, then remove the previous contact. If the Billing page shows that no billing contact is configured, contact [Horizon support](mailto:horizon-support@prefect.io) before changing the plan, payment method, or add-ons. ## Related docs Invite, remove, and review people in your organization. Review organization roles and the access they grant. # Builds Source: https://docs.horizon.prefect.io/builds How Horizon turns your repo into a deployable image. Builds package hosted Python MCP and FastMCP servers into deployable artifacts. A build belongs to one server and one source revision. Successful builds can be served as live deployments or preview deployments. For the hosted-server contract across source, builds, deployments, gateway access, and runtime behavior, start with [Hosted servers](/servers/hosted). For the full lifecycle, defaults, and failure modes, see [Build system](/platform/build-system). ## Build contract A build is an immutable attempt to turn one repository commit and one set of server settings into one deployable artifact. After the build starts, changing a branch, editing an environment variable, or updating server settings does not change that build. Those changes apply to the next build that uses them. | Property | Contract | | -------------------- | ------------------------------------------------------------------------------------------------------------ | | Source revision | Resolved to an exact commit before build work begins. | | Server configuration | Entrypoint, dependency file, build-relevant settings, and environment scope are captured at build start. | | Output | A successful build produces a new artifact and manifest. A failed build does not modify the live deployment. | | Mutability | Build records and artifacts are historical. Rebuild instead of editing an existing build. | | Promotion | Build success makes an artifact eligible for deployment; it does not always change live traffic. | Debug build problems from the build record, not from the current branch tip. The current branch may have moved since the failed build started. ## Build states | State | Meaning | | ----------- | -------------------------------------------------------------------------------------------------------------- | | `queued` | Horizon has accepted the build request and is waiting to start work. | | `building` | Horizon is installing dependencies, packaging the server, inspecting the manifest, or publishing the artifact. | | `succeeded` | The artifact is ready to deploy. | | `failed` | The build did not produce a deployable artifact. Open build logs for the failing step. | | `canceled` | The build was stopped before completion. | ## What starts a build A push to the configured default branch can create a production build. A pull request can create a preview build with preview-scoped environment variables. A user with server edit access can start a build from the dashboard. Changes to build-relevant settings, such as the entrypoint or dependency file, take effect on the next build. ## What does not start a build Not every server change produces a new artifact. Some changes affect gateway or dashboard state without rebuilding Python code. | Change | Usually requires a new build? | Why | | ------------------------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------------------------ | | Source code change | Yes | The artifact must include the new code. | | Entrypoint or dependency file change | Yes | Horizon must package and inspect with the new build configuration. | | Environment variable value change | Yes, for hosted server runtime behavior | Existing artifacts keep the values they were built with. | | Server access grant change | No | Access metadata is evaluated by Horizon and can update without changing Python code. | | Server display name or description | No | Dashboard metadata does not affect the artifact. | | Capability policy change | Redeploy may be required for hosted capability enforcement | The deployed endpoint must use the updated policy metadata. | ## Build inputs | Input | Contract | | --------------------- | ----------------------------------------------------------------------------------------------------- | | Repository revision | Horizon builds from the selected commit. Builds are immutable once started. | | Entrypoint | Defaults to `main.py` unless the server is configured with another entrypoint. | | Dependencies | Horizon installs dependencies from the configured dependency file or discovered Python project files. | | Environment variables | Production builds use production variables. Preview builds use preview variables. | | Access settings | Authentication and authorization metadata are packaged with the deployment path used by the gateway. | ## Source resolution Horizon records the repository, branch context, and exact commit for every hosted build. Branch names are useful for starting builds, previews, and promotion workflows, but the artifact is tied to the commit that was resolved at build start. This matters when a pull request receives several pushes quickly. Preview builds run concurrently, so an older build may finish after a newer one. Use the deployment page to confirm which artifact is selected before assuming the latest completed build is live. ## Build output A successful build produces: * A deployable artifact. * A discovered MCP manifest for tools, resources, prompts, and templates. * Build logs for audit and debugging. * Metadata that Horizon uses for deployment, routing, and observability. Do not put required setup work behind a code path that only runs after a tool call. Horizon inspects the server during the build, so import-time and startup failures can prevent deployment. ## Concurrency and ordering Default-branch deploys to the same server run one at a time. When you push several commits in quick succession, their builds run in parallel but deploy serially, so the newest commit is the last one applied and owns live traffic. Preview builds still run concurrently across branches. | Scenario | Expected behavior | | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | Several commits pushed to the default branch in quick succession | Each commit builds, and the deploys apply in order. The newest commit ends up live. | | Two preview builds run for different branches | Previews deploy concurrently; each branch serves its own artifact. | | A default-branch build succeeds while a preview build fails | The production artifact can still be healthy; preview failure is scoped to its branch context. | | A build fails after a previous build succeeded | The previous successful artifact remains available for deployment or rollback. | ## Manifest inspection Build inspection loads the server entrypoint and records the MCP capabilities Horizon can see. The dashboard, ChatMCP, remix composition, and capability policies depend on that manifest. Playground lists live capabilities from the selected target instead. If a capability is missing after a successful build, check whether the server registers the tool, resource, prompt, or template during import or startup. Conditional registration can make local behavior differ from build inspection, especially when it depends on environment variables. ## Troubleshooting Check the dependency file path, Python version requirements, private package credentials, and any build-time environment variables. Confirm the configured file exists in the repository revision and can be imported without running interactive code. Make sure tools, resources, and prompts are registered before the server is inspected. Build logs usually show whether inspection succeeded. Start a new build after saving the variable. Existing artifacts keep the environment available when they were built. Confirm whether the build was for a preview deployment, whether automatic promotion is enabled, and which artifact the live deployment currently selects. Compare the manifest on the previous successful build with the new build. Then check registration code and environment-dependent imports. ## Related docs Start with the hosted-server contract across source, deployments, access, runtime, and observability. Serve successful builds as live or preview deployments. Manage production and preview environment variables. Understand how artifacts run after deployment. # Connect a client Source: https://docs.horizon.prefect.io/connect-a-client Point Claude Desktop, Cursor, ChatGPT, or any MCP client at your Horizon server. A Horizon server is an MCP endpoint over [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http). Connecting any client comes down to two things: the **server URL** and **how the client authenticates**. Once a client has both, it discovers the server's tools, resources, and prompts and can call them. Every hosted server has a stable URL ending in `/mcp`, shown on its [Deployments](/deployments) page, of the form `https://.fastmcp.app/mcp`. The examples below use `https://weather-mcp.fastmcp.app/mcp`; substitute your own. Horizon's dashboard generates ready-to-paste configuration for each supported client. Open a server, choose **Connect**, and pick your client to get the exact snippet with your URL and any required credentials already filled in. This page explains what those snippets contain so you can read, adapt, and troubleshoot them. ## Authentication How a client proves its identity depends on the server's authentication mode and on what the client supports. For the full model, see [Authentication](/platform/authentication). A server with **Horizon authentication enabled** requires every caller to authenticate, and there are two ways a client does so: * **Interactive sign-in.** Clients that support authenticated MCP connections open a Horizon sign-in flow the first time they connect. The user signs in, and the client holds the resulting credential. This is the smoothest path for desktop and CLI clients that prompt for authorization. * **API key.** Clients that cannot sign in interactively send a Horizon [API key](/api-keys) as a bearer token. Keys start with `fmcp_` and go in the `Authorization` header: ```http theme={null} Authorization: Bearer fmcp_... ``` A server with **Horizon authentication disabled** applies no Horizon access check, so a public server needs no credential and a server that implements its own authentication expects whatever its code requires. The rest of this page shows configuration for a server with Horizon authentication enabled; when it is disabled, omit the Horizon credential or supply the one your server expects. ## Desktop clients Claude Desktop and Cursor read MCP servers from a JSON config. Add your server under `mcpServers` with its URL. Include an `Authorization` header only when the server needs an API key; clients that sign in interactively leave it out. For **Claude Desktop**, open **Settings → Developer → Edit Config**, add the server, and restart Claude: ```json theme={null} { "mcpServers": { "weather-mcp": { "url": "https://weather-mcp.fastmcp.app/mcp", "transport": "http", "headers": { "Authorization": "Bearer fmcp_..." } } } } ``` For **Cursor**, the config is the same shape without the `transport` field: ```json theme={null} { "mcpServers": { "weather-mcp": { "url": "https://weather-mcp.fastmcp.app/mcp" } } } ``` Cursor also accepts a one-click install link. The **Connect** dialog generates a `cursor://` deeplink that adds the server without editing config by hand. ## CLI clients Command-line clients add a server with a single command. Each takes a name, the URL, and the HTTP transport, and accepts `--env` flags for values your client passes through. **Claude Code** authenticates interactively: run the command, then type `/mcp` in Claude Code to complete sign-in. ```bash theme={null} claude mcp add --scope local --transport http weather-mcp https://weather-mcp.fastmcp.app/mcp ``` **Gemini CLI**: ```bash theme={null} gemini mcp add weather-mcp https://weather-mcp.fastmcp.app/mcp --transport http ``` **Codex**: ```bash theme={null} codex mcp add --url https://weather-mcp.fastmcp.app/mcp weather-mcp ``` ## ChatGPT ChatGPT connects to MCP servers from its web interface rather than a config file. Enable **Developer mode** in settings, create a new app, choose **OAuth** for authentication, and paste your server URL into the **MCP Server URL** field. ChatGPT then runs the Horizon sign-in flow and authorizes the requested permissions. ## When a connection fails A failed connection usually stops at the gateway before reaching your server code. The status tells you where: The client sent no credential, an unsupported `Authorization` scheme, or an expired, revoked, or malformed token. Confirm the client is signed in, or that the API key is current and sent as `Authorization: Bearer fmcp_...`. See [Authentication](/platform/authentication). The caller authenticated, but access settings do not allow them to use the server or the requested capability. Check organization membership, server access, and capability policy in [Authorization](/platform/authorization). The URL maps to no live server, or Horizon is concealing a server from a caller without discovery access. Confirm the URL on the [Deployments](/deployments) page and the caller's access. Some clients cache the server's tool list. Reconnect or refresh the client's capabilities. If a tool is still absent, confirm the live deployment selects the build whose manifest contains it. # Custom domains Source: https://docs.horizon.prefect.io/custom-domains Serve a hosted MCP server from a subdomain you control with Horizon-managed DNS verification and TLS. A custom domain gives a hosted server a client-facing URL under a domain your organization controls. Horizon keeps the deployment behind that URL and manages the TLS certificate. Clients can then connect to `https://mcp.example.com/mcp` instead of the default `fastmcp.app` URL. Custom domains are available on . You need permission to edit the server and control of the subdomain's DNS records. A custom domain points to the same production serving target as the server's default URL. Promotion and rollback change the artifact behind both URLs together. Preview deployments cannot receive a custom domain, and a serving target can have one custom domain at a time. ## Add a domain Open the hosted server, then select Settings and Domains. Select Add domain. Enter a fully qualified subdomain such as `mcp.example.com`. Horizon connects it to the production serving target and starts provisioning the domain and its TLS certificate. Copy both CNAME records shown by Horizon into your DNS provider. One record points the subdomain at Horizon. The other proves domain ownership so AWS Certificate Manager can issue the certificate. DNS changes can take time to propagate. Horizon checks the records while the domain shows Verification needed. Select Refresh after the records have propagated if the status has not updated. When the status changes to Valid configuration, connect clients to `https:///mcp`. Authentication and authorization remain the same as on the default server URL. ## DNS records Horizon displays the exact names and values to enter. Keep both records in place while the domain is active because Horizon uses them for routing and certificate validation. | Record | Purpose | | ---------------- | ------------------------------------------------------------------ | | Target CNAME | Routes the chosen subdomain to Horizon. | | Validation CNAME | Proves control of the domain for managed TLS issuance and renewal. | ### CAA records CAA records restrict which certificate authorities can issue certificates. Most domains need no CAA change. For example, AWS Certificate Manager (ACM) can issue a certificate for `mcp.example.com` when `example.com` has no CAA records. If `example.com` only allows another certificate authority, add `CAA 0 issue "amazontrust.com"` alongside its existing CAA records. This lets ACM issue and renew the custom domain's certificate. See the [AWS guide to CAA errors](https://docs.aws.amazon.com/acm/latest/userguide/troubleshooting-caa.html) for more information. Custom domains use CNAME records and Horizon-managed certificates. An A record, a customer-supplied certificate, or a wildcard domain cannot replace this setup. ## Domain states | Status | Meaning | Action | | ------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | Setting up | Horizon is creating the serving alias and certificate request. | Wait for the DNS records to appear. | | Verification needed | Horizon is waiting for the required DNS records. | Add or correct the displayed records, then refresh. | | Valid configuration | DNS and TLS are ready. | Connect clients to the custom URL. | | Failed | Provisioning or validation did not complete. | Review the displayed error. Retry recoverable errors or contact support for a certificate validation failure. | An ACM certificate that reaches `Failed` cannot return to pending validation. If the displayed error says that certificate validation failed, contact [horizon-support@prefect.io](mailto:horizon-support@prefect.io) to restart the setup. For other errors, correct the reported issue and select **Refresh**. ## Remove a domain Removing a custom domain deletes its Horizon routing and managed certificate. Requests to that domain stop working immediately. The default Horizon server URL continues to route to the production deployment. Remove the DNS records after Horizon has removed the domain. Update MCP clients to another URL before removal so they do not lose access. ## Related docs Understand the production serving target behind a custom domain. Configure MCP clients with a server URL and credential. # Deployments Source: https://docs.horizon.prefect.io/deployments Stable MCP URLs, preview deployments, promotion, redeploy, and rollback. Deployments decide which built server artifact receives MCP traffic. A build turns one repository commit into an immutable artifact. A deployment gives that artifact a Horizon URL, records the branch and commit it came from, and can make it the live production version for the server. In practice, clients call a Horizon MCP URL, usually ending in `/mcp`, and Horizon routes that request to the artifact selected by the relevant deployment. Production traffic uses the stable server URL. Pull requests and non-default branches can use preview deployment URLs so you can test changes before they become production. For the build step before deployment, see [Build system](/platform/build-system). For request routing after deployment, see [Gateway](/gateway). Open a server in Horizon, then select **Deployments** in the server sidebar. The page shows the stable MCP URL, current status, recent build activity, and a history of builds and deployments for that server. ## Deployment model The server's stable MCP URL always points clients at the artifact currently selected for production. A pull request or branch build can expose an isolated preview URL for testing that branch's artifact. Every deployment is tied to the build, commit SHA, branch, and pull request metadata that produced it. Promotion changes which default-branch deployment receives production traffic without asking clients to change URLs. ## What starts a deployment A deployment starts after Horizon has a build request and a deployment target. The target determines whether the result is production-ready or preview-only. A push to the server's default branch creates a production build. If auto-promotion is enabled and the build succeeds, Horizon selects the new deployment for the stable production URL. A pull request toward the default branch creates a preview build and preview deployment URL. The preview uses preview-scoped environment variables and can be tested without changing production traffic. From a build row, you can start a new build from that row's commit. Use this when current server settings, environment variables, access policy, or deployment metadata need to be applied to that commit again. If a successful default-branch deployment is not live, use **Promote to live** to select it for production. Promotion keeps the same stable server URL and changes the artifact behind it. A successful build does not always mean production traffic changed. Preview builds stay preview. Default-branch builds can also remain ready-but-not-live when auto-promotion is disabled or when a newer deployment has already been selected. ## Production and preview URLs Production clients should use the stable server URL shown at the top of the Deployments page. That URL is based on the server name and remains the client configuration target through future promotions and rollbacks. Preview deployments use branch-scoped URLs. Horizon derives a deployment slug from the server name and branch name, lowercases it, replaces unsupported characters with hyphens, and truncates long values to fit URL and runtime naming limits. Pushes to the same preview branch reuse the branch deployment slug, so the preview URL can stay stable while its selected artifact changes. | URL type | Typical use | What changes | | -------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------- | | Stable production URL | Client, automation, and production traffic | The selected production artifact changes on promotion or rollback. | | Preview deployment URL | Pull request review and branch testing | The preview artifact changes when that branch receives a new successful deployment. | | [Custom domain](/custom-domains) | Client-facing hostname owned by your organization | It routes to the same Horizon-served endpoint as the default URL. | Do not treat a deployment URL as an access control boundary. Use Horizon authentication, server access, and capability policies for access control. ## Status labels The Deployments page combines build and deployment state into labels that are meant to answer one question quickly: what is this row doing for traffic? | Label | Meaning | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | `Production` | This successful default-branch deployment is selected for the stable production URL. | | `Preview` | This successful non-default branch or pull request deployment has a preview URL and is not production. | | `Ready` | This successful default-branch deployment can be promoted, but it is not currently production. | | `Building` | The build is queued or running. The selected production artifact has not changed yet. | | `Failed` | The build did not produce a deployable artifact. Existing live traffic remains on the previously selected artifact, if one exists. | The page also shows a top-level server status: * `Live` means the server has a live production deployment. * `Building` means at least one build is queued or running. * `Failed` means the latest build failed. * `No deployments` means Horizon has not created a deployment for the server yet. ## Auto-promotion Hosted servers have an **Auto-promote Deployments** setting. When it is enabled, successful default-branch deployments become production automatically. Horizon demotes the previous production deployment for the same server and points the stable URL at the new successful artifact. When auto-promotion is disabled, successful default-branch builds appear as `Ready`. Review the commit, build logs, server logs, and preview behavior, then promote the deployment manually when it should receive production traffic. Disable auto-promotion when you want a human approval step between a successful default-branch build and production traffic. ## Repeat deployments and rollback Starting another deployment from a commit and rolling back production create different operational outcomes. Use a build row's action menu to create a new build and deployment from that row's commit. The new build captures current server settings that affect deployment, such as entrypoint, dependency file, environment values, authentication mode, and capability policy metadata. To roll back, find an earlier successful default-branch deployment and promote it to live. The stable production URL stays the same, but Horizon selects the earlier artifact behind it. Promoting to production is only available for successful default-branch deployments. Preview deployments are for branch testing and do not become production directly. After redeploying or promoting, reconnect MCP clients that cache manifests or capability lists. Some clients do not refresh tools, resources, or prompts until they reconnect or list capabilities again. ## What changes when Deployment behavior depends on which boundary owns the change. | Change | What to do | When clients see it | | ------------------------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------- | | Source code changes | Create a new build from the changed commit. | After the resulting deployment is selected for the URL the client uses. | | Environment variables change | Create a new build for the relevant environment. | After a new deployment uses the updated values. | | Entrypoint or dependency file changes | Create a new build or wait for the next build trigger. | After a successful deployment built with the new settings is selected. | | Authentication mode changes | Create a new deployment for the hosted server. | After a deployment uses the updated setting. | | Server grants or default role changes | Save the access change. | After Horizon updates live access metadata. A code build is not required. | | Capability policy changes | Save the policy, then redeploy. | After the deployed endpoint uses the updated policy metadata. | | Custom domain changes | Complete domain setup or migration. | After DNS, activation, and client configuration all point to the intended endpoint. | ## Debug deployment behavior Start from the row that matches the commit and URL you are testing. The Deployments page gives each row actions for build logs, server logs, copying the deployment URL, redeploying, and promoting when promotion is available. Check whether the row is labeled `Preview`, `Ready`, or `Production`. Preview deployments do not change production. Ready deployments need manual promotion when auto-promotion is disabled. Confirm the production row has the expected commit and manifest. Then reconnect the client or list capabilities again because some clients cache server metadata. Confirm the branch and commit SHA on the deployment row. Branch names start builds, but each build is tied to the exact commit Horizon resolved when the build started. Confirm the deployment belongs to the default branch, succeeded, and is not already production. If the row should be promotable but promotion fails, open build logs and server logs, then retry from the deployments table. The request reached Horizon but did not pass the server's access settings. Check [Authentication](/platform/authentication), [Authorization](/platform/authorization), server access, API keys, and request logs. Gateway routing succeeded. Check your server logs for stdout, stderr, startup failures, handler errors, or upstream failures from the running Python server. ## Operational checklist Before sharing or promoting a deployment, confirm: * The row has the expected branch, pull request, commit SHA, and status. * The stable production URL or preview URL is the URL you are testing. * Build logs show a successful package and inspection step. * Server logs do not show startup or request-time failures for representative calls. * Environment variables are scoped correctly for production or preview. * Authentication mode, server access, and capability policy match the intended audience. * Clients have reconnected or refreshed capabilities after a manifest-changing deployment. ## Related docs Understand the hosted-server lifecycle across source, deployments, access, runtime, and observability. Learn how Horizon produces deployable artifacts from Python MCP and FastMCP source code. Configure production and preview environment variables. Serve the production deployment from a subdomain you control. # Environment variables Source: https://docs.horizon.prefect.io/environment-variables Configure encrypted server values for production and preview deployments. Environment variables are [encrypted](/security/data-protection) configuration values for hosted servers. They are attached to a server, scoped to the deployment environment that will use them, and provided to your Python process when Horizon builds and runs a deployment artifact. Use environment variables for values your server code needs at startup, during manifest inspection, or while handling MCP requests: upstream API keys, database URLs, feature toggles, model names, and similar configuration. Environment variables are available to hosted servers. External and remix servers use their own connection and authorization settings instead of hosted build-time environment variables. ## Variable contract Horizon treats an environment variable as part of the build and deployment input. When Horizon prepares a build or deployment, it decrypts the variables for the target environment. Deployment values take precedence for that deployment. That timing matters. Changing a variable does not alter work already in progress, and changing a variable after an artifact is live does not update the running server by itself. Create a new build and make sure the deployment selects that artifact before expecting server code to see the new value. Avoid printing secret values to stdout or stderr. Horizon captures those streams as server logs. ## Environments Environment variables belong to the environment where a deployment will run. | Environment | Used by | Behavior | | ----------- | -------------------------------------------------- | ---------------------------------------------------- | | Production | Default-branch builds and production deployments | Values used for the live production deployment path. | | Preview | Non-default branch and pull request preview builds | Values used before production promotion. | Keep production and preview credentials separate when the upstream system has separate projects, databases, accounts, or rate limits. Preview builds should be safe to test without touching production data. ## Manage variables Open the hosted server, then go to **Settings** > **Environment Variables**. Choose **Add variable**, then enter the key, value, and an optional description. Check the environments that should receive the value. Checking both Production and Preview creates one variable record in each environment with the same initial value. New variables are sensitive by default. Sensitive variables are write-only after creation. Users who can manage server settings can copy a non-sensitive value to the clipboard; the dashboard does not display stored values. To make an existing non-sensitive variable write-only, open its actions menu and choose **Mark as sensitive**. This change cannot be reversed. Production and Preview values are separate records. Updating one does not update the other unless you edit both. Create a new build and deploy or promote it so server code receives the changed values. ## Key rules Variable names must: * Be 255 characters or fewer. * Start with an uppercase letter or underscore. * Use only uppercase letters, numbers, and underscores. * Not start with `FASTMCP_CLOUD_` or `HORIZON_`, which are reserved by Horizon. The dashboard uppercases keys as you type. Values must not be empty and can contain multiline content such as JSON. ## Build-time and runtime behavior Environment variables can affect both the build and the deployed server. During a build, variables may be available while Horizon installs dependencies, loads the entrypoint, and inspects the server manifest. If your server registers tools conditionally based on an environment variable, preview and production builds can produce different manifests. At runtime, the deployed Python process receives the variables that were included in the selected artifact. Horizon does not reinstall dependencies or refresh environment variables when a request arrives. For the full build lifecycle, see [Build system](/platform/build-system). For runtime behavior, see [Compute model](/platform/compute-model). ## Deployment overrides The `env` values supplied when creating a deployment take precedence over the variables configured for its target environment. For example, a preview deployment can use `MODEL_NAME` from Preview settings while overriding `DATABASE_URL`. Other preview deployments continue to use the saved Preview value. Use deployment overrides for branch-specific configuration and other values that apply to one deployment. Use Production and Preview settings for values shared by every deployment in an environment. See the generated [API reference](https://horizon.prefect.io/api/v0/docs) for the deployment request contract. ## Rotation Rotate a variable in the environment where the credential is used. Keep the old credential active until Horizon has deployed the new value. Update the Production or Preview value. For a deployment override, update the deployment input instead. Start a new build, deploy it to the relevant preview or production deployment, and confirm representative tool calls succeed. Revoke the old value only after the deployment using the new value is live. ## Troubleshooting Confirm a new build started after the variable was saved, then confirm the deployment selects that build artifact. Existing artifacts keep the values they were built with. Confirm that the deployment uses a preview target, then check whether its `env` input overrides the saved Preview value. Check the key name. Keys must start with an uppercase letter or underscore, use only uppercase letters, numbers, and underscores, and avoid reserved prefixes. If tool registration depends on environment variables, compare the manifest for the build that is live with the values available during that build. Rotate the credential, update the matching environment, rebuild and redeploy, then remove or mask the logging statement in server code. ## Related docs Understand the hosted-server lifecycle and ownership boundary. See how build inputs become immutable artifacts. Confirm which artifact is live for production or preview traffic. How values are encrypted at rest and kept out of logs and responses. # Gateway Source: https://docs.horizon.prefect.io/gateway How Horizon receives MCP client requests, checks access, preserves sessions, and routes traffic to deployed servers. 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. 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). ## Request flow Every request follows the same broad path. The client sends an MCP request to a Horizon server URL. Hosted servers usually expose an endpoint ending in `/mcp`. 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. 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. For initialized MCP sessions, Horizon keeps enough routing state to send follow-up requests in the same session to the right backend path. 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. ## What the gateway controls Maps incoming deployment URLs to the currently live deployment artifact. Applies server access settings before requests reach server code. Issues and preserves MCP session IDs for follow-up requests. Records request-level metadata used for logs, analytics, and debugging. Request, session, rate, and payload limits enforced at the gateway. ## 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. If a client is still seeing old behavior, check the deployment page to confirm which artifact is currently live before debugging server code. ## 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 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. | 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. ## 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 The caller did not provide credentials accepted by the server's access mode, or the credentials were expired or malformed. The caller is authenticated, but Horizon access settings do not allow that caller to use the server or requested capability. The URL does not map to a known deployment, the deployment was removed, or a custom domain is not pointing at the expected server. The client attempted a transport operation Horizon does not support for hosted servers, such as a long-lived `GET /mcp` stream or `DELETE /mcp`. Gateway routing succeeded. Check your server logs and the [Compute model](/platform/compute-model) for errors from your Python server. ## Related docs Learn how Horizon runs deployed Python servers. Learn how deployable server artifacts are created. Learn how callers prove identity. Learn how Horizon decides what authenticated callers can do. Review request, session, and compute limits. # GitHub Source: https://docs.horizon.prefect.io/git-integrations/github Connect GitHub repositories to hosted Horizon servers, then build from commits, pushes, and pull requests. Connect GitHub when your code lives in GitHub. After a Horizon organization links a GitHub installation, Horizon can list the repositories that installation grants, create hosted servers from those repositories, and start builds when relevant Git events arrive. The boundary is simple: GitHub owns repository access and source history. Horizon owns the server record, build record, deployment URL, environment selection, access policy, and observability once a repository revision is used for a hosted server. Connect one or more GitHub accounts or organizations to a Horizon organization. Resolve a selected repository branch to an exact commit before creating a build. Use pull request events to create preview builds and preview deployment URLs. Use GitHub access only for repository discovery and builds. MCP clients do not receive GitHub credentials. ## When to use GitHub Use the GitHub integration when the server source lives in GitHub and Horizon should build, deploy, and update a hosted Python MCP or FastMCP server from that source. GitHub works well when: * A repository should become a hosted Horizon server. * Default branch pushes should create new production-ready builds. * Pull requests should create preview builds before production promotion. * Operators need the build list to show branch, commit, pull request, and repository context. * Repository permissions should stay in GitHub while Horizon manages the deployed MCP endpoint. If the server already runs outside Horizon and should stay there, register it as an [external server](/servers/external). If clients should use a composed endpoint made from multiple servers, create a [remix server](/servers/remix). ## Integration contract | Area | What users can expect | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Connection | A Horizon organization can link one or more GitHub installations. Each installation belongs to a GitHub user or organization account. | | Repository list | Horizon lists repositories that the linked installation grants. Repositories outside the installation permission set do not appear in the picker. | | Source selection | Creating a hosted server stores the selected repository, its GitHub repository ID, full name, default branch, entrypoint, and dependency file setting. | | Build revision | Horizon resolves a branch or pull request to a commit SHA before build work begins. Moving the branch later does not change that build. | | Credentials | Horizon uses repository access for discovery and build checkout. GitHub credentials are not exposed to MCP clients. | | Events | Push, pull request, branch deletion, installation, and repository-permission events update Horizon records when they match a linked installation and tracked repository. | | Removal | Removing repository access or deleting the installation can make repositories unavailable to Horizon. Builds that need unavailable source access cannot proceed until access is restored. | A GitHub connection does not make a server public. Hosted server authentication, server access, capability policy, and deployment promotion still follow the hosted-server contract. ## Connect GitHub You can connect GitHub from onboarding or from organization settings. During onboarding, Horizon sends users without a linked installation to the GitHub connection step. In an existing organization, open Settings and then Integrations. Horizon opens a GitHub authorization window. If the signed-in GitHub user already has unlinked Horizon installations available, Horizon asks which account to link. Otherwise, GitHub asks the user to install or configure the Horizon GitHub App. In GitHub, select the repositories Horizon should be able to see. You can grant access to all repositories in the account or only selected repositories. After GitHub redirects back, Horizon verifies the request, links the installation to the organization, and returns to the repository picker or the integrations page that started the flow. Members and admins can start the GitHub connection flow for an organization. The GitHub user completing the popup must administer the selected GitHub account: the owner for a personal account, or an organization admin for a GitHub organization. Accounts you can see but do not administer appear disabled in the picker. If a GitHub organization installed the app before it requested organization member read access, a GitHub organization admin must accept the updated permissions on GitHub before that organization can be linked to another Horizon organization. ## Repository picker The repository picker shows repositories from every GitHub installation linked to the active Horizon organization. Repositories are sorted by recent update time, include public or private visibility, and show the repository default branch. When you select a repository, Horizon carries this information into the hosted server setup form: * repository full name * repository visibility * GitHub installation ID * default branch * suggested server name If no GitHub installation is linked, Horizon redirects the repository picker to the GitHub connection flow. If a repository is missing, edit repository permissions from Settings > Integrations, then refresh the picker. ## Create a hosted server After selecting a repository, Horizon asks for the server configuration that turns the repository into a hosted MCP endpoint. | Field | Behavior | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Server name | Determines the Horizon server name and the deployment slug preview. | | Entrypoint | Points to the Python file, and optional object name, that defines the MCP server. Horizon validates the path format and can check that the file exists in the selected branch. | | Authentication | Horizon authentication is enabled by default. Disabling it is available only when the organization plan allows it. | | Dependency file | Optional. Use it when dependencies are not in the file Horizon would otherwise discover. | | Environment variables | Optional `KEY=value` lines. Names must use uppercase letters, numbers, and underscores, and must start with a letter or underscore. | When you submit the form, Horizon runs through these steps: The first build uses the repository default branch at the time Horizon creates the server. Later changes to the default branch create later builds; they do not rewrite the first build. ## What GitHub events do Horizon processes GitHub events only when they match a linked installation and a repository already tracked by a hosted server. | Event | Horizon behavior | | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Default branch push | Creates a queued build and deployment record for the tracked default branch, unless the same commit already has a build for that server and branch. | | Non-default branch push | Creates a preview build only when Horizon is already tracking that branch for an open pull request. | | Pull request opened or reopened | Creates a branch record, queued build, and preview deployment for each matching hosted server. | | Pull request synchronized | Creates a new queued build and preview deployment for the updated pull request head commit. | | Pull request closed without merge | Removes the preview branch record for the matching server and repository. | | Branch deleted | Removes the matching branch record from Horizon. | | Repository access added | Makes the repository available to linked Horizon organizations after repository data refreshes. | | Repository access removed | Removes the repository from Horizon's authorized source records and starts cleanup for related deployment resources. | | Installation deleted | Removes the linked installation record from Horizon. | A repository can back more than one hosted server. On a matching push or pull request event, Horizon queues a build for every hosted server that tracks that repository and branch. In a monorepo, changing files for one server does not currently skip builds for the other matching servers. If a pull request existed before the hosted server was created, Horizon may not create an automatic preview for that older pull request. Create a new pull request after the server exists, or use the dashboard workflow for the revision you want to test. ## Production and preview behavior Default branch builds and pull request builds use different deployment contexts. | Context | Source | Environment scope | Deployment behavior | | -------------------- | ---------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------ | | Production candidate | The tracked default branch commit. | Production variables. | The build can become the live deployment through the production deployment workflow. | | Pull request preview | The pull request head commit. | Preview variables, with optional deployment overrides. | Horizon creates or updates a preview deployment URL for that branch. | Successful preview builds do not automatically change production traffic. A successful default branch build also does not mean clients changed endpoints; confirm which build the live deployment currently selects on the [Deployments](/deployments) page. ## Repository access and missing source Repository access can change outside Horizon. The most common symptoms are a missing repository in the picker, a build that cannot read source, or a private repository build that cannot start. Open Settings > Integrations, choose the GitHub installation, and edit repository permissions in GitHub. After the repository is granted to the installation, refresh the picker. Confirm the file path is relative to the repository root, does not include parent-directory references, and exists on the selected branch. For an entrypoint with an object name, use server.py:object\_name. Confirm the server was already tracking the repository when the pull request was opened, the pull request branch is still open, and the branch has not been removed from Horizon. Builds are tied to the commit SHA resolved when the build was queued. Open the build details and compare the recorded commit with the current branch tip. The installation may have no selected repositories, access may have been removed, or GitHub may not be returning repositories for that installation. Edit permissions from the integrations page and try again. ## Security boundary GitHub source access is separate from Horizon endpoint access. * GitHub permissions decide which repositories Horizon can discover and build. * GitHub installation administration decides who can link an installation: the account owner for a personal account, or an organization admin for a GitHub organization. * Horizon organization membership decides who can start the integration flow in Horizon. * Server access and capability policies decide who can use the deployed MCP endpoint. * Build-time repository access is not returned to MCP clients. * Environment variables and secrets are scoped through Horizon deployment settings, not through GitHub repository visibility. This separation means a user can have access to a deployed Horizon endpoint without having access to the underlying GitHub repository, and a GitHub collaborator does not automatically receive access to the Horizon server. ## Related docs Understand the hosted-server contract across source, builds, deployments, gateway access, runtime, and observability. See how Horizon packages Python MCP and FastMCP source into build artifacts. Review build states, inputs, outputs, and troubleshooting boundaries. Learn how successful builds become live or preview deployment URLs. Configure production and preview environment variables. Choose how MCP clients authenticate to hosted endpoints. # Limits Source: https://docs.horizon.prefect.io/limits Request timeouts, payload sizes, session TTLs, secret counts, and slug length rules. Limits define the contract for hosted server builds, gateway requests, compute, sessions, and configuration. Some limits protect the interactive MCP request path; others keep deployments predictable and safe to operate. ## Limit contract Limits define what Horizon will attempt to serve, not what every MCP client or upstream system can handle. Design server behavior so clients receive clear errors before a limit becomes a surprise. | Limit type | Design response | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | Request timeout | Return quickly, stream only where supported, or return a job ID and continue work asynchronously. | | Request and response size | Keep tool inputs and outputs small; move large files to external storage and pass a reference instead of inlining bytes. | | Memory allocation | Process data in chunks and avoid unbounded in-memory caches. | | Build timeout | Keep dependency installation and import-time work lightweight. | | Session TTL | Let clients reinitialize sessions and avoid storing durable state in session routing. | | Ephemeral filesystem | Store durable files outside the local runtime filesystem. | ## Runtime limits | Limit | Value | Applies to | | ------------------------- | --------- | ----------------------------------------------- | | Request timeout | seconds | MCP request handlers served through Horizon. | | Request and response size | MB each | MCP requests and responses through the gateway. | | Memory allocation | MB | Hosted server compute. | | MCP session routing TTL | hours | Gateway session routing metadata. | | Local filesystem | Ephemeral | Files created by server runtime. | The MB request and response size is a hard ceiling, not a target. It comes from AWS Lambda's synchronous invocation limit, and JSON and base64 encoding overhead lowers the usable size further, so keep tool inputs and outputs well under MB. There is no streaming; to move a large file, upload it to external object storage out of band and pass a URL or reference through the tool call. ## Build limits | Limit | Value | | ---------------------- | --------------------------------------------------------------------------- | | Build timeout | 15 minutes | | Default Python version | `3.12` | | Default entrypoint | `main.py` | | Dependency discovery | Configured dependency file, nearest `requirements.txt`, or `pyproject.toml` | ## Configuration limits | Area | Guidance | | --------------------- | ------------------------------------------------------------------------------------------ | | Environment variables | Keep keys stable and use separate values for production and preview. | | Slugs | Treat server and deployment slugs as client-facing identifiers. Avoid unnecessary renames. | | API keys | Create separate keys per integration and rotate them when exposed. | | Capability policies | Prefer small, auditable policy changes over broad access exceptions. | Organization-specific quotas, included usage, and contract terms can vary by [plan](/plans). ## What happens at a limit Horizon returns an error to the client and the handler should be treated as failed. Return a job ID quickly and continue long work asynchronously. Each MCP request and response is served as a single synchronous AWS Lambda invocation, which caps the payload at MB with no streaming. JSON and base64 encoding overhead lowers the usable size further, so keep tool inputs and outputs comfortably under this ceiling. To move a large file, upload it to external object storage out of band, then pass a URL or reference through the tool call and have your server read from that storage. The build fails and no new deployable artifact is produced. The previous deployment remains available unless you change it separately. A client may need to initialize a new MCP session before sending follow-up requests. The server instance can fail or restart. Reduce memory use, stream work in smaller chunks, or move large jobs out of the request path. ## Designing within limits | Workload | Recommended shape | | --------------------- | ------------------------------------------------------------------------------------------------ | | Fast read or lookup | Regular MCP tool handler. | | Large export | Return a job ID quickly and continue the work asynchronously. | | Large file transfer | Upload to external object storage out of band and pass a URL or reference through the tool call. | | Repeated polling | Store polling state durably and expose a status tool or resource. | | Expensive startup | Lazy-initialize clients and caches after import when possible. | | Large temporary files | Use durable external storage rather than assuming local files persist. | ## Related failure signals | Signal | Check | | ------------------------------ | -------------------------------------------------------------------------- | | 408 or timeout in request logs | Handler duration and upstream calls. | | Missing server logs | Gateway rejected the request before compute or deployment was unavailable. | | Build timeout | Dependency install time and import-time work. | | Session not found | Client reinitialization and session TTL behavior. | | File missing | Ephemeral filesystem assumptions. | ## Related docs Understand runtime constraints. Understand build defaults and failure modes. Understand request and session behavior. # MCP registry Source: https://docs.horizon.prefect.io/mcp-registry Publish an organization catalog that GitHub Copilot and other MCP Registry clients can discover. Publish your organization’s production MCP servers at a single registry URL. Compatible clients can discover them there, and Horizon keeps the catalog current as servers are added, changed, or removed. Organization MCP registry publication is available on . An organization Admin controls publication. The registry includes every current and future organization server that has a production target. Each catalog entry points to the server's canonical Horizon Streamable HTTP endpoint. A server enters or leaves the catalog when its production target changes. The registry publishes server metadata through an anonymous URL because clients such as GitHub Copilot fetch the catalog without Horizon credentials. The URL is opaque, but it is not a secret or an authentication control. Anyone with the URL can read the published server names, descriptions, and endpoint URLs. Registry publication changes discovery only. The Horizon gateway still applies each server's authentication and authorization settings when a client connects or calls a capability. ## Publish Publish the registry when your organization is ready to expose its production server metadata to an external discovery client. The first publication creates an organization URL that remains stable until an Admin regenerates or unpublishes it. The URL uses the following format: ```text theme={null} https://horizon.prefect.io/api/mcp-registry/ ``` In Horizon, select the organization and open Registry. Select MCP registry in the page header. Review the public metadata notice, then select Publish. Horizon immediately projects every server with a production target into the catalog. Copy the displayed registry URL and store it with the client or company policy that will use it. Organization Members can view and copy an existing registry URL. Only an organization Admin can publish, regenerate, or unpublish the registry. ## GitHub Copilot GitHub Copilot Business and GitHub Copilot Enterprise can use the Horizon registry URL for organization or enterprise MCP discovery. The GitHub owner who configures the policy can use the URL that a Horizon Admin copied from the Registry page. In the GitHub organization or enterprise settings, open the Copilot policy controls for MCP servers. Enable MCP servers in GitHub Copilot for the applicable organization or enterprise. Paste the Horizon registry URL into the MCP Registry URL field, then save the policy. Use Allow all when the registry is for discovery without a server restriction. Use Registry only when GitHub should apply its registry allowlist policy. GitHub controls which products and IDE versions support organization registries. See GitHub's [MCP server access guide](https://docs.github.com/en/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-server-access) for the current policy locations, supported plans, and enforcement behavior. A discovered server still needs a valid Horizon sign-in or API key when its Horizon authentication is enabled. See [Connect a client](/connect-a-client) for the server authentication model. ## URL lifecycle Regenerate the URL when the current URL was shared outside its intended audience or when you want to replace it as an operational precaution. Regeneration invalidates the previous URL immediately and keeps the same organization registry. Update every client and company policy that used the previous URL. Unpublishing deletes the publication and invalidates its URL immediately. The organization server configuration remains unchanged. Publishing again creates a new registry and a new URL. If the organization moves to a plan without registry publication, Horizon suspends anonymous catalog reads and keeps the publication state. Returning to an eligible plan resumes the same URL. An Admin can unpublish the registry while it is suspended. # Members Source: https://docs.horizon.prefect.io/members Invite, remove, and manage people in your organization. Organization member invitations are available on . Team server grants are available on . Members are people who can sign in to an organization. They can join through an invitation or [Directory Sync](/administration/directory-sync). Membership determines which organizations a user can see, which settings they can manage, and which servers they can access through organization and server roles. ## Membership contract Membership gives a person access to an organization. It does not automatically grant access to every restricted server unless the person is an organization admin or the server has a default role. | Property | Contract | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Organization boundary | A user must be a member of the organization to access its Horizon resources. | | Organization role | Admin or Member defines the user's organization-level baseline. | | Server access | Non-admin members need explicit grants, team grants, or server default roles. | | API keys | [Personal API keys](/api-keys#key-types) use the user's current access. Removing a member removes the key's access to that organization but does not revoke the key. | | Organization removal | Removing a member removes their organization membership, explicit server grants, and Horizon-managed Team memberships. Horizon keeps directory Team assertions because the identity provider still owns them, but they grant no access without organization membership. The current [billing contact](/billing-contact) must be reassigned first. | | User deletion | Deleting the Horizon user removes all Team membership rows, including directory assertions. A later directory sync recreates a pending assertion if the identity provider still assigns that email to the group. | Use service accounts for durable automation. User-owned keys should stop being relied on when that user leaves the organization. ## Member lifecycle Organization admins can invite a person by email. An identity provider can also provision them through Directory Sync. An invited person joins after accepting the invitation and completing sign-in. A directory-provisioned person joins when Horizon receives their active WorkOS organization membership. Give the member an organization role and, when needed, explicit server grants or team grants. Use users and request logs to understand how the member's clients interact with deployed servers. Removing a member removes their organization membership, explicit server grants, and Horizon-managed Team memberships. Horizon keeps directory Team assertions because organization removal does not change identity-provider groups, but those assertions stop granting access. Reassign the billing contact before removing the member who currently holds it. ## Invitation states | State | Meaning | | -------- | -------------------------------------------------------------------------- | | Pending | The invitation has been sent but not accepted. | | Accepted | The invited person joined the organization. | | Expired | The invitation can no longer be accepted. Send a new invitation if needed. | | Revoked | An admin canceled the invitation before it was accepted. | ## Roles | Role | Contract | | ------ | --------------------------------------------------------------------------------- | | Admin | Can manage organization settings and has full server access. | | Member | Can access servers through explicit grants, team grants, or server default roles. | Server-level roles can further grant `admin`, `editor`, or `viewer` access to a specific server. See [Authorization](/platform/authorization) for the full resolution model. ## Access changes Organization removal and user deletion use different boundaries. Organization removal keeps identity-provider group assertions because the person can still belong to those groups. User deletion removes all local Team membership state so a replacement user cannot inherit stale access before directory sync confirms it. | Change | Effect | | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Promote member to admin | The user gains organization management access and full server access. | | Demote admin to member | The user loses admin bypass and must rely on explicit grants, team grants, or defaults. | | Remove explicit server grant | The user may still retain access through a team grant, server default role, or organization admin role. | | Remove member from organization | The user can no longer access organization resources. Horizon removes explicit server grants and Horizon-managed Team memberships. Directory Team assertions remain pending and cannot grant access. Horizon requires the current billing contact to be reassigned before removal. | | Delete Horizon user | Horizon removes all Team membership rows for the user, including pending directory assertions for their email. Directory sync can recreate valid assertions from the identity provider. | | Reinvite removed user | New membership does not restore prior explicit server grants or Horizon-managed Team memberships. Directory Team access can return when the identity provider still assigns the person to the group. | ## Reviewing members Review membership from three directions: * Organization view: who can sign in, who is admin, and who counts toward seats. * Server view: which people have explicit access and with what role. * Teams view: which groups a person belongs to and which servers grant access to those Teams. For sensitive servers, review explicit grants, team grants, and the server default role together. A restrictive explicit grant is not useful if a Team or the server default gives the person broader access. ## Operational guidance * Give admin access only to people who should manage billing contact assignment, organization settings, integrations, and access. * Use explicit server grants or team grants for members who need access to restricted servers. * Remove members promptly when they leave the organization. * Use service accounts for automation instead of adding shared human accounts. ## Related docs Group members and review their shared server access. Review the role model used by Horizon. See the full access resolution model for users and servers. # Observability Source: https://docs.horizon.prefect.io/observability Inspect requests, usage, performance metrics, and server logs for a deployed MCP server from one place. Once a server is live, the question shifts from how to deploy it to what it is doing in production. Horizon answers that from the server's own pages: which clients are calling it, which tools they invoke, how long requests take, who is using the server, and what the server printed while it ran. These views share one source of truth. Every MCP request passes through the [gateway](/gateway), which records request-level metadata before your code runs. That metadata is what the logs and usage analytics read from, so the same request shows up consistently wherever you look at it. ## Request logs The **Logs** page is the request-by-request record of traffic to a server. Each entry is one MCP request: the method that was called, whether it was a standard JSON-RPC call such as `tools/list` or a specific tool invocation, the status the gateway returned, and when it happened. A histogram above the list shows request frequency over the selected time range, so a spike or a gap is visible before you read a single row. Selecting a request opens its full detail, including the acting user or service account, identified MCP client, request and response payloads, and gateway processing. Gateway plugins report whether they passed, modified, blocked, or errored on the request. This makes access-policy filtering and sensitive-data redaction visible in the same record as the resulting response. This is the first place to look when a client reports that a call failed: you can confirm whether the request reached the gateway, how it was routed, and whether it was accepted or rejected before assuming the problem is in server code. Filter to errors only to narrow a noisy stream down to the requests that did not succeed. For control over which payloads are stored, see [Data handling](/security/data-handling#payload-logging). Logs refresh live as new requests arrive, and pause when you want to study a fixed window without rows shifting underneath you. ## Usage The **Usage** page summarizes how clients use a server. It shows tool calls, unique actors, error rate, and p95 latency over the selected window, then breaks that traffic down by actor and tool. Actors include both users and service accounts, using the verified identity the gateway attached to each request. Calls that Horizon cannot attribute appear under **Unknown**. This usually means the calls were not authenticated through Horizon. These calls do not increase the unique actor count. Select an actor, including **Unknown**, to see the tools it used, or select a tool to see its callers. Prompt and resource operations appear alongside tools with their operation type, so you can investigate each kind from the same view. After selecting an actor or capability, choose **View logs** to open the request log with the same target, time range, actor, and tool filters applied. For how actor identity is established, see [Authentication](/platform/authentication). ## Performance metrics Where request logs describe individual traffic, the **Observability** page describes the server's behavior in aggregate. Three charts cover the questions you ask when a server feels slow or unhealthy: * **Duration** shows request latency as a distribution, including average, p50, p95, and p99, so a tail of slow requests is visible even when the average looks fine. * **Invocations** shows total requests over time, which is how you confirm whether a latency change tracks a change in load. A request is one MCP JSON-RPC message, including initialization and capability-list operations. A tool call is specifically a `tools/call` request, so every tool call is an invocation while many invocations are not tool calls. * **Memory** shows memory usage and allocation, which is where you look when a server is being terminated for exceeding its limit rather than returning errors. For what those limits are and how the runtime enforces them, see [Compute model](/platform/compute-model). ## Server logs Request logs and metrics describe traffic from the outside. The logs the server itself produced live with the deployment that produced them, reached from a build or deployment on the [Deployments](/deployments) page. | Log type | Captures | When | Answers | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | -------------------------------------------------- | | **Build logs** | The build process that turned a commit into a deployable artifact: dependency installation, entrypoint detection, inspection, and any packaging failure. | Stream while a build runs; remain available once it finishes. | Why a deploy never went live. | | **Server logs** | The running server's `stdout` and `stderr`. | After the server is deployed. | Where your own logging and uncaught errors appear. | Server logs open at the newest output and continue to refresh while you stay at the bottom of the stream. Scroll upward to load earlier output without moving your place. Floating controls jump to the deployment's first log, step backward or forward in time from what you are viewing, or return to the live stream. For what the runtime captures from your server's output, see [Compute model](/platform/compute-model). ## Programmatic access The Horizon REST API exposes request, build, and deployment runtime logs. Authenticate with a personal or service-account API key, then use the log routes in the [API reference](https://horizon.prefect.io/api/v0/docs). ## Time ranges and history Analytics views provide time-range selectors for recent windows, and live views refresh on their own so a dashboard left open keeps current. On the **Observability** performance charts, the longest ranges are plan-gated. Standard plans retain of metrics history; plans extend that to . Selecting one of the longer ranges without that plan prompts you to upgrade rather than silently returning a short window. ## Related docs See how requests are received, routed, and recorded before reaching server code. Learn how the runtime handles requests, memory, timeouts, and server logs. Find build and server logs for a specific build or deployment. Learn how the actor identity shown in usage analytics is verified. # Overview Source: https://docs.horizon.prefect.io/overview What Horizon is, how it hosts FastMCP servers, and the core concepts - projects, deployments, and server types. Horizon is the production platform for MCP servers from the team behind FastMCP, the framework that has become the default way developers build MCP servers. It gives teams a place to deploy the servers they build, secure every request through a gateway, connect the servers they already operate, and understand what agents and clients are doing in production. FastMCP helped make MCP server development feel like ordinary Python development. Horizon applies the same practical approach to production operations: packaging servers, giving them stable endpoints, enforcing access, preserving MCP session routing, recording logs and metrics, and making approved servers discoverable to the rest of your organization. Link a repository, build a Python MCP or FastMCP server, and publish a live endpoint for MCP clients. Authenticate callers, enforce access, preserve sessions, and record request metadata before traffic reaches server code. Inspect requests, server logs, clients, users, builds, and usage patterns from one place. Register remote MCP servers alongside hosted servers so teams can manage access from the same Horizon surface. Give GitHub Copilot and other registry clients one organization catalog for discovering production MCP servers. ## Why Horizon exists FastMCP made MCP servers easy to build. That changed the bottleneck. When teams can create useful servers quickly, the next question is how to share them safely: a remote endpoint clients can reach, a catalog where people can find approved servers, and access controls that match how work actually happens. Some tools should be available to everyone. Some should only be available to engineering, finance, or an on-call team. Some servers should be visible across the organization, while individual tools remain restricted to the people who need them. Horizon exists for that second phase of MCP adoption: turning individually useful servers into shared, governed infrastructure. Local servers are good for development. Shared servers need a lifecycle: builds, deployment URLs, previews, rollbacks, environment variables, authentication, authorization, logs, metrics, and a clear owner. Horizon brings that lifecycle to MCP without asking every team to rebuild the same platform around each server. ## What is an MCP gateway? An MCP gateway sits between MCP clients and MCP servers. Instead of every client connecting directly to every server, the gateway receives requests first and handles shared platform concerns such as routing, authentication, authorization, session handling, and logging. In Horizon, every MCP request to a deployed server passes through the gateway before it reaches your code. That is where Horizon identifies the deployment, applies the server's authentication mode, checks authorization, preserves MCP session routing, and records request metadata for logs and analytics. This matters because MCP access is not only a deployment problem. It is a policy surface. Teams need to decide who can discover a server, who can call it, who can manage it, and which tools a caller should be allowed to use. Horizon puts those decisions in the request path, so every server does not need to reimplement its own authentication, RBAC, logging, and session behavior. Choose how callers prove identity when they connect to deployed MCP servers. Horizon authentication is enabled by default so new hosted endpoints start behind an access check. Use organization roles, server access, custom server roles, and tool-level access to control what authenticated callers can do. See which client called which deployment, how the request was routed, and whether the gateway accepted or rejected it. Learn the full request path from deployment lookup through access checks, session routing, and server forwarding. ## What you can build Horizon supports three server patterns. Build and run Python MCP or FastMCP servers from source code managed in a Git repository. Register MCP servers that run outside Horizon and manage their access through Horizon. Combine selected capabilities from multiple servers into one scoped MCP endpoint. | Server type | Best for | Who runs the code | | ----------- | -------------------------------------------------------------------------------------------- | --------------------------------- | | Hosted | The fastest path when you want Horizon to package, deploy, serve, and observe your MCP code. | Horizon. | | External | Systems that already have their own runtime. | You, outside Horizon. | | Remix | Shaping the exact set of capabilities a team or agent should see. | The upstream servers you combine. | ## How a hosted server works Start with a Python MCP or FastMCP server. The server exposes tools, resources, or prompts that MCP clients can call. Connect the Git repository that contains the server. Horizon uses the repository to create builds and deployment artifacts. A successful build becomes a deployable artifact. Deployments provide stable URLs, preview environments, promotion, and rollback. MCP clients call the Horizon deployment URL. Horizon handles routing, authentication, authorization, sessions, and request observability before traffic reaches your server. ## Core concepts Builds turn repository source into deployable server artifacts. Build logs explain dependency installation, entrypoint detection, inspection, and packaging failures. Deployments decide which artifact serves traffic. Use them to promote, preview, and roll back server versions. The gateway receives MCP traffic, identifies the deployment, enforces access, preserves session routing, and forwards supported requests. The compute model explains startup, request handling, timeouts, memory, environment variables, local state, and logs. Authentication determines how callers prove identity when they connect to Horizon, the REST API, or deployed MCP servers. Authorization determines what authenticated callers can do at the organization, server, and tool level. ## Start here If you want the shortest path to a working server, start with [Quickstart](/quickstart), then [connect a client](/connect-a-client). If you are planning a production rollout, read [Gateway](/gateway), [Build system](/platform/build-system), and [Compute model](/platform/compute-model) before you depend on a server in a critical workflow. # Plans Source: https://docs.horizon.prefect.io/plans Compare Horizon plan features and understand how plan gates work. A Horizon plan applies to an entire organization. It determines which product capabilities the organization can configure and use. Plans are cumulative: includes the capabilities in , and includes the capabilities in both lower tiers. Plan availability and access permissions are separate checks. A plan can make a feature available to the organization, while [organization and server roles](/roles) determine which actors can use or manage it. This page describes standard product availability. Included usage, seat quantities, pricing, and contract terms appear in Horizon's Billing area and can vary by organization. ## Plan tiers For an individual building and operating hosted MCP servers. It includes the core build, deployment, gateway, client, and observability workflow. For teams operating MCP servers together. It adds organization collaboration, durable automation identities, external servers, and more control over hosted server networking and authentication. For organizations that need centralized identity, governed server access, curated MCP surfaces, and custom serving domains. Start with when one person owns the organization and its hosted servers. Choose when multiple people need access or when production automation should use a [service account](/api-keys#create-a-service-account-api-key) instead of a person's identity. Choose when access must be managed with custom roles, capability-level policy, or single sign-on. ## Feature availability A checkmark means the capability is part of the standard plan. A blank cell means the organization needs a higher plan before it can configure or use that capability. | Capability | | | | | -------------------------------------------------------------------------------- | --------------------- | --------------------- | --------------------- | | Build and deploy [hosted servers](/servers/hosted) | | | | | Connect MCP clients, Playground, and ChatMCP | | | | | Git-based builds and deployments | | | | | Request and session observability | | | | | Observability history | | | | | Invite [organization members](/members) | | | | | Create service accounts and service-account keys | | | | | Register [external servers](/servers/external) | | | | | Configure [external authentication](/platform/external-authentication) | | | | | Disable Horizon authentication for a hosted server | | | | | Use [static egress addresses](/platform/networking#static-egress-addresses) | | | | | Publish an [organization MCP registry](/mcp-registry) | | | | | Serve from a [custom domain](/custom-domains) | | | | | Define [custom server roles](/roles#custom-server-roles) | | | | | Apply [capability-level access](/platform/authorization#capability-level-access) | | | | | Create [remix servers](/servers/remix) | | | | | Configure organization [single sign-on](/administration/sso) | | | | The matrix covers generally available plan features. Separately purchased add-ons appear in the organization's **Billing** area. An add-on enables its own capability without changing the organization's base plan. ## Feature gates Horizon checks plan availability at both the dashboard and API boundaries. The dashboard keeps the surrounding workflow visible and marks unavailable actions with the plan required to unlock them. API requests for an unavailable feature return a forbidden response instead of applying the change. Permissions still apply after the plan check succeeds. For example, an organization can define custom server roles, but only an actor with the required organization permission can create or change those roles. A higher plan never grants an actor additional role permissions by itself. ## Plan changes The [billing contact](/billing-contact) manages plan changes for the organization. The effective date shown during checkout or in **Billing** determines when the new feature set applies. An upgrade unlocks the higher tier's features when the change becomes effective. A downgrade keeps the current feature set until its effective date, then applies the lower tier's availability. Before a downgrade, review automations, servers, access policies, and identity settings that depend on the current tier. Requests that require a removed feature are denied after the lower plan takes effect. Product feature availability is distinct from technical and usage limits. See [Limits](/limits) for the runtime, build, request, and configuration boundaries that apply while a feature is in use. # Authentication Source: https://docs.horizon.prefect.io/platform/authentication How MCP clients authenticate to servers served through Horizon, and how users and automation authenticate to Horizon itself. Authentication in Horizon provides a shared identity layer for MCP servers served through the gateway. The gateway verifies callers before requests reach server code, so hosted servers can use the same OAuth-compatible MCP client sign-in, API key validation, organization membership checks, and request outcomes. This moves authentication policy out of individual server implementations and into the platform path. Security and platform teams can define how protected server endpoints accept callers, then apply that behavior consistently across servers. When a hosted server needs to own authentication itself, disabling Horizon authentication lets Horizon forward requests without validating the caller at the gateway. Authentication in Horizon has two related paths: | Path | Caller | Target | Main question | | ------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------ | | Connecting to MCP servers | An MCP client, agent, or automation calling a server endpoint | A hosted, remix, or external MCP server served through Horizon | Who is trying to use this MCP server? | | Signing in to Horizon | A user, script, CI job, service account, or Terraform run | The Horizon dashboard or REST API | Who is trying to manage Horizon resources? | The same Horizon identity can appear in both paths, but the request paths are different. Authenticating to a Horizon-served MCP endpoint lets a client call tools, resources, and prompts exposed by that server. Signing in to the Horizon dashboard lets a user manage servers, members, keys, and settings. A Horizon API key can authenticate a REST API request or an MCP request. The target URL determines which path applies: platform management for REST API routes, server usage for MCP endpoint routes. Authorization is separate. After Horizon identifies the caller, authorization decides what that caller can see, manage, or invoke. For role and access resolution, see [Authorization](/platform/authorization). Authentication is enabled by default for hosted MCP server endpoints. Disabling it for a hosted server is available on . ## Connecting to MCP servers Use this path when the caller is trying to use a server through its MCP endpoint. This includes agents, MCP clients, scripts, and tools that send requests to a Horizon deployment URL. Hosted server traffic passes through the Horizon gateway. The gateway identifies the deployment, reads the server's authentication mode, verifies the caller when required, checks server access, and only then forwards the request to your MCP server. This layer protects the server endpoint. It is separate from the dashboard and REST API sign-in path used to manage that server. A hosted server's authentication settings expose one control: **Horizon Authentication**, which is either enabled or disabled. | Horizon Authentication | What callers must provide | When to use it | | ---------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | | Enabled | A valid Horizon bearer credential, such as a user token from an MCP client sign-in flow or a Horizon API key | Default for shared and production servers | | Disabled | No Horizon bearer credential. Your server may still require its own credentials. | Public endpoints or servers that perform all authentication themselves | When a server is protected by Horizon, the endpoint is scoped to the server's Horizon organization. A caller must authenticate as a user or service account in that organization before the request can reach your server. Authorization settings can then narrow access further. To share a protected server with another person, invite them to the server's organization and grant the appropriate access. When a server is protected by Horizon, callers must authenticate with a bearer credential: ```http theme={null} Authorization: Bearer ``` Horizon accepts two categories of bearer credentials for protected MCP traffic: MCP clients that support authenticated server connections can use the Horizon deployment URL and complete sign-in in the client. Horizon accepts the resulting bearer token as the signed-in user and checks that user against organization and server access. If your client does not support interactive sign-in, use a Horizon API key as the bearer token instead. Non-interactive clients can send a Horizon API key as the bearer token. Horizon API keys start with `fmcp_`, are shown only when created, and can be revoked independently. For key management, see [API keys](/api-keys). An identity provider token is not a substitute for either credential. When a member signs in through SSO, WorkOS validates the identity provider flow and Horizon issues the identity used at its gateway. A bearer token issued directly by Entra ID, Okta, or another provider is not accepted by a Horizon-protected MCP endpoint. If your server must validate an externally issued token itself, use a hosted server with Horizon Authentication disabled. Your server then owns token validation and application authorization, and Horizon cannot apply identity-based server or capability access to those requests. If the credential is valid, the gateway forwards the request with the verified caller identity available to downstream platform checks and request observability. Missing, malformed, expired, revoked, or wrong-organization credentials stop at the gateway. The gateway runs before your server code. A rejected authentication request stops before server compute starts or your Python handler runs. ## Changing a hosted server's authentication Configure hosted server authentication from the server's authentication page. Go to the server, then open Access and Authentication. Enabled means clients must prove identity to Horizon before the request reaches the server. Disabled means Horizon passes requests through without a Horizon authentication check. Authentication changes apply on the next deployment. Existing live traffic continues using the setting attached to the currently deployed artifact until a new deployment applies the change. Disabling Horizon authentication removes Horizon's gateway authentication and server access checks for that hosted server. Do so only when the endpoint is intentionally public or your server implements its own authentication and authorization. Any credentials your server requires are handled by your server, not by Horizon gateway authentication. If you want clients to authenticate with an OAuth provider or token system that your server controls, implement that authentication in your server and disable Horizon authentication. Horizon then does not validate the caller's credential before forwarding the request, and your server is responsible for rejecting unauthenticated or unauthorized requests. Remix servers and external servers always keep Horizon authentication enabled. It cannot be disabled for them. ## External authentication External authentication adds a second credential after Horizon identifies the caller. External servers use it when Horizon calls the remote MCP endpoint. Hosted servers can use delegated authorization to exchange the identified user for an upstream credential before hosted code runs. The Horizon credential and downstream credential protect different boundaries. A valid Horizon sign-in does not guarantee that the caller has authorized the remote provider. A missing, expired, or revoked downstream credential can fail after Horizon authentication and server access checks have succeeded. Horizon supports per-user OAuth, per-user API keys, and organization-shared API keys for the downstream boundary. To choose a mode, configure its credentials, and understand hosted delegated exchange, see [External authentication](/platform/external-authentication). ## Common MCP endpoint outcomes Horizon identified the deployment, accepted the caller's credential, found the caller in the expected organization, and authorization allowed the request to continue. The request omitted a bearer credential, used another `Authorization` scheme, sent an empty token, sent an expired user token, or used an API key that is invalid, inactive, or revoked. Horizon authenticated the caller, but authorization denied access to the organization, server, or requested capability. Check organization membership, server access, custom server roles, and tool-level access settings. For the role model, see [Authorization](/platform/authorization). The deployment URL maps to no live server, or Horizon intentionally conceals a server from an authenticated caller that lacks discovery access. Authentication and gateway routing succeeded. Check your server logs, request logs, and your server's own application-level authentication or authorization logic. ## Signing in to Horizon Use this path when the caller is managing Horizon itself: opening the dashboard, calling the REST API, creating API keys, deploying servers, inviting members, or changing access settings. Human users sign in to Horizon, choose an organization, and manage servers, members, keys, builds, and settings according to their role. API clients authenticate with a bearer credential or an active browser session. API requests are checked against the authenticated user's or service account's access. Service accounts and user-owned API keys authenticate scripts, CI/CD, and other machine workflows. ## Actor identities Every authenticated platform request resolves to an actor. An actor is the Horizon identity that can own API keys, belong to an organization, and receive access grants. | Actor type | Used for | Authentication methods | | --------------- | ----------------------------------------------------------- | -------------------------------------------------------- | | User | Humans using the dashboard, API, playground, or MCP clients | Horizon sign-in, user access tokens, user-owned API keys | | Service account | Automation, CI/CD, Terraform, and other machine clients | Service-account API keys | API keys can belong to users or service accounts. This means a machine workflow can authenticate as a service account while participating in the same organization and server access model as a human user. ## REST API authentication Authenticated REST API routes accept the following credentials: | Credential | Header or source | Typical use | | ----------------- | -------------------------------- | ------------------------------------------------------------------------- | | API key | `Authorization: Bearer fmcp_...` | Scripts, CI/CD, service accounts, Terraform | | User access token | `Authorization: Bearer ` | User-scoped API calls from clients that already completed Horizon sign-in | | Browser session | Horizon session cookie | Dashboard-originated API requests | If both an `Authorization` header and a browser cookie are present, the bearer credential takes precedence. Invalid or missing credentials return `401`. For scripts, CI/CD, Terraform, and other direct API clients, use a bearer credential. Browser sessions are intended for dashboard-originated API requests. API keys inherit access from the user or service account that owns them. If the owning actor loses organization or server access, requests made with that key lose the same access. Revoked keys no longer authenticate. All successful REST API authentication resolves to an actor identity. Horizon uses that actor identity to check organization, server, and feature access. ## Operational guidance For production servers, keep Horizon authentication enabled and use server access controls to decide who can call the server. Prefer service accounts and API keys for automation, and user sign-in for interactive MCP clients. Rotate API keys by creating a new key, updating the client, and revoking the old key after traffic has moved. Because API keys are shown only at creation time, store them in your secret manager before closing the creation dialog. Disable Horizon authentication only when you have a clear public-access requirement or an independent authentication layer inside the server. When it is disabled, Horizon request logs can still show gateway traffic, but Horizon no longer identifies the caller before forwarding the request. ## Related docs Learn how roles and access grants decide what authenticated actors can do. Learn how requests move through routing, authentication, access checks, and MCP session handling. Create, rotate, and revoke keys for users and automation. Configure downstream OAuth and API-key credentials for external and hosted servers. # Authorization Source: https://docs.horizon.prefect.io/platform/authorization How Horizon enforces access in the request path: the authorization decision flow, capability filtering, and where role checks run for management and MCP traffic. Horizon provides a shared access layer for the servers your organization deploys and the platform resources your team manages. After authentication identifies a caller, Horizon uses the same organization membership, server roles, custom roles, and capability policies to decide what that caller can see, manage, or invoke. This moves access policy out of individual server implementations and into the platform path. Security and platform teams can make servers broadly discoverable, restrict sensitive deployments to specific people, Teams, or service accounts, and expose only the MCP capabilities each role should use. Authorization in Horizon has two related paths: | Path | Actor | Target | Main question | | ----------------- | ------------------------------------------------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------- | | Using MCP servers | An MCP client, agent, or automation calling a server endpoint | A hosted, remix, or external MCP server served through Horizon | Can this actor use this server and requested MCP capability? | | Managing Horizon | A user, script, CI job, service account, or Terraform run | The Horizon dashboard or REST API | Can this actor view or change this organization, server, member, key, or setting? | The same Horizon identity can appear in both paths, but the requested action is different. Calling a Horizon-served MCP endpoint lets a client use tools, resources, and prompts exposed by that server. Managing Horizon lets a user or service account change servers, members, API keys, builds, access settings, and organization settings. Authentication is separate. Authentication answers "who is calling?" Authorization answers "what is this caller allowed to do?" For the identity and credential path, see [Authentication](/platform/authentication). Horizon evaluates authorization at three levels: Determines whether the actor belongs to the organization and whether they can manage organization-wide settings such as members, billing contact assignment, SSO, service accounts, and server roles. Determines whether the actor can discover, call, view, edit, deploy, or administer a specific server. Determines which MCP tools, resources, and prompts are available to the actor's resolved server roles. Custom server roles and team server grants are available on . Tool-level access is available on . ## Request decision flow When a protected MCP request reaches Horizon, the gateway checks access before the request reaches server code. Horizon authenticates the bearer credential and resolves it to a user or service account. Invalid, expired, revoked, or missing credentials fail before authorization. The actor must belong to the organization that owns the server. An actor in another organization cannot use that organization's servers. Horizon resolves the actor's server role set from organization privileges, explicit server grants, Team grants, and the server's default role. If the server has tool-level access configured, Horizon allows tools, resources, and prompts whose policy permits at least one resolved role. Allowed requests continue to the server. Denied requests stop at the gateway. Disabling Horizon authentication removes Horizon gateway authentication and server access checks for that hosted server endpoint. Do so only when the endpoint is intentionally public or the server implements its own authentication and authorization. This is available only for hosted servers; remix servers and external servers always keep Horizon authentication enabled. ## Roles and actors Authorization resolves against an **actor** (a user or service account) using that actor's organization role, explicit server grants, Team grants for users, and the server's default role. API keys inherit access from the actor that owns them, so revoking a key disables that credential without changing the owner's roles. [Roles](/roles) is the canonical reference for the role model: the built-in organization roles (`admin`, `member`), the built-in server roles (`admin`, `editor`, `viewer`), custom server roles, default roles, explicit grants, and team grants. This page covers where those roles are enforced in the request path rather than redefining them. Server roles are not hierarchical in capability policies. If both `viewer` and `editor` should access a capability, both roles must be allowed. Use `*` only when every server role should have access. ## Capability-level access Capability-level access controls which MCP tools, resources, and prompts are available to each server role. When an actor holds multiple roles, a capability is available if its policy allows at least one of them. Capability access is configured from a server's Settings pages after Horizon has a manifest for the server. For hosted servers, saved capability policy changes apply to the dashboard immediately, but live MCP endpoint traffic uses the updated policy after the next deployment. Explicit grant, team grant, and server default role changes do not require a code change or rebuild. Capability access has two layers: | Layer | What it controls | | -------------------- | ------------------------------------------------------------------- | | Default permissions | Which roles can access tools, resources, and prompts by default. | | Capability overrides | Per-tool, per-resource, and per-prompt exceptions from the default. | When no capability policy is configured, the server's capabilities are not filtered by tool-level access. After a capability policy is configured, any tool, resource, or prompt without a matching default or override is denied. Per-capability overrides can: | Override | Effect | | ----------------- | ---------------------------------------------------- | | Default | Inherit the server's default capability permissions. | | All roles | Allow every server role. | | One or more roles | Allow only the listed roles. | Horizon keeps the `admin` server role allowed in capability policies. Organization admins and server admins can recover access to server capabilities. Capability policies apply to MCP tools, MCP resources, resource templates, and MCP prompts. Resource templates use the resource access settings. ## Applying changes Authorization changes have different lifecycle boundaries depending on what you change. | Change | When it affects Horizon management | When it affects MCP endpoint traffic | | --------------------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | Organization role change | On the next authenticated dashboard or API request. | On the next authenticated MCP request that resolves the actor's current access. | | Explicit server grant change | After the change is saved. | After Horizon updates the live deployment's access metadata. No code change is required. | | Team server grant change | After the change is saved. | After Horizon updates the live deployment's access metadata. No code change is required. | | Directory-backed Team membership change | After Directory Sync processes the membership. | After Horizon updates the live deployment's access metadata. No code change is required. | | Server default role change | After the change is saved. | After Horizon updates the live deployment's access metadata. No code change is required. | | Capability policy change | After the change is saved in Horizon. | For hosted servers, after redeploying the server so the deployed endpoint uses the updated policy. | | API key revocation | Immediately stops that key from authenticating. | Immediately stops that key from authenticating. | For hosted servers, if you save default capability permissions or a capability override, redeploy the server before expecting MCP clients to see the new tool, resource, or prompt availability. ## Common outcomes Horizon authenticated the actor, found organization membership, resolved one or more server roles, and allowed the requested server or capability. Authentication failed before authorization. Check the bearer credential, API key state, token expiration, and target organization. Authentication succeeded, but authorization denied access to the organization, server, or requested capability. Check the actor's organization role, explicit and team server grants, the server default role, and capability policy. The server or deployment does not exist, or Horizon is concealing a server from a caller without discovery access. The actor may have server access but not capability access. Check the server's default capability permissions and any per-capability overrides, then redeploy hosted servers if the policy was changed. ## Operational guidance For production servers, keep Horizon authentication enabled and use server roles to control who can manage or call it. Prefer service accounts with narrowly scoped server grants for automation. Use `No Access` as the default role for sensitive servers, then grant access to the people, Teams, and service accounts that need it. Use a `Viewer` default for broadly discoverable servers where most members should be able to inspect or call safe capabilities. Use capability-level access for high-impact tools, sensitive resources, and prompts that should not be available to every server user. Keep destructive or write-capable tools restricted to roles that are meant to perform those actions. ## Related docs The canonical reference for organization roles, server roles, custom roles, default roles, explicit grants, and team grants. Learn how users, service accounts, MCP clients, and API keys prove identity. Learn where authentication and authorization run in the request path. Invite, remove, and manage people in your organization. Create, rotate, and revoke bearer credentials. Review product limits that can affect server requests. # Build system Source: https://docs.horizon.prefect.io/platform/build-system How Horizon turns Python MCP and FastMCP source code into a deployable server. When you deploy a hosted server, Horizon transforms your source code into something it can run. For Horizon, that means taking a Python MCP or FastMCP server from a specific repository commit, installing its dependencies, inspecting its MCP surface area, and producing an immutable artifact that can be deployed. This page explains what happens during that transformation, from the moment Horizon receives a build request to the moment the build artifact is ready for a deployment. Horizon builds Python MCP and FastMCP servers. The build system expects a Python entrypoint inside your repository. ## Starting a build A build begins when Horizon has a server revision to prepare. This can happen when: * you push to a connected Git repository * Horizon creates or updates a preview deployment * you redeploy from the dashboard * another deployment action requests a new build Before the build starts, Horizon resolves the request to an exact commit SHA. That means a build is tied to immutable source code, even if it was triggered from a branch name. Horizon also gathers the server configuration needed to build that commit. The repository and commit SHA Horizon should check out. The Python file, and optional object name, that defines the server. A configured dependency file, or the dependency file Horizon discovers from the repository. Environment variables for the target deployment. ## Build environment Each build starts from a clean checkout of the selected commit. Files generated by previous builds are not reused, so anything your server needs must come from the repository, dependency installation, or configured environment variables. The build environment can access the network to download source code and install packages. It is meant to package and inspect your server, not run application workloads. Keep long setup scripts and import-time work small so builds stay fast and predictable. Builds time out after 15 minutes. ## Understanding your project Before Horizon can package your server, it has to understand how your Python project is laid out. It does this by resolving the entrypoint, Python version, dependency file, and execution environment. These rules are evaluated from the checked-out commit, so changing a branch after a build starts does not change that build. ### Entrypoint The entrypoint tells Horizon where your Python MCP or FastMCP server lives in the repository. It must be a relative Python file path, optionally followed by an object name: ```text theme={null} main.py server.py:app services/weather/server.py:mcp ``` If an object name is provided after `:`, it must be a valid Python identifier. Entrypoints cannot be absolute paths, cannot include `..`, and must point to a Python file inside the repository. If no entrypoint is configured, Horizon uses `main.py`. ### Python version Horizon chooses the Python version from the server source tree. It starts in the entrypoint directory and walks up toward the repository root, looking for a version request in: * `.python-version` * `pyproject.toml` project metadata * Python version requirements in `pyproject.toml` Supported versions are Python `3.11`, `3.12`, `3.13`, and `3.14`. If Horizon cannot find a supported version, it uses Python `3.12`. ### Dependencies If you configure a dependency file, Horizon uses that file. The path must point to a file inside the repository. If you do not configure one, Horizon searches from the entrypoint directory up to the repository root and uses the first supported file it finds: | Search order | File | Install method | Effect | | ------------ | -------------------------------------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------- | | 1 | `requirements.txt` | Installed as a dependency file. | Installs the listed requirements. | | 2 | `uv.lock` (only when a `pyproject.toml` sits next to it) | Installed with a frozen `uv sync`. | Uses the exact versions you committed. | | 3 | `pyproject.toml` | Installed as a project when it has a build system. | Your package metadata and local modules are importable when the server runs. | For reproducible builds, commit your lockfiles and make dependency versions as explicit as your project needs. ### Environment variables Environment variables configured for the deployment are included in the build input and made available to the running server. Variable names must use uppercase letters, numbers, and underscores, and must start with a letter or underscore. The same configured values may be available during dependency installation and server inspection. Avoid printing secret values from setup scripts or package hooks. ## Build lifecycle Horizon installs FastMCP and your project dependencies into the server image. Dependency installation happens during the build, so package resolution errors, missing files, and incompatible Python versions surface before a deployment is promoted. In monorepos, keep dependency files close to the server entrypoint or configure an explicit dependency file path. Horizon resolves automatic dependency files by walking from the entrypoint directory toward the repository root. Horizon packages your source code, dependencies, and execution configuration into a deployable server artifact. Horizon inspects the built server with FastMCP. Inspection loads the server entrypoint and produces a manifest of the tools, resources, and prompts exposed by the server. Import-time errors or side effects can fail the build, even if dependency installation succeeded. If the build succeeds, Horizon publishes an immutable artifact and records the artifact reference on the build. ## Producing output A successful build produces: * an immutable server artifact * a server manifest generated by FastMCP inspection * build logs for the full build lifecycle * a terminal build status The artifact is what Horizon deploys. The manifest is what lets Horizon display, route, and govern the MCP surface area of the server. Failed builds do not produce a new deployable artifact. ## Finalizing deployment Build success and deployment promotion are related, but they are separate steps. After a build succeeds, Horizon can use the artifact to create or update a deployment. Whether the live endpoint changes depends on the deployment context. A preview build may update only that preview deployment. A production build may be promoted to the live deployment. A successful build by itself does not necessarily mean the production endpoint changed. If a build succeeded but the endpoint did not change, check whether the build was for a preview deployment, whether promotion is required, and whether a newer build has already been selected for that deployment. ## Defaults and configuration | Setting | Default | How to change it | | --------------- | ------------------------------------------------- | -------------------------------------------------------------------- | | Server language | Python MCP or FastMCP | Use a Python MCP/FastMCP server entrypoint | | Entrypoint | `main.py` | Configure the server entrypoint | | Python version | `3.12` | Add `.python-version` or Python version metadata in `pyproject.toml` | | Dependencies | nearest `requirements.txt`, then `pyproject.toml` | Configure a dependency file path | | Build timeout | 15 minutes | Not configurable | ## Failures and logs Build logs include lifecycle markers, dependency installation output, server inspection output, and packaging output. Use them when a build fails because they usually show the exact command or package step that failed. Horizon could not find the configured Python file, the path pointed outside the repository, or the object name after `:` was not a valid Python identifier. The configured dependency file did not exist, pointed outside the repository, or dependency installation failed. One or more environment variable names used unsupported characters. Names must use uppercase letters, numbers, and underscores, and must start with a letter or underscore. FastMCP inspection could not load the server entrypoint or could not produce a server manifest. Import-time errors commonly appear here. The build ran longer than 15 minutes. Large dependency installs, package hooks, network calls, or expensive import-time work can all contribute. Platform-level failures happen when Horizon cannot complete the build workflow. These failures are reported with a generic message and can usually be retried. ## What affects build speed Build time usually depends on things inside your repository: * dependency count and package size * package install scripts * large repositories or large files in the build context * network calls during setup or import * expensive work that runs when the server module is imported Keep build-time work lightweight. Your server should do expensive application work when it receives requests, not while Horizon is packaging and inspecting it. ## Related docs Install the Horizon GitHub App and deploy servers from your repo. Configure deployment-scoped environment variables. Understand preview deployments, promotion, and rollback. Learn how Horizon runs the deployed build artifact. # Compute model Source: https://docs.horizon.prefect.io/platform/compute-model How Horizon runs deployed Python MCP and FastMCP servers. When you deploy a hosted server, Horizon packages your Python MCP or FastMCP server and makes it available at a deployment URL. The compute model describes what happens when MCP clients call that URL: how Horizon starts your server, handles requests, records logs and metrics, and preserves enough session routing state for clients to continue a conversation. The compute model is separate from build behavior. For entrypoint detection, Python version selection, dependency installation, and server inspection, see [Build system](/platform/build-system). For the request layer in front of deployed servers, see [Gateway](/gateway). ## Execution contract Horizon runs hosted servers as Python HTTP MCP servers. Your server entrypoint is started with FastMCP, bound to an internal HTTP port, and exposed through the Horizon MCP endpoint for that deployment. Horizon runs the Python entrypoint produced by the build system. MCP clients connect to the deployment URL, usually ending in `/mcp`. Hosted server requests have a -second timeout. Hosted servers currently run with MB of memory. Local files are not durable across compute instances or redeploys. ## Where compute fits The compute layer is one part of the hosted server lifecycle. This separation matters when you are debugging: locate which layer a failure came from, then look at the failures typical of that layer. | Layer | Responsibility | Typical failures | | -------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------- | | Build | Creates deployable artifacts. | Dependency installation, entrypoint loading, or server inspection. | | Gateway | Receives MCP traffic. | Routing, access, or unsupported transport methods. | | Server compute | Runs the Python code that handles supported MCP calls. | Python startup, request handlers, memory use, timeouts, or application logs. | ## From artifact to serving A successful build produces a deployable artifact. Horizon serves traffic from the artifact selected by the deployment, such as the current live deployment or a preview deployment. Horizon starts server compute when traffic needs to be served. The first request to a fresh instance can take longer because Python, dependencies, and your server module need to load. Horizon starts your FastMCP server over HTTP. MCP clients continue to call the Horizon deployment URL; the gateway forwards supported requests to the running server. Tool, resource, and prompt handlers run in your Python process. Code that executes at import time may run during startup, before an individual MCP request handler is called. Horizon captures request outcomes, server logs, session activity, duration, memory usage, and cold-start metrics so you can debug deployed behavior. ## What runs in compute The Python version, installed dependencies, source files, and entrypoint come from the build artifact. Horizon does not reinstall dependencies when a request arrives. To change the Python version, dependencies, entrypoint, or packaged source, update the repository or server settings and create a new build. The deployed server changes only after a successful build artifact is promoted. ## Environment variables Deployment environment variables are available to the running server. Treat environment variables as configuration for startup and request handling. Changing environment variables requires a new deployment before the running server sees the new values. Avoid printing secrets to stdout or stderr; those streams become server logs. ## Request window and long-running work Hosted server requests time out after seconds. This limit applies to the request from the MCP client through Horizon to your deployed server. Use request handlers for work that can complete within that window. For longer or retryable work, return a job ID quickly and continue the work asynchronously instead of keeping the MCP request open. ### Choose the right execution path | Use this | For | Avoid using it for | | --------------------------------------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | | MCP request handler | Interactive tool, resource, or prompt work that can finish within seconds | Long-running jobs, polling loops, or work that needs retries after the client disconnects | | Asynchronous job with a returned handle | Longer, retryable, or asynchronous work that should continue outside the MCP request | Work that must return an immediate MCP response body | Horizon does not support long-lived `GET /mcp` streams for hosted servers. Use the standard HTTP request flow for MCP calls, and move long-running work out of the request path. ## Sessions and local state Horizon manages MCP session routing for deployed servers. When a client initializes a session, Horizon returns an MCP session ID and uses it to route later requests in that session. Clients on MCP protocol versions that have no session handshake do not receive a session ID. Session routing state is retained for hours. Clients should send the `mcp-session-id` header on follow-up requests when their MCP client supports it. Session routing helps Horizon keep related MCP requests together. It is not a durable application database or a guarantee that every request in a session reaches the same Python process. Store durable application state outside the local filesystem. ## Instances and reuse Horizon starts server compute as needed to serve traffic. A request may be handled by a fresh Python process or by one that is already running. | Behavior | What it means | | --------------------- | ------------------------------------------------------------------------------------------------ | | Fresh instance | Python, dependencies, and your server module need to load before the request handler runs. | | Warm instance | The Python process is already loaded, so module-level clients or caches may still exist. | | No affinity guarantee | A later request may be handled by a different instance, even for the same deployed server. | | Ephemeral state | Memory and local files can disappear at any time and should not store durable application state. | You can cache reusable clients in memory when that is safe, but in-memory state is opportunistic. Treat it as a performance optimization, not a source of truth. ## Filesystem The deployed artifact contains your source code and installed dependencies. You can use temporary local files while handling a request, but the local filesystem is ephemeral and should not be used for durable application state. Do not rely on files written during one request being available to another request. Do not rely on files written before a redeploy being available after the redeploy. ## Cold starts and startup work Horizon can start server compute on demand. The first request to a new instance may take longer than later requests because Python, dependencies, and your server module need to load. Keep import-time work small: * avoid network calls during module import * lazy-load large clients or models when possible * move expensive setup into request handlers or asynchronous work * cache reusable clients in module-level variables when that is safe ## Defaults and limits | Setting | Default or limit | | ----------------------- | --------------------- | | Server language | Python MCP or FastMCP | | MCP transport | HTTP | | Request timeout | seconds | | Memory allocation | MB | | MCP session routing TTL | hours | | Local filesystem | Ephemeral | | Server logs | Stdout and stderr | For a broader list of product limits, see [Limits](/limits). ## Common compute failures The handler took longer than seconds, or startup work consumed too much of the request window. Move long work out of the request path, reduce import-time work, or split the operation into smaller calls. A fresh compute instance may need to start Python, import dependencies, and load your server module. Keep module imports lightweight and defer expensive setup until it is needed. Local files are temporary. Use local files only as scratch space, and store durable state outside the local filesystem. Reduce per-request memory use, avoid loading large objects at import time, stream or page large results when possible, and check memory metrics in the server overview. Build inputs and deployment environment variables take effect after a new successful build and deployment. Check which deployment artifact is live. Stdout and stderr are captured as server logs. Avoid printing secrets, tokens, credentials, or full environment dumps. ## Related docs Understand the request layer in front of deployed servers. Learn how Horizon creates the artifact that compute runs. Learn how successful build artifacts are promoted and rolled back. Configure values available to builds and deployed servers. Review compute and product limits. # External authentication Source: https://docs.horizon.prefect.io/platform/external-authentication Configure OAuth and API-key credentials for external servers and hosted delegated authorization. External authentication is available on . External authentication controls the credential that Horizon sends to a downstream service. The MCP client first proves who the caller is to Horizon. Horizon then sends an OAuth token or API key for that caller, or sends no credential when a remote MCP server accepts anonymous requests. For credential-based modes, an **external auth configuration** is the saved definition for that second credential. The Horizon dashboard presents it as an **auth source**. It records how to acquire and inject credentials, while each user's OAuth grant or API key is stored separately. The no-authentication mode does not create an auth source. Horizon uses external auth configurations in two places: | Server type | How external authentication is used | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | External server | Horizon authenticates to the remote MCP server after accepting the caller at the Horizon gateway. | | Hosted server | Delegated authorization replaces the caller's Horizon credential with a downstream credential before the request reaches hosted server code. | The two credentials serve different trust boundaries: | Boundary | Credential | Purpose | | --------------------------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | MCP client to Horizon | Horizon sign-in token or Horizon API key | Identifies the caller and lets the gateway check server access. | | Horizon to the downstream service | OAuth token or API key from the auth source, or no credential for a public remote MCP server | Authorizes the downstream request when the service requires authentication. | For the first boundary, see [Authentication](/platform/authentication). Server roles and capability policies are covered in [Authorization](/platform/authorization). ## Authentication modes Choose the mode based on whether the downstream provider requires a credential and who owns that credential. | Mode | Credential owner | Setup behavior | Best fit | | ----------------- | ---------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | No authentication | None | Horizon sends the downstream request without an `Authorization` header. | Public remote MCP servers that accept anonymous requests. | | OAuth | Each user | Each user completes the provider's authorization flow. Horizon stores and refreshes that user's tokens. | Providers with OAuth authorization-code support. | | Per-user API key | Each user | Each user enters their own token. | Providers that issue personal tokens but do not support OAuth. | | Shared API key | Organization | A server administrator enters one token for all users. | Service credentials whose downstream permissions should be shared. | No authentication applies only to external servers. Horizon still authenticates and authorizes the caller at the Horizon endpoint, but it does not forward the caller's Horizon credential to the remote MCP server. Select no authentication only when the remote MCP server accepts anonymous requests. Horizon sends no downstream `Authorization` header in this mode. OAuth and per-user API keys preserve the downstream user's identity and permissions. A shared API key makes every forwarded request act as the same upstream principal, even though Horizon continues to identify and authorize the original caller at its own gateway. A shared API key gives every authorized caller the downstream permissions of the shared upstream principal. Scope that principal to only the access the server needs. ## OAuth setup Horizon starts OAuth setup with the provider or remote MCP URL. It follows protected-resource and authorization-server metadata to locate the OAuth endpoints. | Provider capability | Horizon behavior | | -------------------------------------------------- | ---------------------------------------------------------------------------- | | OAuth metadata with Dynamic Client Registration | Horizon discovers the metadata and registers its OAuth client automatically. | | OAuth metadata without Dynamic Client Registration | Horizon prefills discovered endpoints and asks for a pre-registered client. | | No usable OAuth metadata | You provide the OAuth client and endpoint details manually. | | Dynamic Client Registration fails | You can continue with a pre-registered OAuth client. | When manual setup is required, register Horizon's callback URL with the provider, then enter the resulting client details in Horizon. For the hosted Horizon control plane, use `https://horizon.prefect.io/oauth/external/callback`. | Field | Required | Purpose | | ------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------- | | Client ID | Yes | Identifies the pre-registered Horizon OAuth client. | | Client secret | Provider-dependent | Authenticates a confidential OAuth client. | | Authorization URL | Yes | Sends users to the provider to grant access. | | Token URL | Yes | Exchanges authorization grants and refreshes tokens. | | Issuer | No | Identifies the authorization server. It defaults to the authorization URL. | | Scopes | No | Requests the provider permissions needed by the server. Enter multiple scopes as a comma-separated list. | | Token to forward upstream | Yes | Uses the access token for standard OAuth APIs, or the ID token when an upstream OIDC proxy requires it. | After the configuration is saved, each user completes the provider flow for their own Horizon identity. External-server users can select **Authorize** from the server's **Connection** page. Hosted-server users authorize when connecting an interactive MCP client, as described in [User authorization](#user-authorization). Horizon stores the resulting credential for that Horizon actor. The MCP client never receives the provider token. ## API-key setup API-key configurations define both who supplies the key and how Horizon places it on the downstream request. The default sends a bearer token in the standard HTTP authorization header: | Setting | Default | Example alternative | | ------------ | ---------------- | ---------------------------------- | | Header name | `Authorization` | `X-API-Key` | | Header value | `Bearer {token}` | `{token}` or `Token token={token}` | The header value must contain `{token}`. Horizon replaces that placeholder with the stored key when it forwards a request. This supports raw-key headers as well as providers that require a custom scheme or token format. Use header injection for remote credentials. Query-string credentials are not a supported authentication channel, and a secret embedded in a connector URL can be exposed anywhere that URL is displayed or recorded. A remote MCP server that requires a credential must accept it through an HTTP header. With a per-user API key, the configuration is created without a key. External-server users can select **Add token** from the server's **Connection** page. Hosted-server users add their key through the client authorization flow described in [User authorization](#user-authorization). With a shared API key, a server administrator selects **Add key** and saves the organization-wide value. Saved values are not displayed again. ## External servers An [external server](/servers/external) always authenticates the client at the Horizon-served endpoint. Horizon then uses the configured external credential, or no credential for a public remote MCP server, when it forwards the request. From Servers, start creating an external server. Select a [global connector](/servers/global-connectors), or add and select an organization connector for the remote MCP endpoint. Enter the connector name, then select No authentication, OAuth (per user), Key (per user), or Key (shared). Select no authentication only when the remote server accepts anonymous requests. If authentication is required, review the discovered OAuth registration or open OAuth Configuration and enter a pre-registered client. For an API key, set the header name and header value template expected by the remote server. If authentication is required, complete Authorize with OAuth, enter your personal API key, or enter the shared key. Horizon verifies that a credential has been saved before continuing. Review the remote endpoint and authentication mode, then select Create Connector. Horizon uses the saved credential when one is configured and sends anonymous requests otherwise. For OAuth and per-user API keys, every additional user must authorize the remote server separately. They can open the external server's **Connection** page to authorize, replace, or revoke their credential. A user who has Horizon server access but no valid remote credential can pass Horizon's access checks and still receive an authentication failure from the remote server. ## Delegated authorization Hosted delegated authorization applies the same external auth configuration to a [hosted server](/servers/hosted). It is useful when hosted server code calls an upstream API as the signed-in user and already expects that provider's token in an HTTP request header. The setup flow and runtime flow form a double handshake: 1. The user authorizes Horizon with the upstream provider, or stores a personal token in Horizon. 2. The MCP client authenticates to Horizon. The trusted gateway identifies the Horizon user, exchanges that identity for the saved downstream credential, and injects the credential before invoking hosted server code. **Setup** ```mermaid theme={null} flowchart TB user["Horizon user"] -->|"Authorizes"| provider["Upstream provider"] provider -->|"Stores credential"| store[("Horizon credential store")] ``` **Runtime** ```mermaid theme={null} flowchart TB client["MCP client"] -->|"Horizon credential"| gateway["Horizon gateway"] store[("Credential store")] -->|"Credential for actor"| gateway gateway -->|"Downstream header"| server["Hosted server"] server -->|"Authenticated call"| api["Upstream API"] ``` On a successful exchange, Horizon removes the inbound Horizon `Authorization` header. It then injects the downstream credential using the header name and value configured by the auth source. For the default OAuth configuration, hosted server code receives: ```http theme={null} Authorization: Bearer ``` Horizon completes the exchange before hosted server code runs and injects only the resulting downstream credential. Your code cannot access the auth source or other credentials stored in Horizon. Code that already reads the configured header can use delegated authorization without implementing its own OAuth callback or credential storage. ## Configure delegation Delegated authorization requires Horizon Authentication to remain enabled for the hosted server. Linking and unlinking an auth source updates serving configuration without rebuilding or redeploying the hosted server. The **Access** and **Authentication** page requires server write access, so these configuration steps are for a server administrator. Open the hosted server, then select Access and Authentication. Confirm that Horizon Authentication is enabled. In Delegated authentication, select Link auth source. Give the source a recognizable name and choose OAuth, per-user API key, or shared API key. Complete OAuth discovery and registration, or configure the API-key header and value template. Select Link auth source to create the configuration and attach it to the hosted server. Select Authorize, Add token, or Add key beside the linked source. OAuth and per-user API-key credentials belong to your signed-in Horizon user. A shared key belongs to the organization. Call the hosted server through its Horizon endpoint as an authorized user. Confirm that the hosted handler receives the configured downstream header and that its upstream call succeeds. To stop delegated exchange, open the linked source's action menu and select **Unlink auth source**. Unlinking disables delegated authorization for the hosted server and removes the saved link from serving configuration. Users must authorize again if the source is linked later. ## User authorization OAuth and per-user API-key auth sources require every caller to save their own downstream credential. Users with server access do not need server write access to complete this authorization. An interactive MCP client starts the self-authorization flow when it first connects to the hosted server. After Horizon sign-in, the **Horizon is requesting access** page lists the linked auth source. Select **Needs Access** to complete OAuth or **Needs API key** to save a personal token. Horizon then completes the client connection. A client that uses a personal Horizon API key does not open this browser flow. Before connecting that client, sign in to Horizon in a browser and open: ```text theme={null} https://horizon.prefect.io//authorize ``` Replace `` with the hostname prefix from the hosted server URL. For example, the slug in `https://weather-mcp.fastmcp.app/mcp` is `weather-mcp`. Authorize the provider or add the personal token, then retry the client. This self-authorization page requires server access, while auth-source configuration remains restricted to server writers. Service-account keys cannot use delegated authorization. Authorizing in the browser saves the downstream credential for the signed-in user, not the service account. Automation that authenticates with a service-account key must provide its upstream credential through the hosted server itself, such as with an [environment variable](/environment-variables), until service-account delegated authorization is supported. For user actors, a shared downstream API key needs no per-user authorization. Horizon uses the organization-wide credential configured by the server administrator. ## Current boundaries Hosted delegated authorization has the following first-version boundaries: | Boundary | Current behavior | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | Horizon identity | Exchange runs only for signed-in user actors on hosted servers with Horizon Authentication enabled. Passthrough requests are not eligible. | | Providers | One auth source can be linked to a hosted server. Multi-provider routing is on the roadmap. | | Service accounts | Delegated authorization does not run for service-account actors. Service-account support is on the roadmap. | | Exchange failure | Credential exchange fails open. Horizon forwards the hosted request without replacing the original request credential. | Fail-open behavior preserves hosted server availability when a credential is missing, expired, revoked, or temporarily unavailable. It does not bypass the initial Horizon authentication or server access checks, which happen before the exchange. The hosted server will not receive a usable downstream credential, so its upstream call will normally fail authentication unless the server has an independent fallback. Horizon encrypts OAuth client secrets, OAuth tokens, and API keys at rest. It keeps credential material out of normal responses, logs, and traces. For the storage and encryption boundary, see [Data protection](/security/data-protection). ## Related docs Understand how callers authenticate to Horizon before external exchange. Register and operate remote MCP servers through the Horizon gateway. Build and run MCP servers that can use delegated authorization. Review how Horizon protects credentials and other sensitive values. # Networking Source: https://docs.horizon.prefect.io/platform/networking How hosted and external MCP traffic crosses the Horizon network boundary, including static egress addresses and allowlisting. Horizon exposes public HTTPS endpoints to MCP clients and makes outbound connections when hosted server code or the gateway calls another system. The direction of the connection determines which network control to configure. | Traffic | Direction | Network boundary | | ----------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------- | | MCP client to Horizon | Inbound to Horizon | The client connects to a Horizon server URL or [custom domain](/custom-domains) over HTTPS. | | Hosted server to an upstream API | Outbound from Horizon compute | The upstream system sees a Horizon egress address. | | Horizon gateway to an external MCP server | Outbound from the gateway | The remote endpoint must accept HTTPS connections initiated by Horizon. | ## Static egress addresses Static egress addresses let an upstream system allowlist traffic from Horizon without opening access to the wider internet. They are available on . The addresses are shared Horizon infrastructure rather than dedicated addresses for one server. Allowlist every address for each applicable traffic path so traffic remains available across Horizon's runtime capacity. Once static IPs are enabled, hosted and external servers receive static egress on their next deployment. | Traffic path | Region | Egress addresses | | ----------------------------------- | --------------------------- | ------------------------------------- | | Hosted and external server runtimes | United States (`us-east-1`) | `52.71.188.148`
`54.243.154.171` | | Horizon gateway and build jobs | United States (`us-east-1`) | `3.226.68.95`
`18.213.238.230` | | Horizon web app requests to GitHub | United States | `35.173.11.78`
`54.174.250.177` | For a GitHub organization with an IP allowlist, allowlist both the **Horizon gateway and build jobs** addresses and the **Horizon web app requests to GitHub** addresses. Contact [horizon-support@prefect.io](mailto:horizon-support@prefect.io) if you need help determining which traffic paths apply. Static egress is useful when: * hosted tools call an API that restricts callers by source address * Horizon connects to an external MCP endpoint behind an IP allowlist * a build needs to reach a restricted GitHub Enterprise endpoint A static egress address identifies Horizon as the network source. Horizon [authentication](/platform/authentication) and [authorization](/platform/authorization) still identify and govern the actor inside each MCP request. ## External endpoints The Horizon gateway initiates connections to external MCP servers. The remote endpoint must therefore be reachable from Horizon over public HTTPS. An endpoint that accepts only connections from its own private network cannot be registered directly. To keep a publicly reachable endpoint restricted, expose HTTPS on port 443 and allowlist Horizon's static egress addresses. Continue to require OAuth or an API key at the remote endpoint because an IP allowlist complements application credentials rather than replacing them. An outbound-only firewall rule does not make the remote server reachable from Horizon. ## Related docs Register an MCP endpoint that runs outside Horizon. Serve a hosted server from a subdomain you control. Follow an MCP request through routing and access checks. # Quickstart Source: https://docs.horizon.prefect.io/quickstart Sign up, link a GitHub repo, and deploy your first FastMCP server on Horizon. By the end of this guide you will have a Python MCP server running on Horizon at a stable, protected URL like `https://weather-mcp.fastmcp.app/mcp`, ready for any MCP client to call. Horizon builds the server from your GitHub repository, publishes it behind the gateway, and keeps the endpoint live across future pushes. The path is short: point Horizon at a repository and an entrypoint, and each push to your default branch becomes a new build and deployment. You write the MCP server; Horizon owns everything from source to a protected URL. ## A server to deploy A Horizon hosted server is a Python [FastMCP](https://gofastmcp.com) server in a Git repository. Horizon reads one file, the **entrypoint**, to start the server and to discover the tools, resources, and prompts it exposes. Anything your entrypoint imports at startup is available to the running server. This is a complete server you can deploy. It exposes a single `get_weather` tool: ```python main.py theme={null} from fastmcp import FastMCP mcp = FastMCP("Weather MCP") @mcp.tool def get_weather(city: str) -> str: """Get current weather for a city.""" return f"It is always sunny in {city}." if __name__ == "__main__": mcp.run() ``` Commit this as `main.py` at the root of a repository. `main.py` is the entrypoint Horizon looks for by default, so a repository shaped this way needs no extra configuration. To use a different file, or to point at a named server object, configure the entrypoint as `server.py` or `server.py:mcp` when you create the server. For how Horizon resolves the entrypoint, Python version, and dependencies, see [Build system](/platform/build-system). The server above has no third-party dependencies beyond FastMCP, which Horizon installs for you. When your server imports other packages, commit a `requirements.txt` or `pyproject.toml` and Horizon installs them during the build. For configuring secrets and settings your code needs at runtime, see [Environment variables](/environment-variables). ## Deploy on Horizon Sign in at [horizon.prefect.io](https://horizon.prefect.io). On first sign-in you create an organization, which owns your servers, members, and access settings. Every server you deploy belongs to one organization. Horizon builds from source in GitHub, so the first step is linking a GitHub installation. Authorize the Horizon GitHub App and grant it access to the repositories you want to deploy. The repository picker then lists those repositories with their default branch. Select the one holding your server. For how the GitHub connection works and what each event triggers, see [GitHub](/git-integrations/github). Name the server, then confirm the entrypoint. The name determines the deployment URL, previewed as `{name}.fastmcp.app` while you type. Leave the entrypoint as `main.py` for the server above, or point it at your file. Optionally set a dependency file and environment variables. New servers have **Horizon authentication enabled** by default, so the endpoint starts behind an access check. When you create the server, Horizon resolves your default branch to an exact commit, installs dependencies, packages the server, and inspects it with FastMCP to discover its tools, resources, and prompts. Build logs show each step. A build is tied to the commit it resolved, so moving the branch afterward does not change that build. A successful build produces an artifact, and Horizon deploys it to your server's stable URL. The Deployments page shows the live URL and status. From here, every push to the default branch creates a new build and, when it succeeds, updates the live deployment. ## Confirm it is live Open the server's [Deployments](/deployments) page. The stable production URL sits at the top, ending in `/mcp`, and the status reads `Live` once the first deployment is serving. This URL stays constant as you push new versions; Horizon changes the artifact behind it through promotion and rollback without asking clients to reconfigure. Because Horizon authentication is enabled, callers authenticate before a request reaches your code. The gateway verifies the caller, checks access, and only then forwards the request to your Python handler. To make a server public or to let your own code own authentication, disable Horizon authentication, described in [Authentication](/platform/authentication). With the server live, you are ready to point an MCP client at it. [Connect a client](/connect-a-client) walks through configuring Claude, Cursor, ChatGPT, and the CLI clients against your new endpoint. # Roles Source: https://docs.horizon.prefect.io/roles How organization roles, server roles, custom roles, and capability access decide what people and service accounts can do in Horizon. Roles decide what an authenticated actor can do after Horizon knows who is calling. An actor can be a user or a service account. API keys inherit access from the actor that owns them, so rotating a key changes the credential, not the actor's role. Horizon uses roles in three places: Controls organization-wide management, including members, invitations, billing contact assignment, SSO, service accounts, connectors, and server role definitions. Controls access to a specific server, including who can view it, update it, deploy it, manage access, or administer server settings. Controls which MCP tools, resources, and prompts are visible to each server role after the actor has server access. Custom server roles and team server grants are available on . Tool-level access is available on . ## Decision flow When Horizon evaluates access, it works from the broadest boundary to the specific action. The actor must belong to the organization. Actors outside the organization cannot use or manage its resources. Organization admins can manage organization settings and are treated as the `admin` server role on every server in the organization. Organization members continue to server-specific access checks. For non-admin members, Horizon combines an explicit server grant with roles granted to each Team they belong to. If no explicit or team grant exists, Horizon uses the server's default role. If none applies, the actor has no access to that server. If capability access is configured, Horizon allows a tool, resource, or prompt when its policy permits at least one of the actor's resolved server roles. Server access resolves in layers. Horizon checks organization access first, combines all explicit and team grants next, and uses the default role only when no grant applies: | Order | If | Resulting access | | ----- | ------------------------------------------------- | ---------------------------- | | 1 | The actor is an organization admin | `admin` on every server | | 2 | Explicit or team server grants exist | The set of all granted roles | | 3 | No grant exists and the server has a default role | The default role | | 4 | None of the above | No access | The organization and server planes compose independently. An organization admin reaches every server as `admin` without an explicit or team grant and cannot be locked out of one. An organization member reaches a server through an explicit grant, one or more team grants, or the server's default role. They hold no organization-management permissions regardless of their server roles. For example, a member granted `admin` on one server can fully administer that server but still cannot invite members or change the billing contact, while an organization admin can do both without appearing in that server's access list. For the full request path, see [Authorization](/platform/authorization). ## Organization roles Organization roles answer: "What can this actor manage across the organization?" | Role | Contract | | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Admin | Can manage organization settings and has full access to every server in the organization. Horizon evaluates organization admins as the `admin` server role for server and capability access. | | Member | Can read basic organization information and can access servers through explicit grants, team grants, or server default roles. | Use the Admin role for people and service accounts that should manage broad organization concerns such as membership, billing contact assignment, [single sign-on](/administration/sso), service accounts, connectors, and custom server roles. Admins cannot be hidden from individual servers in that organization. The [billing contact](/billing-contact) is separate from organization roles. Organization admins can reassign the contact, while the contact manages payment methods, plans, add-ons, and invoice emails. Organization role changes affect the next authenticated Horizon dashboard, REST API, or MCP request that resolves the actor's current access. [Directory Sync](/administration/directory-sync) can provision organization membership and synchronize identity provider groups as directory-backed Teams. WorkOS IdP Role Assignment can assign the organization roles Admin and Member. Where team access is enabled, Team grants can assign server roles and coexist with explicit grants. Connecting a directory does not create those server grants. ## Server roles Server roles answer: "What can this actor do with this server?" Horizon includes three built-in server roles. Built-in roles are available in every organization and cannot be edited. | Role | Slug | Server management | MCP endpoint access | | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | Admin | `admin` | Full server access, including access management, configuration, builds, capability policies, and deletion. | Can call the server and all capabilities. The `admin` role stays allowed in capability policies so administrators can recover access. | | Editor | `editor` | Can view and update server configuration, create builds, and edit capability policies. Editors cannot grant or revoke server access and cannot delete the server. | Can call the server. Capability policies can allow or deny specific tools, resources, and prompts for this role. | | Viewer | `viewer` | Can view the server, its access list, and its capability policy. Viewers cannot modify the server. | Can call the server. Capability policies can allow or deny specific tools, resources, and prompts for this role. | `No Access` is a server default setting, not a role. Use it when the rest of the organization should have no server access unless they receive an explicit or team grant. ## Custom server roles Custom server roles are organization-scoped server roles. Use them when the built-in Admin, Editor, and Viewer roles are too broad for a server management workflow. Each custom role has: | Field | Contract | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Name | Stable role slug used in API responses and capability policies. It must be lowercase alphanumeric with optional hyphens or underscores. Built-in slugs such as `admin`, `editor`, and `viewer` are reserved. The name cannot be changed after creation. | | Label | Human-readable name shown in Horizon. The label can be updated. | | Permissions | Server management permissions for the role. Horizon always includes the `read:access` permission, which the dashboard shows as **Read access**. You cannot remove it. This permission lets role holders view server access assignments. Updating permissions changes what holders of the role can do in Horizon. | Custom roles can be used as a server default role, assigned through explicit or team server grants, and referenced by capability access policies. Server role permissions control Horizon management actions. They do not decide which individual MCP tools, resources, or prompts a caller can use. Capability access handles that narrower decision. Before deleting a custom role, remove it from server defaults, explicit grants, and team grants. Also review capability policies that mention the role slug, so stale policy entries do not remain after the role is gone. ## Default role A server's default role is the access level given to the rest of the organization when an actor has no explicit or team grant for that server. Set the default role from a server's Access > Members page. Only organization admins and actors with explicit or team server grants can access the server. Every current and future organization member gets that server role unless they receive access through an explicit or team server grant. For sensitive servers, start with `No Access` and grant access only to the users, Teams, or service accounts that need it. For broadly shared servers, use a default role that matches the access most organization members should have. Default role changes are saved in Horizon and then applied to live server access metadata. No code change is required. ## Explicit server grants Explicit grants are actor-specific access entries on a server. Use them for exceptions to the default role, such as a service account that can deploy a server while the rest of the organization can only view it. Manage explicit grants from a server's Access > Members page. An explicit grant contributes one role and takes precedence over the server default role. If you remove it, the actor may still keep access through a team grant, the server default role, or organization admin access. ## Team grants A team grant assigns one server role to every current member of a Team. A person receives roles from every Team with a grant on that server, combined with any explicit grant they hold. These roles form a set rather than replacing one another. Directory Sync can manage the membership of a directory-backed Team, but it never creates, changes, or removes the Team's server grants. Removing a person from the identity provider group removes the corresponding team-derived role after membership synchronization without changing the grant for the remaining Team members. Team server grants are available on . Add, change, or remove a Team grant from the server's Access > Members page. Review all server grants for one Team from Govern > Teams by selecting the Team and opening its Servers tab. ## Capability access Capability access answers: "Which MCP tools, resources, and prompts can these server roles use?" It is evaluated after Horizon has resolved the actor's server role set. Capability policies can set default access for all capabilities, then override individual tools, resources, and prompts. A capability is available when its policy allows at least one role in that set. When no capability policy is configured, Horizon does not filter the server's capabilities by role. After a policy is configured, a capability without a matching default or override is denied. Resource templates use the resource access settings. For hosted servers, saved capability policy changes are visible in Horizon immediately, but MCP endpoint traffic uses the updated policy after the next deployment. Server default role, explicit grant, and team grant changes do not require a redeploy. ## Where to manage roles | Task | Horizon location | | ----------------------------------------- | --------------------------------------------- | | Change a user's organization role | Organization settings > Members | | Create or edit custom server roles | Organization settings > Roles | | Set a server default role | Server > Access > Members | | Add or update explicit server grants | Server > Access > Members | | Add, update, or remove Team server grants | Server > Access > Members | | Review a Team's server grants | Govern > Teams > select a Team > Servers | | Set tool, resource, and prompt access | Server settings > Authorization > Permissions | ## Related docs Learn the full request decision model for Horizon management and MCP endpoint traffic. Invite, remove, and review people in your organization. Create bearer credentials that inherit access from their owning actor. # Data handling and sensitive-data redaction Source: https://docs.horizon.prefect.io/security/data-handling What request and response data Horizon records for served MCP traffic, and how the experimental sensitive-data redaction plugin blocks or masks PII in tool results. Horizon sits in the request path of every MCP server it serves, so it sees the traffic those servers handle. This page covers two things that follow from that position: what request and response data Horizon records for served traffic, and the experimental **sensitive-data redaction** feature, which can block or mask sensitive values in a tool result before it reaches the caller. For how stored values are encrypted and where secrets can appear, see [Encryption and data protection](/security/data-protection). ## Traffic records Every MCP request passes through the [gateway](/gateway), which writes one record per request to Horizon's traffic log. That record is what the [Observability](/observability) views read from, and it holds three kinds of data. | Kind | What it contains | | ----------------- | ------------------------------------------------------------------------------------------ | | Request metadata | The MCP method, the tool name, the session and client, and the acting identity. | | Response metadata | The status the gateway returned, whether it errored, and how long it took. | | Payloads | The full request `params` (a tool call's arguments) and the full response result or error. | Metadata is recorded for every request. Payloads are recorded by default, which is why a tool call's arguments and its result appear in the request detail on the [Logs](/observability#request-logs) page. That makes the Logs page useful for debugging a failed call, and it also means whatever your tools receive and return is stored there unless you turn payload logging off. ### Payload logging Whether payloads are stored is a per-project choice. Under the server's **Settings**, the **General** tab has a **Data Controls** card with two switches: | Switch | Effect when off | | ----------------- | ----------------------------------------------------------------------------------------- | | Log MCP Requests | Request `params` are no longer stored, so tool-call arguments stop appearing in Logs. | | Log MCP Responses | Response results and errors are no longer stored, so tool output stops appearing in Logs. | Both are on by default. Turning one off stops that payload from being recorded at all, so it never reaches the traffic record or the Logs page. Metadata is still recorded either way, and a failed request is still marked as an error, so turning payloads off narrows what is stored without hiding operational history. This control decides what Horizon **stores**. Sensitive-data redaction, below, decides what a caller **receives**, and masks detected values in what gets stored as a side effect. The two work independently. ### Retention Horizon currently retains traffic records indefinitely. Customers cannot set a retention period. Turning request or response payload logging off affects future requests only and does not delete existing traffic records. ## Sensitive-data redaction Sensitive-data redaction is experimental and best-effort. Detection is not exhaustive, and this feature is not a compliance control. Do not rely on it to meet HIPAA, PII, or other regulatory obligations. Sensitive-data redaction is a per-project gateway feature that scans each tool result after your server produces it and before it reaches the caller. When it finds sensitive data, it acts on the result: it either blocks the whole result or masks the matched spans. Detection combines managed PII detection with custom regex rules you define, and both run on every tool result while the feature is enabled. ### How detection works Automatic PII detection finds entities such as email addresses, phone numbers, and government identifiers in the result text. Each finding carries a confidence value, and a **sensitivity** setting decides how confident a finding must be to count. A higher sensitivity accepts lower-confidence findings, so it catches more but is likelier to flag safe text. | Sensitivity | Behavior | | ---------------- | ------------------------------------------ | | Low | Catches only high-confidence matches | | Medium (default) | Balanced | | High | Catches more, at the cost of over-flagging | Detection runs on the text in the result, including text inside embedded resources and string values in structured content. The scan operates on English text. ### Custom rules Custom rules match values that automatic detection would not recognize, such as an internal account number or record identifier. Each rule pairs a **pattern** with a **label**, uses Python's regular-expression syntax, and runs in addition to automatic detection. A regex match always counts, regardless of the sensitivity setting. | Field | Format | | ------- | ---------------------------------------------------------------------------------------------------------------------------------- | | Label | Uppercase letters, digits, and underscores (`^[A-Z0-9_]+$`), 1–64 characters. Names the rule and appears in the recorded findings. | | Pattern | A Python regular expression, 1–500 characters. Inline flags such as `(?i)` for case-insensitive matching are allowed. | A project can define up to **10** custom rules. ### What happens on a match When a scan finds sensitive data, the project's **action** decides what the caller receives. | Action | Result the caller receives | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Block (default) | The entire tool result is replaced with a single error result reading `Tool result blocked`. None of the original content is returned. | | Redact | Each matched span is replaced with `[REDACTED]`, and the rest of the result is returned unchanged. If a match cannot be safely masked in place, that result is blocked instead. | Detection **fails closed**. If a scan cannot complete, for any reason, the result is blocked rather than returned unscanned. A result whose text is too large to scan is treated the same way. Failing closed means a scanning problem withholds data rather than leaking it. ### Enabling redaction Redaction is configured per project, under the server's experimental settings. Because it is experimental, it is gated: it may not be available for your organization yet. If the experimental settings do not offer it, contact [horizon-support@prefect.io](mailto:horizon-support@prefect.io) to request access. Go to the server's **Settings**, then **Experimental**, and find **Sensitive data redaction**. Turn on **Enabled**. Set the **Action** to **Block** or **Redact**, and set the **Sensitivity** to **Low**, **Medium**, or **High**. New configurations default to blocking at medium sensitivity. Under **Custom rules**, choose **Add custom rule**, enter a **label** (such as `ACCOUNT_ID`) and a regex **pattern** (such as `\bCUSTOM-[0-9]{6}\b`). Add up to ten rules. Choose **Save** to apply the configuration. It takes effect for subsequent tool results. ### What gets recorded When redaction acts on a result, Horizon records a summary of what matched: a count per rule and per detected entity type, such as `EMAIL`. It never records the matched values themselves. Because redaction runs before the response is finalized, it also shapes what lands in the [response payload](#payload-logging) when response logging is on. A blocked result stores the `Tool result blocked` placeholder, and a redacted result stores the masked text, so the sensitive values never reach the traffic record. Redaction acts only on responses. It does not change what a request payload records. ## Security reviews If your procurement or security review needs implementation detail beyond this page, such as the detection provider's data handling or retention specifics, contact [horizon-support@prefect.io](mailto:horizon-support@prefect.io) to request a formal security review. ## Related docs How Horizon encrypts stored values and where secrets can appear. Inspect the request logs, sessions, and payloads recorded for a server. How requests move through routing, access checks, and the plugins that run on them. Create, scope, and rotate the encrypted values your hosted servers use. # Encryption and data protection Source: https://docs.horizon.prefect.io/security/data-protection How Horizon protects environment variables and connector credentials at rest and in transit, and where secret values can appear. Horizon stores two categories of sensitive values on your behalf: | Category | What it holds | Who provides it | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | | Environment variables | Values a hosted server needs at build time or runtime: upstream API keys, database URLs, and similar configuration. | You. See [Environment variables](/environment-variables). | | Connector credentials | Values Horizon uses to authenticate to external systems: OAuth client secrets, remote-server tokens, and the rest of a connector's configuration. | You or Horizon, depending on the connector. | Both categories are encrypted at rest, and all traffic to and through Horizon is encrypted in transit. This page describes the guarantees that follow: where secret values can appear, how they reach a running server, and what Horizon keeps them out of. ## Encryption at rest Every environment variable and connector credential is individually encrypted before it is stored, and the platform's underlying data stores are themselves encrypted at rest. ## Secret handling ### Dashboard and API responses Sensitive environment variables are write-only in the dashboard after creation. Listing a server's environment variables returns metadata only (key names, scopes, timestamps), never stored values. An environment variable's plaintext is returned only through an explicit per-value reveal, and only to a caller whose organization access authorizes it. Connector credentials are never returned to callers. Connector listings redact client credentials, updating a connector writes a new configuration without reading the stored plaintext back, and decryption happens only server-side when Horizon authenticates to the external system on your behalf. ### Deployments A hosted server receives its environment variables when Horizon builds and deploys it. A running server never reads Horizon's secrets store, so changing or rotating a value takes effect on the next deployment, and an existing artifact keeps the values it was built with. The [rotation procedure](/environment-variables#rotation) walks through replacing a credential without downtime. ### Logs Platform logs record key names and identifiers, never secret values. Anything your own server code prints to stdout or stderr is captured as server logs. Keep secret values out of your server's logging statements. ## Encryption in transit All traffic to the Horizon dashboard and API, and to servers served through Horizon, uses HTTPS. Plain-HTTP requests are redirected to HTTPS rather than served. ## Security reviews If your procurement or security review needs implementation detail beyond this page, such as key management, encryption algorithms, or infrastructure controls, contact [horizon-support@prefect.io](mailto:horizon-support@prefect.io) to request a formal security review. ## Related docs Create, scope, and rotate the encrypted values your hosted servers use. How callers prove identity to servers and to Horizon itself. How roles and access grants decide what authenticated actors can do. How requests move through routing, authentication, and access checks. Find current reports and security review resources. # Security and compliance Source: https://docs.horizon.prefect.io/security/overview An overview of Horizon's identity, access control, data handling, encryption, deployment, and monitoring posture for security reviews. Horizon centralizes identity, access policy, traffic handling, and hosted server operations at the platform boundary. For protected MCP endpoints, the gateway applies identity and access controls before requests reach server code. Formal assurance reports, security-review materials, and the current subprocessor list are available through the [Prefect Trust Center](https://trust.prefect.io). For implementation detail beyond the public documentation, contact [horizon-support@prefect.io](mailto:horizon-support@prefect.io). ## Identity and authentication Human sign-in, including SAML or OIDC single sign-on, is backed by WorkOS. The Horizon gateway identifies callers to protected MCP servers through interactive MCP client sign-in tokens or personal and service-account API keys before server code runs. Dashboard requests act as the signed-in user. REST API requests act as the user or service account that owns the credential. See [Authentication](/platform/authentication) for both request paths and [Single sign-on](/administration/sso) for enterprise identity provider setup. [API keys](/api-keys) covers credential ownership and lifecycle, while [external authentication](/platform/external-authentication) covers downstream OAuth and API-key credentials. ## Access control After authenticating a caller, Horizon evaluates organization membership, organization role, server access, and capability policy. Organization roles govern members and organization settings. Server roles and explicit grants control access to individual servers, while capability policies can narrow access to specific MCP tools, resources, and prompts. These checks run at the gateway before protected MCP traffic reaches server code. See [Authorization](/platform/authorization) for the request decision flow and [Roles](/roles) for role definitions. ## Data handling For each served MCP request, Horizon records the method, server, session, client, outcome, and acting identity. It also records request and response payloads by default. You can disable either one per project for future traffic. Horizon retains traffic records indefinitely, and customers cannot set a retention period. The experimental sensitive-data redaction feature can block or mask detected values in tool results, but it is best-effort and is not a compliance control. See [Data handling and sensitive-data redaction](/security/data-handling) for the full storage and redaction behavior. ## Encryption Horizon individually encrypts stored environment variables and connector credentials, and its underlying data stores are encrypted at rest. Traffic to the Horizon dashboard, API, and served MCP endpoints uses HTTPS. Horizon-generated logs omit stored secret values, and connector credentials are never returned to callers. The [data protection](/security/data-protection) page documents these guarantees and where plaintext can appear. ## Deployment and residency Horizon's generally available control plane and hosted server compute operate in AWS `us-east-1`, with MCP requests entering through a global serving edge. Customer-selectable regional residency is not available today. AWS PrivateLink and customer-managed deployments through BYOC or self-hosting are also not available today. There is no committed availability date for these options. See [Hosted servers](/servers/hosted), [Compute model](/platform/compute-model), and [Deployments](/deployments) for the current hosted deployment model. ## Audit and monitoring Horizon provides customer-facing request logs and usage views for served MCP traffic, including session, client, tool, outcome, and verified user or service account attribution when Horizon authentication is enabled. These views are operational records of MCP traffic. Horizon does not currently provide a customer-facing audit UI for membership, role, access, deployment, configuration, or secret events. Horizon maintains separate infrastructure audit records for platform operations, but customers cannot access them in the dashboard. See [Observability](/observability) for the available views and [Data handling](/security/data-handling) for what each traffic record contains. # External Source: https://docs.horizon.prefect.io/servers/external Bring an MCP server you already run into Horizon for managed discovery, access, and request observability. External servers are available on . External servers connect MCP servers that run outside Horizon to the Horizon gateway. You keep the remote server in the runtime, network, and release process it already uses. Horizon gives that server a managed entry, a protected MCP endpoint, access policies, request logs, and a place in the same catalog as hosted Horizon servers. Use an external server when the server already exists, already has an owner, or must stay where it runs today, but clients and administrators still need the benefits of a shared Horizon access layer. The remote endpoint must accept HTTPS connections initiated by Horizon. If it uses an IP allowlist, see [Networking](/platform/networking#external-endpoints). Horizon maintains [global connectors](/servers/global-connectors) for supported provider-hosted MCP servers. Select one to use its preconfigured remote endpoint, or create an organization connector for another endpoint. Register an existing MCP endpoint instead of moving its code into a Horizon hosted server first. Let clients call a Horizon endpoint while Horizon handles access checks and forwards accepted traffic to the remote server. When the remote endpoint requires authentication, store its credentials in Horizon so clients do not need direct access to them. Use Horizon request logs, clients, and users to understand how the external server is used through Horizon. ## When to use an external server Choose an external server when the runtime should stay outside Horizon, but MCP access should be managed through Horizon. | Situation | Why external helps | | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | The MCP server is already deployed and operated by another team | Horizon can protect and expose the server without changing its ownership model. | | The server must stay in a specific network or runtime environment | Horizon forwards accepted requests while you keep the remote deployment where it is. | | You want to standardize client access before migrating runtime | Clients can move to a Horizon endpoint first; runtime migration can be a separate decision. | | A sensitive remote server should not be connected to every client directly | Clients authenticate to Horizon, and Horizon uses the configured remote credentials when it forwards requests. | | A remote server should become a backend for a remix server | Register it once, then include selected capabilities in a curated remix endpoint. | If you want Horizon to build and run the server code, use a [hosted server](/servers/hosted). If you want one endpoint made from selected capabilities across multiple servers, use a [remix server](/servers/remix). ## User-visible contract To clients, an external server behaves like a Horizon MCP server. The client connects to the Horizon endpoint, authenticates to Horizon when required, and discovers the capabilities that Horizon has learned from the remote server. Horizon does not change the remote server's tool behavior. It governs the path to the server, records gateway activity, and forwards accepted MCP requests to the remote endpoint you configured. | Concern | Contract | | --------------------- | ------------------------------------------------------------------------------------------------------------- | | Client endpoint | Clients use the Horizon-served MCP URL, not the remote server URL. | | Capabilities | Horizon discovers tools, resources, and prompts from the remote server. | | Access | Horizon checks server and capability access before forwarding client traffic. | | Remote authentication | Horizon uses the configured remote credential, or no credential when the endpoint accepts anonymous requests. | | Runtime behavior | The remote server still executes the request and owns its application behavior. | | Observability | Horizon records gateway-level requests; remote runtime logs remain outside Horizon. | If a request is rejected before it reaches the remote server, start with Horizon request logs. If the remote server receives the request and fails, debug the remote runtime where that server is operated. ## Ownership boundary External servers intentionally split platform responsibility from remote runtime responsibility. | Concern | Horizon manages | You manage | | ------------------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | Client access | Horizon endpoint, authentication, authorization, capability policies, and client-facing request logs. | Which users and service accounts should receive access. | | Remote runtime | Forwarding accepted requests to the configured endpoint. | Deployment, uptime, scaling, dependencies, and application logs for the remote server. | | Remote credentials | Secure storage and use of a configured remote credential. | Whether the endpoint requires authentication, plus credential issuance, rotation timing, and remote-server policy. | | Metadata snapshot | Discovery, display, and policy attachment for remote capabilities. | Keeping the remote capability lists valid, stable, and descriptive. | | Failures | Gateway status, forwarding outcomes, and request metadata. | Remote server errors, remote availability, and incorrect application responses. | This boundary is the main reason to choose an external server: Horizon becomes the access and observability layer for MCP traffic without taking ownership of the remote server's runtime. ## Request flow The MCP client uses the Horizon server URL. If the server is protected, the client authenticates to Horizon. The gateway identifies the caller and applies the external server's access settings, including capability policies when they are configured. Horizon sends the MCP request to the configured remote endpoint. It injects the configured remote credential, or sends no Authorization header when no authentication is selected. The remote server executes the tool, resource, prompt, or template request and returns the MCP response. Horizon returns the remote response to the client and records gateway observability for the request. ## Authentication model The MCP client signs in to the Horizon endpoint first. Horizon then presents a separate OAuth token or API key to the remote MCP server, unless the remote server accepts anonymous requests. | Layer | Credential | Failure boundary | | ----------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Client to Horizon | Horizon sign-in token or Horizon API key | The request stops at the gateway before the remote server is called. | | Horizon to an authenticated remote server | Per-user OAuth token, per-user API key, or organization-shared API key | Horizon accepted the caller, but the forwarded request can fail remote authentication. | | Horizon to a public remote server | No credential | Horizon removes the caller's `Authorization` header before it forwards the request. | The external server's **Connection** page shows the current remote credential status. For a public remote server, it shows that no authentication is required. Users can authorize OAuth, add or revoke a personal token, and repair an expired credential there. Server administrators can manage a shared key and its injection format. When the remote provider requires a pre-registered OAuth client, register Horizon's callback URL exactly as `https://horizon.prefect.io/oauth/external/callback`. Providers that support Dynamic Client Registration receive the same callback URL during registration. For OAuth discovery, Dynamic Client Registration, manual client fields, API-key header formatting, and the complete setup flow, see [External authentication](/platform/external-authentication). ## Register an external server In Horizon, create a server and choose the external server type. Provide the HTTP endpoint Horizon should call when clients use the server. The endpoint must be reachable by Horizon and speak a supported MCP HTTP transport. Choose no authentication, OAuth, or token authentication for the remote server. Select no authentication only when the endpoint accepts anonymous requests. Keep required remote credentials in Horizon instead of embedding them in MCP client setup. Horizon connects to the server, performs MCP discovery, and stores the metadata it will show in the dashboard and use for access policy setup. Configure server access and capability policies for the users and service accounts that should use the external server. ## How discovery works When you register or refresh an external server, Horizon captures a snapshot of the remote server's MCP metadata. Horizon initializes an MCP session with the remote endpoint, then asks for tools, prompts, and resources. Those list requests run independently, so one failed list method does not automatically discard metadata returned by the others. Horizon stores that snapshot with the external server metadata. The snapshot is used for the dashboard, client setup surfaces, remix composition, and capability policy setup. If initialization fails, metadata capture fails. If every list response is empty or unavailable, metadata capture fails because Horizon cannot tell whether the server has no useful capabilities or discovery did not work. If at least one list response succeeds, Horizon can store the metadata it received and leave the missing capability types empty. Runtime traffic still goes to the remote server. When a client calls `tools/list`, `prompts/list`, or `resources/list` through the Horizon endpoint, Horizon asks the remote server for the current list and applies access filtering before returning the response. Playground, ChatMCP, and external clients still depend on the runtime endpoint being able to reach the remote server. If capability policy does not filter it, a newly added remote tool can appear in a runtime `tools/list` response before the dashboard snapshot has been refreshed. If capability policy is configured, refresh the external server metadata and update policy before expecting the new tool to appear. ## Metadata and policy lifecycle Treat remote metadata changes as compatibility changes. If the remote server adds, removes, renames, or changes a capability schema, refresh discovery and review policies before relying on the new shape in production clients. | Remote change | Horizon impact | Recommended action | | ---------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | | Add a capability | Runtime lists may show it before the dashboard snapshot or policy setup includes it. | Refresh metadata, inspect the capability, and update policy if it should be governed. | | Remove a capability | Clients or remixes that depend on it may fail or stop showing it. | Update clients, remixes, and policies before removing it from production use. | | Rename a capability | Horizon treats the old and new names as different capabilities. | Update policies and client instructions that reference the old name. | | Change input schema | Existing client calls may become invalid. | Test the updated schema in Playground before updating clients. | | Change remote authentication | Horizon may fail to forward requests once the old credential stops working. | Update the stored remote credential before rotating the old one out. | ## List and call failure behavior An external server has one remote backend, so list behavior depends on that backend's response. | Event | What clients see through Horizon | | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | The remote server does not support a list method | Horizon returns an empty list for that capability type. | | The remote server is unavailable during a list request | Horizon can return an empty list for that capability type. | | The remote server returns an authentication, authorization, or protocol error | The list request can fail instead of being treated as an empty list. | | A caller is denied by Horizon capability policy | The capability is hidden from list responses and blocked when called directly. | | The remote server fails while handling a selected call | Horizon returns the remote failure to the client and records gateway request metadata. | ## Before clients use the server An external server is ready for client use when Horizon can discover it, use the expected remote authentication mode, and enforce the access boundary you expect. Check the server from Horizon before updating client configuration: * Discovery shows the tools, resources, and prompts clients should see. * Playground can call representative capabilities through the Horizon endpoint. * A caller without access receives a gateway denial instead of reaching the remote server. * Request logs show forwarded calls with the expected client and user metadata. * The team that owns the remote server can debug runtime errors outside Horizon. These checks matter because there are two systems in the request path. Horizon can prove the gateway accepted, denied, or forwarded a request. The remote owner still needs enough runtime visibility to explain what happened after forwarding. ## Troubleshooting Start with where the request stopped. Horizon request logs show whether the gateway denied, forwarded, or received an error from the remote server. If forwarding succeeded, debug the remote runtime. Confirm the remote URL is reachable from Horizon, uses a supported MCP HTTP transport, initializes successfully, and returns at least one non-empty capability list during discovery. If the remote endpoint requires authentication, confirm the remote credential configured in Horizon is current. The client did not authenticate to Horizon successfully. Check the client credential and the external server's Horizon access mode. The caller authenticated to Horizon, but server access or capability policy does not allow the requested operation. Review [Authorization](/platform/authorization). Allow popups for Horizon, then retry Authorize with OAuth. If the popup opens but returns an error, restart the OAuth flow so Horizon can create a fresh authorization request. The client-to-Horizon credential is valid, but the Horizon-to-remote credential is missing, expired, revoked, or invalid. Reauthorize with OAuth or update the stored token for the external server. Check the remote credential configured in Horizon, then inspect the remote server's runtime logs. Horizon can show that forwarding occurred, but the remote server owns the application response. Confirm Playground is connected to the intended target, then refresh its live capability list. Refresh external server metadata separately when the dashboard or capability policy setup is stale. Gateway forwarding succeeded. Inspect the remote server's application logs and the systems that remote server calls while handling the request. ## Related docs Learn how Horizon routes and protects MCP traffic. Configure whether the remote server needs a downstream credential and how Horizon supplies it. Aggregate selected capabilities from multiple MCP backends behind one server. Configure reachability and source-address allowlisting for remote endpoints. # Global connectors Source: https://docs.horizon.prefect.io/servers/global-connectors Find the provider documentation for the remote MCP servers preconfigured in Horizon. Global connectors are available on . Global connectors are remote MCP server definitions maintained by Horizon and available to every organization. Each one supplies a known remote endpoint so you can create an [external server](/servers/external) without entering the endpoint yourself. You still choose how Horizon authenticates to the provider, and each user may need to authorize their own provider account. An organization connector serves the same purpose for an endpoint that is not in the global catalog. Organization Admins create and manage those connectors for their organization; Horizon maintains global connectors. ## Provider documentation The provider documentation below explains each remote MCP server's account, authorization, scope, and availability requirements. Provider requirements can change independently of Horizon. | Horizon connector | Provider documentation | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Asana | [Using Asana's MCP Server](https://developers.asana.com/docs/using-asanas-mcp-server) | | Atlassian | [Getting started with the Atlassian Rovo MCP Server](https://support.atlassian.com/atlassian-rovo-mcp-server/docs/getting-started-with-the-atlassian-remote-mcp-server/) | | BigQuery | [Use the BigQuery MCP server](https://docs.cloud.google.com/bigquery/docs/use-bigquery-mcp) | | Box | [Box MCP server](https://developer.box.com/guides/box-mcp/) | | Canva | [Connect AI assistants to Canva with the AI Connector](https://www.canva.com/help/mcp-agent-setup/) | | ClickHouse | [Enable and connect ClickHouse Cloud remote MCP server](https://clickhouse.com/docs/use-cases/AI/MCP/remote_mcp) | | Cloudflare Docs | [Cloudflare's own MCP servers](https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/) | | Daloopa | [Daloopa MCP](https://docs.daloopa.com/docs/daloopa-mcp) | | Datadog | [Set up the Datadog MCP server](https://docs.datadoghq.com/mcp_server/setup/) | | Fireflies | [MCP server configuration](https://docs.fireflies.ai/getting-started/mcp-configuration) | | GitHub | [Setting up the GitHub MCP Server](https://docs.github.com/en/copilot/how-tos/provide-context/use-mcp-in-your-ide/set-up-the-github-mcp-server) | | Gmail | [Configure the Gmail MCP server](https://developers.google.com/workspace/gmail/api/guides/configure-mcp-server) | | Google Calendar | [Configure the Calendar MCP server](https://developers.google.com/workspace/calendar/api/guides/configure-mcp-server) | | Google Drive | [Configure the Drive MCP server](https://developers.google.com/workspace/drive/api/guides/configure-mcp-server) | | Hex | [Hex MCP Server](https://learn.hex.tech/docs/api-integrations/mcp-server) | | Hubspot | [Integrate AI tools with the HubSpot MCP server](https://developers.hubspot.com/docs/apps/developer-platform/build-apps/integrate-with-the-remote-hubspot-mcp-server) | | Huggingface | [Hugging Face MCP Server](https://huggingface.co/docs/hub/agents-mcp) | | Intercom | [Model Context Protocol](https://developers.intercom.com/docs/guides/mcp) | | Invideo | [Invideo Model Context Protocol Server](https://help.invideo.io/en/articles/11316042-invideo-model-context-protocol-server) | | Jam | [Jam MCP](https://jam.dev/docs/jam-mcp) | | Linear | [Linear MCP server](https://linear.app/docs/mcp) | | Monday | [Get started with monday MCP](https://support.monday.com/hc/en-us/articles/28588158981266-Get-started-with-monday-MCP) | | Neon | [Neon MCP Server overview](https://neon.com/docs/ai/neon-mcp-server) | | Netlify | [Agent setup overview](https://docs.netlify.com/build/build-with-ai/agent-setup-guides/agent-setup-overview/) | | Notion | [Notion MCP](https://developers.notion.com/guides/mcp/overview) | | PagerDuty | [PagerDuty MCP Server](https://support.pagerduty.com/main/docs/pagerduty-mcp-server) | | Paypal | [MCP server quickstart guide](https://docs.paypal.ai/developer/tools/ai/mcp-quickstart) | | Plaid | [Dashboard MCP server](https://plaid.com/docs/resources/mcp/) | | Sentry | [Sentry MCP](https://mcp.sentry.dev/) | | Slack | [Slack MCP server](https://docs.slack.dev/ai/slack-mcp-server) | | Square | [Square Model Context Protocol Server](https://developer.squareup.com/docs/mcp) | | Stripe | [Model Context Protocol](https://docs.stripe.com/mcp) | | Stytch | [Stytch MCP Server](https://stytch.com/docs/resources/workspace-management/stytch-mcp-server) | | Vercel | [Use Vercel's MCP server](https://vercel.com/docs/agent-resources/vercel-mcp) | | Webflow | [Webflow MCP server](https://developers.webflow.com/mcp/reference/overview) | | Zoom | [Zoom MCP Server](https://developers.zoom.us/docs/mcp/zoom-mcp-server/) | Google's Gmail, Google Drive, and Google Calendar remote MCP servers are in Developer Preview. Check the linked provider page for current access requirements before creating the external server. ## Related docs Understand how Horizon governs and forwards traffic to remote MCP servers. Configure the OAuth or API-key credential Horizon sends to the provider. # Hosted servers Source: https://docs.horizon.prefect.io/servers/hosted Build, deploy, protect, and observe Python MCP and FastMCP servers on Horizon. Hosted servers are the Horizon-managed path for Python MCP and FastMCP servers you keep in Git. Point Horizon at a repository and entrypoint, and each build turns one source revision into an artifact that can be served from a stable MCP URL, promoted, previewed, or rolled back. The important boundary is simple: Horizon owns the path from source to a protected deployment URL. Your server code still owns the MCP behavior behind that URL, including tools, resources, prompts, upstream API calls, durable state, and application errors. Resolve a branch, pull request, or manual selection to an exact Git commit before packaging the server. Give clients a Horizon deployment URL, usually ending in /mcp, instead of exposing a process or runtime address. Check Horizon authentication, server access, and capability policy before a protected request reaches your handlers. Use build logs, request logs, server logs, clients, and user activity to see where a request stopped. ## When hosted is the right shape Choose a hosted server when the server code should be built, deployed, and run by Horizon. Hosted works well when: * You are creating a new Python MCP or FastMCP server and want the deployment lifecycle attached from the start. * The source lives in Git and each deployment should point to a specific commit. * Pull request previews should test code, manifests, and environment changes before production promotion. * Clients should use a shared Horizon endpoint with server and capability access checks in front of the code. * Operators need build logs, request logs, server logs, clients, and users connected to the same server record. If the server already runs somewhere else and should stay there, register it as an [external server](/servers/external). If clients should connect to one curated endpoint made from selected capabilities across multiple servers, use a [remix server](/servers/remix). ## Client contract To clients, a hosted server is a Horizon MCP endpoint. Clients call the live or preview deployment URL. Horizon identifies the selected deployment, checks access when the endpoint is protected, and routes accepted MCP requests to the artifact selected for that deployment. | Concern | What clients and operators can expect | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Source | A build uses a specific Git commit. Moving a branch after the build starts does not change that artifact. | | Endpoint | Clients call the Horizon deployment URL, not an internal runtime address. | | Build output | A successful build creates an artifact, a discovered MCP manifest, and build logs. | | Deployment | A live or preview deployment selects one successful artifact to serve behind a stable URL. | | Authentication | Hosted servers have Horizon authentication enabled by default. They can optionally exchange an authenticated user's credential for a linked upstream credential. Disabling Horizon authentication is available on . | | Authorization | Server access and capability policies are evaluated before protected hosted server code runs. | | Runtime | Python handlers run with a -second request window and MB of memory. | | State | Local memory and files are ephemeral. Store durable application state outside the local filesystem. | | Observability | Horizon records hosted traffic across requests, clients, users, builds, and server logs. | Most hosted-server debugging starts by locating the boundary. Build failures happen before an artifact exists. Gateway denials happen before Python code runs. Runtime failures happen after Horizon has forwarded the request to your deployed server. ## Ownership boundary Hosted does not make Horizon the owner of every part of the application. It separates platform mechanics from server behavior. | Area | Horizon owns | You own | | ----------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | Source and builds | Resolving commits, installing dependencies, packaging artifacts, and recording build logs. | Repository content, entrypoint behavior, dependency declarations, and code review. | | Deployments | Selecting which successful artifact serves live or preview traffic. | Deciding what to promote, test, or roll back. | | Gateway | Deployment URLs, Horizon authentication, server access, capability policy, and session routing. | Choosing who should have access and which capabilities are sensitive. | | Runtime | Starting the hosted Python process and enforcing the request window and memory allocation. | Handler behavior, startup work, upstream calls, application errors, and durable storage choices. | | Observability | Horizon-side request, client, user, build, and server log records. | Interpreting application output and adding useful server logs. | ## Lifecycle Link the Git repository that contains the Python MCP or FastMCP server. Choose the entrypoint and dependency file Horizon should use for builds. Horizon resolves the requested branch, pull request, or manual selector to an exact commit, installs dependencies, packages the server, and inspects the MCP surface area. A successful build produces an immutable artifact and manifest. A failed build leaves the currently selected live artifact unchanged. A live or preview deployment selects one successful artifact. Promotion and rollback change the artifact behind the same deployment URL. MCP clients call the deployment URL. Horizon identifies the deployment, checks access, preserves MCP session routing for up to hours, and forwards accepted requests to the hosted runtime. Tool, resource, and prompt handlers execute in your Python process. Horizon captures request outcomes and stdout or stderr emitted by the running server. ## Authentication model Hosted servers expose one authentication control, Horizon Authentication, which is either enabled or disabled. | Horizon Authentication | User experience | Request behavior | | ---------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | | Enabled | The MCP client completes Horizon sign-in or sends a Horizon API key. | Horizon validates the caller, checks server and capability access, then forwards accepted requests. | | Disabled | The MCP client sends whatever the server code expects, or no credential if the server is public. | Horizon forwards without validating a Horizon bearer credential; the server owns authentication and authorization. | Horizon authentication is enabled by default for shared and production hosted servers. When a client supports interactive sign-in, the user completes a Horizon authorization flow and the client receives a Horizon credential for that hosted endpoint. Non-interactive clients can send a Horizon API key as a bearer token. Disabling Horizon authentication is for endpoints that are intentionally public or for servers whose code implements the full authentication and authorization model. It is available on , and the change applies after a new deployment uses the updated setting. A protected hosted server can optionally use [delegated authorization](/platform/external-authentication). You link one OAuth or API-key auth source from **Access** and **Authentication**. For an eligible user request, Horizon exchanges the identified actor for the saved downstream credential and injects it into the configured request header before hosted code runs. This lets code that already expects the upstream credential in that header call the provider without implementing an OAuth callback or token store. Delegated authorization is optional. Without a linked auth source, your Python code chooses how to authenticate upstream calls, such as with deployment environment variables or an authorization flow implemented by the server. ## What changes when Builds, deployments, gateway metadata, and client manifests update on different timelines. When a change appears stale, check which boundary owns it. | Change | When clients see it | | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | Source code, dependency, entrypoint, or Python version change | After a new build succeeds and that artifact is selected by a deployment. | | Environment variable change | After a new build and deployment selection for hosted runtime behavior. | | Authentication mode change | After a new deployment uses the updated server setting. | | Server explicit grant or default role change | After Horizon updates live access metadata; no code change is required. | | Capability policy change | After the policy is saved and the hosted server is redeployed with updated policy metadata. | | Live deployment promotion or rollback | As soon as the deployment selects the new artifact, subject to client-side caching. | | Client manifest refresh | After the client reconnects or lists capabilities again. Some clients cache tools and prompts. | ## Manifest and discovery Horizon inspects the hosted server during build and stores the discovered MCP manifest for the artifact. The dashboard, remix composition, and capability policy setup use the manifest from the relevant build or deployment. Playground and Chat connect to the selected target and use its live capabilities. A deployment serves one selected artifact at a time. A new tool, resource, or prompt is not visible to clients until the code that registers it is built and the resulting artifact is selected by the deployment the client is using. Register capabilities during import or startup instead of after a request-time side effect. If a tool is registered only after a handler runs, Horizon may not see it during build inspection. If registration depends on environment variables, preview and production builds can intentionally produce different manifests. ## Troubleshooting Check your build logs for dependency installation, entrypoint loading, import-time, or inspection errors. A failed build does not replace the live artifact. Check [Deployments](/deployments). The successful artifact may belong to a preview deployment, may not have been promoted, or may have been superseded by another selected artifact. The request stopped before Python code ran. Check [Authentication](/platform/authentication), [Authorization](/platform/authorization), server access, capability policy, and request logs. Gateway routing succeeded. Check your server logs, handler code, upstream calls, and any application errors written to stdout or stderr. The handler or startup path exceeded the -second request window. Move long work out of the request path, reduce startup work, or return earlier and continue work asynchronously. The server used ephemeral local state as durable state. Store durable application state outside the local filesystem. Confirm the selected target serves the build that contains the tool. Then refresh Playground or reconnect clients that cache tools and prompts. ## Before clients use the server Before sharing a hosted endpoint, check the parts of the contract clients will depend on: * The live deployment selects the intended successful build artifact. * Playground can list capabilities and call representative tools through the Horizon endpoint. * Authentication matches the audience: Horizon authentication enabled for shared servers, disabled only when server code owns authentication. * Server access and capability policies cover sensitive actions. * Environment variables are scoped correctly for production and previews. * Request logs and server logs make it clear whether failures stop at the gateway or inside Python code. ## Related docs Understand build inputs, states, artifacts, and manifest inspection. Serve successful artifacts as live or preview deployments. Review request timeouts, memory, startup, sessions, and ephemeral state. Configure production and preview environment variables. Learn how clients and API keys authenticate to hosted endpoints. Configure an MCP client for a Horizon deployment URL. # Remix Source: https://docs.horizon.prefect.io/servers/remix Publish one curated MCP endpoint from selected capabilities across multiple servers. Remix servers are available on . Remix servers let you publish a purpose-built MCP server without writing a new backend. Select capabilities from hosted and external servers, give clients one Horizon-served endpoint, and let Horizon route each MCP call to the backend that owns that capability. Use a remix server when the right client experience is not "connect to every server we operate." It is "connect to the approved set of tools for this team, workflow, customer, partner, or agent." Give a client one MCP URL that contains the capabilities needed for a specific job. Expose selected tools, resources, prompts, and templates instead of every capability from every backend. Apply server access and capability policies to the composed endpoint users call. Keep client configuration stable while backend owners add, move, or retire capabilities behind the remix. ## When to use a remix server Remix servers are useful when clients should see a smaller, more intentional tool surface than the full set of servers your organization operates. | Situation | Why remix helps | | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | A team needs tools from several MCP servers | Publish one endpoint with only the capabilities that team needs. | | A partner or customer should get a narrow interface | Expose a small set of approved capabilities without revealing the full backend servers. | | An agent performs a specific workflow | Give the agent a focused manifest so tool selection is clearer and safer. | | Read-only access should be broad, but write access should be restricted | Put read and write capabilities behind different policies on the remix. | | A backend is changing, but clients need a stable endpoint | Keep the remix URL stable while updating which backend owns selected capabilities. | If you need Horizon to build and run server code, use a [hosted server](/servers/hosted). If a server already runs elsewhere and should be managed through Horizon, register it as an [external server](/servers/external). ## User-visible contract A remix server is a composed Horizon MCP endpoint. Clients discover one manifest and call one server URL. Behind that URL, Horizon routes each selected capability to the backend server that owns it. A remix is not a copy of the backend servers, and it does not make backend servers share runtime state. It is a governed composition layer for MCP capabilities. | Concern | Contract | | ----------------- | ------------------------------------------------------------------------------------------------------- | | Client URL | One Horizon MCP endpoint represents the composed surface. | | Manifest | Clients see the selected, namespaced capabilities exposed by the remix. | | Access | The remix has its own server access and capability policies. | | Routing | Horizon forwards each call to the backend that owns the selected capability. | | Backend ownership | Each backend still owns its implementation, runtime, logs, and application behavior. | | Failures | A failing backend affects the capabilities routed to that backend, not the meaning of the entire remix. | Granting a user access to a backend server does not automatically grant access to a remix that includes it. The remix is its own Horizon server with its own access settings. ## Ownership boundary A remix separates the endpoint clients use from the backend servers that implement each capability. Keep those responsibilities distinct when you design the surface, grant access, or debug a failed call. Manages the remix URL, stored manifest, namespacing, routing, request logs, session records, access checks, and remote credential flow for selected external backends. Chooses the workflow, selects capabilities, grants access, applies capability policy, and confirms the composed surface still matches the client task. Owns the source server, handler behavior, availability, runtime logs, schemas, descriptions, compatibility changes, and any policy applied after a call reaches the backend. ## Design the remix surface The most important remix decision is not which servers to include. It is which user task the endpoint should support. Start from the client or agent's job, then select only the capabilities needed to do that job well. Include the specific tools, resources, prompts, and templates the workflow needs. Leave unrelated backend capabilities out. Capability names and descriptions are part of the user interface. Make the composed manifest clear without requiring users to understand backend ownership. Use capability policies for sensitive operations, especially writes or actions with external side effects. Make sure each backend has an owner who can respond when its selected capabilities fail or change. ## How remix works A remix has two related views of capabilities: * A stored manifest that Horizon builds when the remix is created or updated. * Runtime list responses that Horizon produces when a client asks the remix for capabilities. The stored manifest is built from the selected backend capabilities. Horizon uses it for the dashboard, client setup, and capability policy generation. It is not the only source used at runtime. When a client calls `tools/list`, `prompts/list`, `resources/list`, or `resources/templates/list`, the remix gateway asks the configured backends for their current lists, namespaces the returned names or URIs, then filters the results by the remix selection and the caller's capability access. That means a remix is dynamic enough to reflect backend list responses at request time, but it is still governed by the capabilities selected for the remix. A backend can change the schema or description of a selected capability and the runtime list can reflect that change. A brand-new backend capability is not exposed just because the backend started listing it; add it to the remix before clients can use it through the remix endpoint. Playground, ChatMCP, and external clients call the remix endpoint. They depend on the runtime gateway being able to reach the selected backends, not only on the stored manifest existing in Horizon. Choose hosted or external MCP servers that should contribute capabilities to the remix. Include the tools, resources, prompts, and templates that should be exposed through the remix endpoint. Horizon keeps backend capabilities distinguishable so clients can call the intended tool, resource, prompt, or template. Apply server and capability policies to the remix server. Clients use the remix endpoint the same way they use any other Horizon MCP endpoint. ## Authentication model A remix has one client-facing Horizon endpoint, but its backends may have different authentication requirements. The MCP client authenticates to Horizon for the remix. Horizon then handles any remote-server credentials needed for the selected backend capability. | Backend in the remix | What the user experiences | What happens at runtime | | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | Hosted backend | No separate backend authorization prompt. The user signs in to Horizon for the remix endpoint. | Horizon routes accepted calls to the hosted backend. | | External backend with OAuth | If the user has not already authorized that remote server, Horizon asks for remote access before completing client authorization. | Horizon forwards selected calls with the user's stored remote OAuth credential. | | External backend with token auth | The user must have a stored remote token in Horizon. Token entry happens in Horizon, not through an OAuth redirect. | Horizon forwards selected calls with the user's stored remote token. | The MCP client receives a Horizon credential for the remix endpoint. It does not receive the remote server's access token, refresh token, or API token. ### OAuth user experience When a user connects an MCP client to a remix that includes OAuth-backed external servers, Horizon can pause the client authorization flow until the user has granted the required remote access. In that case, the user sees a Horizon authorization page that lists the external servers needing access. Each server moves from Needs Access to Authorized after its remote OAuth popup succeeds. Once all required external servers are authorized, the user can complete authorization and return to the MCP client. The MCP client opens the Horizon authorization page for the remix endpoint. The user signs in to Horizon and Horizon checks access to the remix server. Horizon checks whether the remix includes external servers that need a per-user remote credential. Hosted backends do not add a separate prompt. If the user already has active remote credentials for all required external servers, Horizon completes client authorization and redirects back to the MCP client. If an OAuth-backed external server needs access, Horizon shows it on the authorization page with a Needs Access action. Selecting it opens a popup for that remote server's OAuth flow. The user approves the requested scopes in the popup. When the popup returns to Horizon, that backend is marked authorized. If the remix needs multiple external OAuth grants, the user completes them one at a time. After the required external credentials are active, Horizon finishes the original client authorization. The MCP client is connected to the remix endpoint, and Horizon uses the stored remote credentials only when calls route to those external backends. ### Auth failure behavior | Event | What the user sees | Remix impact | | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | The user has remix access but has not authorized an OAuth-backed external backend | Horizon shows that backend as needing access before client authorization can complete. | The user must authorize the remote backend before using the remix from that client flow. | | The user closes or denies one backend's OAuth popup | That backend remains unauthorized and the completion button stays blocked. | Other successful backend grants remain saved, but the remix authorization is not complete until required backends are authorized. | | A token-backed external backend has no stored token | The user needs to add the token in Horizon. | Calls routed to that backend cannot authenticate until the token is saved. | | A stored remote credential expires or is revoked after the client connected | Calls routed to that external backend fail authorization until the user reauthorizes or updates the token. | Other backends in the remix are not automatically affected. | | Horizon policy denies the caller or selected capability | The user cannot discover or call the denied capability through the remix. | Remote backend authorization does not override Horizon access policy. | ## Common patterns Combine the approved engineering, support, operations, or documentation capabilities a team needs into one client configuration. Expose broad read-only capabilities while restricting write tools to narrower roles. Publish a limited set of capabilities for external collaborators without exposing every backend server. Keep one client endpoint while moving selected capabilities between backend servers. For example, a support workflow remix might expose customer lookup, recent activity search, runbook retrieval, and incident summary creation while leaving administrative or destructive backend tools out of the manifest. The client gets one focused server, and backend owners keep their existing servers. ## Changes and failures A remix has three moving parts that can change independently: the selected capability set, the backend behavior behind those capabilities, and the caller's access. When something looks stale or broken, check which part changed before changing the client configuration. New backend capabilities stay hidden until they are selected for the remix. Removed or renamed selected capabilities can disappear from the client or fail when called. Input schema, output shape, and description changes can affect clients even when the capability name stays the same. Remix access controls discovery and calls through the composed endpoint. Backend authorization does not override remix policy. Outages, remote credential problems, and protocol errors affect the capabilities routed to that backend. Runtime list requests fan out to the selected backends. Tool, resource, and prompt calls are routed by name or URI. In a multi-backend remix, namespacing keeps backend capabilities distinguishable so the requested capability tells the gateway which backend should receive the call. Horizon can return capabilities from reachable backends and omit capabilities from the unavailable backend for that list response. If the backend returns an authentication, authorization, or protocol error while listing, the list request can fail instead of returning a partial list. Horizon treats that capability type as empty for that backend. Other supported capability types can still appear when their list requests succeed. A new backend capability stays hidden until the remix is updated to include it. A removed selected capability disappears from runtime list responses and calls to it fail. Treat a rename as removing the old capability and adding a new one because existing clients and policies may reference the old name. Existing client calls may become invalid. Test the updated capability in Playground before updating clients, and update descriptions when the client needs different instructions. The capability is filtered out of list responses and blocked when called directly. Updating backend authorization will not grant access through the remix if remix policy denies the caller. Calls routed to that backend can fail until the backend connection settings or stored remote credentials are updated. Other selected backends are not automatically affected. That call fails for the client; calls routed to other backend capabilities are not automatically affected. Use remix request logs to identify the selected capability and backend, then inspect the backend's runtime logs. A remix can narrow what clients see, but it does not make sensitive backend behavior safe by itself. Treat sensitive backend tools as sensitive even when they are exposed through a smaller remix surface. ## Before clients use the remix A remix is ready for client use when its manifest is small enough to be understandable, its sensitive capabilities are governed, and each selected backend can be debugged by the team that owns it. Check the composed endpoint before updating client configuration: * The remix manifest includes only the capabilities needed for the workflow. * Capability names and descriptions make sense without knowing which backend owns them. * Playground or ChatMCP can call representative capabilities through the remix endpoint. * Capability policies cover sensitive write actions and external side effects. * Request logs identify which selected capability and backend handled each call. * Backend owners know which of their capabilities are exposed through the remix. These checks keep the remix from becoming a hidden bundle of every useful tool. The value of a remix is the smaller, clearer surface it gives to clients. If the manifest is hard to explain, split it by workflow or remove capabilities that do not belong. ## Troubleshooting Start with the user's symptom. If a capability is missing, check the manifest, selection, and capability policy. If a call reached the remix endpoint, use request logs to identify the selected capability and backend before debugging the backend runtime. Confirm the backend manifest includes the capability and that the remix selected it for exposure. New backend capabilities are not automatically exposed through an existing remix. Review access on the remix server. The remix has its own server and capability policies. Check capability policy on the remix. The user may have server access without permission for that selected capability. The remix includes one or more external servers that require per-user remote credentials. The user needs to authorize each OAuth-backed external server before Horizon completes the client authorization flow. Check whether another external backend in the remix still needs access or a token-backed backend still needs a stored token. Horizon completes client authorization only after the required external credentials are active. Check remix request logs first to identify the selected capability and backend. Then inspect the backend server's logs or remote runtime depending on where that backend runs. Review backend namespacing and descriptions. Client users usually see the remix manifest, so names and descriptions should make the capability's purpose clear without exposing backend details. Refresh or edit the remix so its selected capabilities match the new backend manifest. ## Related docs Register remote MCP servers as backends for Horizon and remix servers. Apply server and capability policies. Add the remix endpoint to an MCP client. # Teams Source: https://docs.horizon.prefect.io/teams Group organization members and manage shared server access. Teams group organization members so that you can review and grant server access for the group. A Horizon-managed Team is the default team type. A directory Team mirrors a group from an identity provider through [Directory Sync](/administration/directory-sync). Team management is available on . Team server grants are available on . ## Team types Horizon shows where each Team is managed. | Type | Managed in | Available actions in Horizon | | -------------------- | -------------------- | ----------------------------------------------------------------------- | | Horizon-managed Team | Horizon | Create the Team, manage its members, review access, or delete the Team. | | Directory Team | An identity provider | Review the Team, its members, and its server grants. | ## Find a Team Open **Govern** and select **Teams**. Use the search field to find a Team by name. Select a Team to review its members and server grants. ## Create a Team If you can manage Teams, you can create a Horizon-managed Team. 1. Open **Govern** and select **Teams**. 2. Select **Create team**. 3. Enter a Team name. 4. Select **Create team**. ## Manage members A Horizon-managed Team can include active organization members and active service accounts. Team membership lets you grant server access to the group instead of maintaining each server grant separately. To add a member: 1. Open the Horizon-managed Team. 2. Select **Add member** on the **Members** tab. 3. Search for an organization member or service account. 4. Select the member or service account, then select **Add member**. To remove a member: 1. Open the member action menu on the **Members** tab. 2. Select **Remove member**. 3. Review the access warning, then confirm the removal. Removing a member can remove server access that the actor receives through the Team. Access from another Team, an explicit grant, or the server default role remains unchanged. ## Delete a Team You can delete a Horizon-managed Team from its detail page. 1. Open the Team. 2. Select the action menu. 3. Select **Delete team**. 4. Review the membership and server grant counts. 5. Confirm the deletion. Deletion permanently removes the Team, its memberships, and its server grants. Updates can take a moment to apply. ## Manage a directory Team Use the connected identity provider to change a directory Team's name or membership. Horizon preserves a directory Team and its server grants when its source group becomes unavailable. The Team state identifies why its membership is inactive. | State | Meaning | | ---------------------- | ------------------------------------------------------------------------------- | | Group missing in IdP | The directory is connected, but its latest snapshot does not contain the group. | | Directory disconnected | The Team's source directory is frozen or no longer connected. | In both states, Horizon keeps the server grants so a restored source can reuse them. The saved grants provide no access while the Team has no active members. Horizon does not make these Teams available for new server grants. If a person loses organization membership, Horizon keeps the last directory assertion but removes its link to the Horizon user. The assertion cannot grant access until the person rejoins the organization. Horizon keeps it because organization removal does not change the identity provider group. If the Horizon user is deleted, Horizon removes all Team membership rows for that user. This prevents a replacement user from inheriting the deleted user's Team access before a new directory sync. If the identity provider still assigns the email to the group, the next sync recreates a pending assertion. Use **Sync now** on the Teams page to request a directory update when this action is available. See [Directory Sync](/administration/directory-sync) for setup and sync guidance. ## Server access A Team grant assigns a server role to each current Team member. Team grants combine with explicit member grants and grants from other Teams. Add, change, or remove a Team grant from the server's **Access** > **Members** page. Use the Team's **Servers** tab to review all server grants for that Team. See [Roles](/roles#team-grants) for the complete server access model.