Back to Blog
Tutorial

How to Deploy an MCP Server to Production

Daniel Brooks10 min read
How to Deploy an MCP Server to Production

MCP (Model Context Protocol) is the open standard for connecting AI assistants to external tools and data. Most MCP servers run locally over stdio, accessible only to the developer who started them. That works for personal use, but production use requires something different: a deployed, always-available server that your entire team and AI workflows can reach over HTTPS.

This guide walks through building a Python MCP server with HTTP transport and deploying it to production on Out Plane — from an empty directory to a live URL with automatic SSL.

What Is MCP and Why Deploy It?

A Model Context Protocol server is a lightweight service that exposes tools, resources, and prompts to AI assistants like Claude through a standardized interface. The protocol, developed by Anthropic and now an open standard, defines how AI assistants discover and invoke external capabilities.

When Claude or another MCP-compatible client connects to an MCP server, it can call the tools that server exposes — querying databases, fetching live data, running computations, or interacting with third-party APIs. The AI assistant handles the reasoning; the MCP server handles the execution.

Local vs. Remote MCP Servers

Most MCP documentation shows the stdio transport: the client spawns the server as a child process and communicates over standard input/output. This works well for personal tools on a single machine.

The HTTP transport is different. The server runs as a standalone process at a fixed URL. Any authorized MCP client — Claude Desktop, a CI/CD pipeline, a teammate's workstation, or an automated agent — can connect to it. The server is available whether or not you're at your desk.

Why Host an MCP Server in Production

Deploying an MCP server to production gives you several capabilities that local stdio servers cannot provide:

  • Team access: Share tools across your engineering team without requiring each member to run the server locally
  • Persistence: The server stays online when your laptop closes, your CI jobs run, or your automated agents execute overnight
  • Reliability: A deployed server has health checks, automatic restarts, and a stable URL
  • Integration: Webhooks, scheduled jobs, and external systems can trigger MCP tool calls over HTTP
  • Separation of concerns: Credentials and business logic live on the server, not distributed across every developer's machine

What You'll Build

This guide builds a Python MCP server using FastMCP, the most concise way to write MCP-compliant servers in Python. The server will:

  • Expose multiple tools accessible to any MCP client
  • Expose a resource for configuration data
  • Run over HTTP transport (not stdio) for remote access
  • Deploy to Out Plane with automatic HTTPS and a stable .outplane.app URL

The same deployment pattern works for any MCP server regardless of what tools it exposes. You can substitute your own domain logic, database queries, or API integrations.

Prerequisites

Before starting, make sure you have:

  • Python 3.11+ installed on your machine
  • A GitHub account — Out Plane uses GitHub OAuth for authentication
  • An Out Plane account at console.outplane.com

No CLI tool is required. The entire deployment happens through the Out Plane console.

Step 1: Create the MCP Server

Create a new directory for your project and set up a virtual environment:

bash
mkdir my-mcp-server && cd my-mcp-server
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install fastmcp

The Server Code

Create server.py with your MCP tools and resources:

python
from fastmcp import FastMCP

mcp = FastMCP("My Production MCP Server")


@mcp.tool()
def search_docs(query: str) -> str:
    """Search documentation for relevant information."""
    # Replace with your actual search logic
    return f"Results for: {query}"


@mcp.tool()
def get_status(service: str) -> dict:
    """Get the current status of a service."""
    return {"service": service, "status": "healthy", "uptime": "99.9%"}


@mcp.resource("config://app")
def get_config() -> str:
    """Current application configuration."""
    return "Production environment configuration..."


if __name__ == "__main__":
    mcp.run(transport="http", host="0.0.0.0", port=8080)

The host="0.0.0.0" binding is important. If you bind to 127.0.0.1 or localhost, the server will only accept connections from within the same container and will not be reachable from outside. Always bind to 0.0.0.0 when deploying.

Dependencies

Create requirements.txt:

text
fastmcp>=2.0.0

Push both files to a GitHub repository. The repository can be public or private — Out Plane can access private repositories through your GitHub OAuth connection.

Step 2: Configure HTTP Transport

MCP supports two transport modes: stdio and HTTP (Streamable HTTP). Understanding the difference is important before deploying.

stdio transport starts the server as a child process. The client and server communicate through pipes. This only works when client and server run on the same machine. Remote deployment is not possible with stdio.

HTTP transport runs the server as a standard web service. Clients connect over the network using HTTP or HTTPS. Multiple clients can connect simultaneously. The server runs independently of any client lifecycle.

For a deployed MCP server, HTTP transport is the only viable option.

FastMCP's HTTP transport exposes the MCP endpoint at /mcp by default. When your server is deployed and running, MCP clients connect to https://your-app.outplane.app/mcp.

The complete server entry point is already shown above. The key line is:

python
mcp.run(transport="http", host="0.0.0.0", port=8080)

Out Plane routes external HTTPS traffic to port 8080 inside your container. Port 8080 is the standard port for Out Plane applications.

The Dockerfile

FastMCP does not have a Buildpack detector, so use a Dockerfile for reliable builds. Create Dockerfile in your project root:

dockerfile
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["python", "server.py"]

This image uses the official Python slim base, installs dependencies in a separate layer for build caching, and starts the server. The total image size is typically under 150MB.

Your repository should now contain three files:

text
my-mcp-server/
├── Dockerfile
├── requirements.txt
└── server.py

Push this to GitHub before proceeding.

Step 3: Deploy to Out Plane

With your code in GitHub, deploying takes about three minutes.

Connect Your Repository

  1. Go to console.outplane.com
  2. Sign in with your GitHub account
  3. Click New Application
  4. Select your MCP server repository from the list

Configure the Application

In the application creation form, configure the following settings:

Build Method

Select Dockerfile. Out Plane will use the Dockerfile in your repository root to build the container image. This gives you precise control over the Python version, dependencies, and startup command.

Basic Settings

  • Port: Set to 8080 — this must match the port in your mcp.run() call
  • Branch: Select main or your default branch
  • Region: Choose the region closest to your users or AI workloads

Environment Variables

If your MCP tools call external APIs or connect to databases, add those credentials here. Use the Add button for individual variables or Raw Edit to paste multiple variables at once.

For example, if your tools call an external API:

text
API_KEY=your_api_key_here
API_BASE_URL=https://api.example.com

Never commit credentials to your repository. Set them as environment variables here.

Deploy

Click Deploy Application. Out Plane will build and start your server through four stages:

  1. Queued — Waiting for build resources
  2. Building — Running docker build with your Dockerfile
  3. Deploying — Starting the container and running health checks
  4. Ready — Your server is live

Once the status shows Ready, your MCP server is accessible at https://your-app.outplane.app. The MCP endpoint is at https://your-app.outplane.app/mcp.

Automatic deployments trigger on every push to your selected branch. Update your tools, push to GitHub, and the server redeploys automatically.

Step 4: Connect Your AI Assistant

With the server deployed, configure your MCP client to connect to it.

Claude Desktop

Open your Claude Desktop configuration file. On macOS, this is at ~/Library/Application Support/Claude/claude_desktop_config.json. On Windows, it is at %APPDATA%\Claude\claude_desktop_config.json.

Add your deployed server under mcpServers:

json
{
  "mcpServers": {
    "my-server": {
      "transport": "http",
      "url": "https://your-app.outplane.app/mcp"
    }
  }
}

Replace your-app with the actual subdomain shown in your Out Plane dashboard. Save the file and restart Claude Desktop. Claude will connect to your server and list its tools in the interface.

Programmatic MCP Clients

If you are building an agent or automation that uses MCP programmatically, the connection configuration follows the same pattern. The URL https://your-app.outplane.app/mcp is a standard HTTP endpoint compatible with any MCP client library.

Step 5: Add a Custom Domain

The default .outplane.app URL works immediately, but you may want a cleaner endpoint like mcp.yourdomain.com for documentation and client configuration.

To map a custom domain:

  1. Navigate to Domains in the sidebar
  2. Click Map Domain
  3. Enter your domain (e.g., mcp.yourdomain.com)
  4. Add a CNAME record pointing to domains-management.outplane.app at your DNS registrar
  5. Wait for DNS propagation (typically 5–30 minutes)

Out Plane automatically provisions an SSL certificate once DNS resolves. After setup, your MCP endpoint is at https://mcp.yourdomain.com/mcp.

Update your Claude Desktop config and any other MCP clients to use the custom domain URL.

Production Considerations

Authentication

FastMCP supports custom middleware. For team-shared servers, add API key validation to prevent unauthorized access:

python
import os
from fastmcp import FastMCP
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response

mcp = FastMCP("Secured MCP Server")

VALID_API_KEY = os.environ["MCP_API_KEY"]


class APIKeyMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        api_key = request.headers.get("X-API-Key")
        if api_key != VALID_API_KEY:
            return Response("Unauthorized", status_code=401)
        return await call_next(request)

Add MCP_API_KEY as an environment variable in the Out Plane console. Never hardcode secrets in your repository.

Scaling

MCP servers using HTTP transport are stateless by design. Each request carries all the information needed to execute the tool. This means you can scale horizontally without coordination between instances.

In your Out Plane application settings, configure instance sizing based on your tools' resource requirements:

  • op-20 (0.5 vCPU, 512 MB): Suitable for lightweight tools that call external APIs
  • op-40 (1 vCPU, 1 GB): Good for most general-purpose MCP servers
  • op-60 (2 vCPU, 2 GB) and above: Use when tools perform in-process computation or load models

Per-second billing means you only pay for actual execution time. An MCP server that handles occasional tool calls costs significantly less than a continuously busy web application.

Monitoring

Out Plane provides built-in HTTP logs accessible from the sidebar. Each tool call appears as an HTTP request to /mcp. Monitor:

  • Response times: Tool calls should complete quickly; investigate any that exceed a few seconds
  • Error rates: HTTP 500 responses indicate tool execution failures — check the Logs view for Python tracebacks
  • Request volume: Unusual spikes may indicate a runaway agent or unauthorized access

For detailed tool-level metrics, add structured logging inside your tool functions:

python
import logging

logger = logging.getLogger(__name__)


@mcp.tool()
def search_docs(query: str) -> str:
    """Search documentation for relevant information."""
    logger.info("search_docs called", extra={"query": query})
    result = perform_search(query)
    logger.info("search_docs completed", extra={"query": query, "results": len(result)})
    return result

Logs appear in real time in the Out Plane console.

Database Integration

MCP servers often need persistent storage — for caching results, tracking state, or querying application data. Out Plane provides managed PostgreSQL (versions 14 through 18).

To create a database:

  1. Go to Databases in the sidebar
  2. Click Create Database
  3. Select a PostgreSQL version and region
  4. Copy the connection URL

Add the connection URL as an environment variable in your application settings:

text
DATABASE_URL=postgresql://user:password@host/database

Advanced: MCP Server with Database Integration

Here is a complete example of an MCP server that queries a PostgreSQL database:

python
import os
import psycopg2
from psycopg2.extras import RealDictCursor
from fastmcp import FastMCP

mcp = FastMCP("Database MCP Server")

DATABASE_URL = os.environ["DATABASE_URL"]


def get_connection():
    return psycopg2.connect(DATABASE_URL, cursor_factory=RealDictCursor)


@mcp.tool()
def list_records(table: str, limit: int = 10) -> list:
    """List records from a database table. Use limit to control result count."""
    with get_connection() as conn:
        with conn.cursor() as cur:
            # Restrict to read-only queries against known tables
            allowed_tables = ["products", "orders", "customers"]
            if table not in allowed_tables:
                return {"error": f"Table '{table}' is not accessible"}
            cur.execute(
                f"SELECT * FROM {table} ORDER BY id DESC LIMIT %s",
                (min(limit, 100),),
            )
            return cur.fetchall()


@mcp.tool()
def search_records(table: str, column: str, value: str) -> list:
    """Search records in a table by column value."""
    allowed_tables = ["products", "orders", "customers"]
    if table not in allowed_tables:
        return {"error": f"Table '{table}' is not accessible"}
    with get_connection() as conn:
        with conn.cursor() as cur:
            cur.execute(
                f"SELECT * FROM {table} WHERE {column} ILIKE %s LIMIT 50",
                (f"%{value}%",),
            )
            return cur.fetchall()


if __name__ == "__main__":
    mcp.run(transport="http", host="0.0.0.0", port=8080)

Update requirements.txt to include psycopg2-binary:

text
fastmcp>=2.0.0
psycopg2-binary>=2.9.0

This pattern — reading DATABASE_URL from an environment variable and using a connection-per-request approach — works reliably in containerized deployments. For high-volume servers, replace psycopg2 with a connection pool using psycopg2.pool or switch to an async driver.

Common Issues

Port Binding Errors

Symptom: The server starts but Out Plane reports it as unhealthy.

Cause: The server is binding to 127.0.0.1 or localhost instead of 0.0.0.0. Container networking requires 0.0.0.0 to accept external connections.

Fix: Verify your mcp.run() call uses host="0.0.0.0".

Missing Dependencies in Docker Image

Symptom: The build succeeds but the container fails to start with a ModuleNotFoundError.

Cause: A dependency is installed locally but not listed in requirements.txt.

Fix: Run pip freeze > requirements.txt in your virtual environment to capture all installed packages, then remove packages your application does not actually use.

Environment Variable Not Set

Symptom: The server fails at startup with a KeyError or crashes when a tool is first called.

Cause: A required environment variable is not configured in the Out Plane console.

Fix: Go to your application settings, navigate to Environment Variables, and add the missing variable. The application redeploys automatically after saving.

Transport Misconfiguration in MCP Client

Symptom: Claude Desktop reports it cannot connect to the server.

Cause: The client configuration uses stdio transport settings instead of HTTP, or the URL is missing /mcp at the end.

Fix: Verify your claude_desktop_config.json specifies "transport": "http" and that the URL ends in /mcp: https://your-app.outplane.app/mcp.

Slow or Timing Out Tool Calls

Symptom: Tool calls work but take several seconds, or Claude reports a timeout.

Cause: The tool is performing a slow external API call or database query, or the instance type is undersized for the workload.

Fix: Add logging to identify which operation is slow. For compute-heavy tools, upgrade the instance type in your application settings.

Summary

Deploying an MCP server to production makes it available to your entire team, your CI/CD pipelines, and your automated agents — not just your local machine. The deployment process on Out Plane is:

  1. Build your FastMCP server with HTTP transport and a Dockerfile
  2. Connect your GitHub repository at console.outplane.com
  3. Configure port 8080, environment variables, and build method (Dockerfile)
  4. Deploy and receive a stable HTTPS URL at your-app.outplane.app/mcp
  5. Connect Claude Desktop or other MCP clients to the deployed URL

Model Context Protocol server hosting on Out Plane gives you per-second billing, automatic HTTPS, managed PostgreSQL for persistent data, and real-time HTTP logs for every tool call. There is no infrastructure to manage and no server configuration to maintain.

Ready to host your MCP server? Deploy on Out Plane and receive $20 in free credit.


Tags

mcp
ai
deployment
model-context-protocol
tutorial
claude

Start deploying in minutes

Connect your GitHub repository and deploy your first application today. $20 free credit. No credit card required.