Remote MCP Server Generator
Generate a remote MCP server from a short form: list your tools and parameters, pick TypeScript or Python, and download a ZIP that serves MCP over Streamable HTTP at /mcp, with a Dockerfile, a /health check, a Host allowlist and copy-paste config for Claude Code, Cursor and VS Code. Everything runs in your browser. Tool handlers are stubs you replace, and there is no built-in authentication.
Build your remote MCP server
#!/usr/bin/env node
import { createServer } from "node:http";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";
const PORT = Number(process.env.PORT ?? 8000);
// 127.0.0.1 for local use; set HOST=0.0.0.0 inside Docker or on a server.
const HOST = process.env.HOST ?? "127.0.0.1";
const list = (v: string | undefined) =>
(v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
// Host headers we answer (DNS rebinding protection). Add your public
// hostname, e.g. ALLOWED_HOSTS=mcp.example.com
const ALLOWED_HOSTS = new Set([
`localhost:${PORT}`,
`127.0.0.1:${PORT}`,
...list(process.env.ALLOWED_HOSTS),
]);
// Browser Origins allowed to call /mcp. Requests without Origin (CLI and
// desktop clients) are allowed.
const ALLOWED_ORIGINS = new Set(list(process.env.ALLOWED_ORIGINS));
function buildServer(): McpServer {
const server = new McpServer({
name: "weather-remote",
version: "1.0.0",
});
server.registerTool(
"get_weather",
{
description: "Get current weather for a city",
inputSchema: z.object({
city: z.string().describe("City name, e.g. San Francisco")
}),
},
async ({ city }) => {
// TODO: replace this placeholder with real logic for get_weather
return {
content: [{ type: "text", text: "[get_weather] " + "city=" + JSON.stringify(city) }],
};
}
);
return server;
}
const httpServer = createServer(async (req, res) => {
const path = new URL(req.url ?? "/", "http://localhost").pathname;
if (path === "/health") {
res.writeHead(200, { "content-type": "text/plain" }).end("ok");
return;
}
if (path !== "/mcp") {
res.writeHead(404, { "content-type": "text/plain" }).end("Not found");
return;
}
if (!ALLOWED_HOSTS.has(req.headers.host ?? "")) {
res.writeHead(421, { "content-type": "text/plain" }).end("Invalid Host header");
return;
}
const origin = req.headers.origin;
if (origin && !ALLOWED_ORIGINS.has(origin)) {
res.writeHead(403, { "content-type": "text/plain" }).end("Invalid Origin header");
return;
}
// Stateless: a fresh server and transport for every request, so any
// number of clients (or replicas behind a load balancer) can call it.
const server = buildServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => {
transport.close();
server.close();
});
try {
await server.connect(transport);
await transport.handleRequest(req, res);
} catch (err) {
console.error(err);
if (!res.headersSent) {
res.writeHead(500, { "content-type": "text/plain" }).end("Internal server error");
}
}
});
httpServer.listen(PORT, HOST, () => {
console.error(`weather-remote MCP server listening on http://${HOST}:${PORT}/mcp`);
});
What the remote MCP server ZIP contains
| TypeScript | Python | |
|---|---|---|
| Server | src/index.ts: McpServer + StreamableHTTPServerTransport on node:http (no Express) | server.py: MCPServer.streamable_http_app() served by uvicorn |
| Endpoint | POST /mcp (JSON-RPC), GET /health returns ok, anything else 404 | |
| Mode | Stateless, JSON responses (no session id, no SSE stream), so replicas need no sticky sessions | |
| Security | Host allowlist (ALLOWED_HOSTS, else 421), browser Origin allowlist (ALLOWED_ORIGINS, else 403). No authentication. | |
| Config | PORT (default 8000) and HOST (default 127.0.0.1; the Dockerfile sets 0.0.0.0) | |
| Container | Dockerfile on node:22-slim, builds with tsc, runs as node | Dockerfile on python:3.12-slim, runs as a non-root user |
| Also | package.json, tsconfig.json, .dockerignore, README | requirements.txt, pyproject.toml, .dockerignore, README |
The stdio button gives you the same project as the home page generator (TypeScript) or the Python MCP server generator. If you start from an OpenAPI spec instead of a form, use the OpenAPI to MCP converter.
We ran both generated servers
On 2026-10-28 we generated a three-tool project (get_weather, add with a number, an optional number and a reserved-word boolean, and ping with no arguments) in both languages, started each with ALLOWED_HOSTS=mcp.example.com, and called it with curl and with the official TypeScript SDK client (StreamableHTTPClientTransport, the same transport Claude Code and other SDK-based clients use). TypeScript: Node 20.19.2, @modelcontextprotocol/sdk 1.32.2, tsc --strict clean. Python: 3.13.5, mcp 2.3.0, uvicorn 0.54.0.
| Check | TypeScript | Python |
|---|---|---|
GET /health | 200 ok | 200 ok |
initialize | OK, protocol 2025-06-18 | OK, protocol 2025-06-18 |
tools/list | 3 tools, required correct | 3 tools, required correct |
tools/call with a missing required argument | isError: true, "Input validation error … Required at city" | isError: true, "Field required" from pydantic |
| SDK client: connect, list, call | OK | OK |
Host: evil.example.com | 421 | 421 |
Origin: https://evil.example.com | 403 | 403 |
Host: mcp.example.com (allowed) | 200 | 200 |
The Dockerfiles follow the same commands but were not built in this run (no Docker engine on the test machine), so treat the first docker build as your own check.
Same check from your terminal, after npm start or python server.py:
curl -s http://127.0.0.1:8000/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2025-06-18' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
For the visual check, run npx @modelcontextprotocol/inspector, choose transport Streamable HTTP and enter the URL. More test recipes are in how to test an MCP server.
Deploy the remote MCP server
- Build and run the container.
docker build -t weather-remote .thendocker run -p 8000:8000 -e ALLOWED_HOSTS=mcp.example.com weather-remote. Inside the containerHOSTis0.0.0.0; on 127.0.0.1 the port mapping would have nothing to reach. - Give it HTTPS. Any container host works: a VM behind Caddy or nginx, Google Cloud Run, Fly.io, Railway, Render, AWS ECS or Kubernetes. Clients expect
https://…/mcp; terminate TLS at the proxy or platform. - Allow your hostname. Set
ALLOWED_HOSTSto the name clients use (comma-separated). The Python server also acceptsname:*for any port. Make sure the proxy passes the publicHostheader through, or every request gets421. - Add authentication before real data. The generated server has none. Use your platform's IAM (for example Cloud Run without unauthenticated access), a gateway or proxy that checks a bearer token, or MCP OAuth. How to test an OAuth-protected server is covered in testing MCP OAuth.
- Replace the stubs. Every handler echoes its arguments with a
TODO. For handlers that call a private REST API with a token and readable errors, copy the tested helpers from build an MCP server for an internal API.
Connect Claude Code, Cursor and VS Code to a remote MCP server
| Client | How to add the URL |
|---|---|
| Claude Code | claude mcp add --transport http weather-remote https://mcp.example.com/mcp |
| Cursor | .cursor/mcp.json: {"mcpServers": {"weather-remote": {"url": "https://mcp.example.com/mcp"}}} |
| VS Code | .vscode/mcp.json: {"servers": {"weather-remote": {"type": "http", "url": "https://mcp.example.com/mcp"}}} |
| Codex | ~/.codex/config.toml: [mcp_servers.weather-remote] with url = "https://mcp.example.com/mcp" |
| Claude Desktop | Not in claude_desktop_config.json; add the public HTTPS URL as a custom connector in the app's settings |
The MCP config generator writes and validates these files (including a bearer header and Codex TOML). For local stdio paths and troubleshooting, see connect an MCP server to Claude Desktop and Cursor.
Local stdio or remote HTTP: which one do you need?
- Stay on stdio when the tools are for you, on your machine: reading local files, running local commands, a personal API key. Nothing to host and nothing exposed.
- Go remote when a team, several machines, a hosted agent or a web app must use the same tools, when the tools live next to a database or service in your cloud, or when you do not want everyone to install Node or Python.
- Remote costs you hosting, TLS, authentication and monitoring. If you only need one person's editor to see the tools, stdio is simpler. New to the protocol? Start with what is an MCP server.
Limitations (honest)
- No authentication in the generated code; see the deploy steps.
- Stateless JSON only. No sessions, no SSE streams, no server-to-client notifications or resumable streams. Fine for tools; switch to a session-based setup if you need streaming progress.
- Tools only. No resources or prompts; the MCP server template guide shows how to add them.
- Simple parameter types (string, number, boolean). For enums, arrays, annotations or outputSchema, use the MCP tool schema generator.
FAQ
How do I create a remote MCP server?
Pick Streamable HTTP and TypeScript or Python in the form above, add your tools, and download the ZIP. Run it locally with npm start or python server.py, check http://127.0.0.1:8000/mcp with curl or the MCP Inspector, then build the included Dockerfile and run the container on any host that gives you an HTTPS URL. Set ALLOWED_HOSTS to your public hostname and add authentication before the tools touch real data.
What is the difference between a local (stdio) and a remote (Streamable HTTP) MCP server?
A stdio server is a local process that the host app starts and talks to over stdin and stdout; only that one app on that one machine can use it. A remote server is an HTTP service: clients POST JSON-RPC messages to one URL such as https://mcp.example.com/mcp, so many users, machines and hosted agents can share it. Streamable HTTP is the current remote transport; the older HTTP+SSE transport is deprecated.
Is the generated remote MCP server stateless?
Yes. Every POST to /mcp gets a fresh server and transport (TypeScript) or runs in stateless_http mode (Python), and responses are plain JSON instead of an SSE stream. Nothing has to be kept between requests, so you can run several replicas behind a load balancer without sticky sessions. If you need server-to-client notifications or long-running streams, switch to a session-based transport.
Does the generated server include authentication?
No. It checks the Host header (DNS rebinding protection) and rejects unknown browser Origins, but anyone who can reach the URL can call the tools. Put it behind your platform's IAM, an API gateway or reverse proxy that checks a bearer token, or implement MCP OAuth before you expose tools that read or change real data.
Why do I get 421 Invalid Host header after deploying?
The server only answers Host headers it knows: localhost and 127.0.0.1 on its port, plus whatever you put in ALLOWED_HOSTS. Behind a real domain every request has Host: mcp.example.com, so set ALLOWED_HOSTS=mcp.example.com (for the Python server also mcp.example.com:* if clients send a port) and make sure your proxy passes the public Host header through.
How do I connect Claude Code, Cursor or VS Code to the remote server?
Claude Code: claude mcp add --transport http my-server https://mcp.example.com/mcp. Cursor: add {"mcpServers": {"my-server": {"url": "https://mcp.example.com/mcp"}}} to .cursor/mcp.json. VS Code: add {"servers": {"my-server": {"type": "http", "url": "https://mcp.example.com/mcp"}}} to .vscode/mcp.json. Claude Desktop does not read remote URLs from claude_desktop_config.json; add the HTTPS URL as a custom connector in its settings.
Where can I deploy the remote MCP server for free or cheaply?
The ZIP is a normal container (Node 22 or Python 3.12 image listening on port 8000), so it runs anywhere that runs containers: a small VM behind Caddy or nginx, Google Cloud Run, Fly.io, Railway, Render, AWS ECS or Kubernetes. Free tiers change often, so check the provider's current pricing. Because the server is stateless it also scales to zero cleanly on platforms that support it.