#!/usr/bin/env node
/**
* HTTP Server for MinerU MCP
* Run with: npm run start:http or node dist/server.js
*/
import express from "express";
import { randomUUID } from "node:crypto";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import createServer, { configSchema } from "./index.js";
// Load config from environment
const config = configSchema.parse({
mineruApiKey: process.env.MINERU_API_KEY || "",
mineruBaseUrl: process.env.MINERU_BASE_URL,
mineruDefaultModel: process.env.MINERU_DEFAULT_MODEL,
});
// Express app setup
const app = express();
app.use(express.json());
// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
// Handle POST requests for client-to-server communication
app.post("/mcp", async (req, res) => {
const sessionId = req.headers["mcp-session-id"] as string | undefined;
let transport: StreamableHTTPServerTransport;
if (sessionId && transports[sessionId]) {
// Reuse existing transport
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
// New initialization request
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (newSessionId) => {
transports[newSessionId] = transport;
console.log(`Session initialized: ${newSessionId}`);
},
});
// Clean up transport when closed
transport.onclose = () => {
if (transport.sessionId) {
console.log(`Session closed: ${transport.sessionId}`);
delete transports[transport.sessionId];
}
};
// Create MCP server instance for this session
const server = createServer({ config });
await server.connect(transport);
} else {
// Invalid request
res.status(400).json({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Bad Request: No valid session ID provided",
},
id: null,
});
return;
}
// Handle the request
await transport.handleRequest(req, res, req.body);
});
// Handle GET requests for server-to-client notifications via SSE
app.get("/mcp", async (req, res) => {
const sessionId = req.headers["mcp-session-id"] as string | undefined;
if (!sessionId || !transports[sessionId]) {
res.status(400).send("Invalid or missing session ID");
return;
}
const transport = transports[sessionId];
await transport.handleRequest(req, res);
});
// Handle DELETE requests for session termination
app.delete("/mcp", async (req, res) => {
const sessionId = req.headers["mcp-session-id"] as string | undefined;
if (!sessionId || !transports[sessionId]) {
res.status(400).send("Invalid or missing session ID");
return;
}
const transport = transports[sessionId];
await transport.handleRequest(req, res);
});
// Health check endpoint
app.get("/health", (req, res) => {
res.json({ status: "ok", service: "mineru-mcp" });
});
// Start the HTTP server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`MinerU MCP HTTP Server v1.0.2 listening on port ${PORT}`);
console.log(`Endpoint: http://localhost:${PORT}/mcp`);
console.log(`Health: http://localhost:${PORT}/health`);
});