Add Streamable HTTP transport for cloud deployment
Summary
Add support for running the MCP server over HTTP using the MCP SDK's StreamableHTTPServerTransport, in addition to the existing stdio transport. This enables deploying the server as a hosted service (e.g., on Cloud Run, GKE, or any container platform) that clients can reach over the network.
Motivation
The server currently only supports stdio transport, which works for local MCP clients (Claude Code, VS Code, Gemini CLI, etc.) that spawn the server as a subprocess. However, many teams want to deploy the MCP server as a shared, always-on service accessible over HTTP — for example:
- Running on a container platform behind a load balancer
- Sharing a single instance across multiple users or agents
- Integrating with services that can't spawn local subprocesses
The MCP specification includes Streamable HTTP transport for exactly this use case.
Proposed changes
1. New file: src/transports/http.ts
An HTTP server that wraps the MCP server with StreamableHTTPServerTransport:
import { createServer } from "node:http";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
export function startHttpServer(
createMcpServer: () => McpServer,
port: number,
): void {
const httpServer = createServer(async (req, res) => {
if (req.method === "POST" && req.url === "/mcp") {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // stateless — each request is independent
});
const server = createMcpServer();
res.on("close", () => {
transport.close();
server.close();
});
await server.connect(transport);
await transport.handleRequest(req, res);
return;
}
if (req.method === "GET" && req.url === "/health") {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("ok");
return;
}
res.writeHead(404);
res.end();
});
httpServer.listen(port, () => {
console.log(`MCP server listening on port ${port}`);
});
}
Key design decisions:
- Stateless mode (
sessionIdGenerator: undefined) — each HTTP request is self-contained. No session affinity needed, works naturally with container autoscaling.
- Health check endpoint at
/health — required by most container platforms for liveness/readiness probes.
- Fresh server per request — avoids shared state across concurrent requests.
2. Update src/server.ts
Refactor runServer() to select transport based on environment:
export const runServer = async () => {
const port = process.env.PORT;
if (port) {
// HTTP mode — for cloud deployment
startHttpServer(createServer, parseInt(port, 10));
} else {
// Stdio mode — for local MCP clients (default, existing behavior)
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
}
};
When PORT is set (automatically provided by Cloud Run, GKE, Heroku, etc.), the server starts in HTTP mode. Otherwise it falls back to stdio — preserving full backward compatibility.
3. CLI flag (optional)
Add a --http flag as an explicit way to start in HTTP mode:
pdf-analyzer --http --port 8080
4. Dockerfile (optional, could be in docs instead)
FROM node:22-slim
WORKDIR /app
COPY package.json ./
RUN npm install --omit=dev
COPY dist/ ./dist/
ENV PORT=8080
EXPOSE 8080
CMD ["node", "dist/index.js"]
Endpoints
| Method |
Path |
Purpose |
POST |
/mcp |
MCP Streamable HTTP endpoint |
GET |
/health |
Health check (returns 200 ok) |
Backward compatibility
- No breaking changes. Without
PORT set, the server behaves exactly as it does today (stdio).
- The
--setup flow, provider system, and all tool logic are unchanged.
- Existing MCP client configurations (Claude Code, VS Code, etc.) continue to work.
Related
Add Streamable HTTP transport for cloud deployment
Summary
Add support for running the MCP server over HTTP using the MCP SDK's
StreamableHTTPServerTransport, in addition to the existing stdio transport. This enables deploying the server as a hosted service (e.g., on Cloud Run, GKE, or any container platform) that clients can reach over the network.Motivation
The server currently only supports stdio transport, which works for local MCP clients (Claude Code, VS Code, Gemini CLI, etc.) that spawn the server as a subprocess. However, many teams want to deploy the MCP server as a shared, always-on service accessible over HTTP — for example:
The MCP specification includes Streamable HTTP transport for exactly this use case.
Proposed changes
1. New file:
src/transports/http.tsAn HTTP server that wraps the MCP server with
StreamableHTTPServerTransport:Key design decisions:
sessionIdGenerator: undefined) — each HTTP request is self-contained. No session affinity needed, works naturally with container autoscaling./health— required by most container platforms for liveness/readiness probes.2. Update
src/server.tsRefactor
runServer()to select transport based on environment:When
PORTis set (automatically provided by Cloud Run, GKE, Heroku, etc.), the server starts in HTTP mode. Otherwise it falls back to stdio — preserving full backward compatibility.3. CLI flag (optional)
Add a
--httpflag as an explicit way to start in HTTP mode:4. Dockerfile (optional, could be in docs instead)
Endpoints
POST/mcpGET/health200 ok)Backward compatibility
PORTset, the server behaves exactly as it does today (stdio).--setupflow, provider system, and all tool logic are unchanged.Related