The MCP streamable-HTTP transport keeps a session alive across many requests from the same agent, which only works if the server backing it doesn't get recycled mid-conversation. An Express app on a DenkOps slot is one long-lived process, so the in-memory session map an MCP transport relies on stays intact for the full length of an agent's connection.
import express from "express";
import { z } from "zod";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const app = express();
app.use(express.json());
const server = new McpServer({ name: "denkops-tools", version: "1.0.0" });
server.tool("get_status", { node_id: z.string() }, async ({ node_id }) => ({
content: [{ type: "text", text: `node ${node_id} is running` }],
}));
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
app.post("/mcp", (req, res) => transport.handleRequest(req, res, req.body));
await server.connect(transport);
app.listen(3000);Deploy it: install the plugin, say "deploy on DenkOps", and get a live SSL URL.
Start on DenkOps →Yes. The transport's session state lives in the same long-running process as your Express app, and the slot never recycles it mid-session.
Yes, every deploy returns a live SSL-secured subdomain that stays the same across future deploys, ready to paste into any MCP client config.