Deploy an MCP server

A remote MCP server on a live HTTPS URL — any client can connect over the web, no local install.

MCP servers expose tools to AI clients. Run locally they talk over stdio; to share one over the web you serve the Streamable HTTP transport on the port Dockhold assigns. Then any MCP client connects to your URL. This recipe is the whole path.

In a hurry? Start from the ready-made mcp-server-starter template — click Use this template on GitHub, then deploy it. Or follow the steps below.

1. Serve MCP over Streamable HTTP

Use the official SDK and expose a single /mcp endpoint. In stateless mode you build a fresh server per request — simple, and it scales with no shared session state:

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

function buildServer() {
  const server = new McpServer({ name: "my-mcp", version: "1.0.0" });
  server.registerTool(
    "add",
    { title: "Add", description: "Add two numbers.", inputSchema: { a: z.number(), b: z.number() } },
    async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }] })
  );
  return server;
}

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

app.post("/mcp", async (req, res) => {
  const server = buildServer();
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
  res.on("close", () => { transport.close(); server.close(); });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

app.listen(process.env.PORT || 3000, "0.0.0.0");

Your app must listen on 0.0.0.0 and read its port from the PORT environment variable — never localhost, never a hardcoded port. Dockhold assigns PORT at runtime; an app that ignores it can't receive traffic.

2. Connect the repo and deploy

  1. Push the project to a GitHub repository.
  2. Open the dashboard, connect GitHub, and pick the repo.
  3. Dockhold detects the Node project and runs it — no Dockerfile needed.

Your server goes live at https://<your-app>.dockhold.app, with the MCP endpoint at /mcp. Every later push redeploys.

3. Connect a client

Point any MCP client at your /mcp URL using the Streamable HTTP transport.

  • MCP Inspector (quickest test): run npx @modelcontextprotocol/inspector, pick "Streamable HTTP", and enter your /mcp URL.
  • Claude Code: claude mcp add --transport http my-server https://<your-app>.dockhold.app/mcp
  • Other clients: add it as a remote / HTTP MCP server with the /mcp URL.

4. Lock it down

The endpoint is public by default — anyone with the URL can call your tools. To require a token, make it a private app (Access tab → Private → mint a token); clients that support custom headers then send Authorization: Bearer <token>. See the private-app pattern, and never expose a tool that acts on secrets without auth.

Troubleshooting

  • Client can't connect: confirm the transport is Streamable HTTP (not stdio or SSE) and the URL ends in /mcp.
  • 406 Not Acceptable: the client must send Accept: application/json, text/event-stream — every real MCP client does this automatically.
  • URL times out: the server must listen on 0.0.0.0:$PORT, not a fixed port.

Next