# How to deploy a remote MCP server

- Tool: MCP
- Difficulty: Advanced
- Time required: 30-45 min
- Compatibility: MCP SDK 1.0+, Node.js 18+, Vercel/Railway/Render
- Last updated: March 2026

## TL;DR

Deploy an MCP server remotely by switching from stdio to Streamable HTTP transport and hosting on platforms like Vercel, Railway, or Render. You need to wrap your server with an HTTP framework like Express, configure environment variables on your hosting platform, and update your MCP host config to point to the deployed URL instead of a local command.

## Deploying MCP Servers to the Cloud

Most MCP servers start as local stdio processes, but sharing them with a team or accessing them from multiple machines requires remote deployment. This tutorial walks you through converting your server to use Streamable HTTP transport and deploying it to popular cloud platforms. You will learn how to handle environment variables, set up health checks, and configure your MCP hosts to connect to the remote endpoint.

## Before you start

- A working MCP server built with @modelcontextprotocol/sdk
- Node.js 18+ installed locally
- A GitHub account for deployment integration
- An account on Railway, Render, or Vercel
- Basic familiarity with Git and deploying web applications

## Step-by-step guide

### 1. Add Streamable HTTP transport to your server

Your stdio server communicates via stdin/stdout, which does not work over the network. Install Express and update your server to use the Streamable HTTP transport provided by the MCP SDK. The createMcpExpressApp helper wraps your McpServer instance in an Express application that exposes a /mcp endpoint. This is the only code change needed — all your existing tools and resources remain the same.

```
npm install express
npm install -D @types/express
```

**Expected result:** Your project has Express installed and you are ready to create the HTTP entry point.

### 2. Create the HTTP server entry point

Create a new file that imports your MCP server and wraps it with the Express Streamable HTTP transport. This file will be your deployment entry point. The server listens on the PORT environment variable (which cloud platforms set automatically) and logs to stderr. Add a health check endpoint at the root so load balancers and uptime monitors can verify your server is running.

```
// src/http.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import { registerTools } from "./tools.js";

const server = new McpServer({
  name: "my-remote-server",
  version: "1.0.0",
});

registerTools(server);

const app = createMcpExpressApp(server);

// Health check endpoint
app.get("/", (_req, res) => {
  res.json({ status: "ok", name: "my-remote-server" });
});

const PORT = parseInt(process.env.PORT || "3000", 10);
app.listen(PORT, () => {
  console.error(`MCP server running on port ${PORT}`);
});
```

**Expected result:** Running node dist/http.js starts an HTTP server that responds on /mcp for MCP traffic and / for health checks.

### 3. Deploy to Railway

Railway is the easiest platform for deploying MCP servers because it supports long-running processes and WebSocket upgrades natively. Push your repository to GitHub, then create a new Railway project and connect it to your repo. Railway auto-detects Node.js and runs npm start. Set your start command to node dist/http.js and add any required environment variables (like API keys) in the Railway dashboard under Variables. Railway assigns a public URL automatically.

```
// package.json — add start script
{
  "scripts": {
    "build": "tsc",
    "start": "node dist/http.js"
  }
}
```

**Expected result:** Your MCP server is live at https://your-project.up.railway.app with the /mcp endpoint accessible.

### 4. Deploy to Render

Render is another excellent option with a generous free tier for web services. Create a new Web Service on Render, connect your GitHub repo, set the build command to npm install && npm run build, and the start command to node dist/http.js. Add environment variables in the Environment section. Render provides a .onrender.com subdomain automatically. Note that Render free tier services spin down after 15 minutes of inactivity, which will cause the first request to take 30-60 seconds.

```
// render.yaml (optional — Infrastructure as Code)
services:
  - type: web
    name: my-mcp-server
    runtime: node
    buildCommand: npm install && npm run build
    startCommand: node dist/http.js
    envVars:
      - key: API_KEY
        sync: false
```

**Expected result:** Your MCP server is live at https://your-project.onrender.com with the /mcp endpoint accessible.

### 5. Deploy to Vercel as a serverless function

Vercel works differently — it runs serverless functions, not long-running processes. This means each request is handled by a new function invocation. Streamable HTTP's stateless design supports this pattern. Create an api/mcp.ts file that exports the Express app as a Vercel function. Note that Vercel has a 10-second timeout on the free tier (60s on Pro), so tools that take longer will need a different approach.

```
// api/mcp.ts — Vercel serverless function
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import { registerTools } from "../src/tools.js";

const server = new McpServer({
  name: "vercel-mcp-server",
  version: "1.0.0",
});

registerTools(server);

const app = createMcpExpressApp(server);

export default app;
```

**Expected result:** Your MCP server is deployed as a Vercel serverless function at https://your-project.vercel.app/api/mcp.

### 6. Connect your MCP host to the remote server

Update your Claude Desktop or Cursor configuration to point to the remote URL instead of a local command. For Claude Desktop, edit ~/Library/Application Support/Claude/claude_desktop_config.json. For Cursor, edit .cursor/mcp.json in your project root. Replace the command/args configuration with a url field pointing to your deployed endpoint. If your deployment requires complex infrastructure decisions or you want help choosing the right platform, the RapidDev engineering team can assist with architecture planning.

```
// Claude Desktop — remote server config
{
  "mcpServers": {
    "my-remote-server": {
      "url": "https://your-project.up.railway.app/mcp"
    }
  }
}

// Cursor — remote server config (.cursor/mcp.json)
{
  "mcpServers": {
    "my-remote-server": {
      "url": "https://your-project.up.railway.app/mcp"
    }
  }
}
```

**Expected result:** Your MCP host connects to the remote server and you can use all tools and resources as if they were running locally.

## Complete code example

File: `src/http.ts`

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import { z } from "zod";

const server = new McpServer({
  name: "my-remote-mcp-server",
  version: "1.0.0",
});

// Example tool — replace with your own
server.tool(
  "lookup",
  "Look up information by ID",
  { id: z.string().describe("The ID to look up") },
  async ({ id }) => {
    // Replace with your actual logic
    const data = { id, name: "Example", status: "active" };
    return {
      content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
    };
  }
);

// Create Express app with Streamable HTTP transport
const app = createMcpExpressApp(server);

// Health check endpoint
app.get("/", (_req, res) => {
  res.json({
    status: "ok",
    server: "my-remote-mcp-server",
    version: "1.0.0",
    transport: "Streamable HTTP",
  });
});

const PORT = parseInt(process.env.PORT || "3000", 10);
app.listen(PORT, () => {
  console.error(`MCP server listening on port ${PORT}`);
  console.error(`MCP endpoint: http://localhost:${PORT}/mcp`);
  console.error(`Health check: http://localhost:${PORT}/`);
});
```

## Common mistakes

- **Deploying a stdio server directly to the cloud without switching transports** — undefined Fix: Stdio uses subprocess pipes, which require the server to run on the same machine as the host. You must switch to Streamable HTTP transport for remote deployment.
- **Hardcoding API keys in server code instead of using environment variables** — undefined Fix: Use process.env.API_KEY and set the value in your hosting platform's environment variable configuration. Never commit secrets to your repository.
- **Using Vercel free tier for long-running MCP tools** — undefined Fix: Vercel free tier has a 10-second function timeout. If your tools call slow APIs, use Railway or Render which support long-running processes.
- **Forgetting to add a health check endpoint** — undefined Fix: Cloud platforms need a health check endpoint to verify your server is running. Add a simple GET / route that returns a 200 status.
- **Not setting the PORT environment variable** — undefined Fix: Cloud platforms inject the PORT environment variable. Always use process.env.PORT instead of a hardcoded port number.

## Best practices

- Keep tool definitions in a shared module that both stdio and HTTP entry points can import
- Always use process.env.PORT for the listening port — cloud platforms set this automatically
- Add a health check endpoint at / or /health for load balancer and uptime monitoring
- Store all secrets as environment variables on your hosting platform, never in code
- Use console.error() for all logging — never console.log() which can interfere with protocols
- Set up a staging environment to test changes before deploying to production
- Monitor your server logs through your hosting platform's dashboard
- Consider adding rate limiting middleware for public-facing MCP servers

## Frequently asked questions

### Can I deploy an MCP server to AWS Lambda?

Yes, since Streamable HTTP supports stateless request-response patterns, it works with serverless platforms including AWS Lambda. Wrap your Express app with a Lambda adapter like @vendia/serverless-express. Be aware of Lambda's timeout limits (15 minutes max, but API Gateway has a 30-second limit).

### Do I need a custom domain for my remote MCP server?

No, you can use the default URL provided by your hosting platform (e.g., your-project.up.railway.app). Custom domains are optional and mainly useful for branding or organizational requirements.

### How do I handle authentication for a remote MCP server?

Streamable HTTP supports OAuth 2.1 authentication as defined in the MCP specification. For simpler setups, you can validate a shared secret passed as a header. Avoid exposing MCP servers without any authentication on the public internet.

### What happens if my Render free tier service spins down?

The first request after a spin-down will take 30-60 seconds while Render cold-starts your service. This may cause MCP client timeouts. Upgrade to a paid plan or use Railway to keep the server running continuously.

### Can multiple users share the same remote MCP server?

Yes, Streamable HTTP supports multiple concurrent sessions via the Mcp-Session-Id header. Each connected client gets its own session, and the server handles them independently.

### How much does it cost to run an MCP server in the cloud?

Railway Hobby is $5/month, Render free tier is $0 (with spin-down), and Vercel free tier handles low-traffic servers at no cost. For most personal and small team use cases, costs are under $10/month.

---

Source: https://www.rapidevelopers.com/mcp-tutorial/how-to-deploy-mcp-server-remotely
© RapidDev — https://www.rapidevelopers.com/mcp-tutorial/how-to-deploy-mcp-server-remotely
