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
- Push the project to a GitHub repository.
- Open the dashboard, connect GitHub, and pick the repo.
- 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/mcpURL. - 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
/mcpURL.
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
- Deploy a plain Node API
- Agent rules — make your AI tool generate Dockhold-ready apps.
- All recipes