# 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.