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

# Self-Hosted MCP Servers

> How MCP Manager's guided self-hosting wizard deploys an open-source or custom-built MCP server to your own Railway account: the prompt your coding agent runs, the mcpmanager/self-host base image that wraps your server with authentication and enrollment, how the server enrolls and connects, what it costs, and how to finish, resume, or tear down a deployment.

**Guided self-hosting** takes an MCP server that exists only as source code — an open-source project on GitHub, or one you built yourself — and gets it running at a public URL that **MCP Manager** governs, without you standing up a server by hand. You paste the repository URL into the **Help me host my MCP server** wizard, pick the coding agent you already use, and copy one generated prompt. Your coding agent then deploys the server to **your own Railway account**, wrapped in MCP Manager's `mcpmanager/self-host` base image, and the deployed container enrolls itself with MCP Manager. When enrollment succeeds the server appears as connected, with its tools learned, and it is assigned to gateways like any other server.

MCP Manager never runs your server on its own hardware and never holds a credential for your hosting account. The deployment lands in infrastructure you own, and you pay the provider for it. The result is a [managed server instance](/mcp-gateway-concepts/mcp-servers/managed) with one instance inside it; MCP Manager reaches it over HTTPS with a per-server access token, exactly as it reaches any managed server.

<Warning>
  **Railway is a separate company and service.** Railway is not affiliated with MCP Manager or Usercentrics. You create your own Railway account, agree to Railway's terms, and are billed by Railway for whatever your deployment uses. MCP Manager holds no Railway credential at any point: the wizard only generates a prompt, and your coding agent deploys with the Railway login on your own machine.
</Warning>

<Info>
  **Railway offers a free way to try this.** At the time of writing, Railway's own pricing lists a **Free** plan with a small monthly usage credit and a **Free Trial** with a one-time credit that needs no credit card, alongside paid plans. That means you can deploy a small MCP server through the wizard and try it end to end without committing to a paid plan until your usage passes the free allowance. Plan terms are Railway's to change; check [Railway's pricing page](https://railway.com/pricing) before you rely on them. If your coding agent hits a billing gate on Railway, the generated prompt tells it to stop and ask you rather than guess.
</Info>

<Note>
  Guided self-hosting is gated by the **Create and configure managed and workstation servers** capability. If you do not see the **Set up now** callout when adding a server, or the **Setup instance** and **Finish setup** actions on a self-hosted server, your role does not have that capability — ask a workspace administrator to grant it. See the [capabilities reference](/deployment/rbac-and-roles/capabilities).
</Note>

## Why the wizard deploys to Railway

The wizard targets Railway because it meets three requirements at once, and every other provider evaluated fell short on at least one of them:

* **A free way to start.** Railway's Free plan and no-card Free Trial mean the first deployment costs nothing until usage passes the free allowance, so you can prove the server works before committing to a plan.
* **Cost that tracks actual use.** Railway bills per second for the CPU, memory, volume, and egress a service consumes, so a small MCP server that sits idle between calls stays cheap; you pay for the compute you use rather than for a machine.
* **A CLI an agent can drive end to end.** Creating the project and service, setting environment variables, generating the public domain, deploying, reading logs, and later deleting the service are all `railway` commands. That is what lets a coding agent do the whole setup, and the whole teardown, from one prompt without you touching a dashboard.

Railway remains an independent company you contract with directly; nothing in the flow depends on a relationship between Railway and MCP Manager.

## Where guided self-hosting starts

The **Help me host my MCP server** wizard opens from two places in [MCP Servers](https://app.mcpmanager.ai/settings/servers):

* **Add server → the "Need your own MCP server?" callout.** Beside the Remote, Managed, and Workstation choices, a callout reads "We'll guide you through a quick setup." Its **Set up now** button opens the wizard.
* **Add a Remote server → the URL step.** The link **Help me connect and host my MCP server** opens the wizard for someone who arrived expecting to paste a URL and has none yet.

A self-hosted server that never finished its setup can also reopen the wizard from its own pages. See [Finishing or resuming an interrupted setup](#finishing-or-resuming-an-interrupted-setup).

## How the wizard flow works

The wizard has five screens. Deployment itself happens outside MCP Manager, in your coding agent and on Railway; the wizard's job is to hand your agent everything it needs and then wait for the server to connect.

<Steps>
  <Step title="Choose how your server is built">
    Pick **Open-source** ("My server's code lives in a public Git repository") or **Custom-built** ("I built this server myself and it isn't in a public repository"). The two paths differ only in wording and in one hint in the generated prompt: for a private repository the prompt tells your agent to clone with the Git credentials already on your machine, so MCP Manager never sees or stores them.
  </Step>

  <Step title="Connect your repository">
    Paste the **Git repository URL**. It must be an `https://` URL with no username or password embedded in it. For a public repository, MCP Manager runs an advisory reachability check and shows an inline note if the repository looks private, missing, or unreachable. The note is advice only: **Continue** stays enabled whatever it says, because your agent clones from your machine with your credentials.
  </Step>

  <Step title="Deploy your server">
    Choose the coding agent you will paste the prompt into — **Claude Code**, **Codex CLI**, or **Gemini CLI** — and press **Copy prompt**. Entering this step provisions the server: MCP Manager mints a per-server access token and a registration token, and builds the prompt around them. Paste the prompt into your agent in a terminal on your own machine and let it work. When the prompt is copied, press **Continue**.
  </Step>

  <Step title="Connecting your server">
    The wizard waits while your agent builds and deploys. A cold build can take several minutes. You can close the wizard and check back from the server list; MCP Manager keeps watching for the connection either way.
  </Step>

  <Step title="Server connected">
    Once the deployed container has enrolled and MCP Manager has verified it, the wizard shows **Server connected** and **View server** opens the server's page. If verification fails, the wizard shows **We couldn't connect your server** with the reason, and **Try again** returns you to the repository step.
  </Step>
</Steps>

## What the generated prompt makes your coding agent do

The prompt is a complete, self-contained instruction set for an autonomous coding agent. It authenticates to Railway as you, on your machine, so MCP Manager is never in the loop. In order, the agent:

1. **Installs the Railway CLI** if it is missing and **logs in** with `railway login`, pausing to tell you when the login needs your approval in a browser.
2. **Clones your repository.** If the repository holds more than one MCP server, the agent must stop and ask you which to deploy rather than guess.
3. **Writes a Dockerfile** whose final stage builds on `mcpmanager/self-host:0.1`, copies your built server in, and sets `MCPM_START_COMMAND` to the command that starts your server over stdio. The base image's entrypoint runs MCP Manager's authentication and enrollment layer, so the agent never overrides it.
4. **Creates a Railway project and service**, and **generates the public domain before the first deploy**, because the container reads its own public URL from the domain Railway injects.
5. **Sets two environment variables**: `MCPM_REGISTRATION_TOKEN`, which the container redeems once to enroll, and `MCPM_ACCESS_TOKEN`, which MCP Manager sends on every request.
6. **Deploys with `railway up`.** Railway builds the image, so Docker is not needed on your machine.
7. **Confirms enrollment** by reading the runtime logs for `Enrollment succeeded`, then reports the domain to you.

The prompt closes with "Only touch this one new project/service — don't change anything else in my Railway account. Railway may require a payment method on file even for low usage; if you hit a billing gate, stop and tell me rather than guessing."

<Tip>
  Both tokens are live credentials. The registration token expires **90 minutes** after you enter the Deploy step. If it expires before the container first boots, return to the wizard for a fresh prompt. The prompt tells your agent never to paste either token into a website or online decoder.
</Tip>

## How the deployed server enrolls and connects

Enrollment is what turns a running container into a connected MCP Manager server. The container does it on its own at boot, with no action from you.

```mermaid theme={null}
%%{init: {'theme':'base','themeVariables':{'fontFamily':'Lato, sans-serif','actorBkg':'#aed8ff','actorBorder':'#0b4880','actorTextColor':'#062b4c','signalColor':'#6a6b76','signalTextColor':'#12141d','noteBkgColor':'#fff8e4','noteBorderColor':'#ffa535','noteTextColor':'#12141d'}}}%%
sequenceDiagram
  actor U as You
  participant W as Help me host my MCP server wizard
  participant A as 🤖 Your coding agent
  participant R as 🔌 Railway (your account)
  participant C as 🖥️ Your server in mcpmanager/self-host
  participant G as 🛡️ MCP Manager gateway
  U->>W: Paste repository URL, pick agent
  W->>G: Provision server
  G-->>W: Registration token (90 min) + access token
  W-->>U: Generated deploy prompt
  U->>A: Paste prompt
  A->>R: Build image, create service, set tokens, deploy
  R->>C: Start container with public domain
  C->>C: Start your server, MCP initialize
  C->>G: Enroll: registration token + public URL + server info
  G->>C: MCP initialize with access token
  G-->>W: Server connected, tools learned
```

In prose: the wizard asks MCP Manager to provision the server, receiving a short-lived registration token and the long-lived access token, and builds the prompt around them. Your agent deploys to Railway. On boot, the container starts your MCP server, performs an MCP `initialize` against it to confirm it answers and to capture its name and version, then posts the registration token, its public URL, and that server info to MCP Manager's enroll endpoint. MCP Manager verifies the token, records the URL, adopts the name your server reported, and runs its own MCP `initialize` against the new URL with the access token. Only when that succeeds does the server become connected and its tools get learned.

The `mcpmanager/self-host` image is public on Docker Hub and provides three things around your server: an **authentication layer** on the container's public port that requires the `mcpm-access-token` header on every request, a **stdio-to-HTTP bridge** that starts your server from `MCPM_START_COMMAND` and speaks Streamable HTTP on `/mcp`, and the **enrollment** step above. The access token authenticates callers to your container; it grants nothing on MCP Manager, which stores its copy encrypted at rest and sends it on every proxied request, the same model as the shared secret on any [managed server](/mcp-gateway-concepts/mcp-servers/managed#how-requests-reach-a-managed-server).

## Finishing or resuming an interrupted setup

A self-hosted server exists in MCP Manager from the moment you reach the Deploy step, before anything has been deployed. Until the container enrolls, its instance shows **Awaiting enrollment** as its credential status and **Pending** as its authentication type and URL, and it cannot be selected when a user connects a client through a gateway.

If you closed the wizard before your agent finished, or the deployment never enrolled, MCP Manager offers the way back into the same instance rather than a second one:

* On the server's **Overview** tab, a **Finish setting up this server** callout explains the state. Its **Finish setup** button reopens the wizard on that exact instance; when the server has no instance at all, the button reads **Setup instance**.
* On the **Instances** tab, the header **Setup instance** button appears while nothing inside the server has enrolled, and each awaiting row has its own **Finish setup** action.

A resumed run reuses the same instance, so the prompt it generates is a **resume prompt**: it tells your agent that a Railway project and service may already exist from the earlier attempt, to look for one by the deployment's exact name first and reuse it, to stop and ask you if it finds more than one candidate, and to create a new service only after you confirm none exists. This keeps a retry from leaving a second billed service behind.

<Warning>
  MCP Manager cannot see your Railway account, so it cannot tell whether an earlier attempt left a service running. If a setup fails partway, check your Railway dashboard as well as MCP Manager.
</Warning>

## Deleting a self-hosted server

Deleting a self-hosted server in MCP Manager removes MCP Manager's record of it and stops every gateway from reaching it. **It does not stop the container on Railway**, because MCP Manager holds no credential for your account. The delete confirmation says so, and once the delete has landed MCP Manager offers a **teardown prompt** for your coding agent that names the deployment's URL, has the agent find exactly the matching Railway service, show you the list it is about to delete, wait for your explicit confirmation, delete only those services, and verify each URL no longer responds.

If you tear down on Railway first, MCP Manager does not notice on its own; delete the server in MCP Manager too, or it keeps pointing at a URL that no longer answers.

## Troubleshooting

Most setups finish without touching any of this. When one does not, the symptom you see in MCP Manager or in Railway's logs points at one of the cases below.

<AccordionGroup>
  <Accordion title="The server stays on Awaiting enrollment" icon="hourglass-half">
    **Awaiting enrollment** says only that MCP Manager has not seen the server enroll. Your agent may not have deployed yet, or may have deployed a service that failed to enroll. A live deployment with a public domain is not enough; a server that never enrolled has both and still never connects.

    Read the service's runtime logs on Railway:

    * `Enrollment succeeded` means the container enrolled. If MCP Manager still shows the server as awaiting, wait for verification to finish or reopen the wizard from the server's **Overview** tab.
    * `Enrollment is NOT configured` means `MCPM_REGISTRATION_TOKEN` or the public domain is missing. The container reads its own public URL from the domain Railway injects, so a service whose domain was generated **after** the first deploy cannot enroll until it is redeployed.
    * No enrollment line at all usually means the build failed or the container is crashing before it reaches enrollment. Check the build logs and `MCPM_START_COMMAND`.

    The container tries to enroll up to **8 times** with exponential backoff between 1 and 30 seconds, each attempt capped at 30 seconds. A 429 or 5xx response from MCP Manager is retried; any other 4xx is terminal, and the container keeps running without connecting.

    Each environment variable alone fails differently: only the registration token gives a server that works until its first restart, and only the access token gives a server that serves traffic but never connects. Set both.
  </Accordion>

  <Accordion title="The registration token expired" icon="clock">
    The registration token expires **90 minutes** after you enter the Deploy step, sized so a person who steps away mid-deploy still finishes while limiting replay if the prompt leaks through shell history or a screen share. The prompt prints the exact expiry and tells your agent to stop and ask for a fresh prompt if fewer than 10 minutes remain. Past the expiry the container cannot enroll. Reopen the wizard with **Finish setup** on the server's Overview tab for a fresh prompt; the resume prompt reuses the Railway service your agent already created.
  </Accordion>

  <Accordion title="Enrolled, but MCP Manager says the server didn't respond like an MCP server" icon="circle-question">
    Verification can fail after enrollment succeeds. The wizard shows "We connected to your server, but it didn't respond the way MCP Manager expects from an MCP server. Double-check the repository, or view the server's logs on your hosting provider." This happens when the repository is not actually an MCP server, when `MCPM_START_COMMAND` starts the wrong thing, or when your server speaks a transport other than stdio. Fix the command or the repository, redeploy, and press **Try again** in the wizard.
  </Accordion>

  <Accordion title="Requests to the container return 401 or 403" icon="lock">
    The authentication layer on the container's public port (**8080**) rejects requests it cannot attribute to MCP Manager. Each response names the reason:

    | Response                   | Meaning                                                                                                                                                                          |
    | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `401 not_yet_enrolled`     | The container is up but has not completed enrollment, so it accepts no traffic yet.                                                                                              |
    | `401 missing_access_token` | The request carried no `mcpm-access-token` header. An unauthenticated probe returning 401 is the layer working, not a fault.                                                     |
    | `401 invalid_access_token` | The header was present but did not match `MCPM_ACCESS_TOKEN`. The value in Railway and the one MCP Manager holds have drifted; redeploy with the value from the wizard's prompt. |
    | `403 forbidden_origin`     | A browser origin outside the optional `MCPM_ALLOWED_ORIGINS` allowlist called the container directly.                                                                            |

    Tokens are compared in constant time. Request bodies are capped at 16 MiB, the same limit as the gateway.
  </Accordion>

  <Accordion title="The logs show 410 on every restart" icon="rotate">
    Expected. The registration token is single-use in effect: once a server has a URL bound, a later enrollment with the same or an older token receives **410**. Restarts, redeploys, and out-of-memory kills do not require re-enrolling, because `MCPM_ACCESS_TOKEN` in the service's environment is the copy that survives. The container serves authenticated traffic again as soon as it is back.
  </Accordion>

  <Accordion title="The public URL was rejected" icon="link-slash">
    Before the URL is bound it must be `https://`, carry no embedded credentials, and must not be a loopback, private, link-local, or cloud-metadata address. A Railway-generated domain passes all of these; a custom domain that is not yet serving HTTPS does not. The MCP endpoint is the domain plus `/mcp`.
  </Accordion>

  <Accordion title="The server needs Python, another runtime, or more memory" icon="microchip">
    The base image ships Node only. A server written in Python, or one that needs any other runtime, gets that runtime installed by your agent in the Dockerfile's final stage; the prompt covers this, including the venv step Debian's externally-managed Python requires. The final stage must be exactly `FROM mcpmanager/self-host:0.1`, and Railway's builder rejects BuildKit cache mounts, so the prompt forbids them.

    Memory is the other thing to size for: the wrapper itself uses roughly 80 MB, each MCP session adds a child process of your server, and a server that loads a model or an embedding index can need 2 GB or more, which is beyond Railway's smallest plans. Out-of-memory kills show up in Railway's logs and as tools that stop responding.

    The image is pinned to a **minor version tag** (`0.1`), so a rebuild picks up patch fixes to the wrapper without changing your Dockerfile; the wizard never emits `latest`.
  </Accordion>

  <Accordion title="A retry left a second Railway service, or the server was torn down on Railway first" icon="clone">
    The resume prompt looks for an existing service by the deployment's exact name (`mcpm-` followed by an identifier derived from the server's ID) before creating one, and stops to ask you if it finds more than one candidate. If a second service exists anyway, delete the extra one in Railway; only the one whose domain MCP Manager holds is reached.

    Deleting a self-hosted server in MCP Manager removes the instance inside it as well and offers a teardown prompt for every deployment that had a URL. If you tore down on Railway first, delete the server in MCP Manager too, or it keeps pointing at a URL that no longer answers. Once the instance is live there is nothing to add; replacing it means deleting it and setting up again.
  </Accordion>

  <Accordion title="Tools stopped responding after the server was connected" icon="heart-pulse">
    Health after connection follows the same model as any managed server: a container that dies is seen when requests to it fail, so check Railway's logs when tools stop responding. The wizard's waiting screen changing its wording after 45 seconds, and provisioning reporting "taking longer than usual" after 10 seconds, are not timeouts; closing the wizard does not stop MCP Manager from connecting the server when it enrolls.
  </Accordion>

  <Accordion title="The repository check said the repository looks private or unreachable" icon="code-branch">
    The check is advisory and never blocks **Continue**. It probes only public reachability from MCP Manager, which is not where your agent clones from. If you can clone the repository on your machine, your agent can too, using the Git credentials already there.
  </Accordion>
</AccordionGroup>

## Further reading

<CardGroup cols={2}>
  <Card title="Workstation MCP Servers" icon="laptop" href="/mcp-gateway-concepts/mcp-servers/workstation">
    Govern a local-machine server through an encrypted tunnel.
  </Card>

  <Card title="Managed MCP Servers" icon="cubes" href="/mcp-gateway-concepts/mcp-servers/managed">
    The server type a self-hosted deployment becomes, and how requests reach it.
  </Card>

  <Card title="Remote MCP Servers" icon="cloud" href="/mcp-gateway-concepts/mcp-servers/remote">
    For a server already running at a URL — the wizard's entry point when you have none yet.
  </Card>

  <Card title="Capabilities reference" icon="key" href="/deployment/rbac-and-roles/capabilities">
    The capability that gates guided self-hosting.
  </Card>
</CardGroup>

## External sources

<CardGroup cols={2}>
  <Card title="Railway pricing" icon="credit-card" href="https://railway.com/pricing">
    Railway's current plans, including the Free plan and the Free Trial.
  </Card>

  <Card title="Railway CLI guide" icon="terminal" href="https://docs.railway.com/guides/cli">
    The CLI your coding agent drives to deploy the server.
  </Card>
</CardGroup>
