🤖AI Agents Guide
TutorialsComparisonsReviewsExamplesIntegrationsUse CasesTemplatesGlossary
Get Started
🤖AI Agents Guide

Your comprehensive resource for understanding, building, and implementing AI Agents.

Learn

  • Tutorials
  • Glossary
  • Use Cases
  • Examples

Compare

  • Tool Comparisons
  • Reviews
  • Integrations
  • Templates

Company

  • About
  • Contact
  • Privacy Policy

© 2026 AI Agents Guide. All rights reserved.

Home/Tutorials/Deploy a Remote MCP Server (HTTP/SSE)
intermediate14 min read

Deploy a Remote MCP Server (HTTP/SSE)

Step-by-step guide to deploying an MCP server for remote access using HTTP/SSE transport. Covers hosting on Vercel, Railway, and AWS, authentication setup, and connecting Claude Desktop and custom clients to your hosted server.

a box with a red cord connected to it
Photo by Steve Johnson on Unsplash
By AI Agents Guide Team•March 1, 2026

Table of Contents

  1. Prerequisites
  2. Overview
  3. Step 1: Convert to HTTP/SSE Transport
  4. Step 2: Add Authentication
  5. Step 3: Deploy to Your Platform
  6. Option A: Railway (Recommended for Persistent MCP Servers)
  7. Option B: Vercel (Good for Short-Lived Sessions)
  8. Option C: AWS with App Runner
  9. Step 4: Connect Clients to the Remote Server
  10. Claude Desktop
  11. Custom TypeScript Client
  12. Custom Python Client
  13. Common Issues and Solutions
  14. Next Steps
  15. Frequently Asked Questions
Server monitoring dashboard representing MCP server deployment and management
Photo by Markus Spiske on Unsplash

How to Deploy a Remote MCP Server (HTTP/SSE Transport)

Local stdio transport gets you started quickly, but it has a fundamental limitation: only the local machine can connect. If you want multiple team members, multiple machines, or production AI agents to access the same MCP server, you need to deploy it for remote access using HTTP with Server-Sent Events (SSE) transport.

This tutorial walks through the full process: converting a local server to HTTP/SSE, adding authentication, and deploying to three different hosting platforms.

Prerequisites#

  • Completed the build MCP server tutorial or have an existing MCP server
  • Node.js 18+ and npm installed
  • An account on at least one of: Railway, Vercel, or AWS
  • Familiarity with environment variables and basic Express.js

Overview#

The deployment process has four stages:

  1. Convert your server from stdio to HTTP/SSE transport
  2. Add authentication to protect the remote endpoint
  3. Deploy to your chosen cloud platform
  4. Connect Claude Desktop and custom clients to the remote server

Step 1: Convert to HTTP/SSE Transport#

A stdio server and an HTTP/SSE server use the same tool definitions — you only change the transport layer. Here is a minimal stdio server:

// Before: stdio transport
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

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

server.tool("get_status", "Get service status", {}, async () => ({
  content: [{ type: "text", text: "Status: operational" }],
}));

const transport = new StdioServerTransport();
await server.connect(transport);

Convert it to HTTP/SSE by replacing the transport and adding Express:

// After: HTTP/SSE transport
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
import { z } from "zod";

const app = express();
app.use(express.json());

// Create the MCP server (same tools as before)
const mcpServer = new McpServer({ name: "my-server", version: "1.0.0" });

mcpServer.tool("get_status", "Get service status", {}, async () => ({
  content: [{ type: "text", text: "Status: operational" }],
}));

// Track active SSE transports by session ID
const transports = new Map<string, SSEServerTransport>();

// SSE endpoint — clients connect here to receive server events
app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  transports.set(transport.sessionId, transport);

  res.on("close", () => {
    transports.delete(transport.sessionId);
  });

  await mcpServer.connect(transport);
});

// POST endpoint — clients send messages here
app.post("/messages", async (req, res) => {
  const sessionId = req.query.sessionId as string;
  const transport = transports.get(sessionId);

  if (!transport) {
    res.status(404).json({ error: "Session not found" });
    return;
  }

  await transport.handlePostMessage(req, res, req.body);
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`MCP server listening on port ${PORT}`);
});

Install the new dependency:

npm install express
npm install --save-dev @types/express

Test locally before deploying:

npx ts-node --esm src/server.ts
# In another terminal:
curl http://localhost:3000/sse

Step 2: Add Authentication#

An internet-accessible MCP server without authentication is a security liability. Add API key validation before proceeding to deployment. See MCP authentication for the full security picture.

// Authentication middleware
function requireApiKey(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) {
  const authHeader = req.headers.authorization;

  if (!authHeader?.startsWith("Bearer ")) {
    res.status(401).json({
      error: "Missing Authorization header. Use: Authorization: Bearer <api-key>",
    });
    return;
  }

  const apiKey = authHeader.slice(7);
  const validKey = process.env.MCP_API_KEY;

  if (!validKey || apiKey !== validKey) {
    res.status(403).json({ error: "Invalid API key" });
    return;
  }

  next();
}

// Apply authentication to both endpoints
app.get("/sse", requireApiKey, async (req, res) => { /* ... */ });
app.post("/messages", requireApiKey, async (req, res) => { /* ... */ });

Generate a secure API key:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Store this key in your environment — never commit it to version control. Add a .env file for local development:

MCP_API_KEY=your-generated-key-here
PORT=3000

Also add a health check endpoint — most platforms use this to verify deployment success:

app.get("/health", (req, res) => {
  res.json({ status: "ok", server: "my-mcp-server", version: "1.0.0" });
});

Step 3: Deploy to Your Platform#

Option A: Railway (Recommended for Persistent MCP Servers)#

Railway supports long-lived HTTP connections without function timeouts, making it the best fit for MCP SSE servers.

Prepare your project:

// package.json
{
  "scripts": {
    "start": "node dist/server.js",
    "build": "tsc"
  },
  "engines": {
    "node": ">=18"
  }
}
// tsconfig.json — ensure output goes to dist/
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

Deploy:

  1. Push your code to a GitHub repository
  2. Go to railway.app ↗ and create a new project
  3. Select "Deploy from GitHub repo" and choose your repository
  4. In the project settings, add the environment variable MCP_API_KEY with your generated key
  5. Railway auto-detects Node.js and runs npm run build && npm start
  6. Your server URL will be https://your-project.up.railway.app

Test the deployment:

curl -H "Authorization: Bearer your-api-key" \
  https://your-project.up.railway.app/health

Option B: Vercel (Good for Short-Lived Sessions)#

Vercel works well for MCP servers where each tool call is fast and sessions are short. Be aware of the execution timeout limits on free plans.

Create a vercel.json:

{
  "functions": {
    "api/sse.ts": {
      "maxDuration": 60
    },
    "api/messages.ts": {
      "maxDuration": 30
    }
  }
}

Structure your project as Vercel API routes:

// api/sse.ts
import { createMcpServer } from "../src/server-core";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";

const sessions = new Map<string, SSEServerTransport>();

export default async function handler(req: any, res: any) {
  if (!validateApiKey(req)) {
    res.status(401).json({ error: "Unauthorized" });
    return;
  }

  const server = createMcpServer();
  const transport = new SSEServerTransport("/api/messages", res);
  sessions.set(transport.sessionId, transport);
  await server.connect(transport);
}

Deploy:

npm install -g vercel
vercel --prod
# Set environment variable in Vercel dashboard: MCP_API_KEY

Option C: AWS with App Runner#

AWS App Runner is well-suited for containerized MCP servers that need auto-scaling.

Create a Dockerfile:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist/ ./dist/
EXPOSE 3000
CMD ["node", "dist/server.js"]

Build and push to ECR, then create an App Runner service pointing to your image. Set the MCP_API_KEY environment variable in the App Runner service configuration.

Step 4: Connect Clients to the Remote Server#

Claude Desktop#

Recent versions of Claude Desktop support connecting directly to remote HTTP/SSE servers. Edit ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "my-remote-server": {
      "url": "https://your-project.up.railway.app/sse",
      "headers": {
        "Authorization": "Bearer your-api-key-here"
      }
    }
  }
}

Restart Claude Desktop and verify the server appears in the tools panel.

Custom TypeScript Client#

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";

const transport = new SSEClientTransport(
  new URL("https://your-project.up.railway.app/sse"),
  {
    requestInit: {
      headers: {
        Authorization: `Bearer ${process.env.MCP_API_KEY}`,
      },
    },
    eventSourceInit: {
      fetch: (url, init) =>
        fetch(url, {
          ...init,
          headers: {
            ...init?.headers,
            Authorization: `Bearer ${process.env.MCP_API_KEY}`,
          },
        }),
    },
  }
);

const client = new Client(
  { name: "my-client", version: "1.0.0" },
  { capabilities: {} }
);

await client.connect(transport);
const tools = await client.listTools();
console.log("Remote tools:", tools.tools.map((t) => t.name));

Custom Python Client#

import asyncio
import os
from mcp import ClientSession
from mcp.client.sse import sse_client

async def main():
    headers = {"Authorization": f"Bearer {os.environ['MCP_API_KEY']}"}

    async with sse_client(
        "https://your-project.up.railway.app/sse",
        headers=headers
    ) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            print("Remote tools:", [t.name for t in tools.tools])

asyncio.run(main())

Common Issues and Solutions#

SSE connection closes immediately

Most likely an authentication failure — the server returns a 401/403, the HTTP layer closes the connection, and the client sees a disconnection. Check your API key and Authorization header format. Add request logging on the server to confirm the header is arriving.

Messages endpoint returns 404 for session

The session ID in the ?sessionId= query parameter does not match any active SSE session. This can happen if the SSE connection closed between the client connecting and sending the first message, or if you have multiple server instances without shared session storage. For multi-instance deployments, store sessions in Redis rather than an in-memory Map.

Deployment succeeds but tools time out

If your tools call external APIs or databases, the connection strings and API keys must be set as environment variables on the deployment platform. The server process cannot access your local .env file.

CORS errors in browser-based clients

Add CORS headers if your MCP client runs in a browser:

import cors from "cors";
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(",") || "*",
  methods: ["GET", "POST"],
  allowedHeaders: ["Content-Type", "Authorization"],
}));

Next Steps#

With your remote MCP server deployed:

  • Implement comprehensive security controls from the MCP server security tutorial
  • Set up monitoring and alerting — Railway and Vercel both expose metrics dashboards
  • Explore advanced patterns including resource subscriptions in the advanced MCP patterns tutorial
  • Test your deployment thoroughly using the test and debug MCP server guide

Frequently Asked Questions#

How much does hosting an MCP server cost?

Railway's free tier includes 500 hours/month of compute — enough for a personal MCP server running continuously. For team use, Railway's Starter plan at $5/month covers most needs. Vercel's free tier works for low-traffic servers. AWS App Runner starts at around $10-15/month for minimal compute. The main cost driver is not the MCP server itself but any external services your tools call.

Can multiple users share one remote MCP server?

Yes. An HTTP/SSE server handles multiple simultaneous SSE connections. Each connected client gets its own session ID and isolated tool call context. If your tools access user-specific data, implement per-session authentication that maps the API key or token to the user's data scope to prevent data leakage between users.

How do I update my deployed MCP server without downtime?

Railway and Vercel both support zero-downtime deployments. Push to your connected GitHub repository and the platform builds and deploys the new version, routing traffic to the new instance before terminating the old one. Active SSE sessions on the old instance will be disconnected during the cutover — design your clients to reconnect automatically when the SSE connection drops.

Related Tutorials

How to Create a Meeting Scheduling AI Agent

Build an autonomous AI agent to handle meeting scheduling, calendar checks, and bookings intelligently. This step-by-step tutorial covers Python implementation with LangChain, Google Calendar integration, and advanced features like conflict resolution for efficient automation.

How to Manage Multiple AI Agents

Master managing multiple AI agents with this in-depth tutorial. Learn orchestration, state sharing, parallel execution, and scaling using LangGraph and custom tools. From basics to production-ready swarms for complex tasks.

How to Train an AI Agent on Your Own Data

Master training AI agents on custom data with three methods: context stuffing, RAG using vector databases, and fine-tuning. This beginner-to-advanced guide includes step-by-step code examples, pitfalls, and best practices to build knowledgeable agents for your specific needs.

← Back to All Tutorials