Learn Build MCP Servers: Extend Claude with Custom Tools HTTP Transport: Building Remote Servers

HTTP Transport: Building Remote Servers

Advanced 🕐 25 min Lesson 13 of 15
What you'll learn
  • Build an HTTP MCP server using StreamableHTTPServerTransport (TypeScript) or FastMCP transport='streamable-http' (Python)
  • Deploy a remote MCP server and connect it to Claude.ai via Settings > Integrations
  • Understand the difference between stateless and stateful HTTP server designs
  • Choose the right deployment platform based on your server's state requirements

Why HTTP Transport?

Every server you've built so far uses stdio transport: the client launches a subprocess and communicates over stdin/stdout. That works perfectly for local tools, but it has hard limits. A stdio server runs on the machine where the client lives. You can't share it with teammates, deploy it to the cloud, or connect multiple clients at once. HTTP transport removes all three restrictions.

When you expose your MCP server over HTTP, any authorised client — Claude.ai in a browser, Claude Desktop on a colleague's laptop, or a CI pipeline — can reach it by URL. That turns a personal automation into a team resource, or a prototype into a production service.

Streamable HTTP: The Current Standard

The MCP specification formalised Streamable HTTP as the standard remote transport in the 2025-03-26 spec revision, with further refinements in 2025-11-25. If you find older tutorials that describe an "HTTP+SSE" transport, that design is now deprecated — don't use it for new servers.

Streamable HTTP keeps the design simple:

  • A single endpoint — typically /mcp — handles all traffic.
  • The client sends JSON-RPC requests as HTTP POST bodies.
  • For short operations the server responds with a standard HTTP response and the JSON-RPC result.
  • For long-running operations the server upgrades the response to a Server-Sent Events (SSE) stream and pushes incremental messages until the operation completes.
  • Session identity is carried in the mcp-session-id header so the server can match follow-up requests to the right context.
  • A GET request to /mcp optionally opens a persistent SSE stream for server-initiated messages (useful for resource change notifications).
  • A DELETE request to /mcp signals a clean session teardown.

You don't have to implement this protocol by hand — the official SDK handles it through StreamableHTTPServerTransport.

TypeScript: StreamableHTTPServerTransport

Install the dependencies for an Express-based HTTP server:

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

Then wire up the transport:

import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";

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

const server = new McpServer({ name: "my-server", version: "1.0.0" });
// ... register tools here ...

const transport = new StreamableHTTPServerTransport({
  sessionIdGenerator: () => crypto.randomUUID()
});
await server.connect(transport);

app.post("/mcp", (req, res) => transport.handlePostRequest(req, res));
app.get("/mcp",  (req, res) => transport.handleGetRequest(req, res));
app.delete("/mcp", (req, res) => transport.handleDeleteRequest(req, res));

app.listen(3000, () => console.log("MCP server listening on port 3000"));

Three routes cover the full Streamable HTTP surface. The transport object manages session state and decides whether to respond inline or upgrade to SSE — your tool handlers don't need to know the difference.

Python: FastMCP HTTP Mode

FastMCP makes the switch to HTTP even simpler. Register your tools exactly as you would for stdio, then change the run() call:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("my-server")

@mcp.tool()
def greet(name: str) -> str:
    """Return a greeting."""
    return f"Hello, {name}!"

if __name__ == "__main__":
    mcp.run(transport="streamable-http", port=3000)

That's a single-line transport change. FastMCP handles the Express-equivalent routing internally.

Connecting to Claude.ai

Once your server is deployed and publicly reachable, add it to Claude.ai:

  1. Open Claude.ai and go to Settings > Integrations.
  2. Click Add Integration.
  3. Enter your server URL: https://yourserver.com/mcp.
  4. Claude.ai will perform a discovery handshake and list the server's available tools.

For clients that use a JSON config file (Claude Desktop, Cursor, Continue), remote servers use a "url" key instead of "command" + "args":

{
  "mcpServers": {
    "my-remote-server": {
      "url": "https://yourserver.com/mcp"
    }
  }
}

Stateless vs. Stateful Designs

Before you pick a deployment platform, decide whether your server needs state between requests.

Stateless servers treat every POST as independent. They don't store session data on the server; all context lives in the request itself or in an external database. Stateless designs deploy anywhere, including serverless platforms like Vercel and Cloudflare Workers where a new function instance handles each request.

Stateful servers maintain in-memory session state: open file handles, streaming progress, or subscription contexts. These need a persistent process — or an external store like Redis — so that the mcp-session-id header can be resolved to the right context. Platforms like Railway, Render, and Fly.io keep your process alive and work well here.

A basic tool server that calls an API and returns a result is naturally stateless. Only add statefulness when a feature genuinely requires it.

Deployment Options at a Glance

  • Railway — push a repo, get a persistent container with a public URL. Good default choice for stateful servers.
  • Render — similar to Railway; free tier available for low-traffic servers.
  • Fly.io — container-based with global edge nodes; good when latency matters.
  • Vercel — serverless functions; requires a stateless design but scales to zero automatically.
  • Cloudflare Workers — ultra-low-latency serverless at the edge; stateless only.

CORS Headers

If a browser-based client will call your server directly (rather than through Claude.ai's proxy), you'll need CORS headers. Add them before your route handlers:

app.use((req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", "https://claude.ai");
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id, Authorization");
  if (req.method === "OPTIONS") return res.sendStatus(204);
  next();
});

Scope Allow-Origin to the specific domains that need access — avoid * on authenticated servers.

What to Try Next

  • Deploy the TypeScript example above to Railway or Render and verify it appears in Claude.ai's Integrations panel.
  • Add a second tool to the server and confirm Claude can call both without restarting.
  • Try a Vercel deployment to see what breaks when you introduce stateful session logic — it's a useful lesson in stateless design constraints.
  • In the next lesson you'll add authentication so only authorised clients can call your remote server.
Key takeaways
  • Streamable HTTP is the current MCP standard for remote servers — it uses a single /mcp POST endpoint and replaces the deprecated HTTP+SSE transport
  • The client sends JSON-RPC over HTTP POST; the server can respond inline or upgrade to an SSE stream for multi-message responses
  • Python FastMCP switches to HTTP mode with one line: mcp.run(transport='streamable-http', port=3000)
  • Remote servers connect to Claude.ai via Settings > Integrations; Claude Desktop and Cursor connect via the 'url' key instead of 'command' + 'args' in the config
  • Stateless servers work on any platform including serverless (Vercel, Cloudflare Workers); stateful servers need sticky sessions or external state storage like Redis