@rockship/apollo-io-mcp
Version:
A powerful Model Context Protocol (MCP) server implementation for seamless Apollo.io API integration, enabling AI assistants to interact with Apollo.io data
104 lines • 4.22 kB
JavaScript
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import express from "express";
import { randomUUID } from "node:crypto";
import { createMcpServer } from "../common/mcp-server.js";
import { getServerConfig, setupExpressApp, setupGracefulShutdown, setupHealthCheck, setupListenServer, } from "../common/server-utils.js";
let server;
function createHttpStreamServer() {
const app = express();
const transports = {};
setupExpressApp(app);
setupHealthCheck(app, transports);
// Handle POST requests for client-to-server communication
app.post("/mcp", async (req, res) => {
const apiKey = req.query.apiKey || process.env.APOLLO_IO_API_KEY;
if (!apiKey) {
res.status(400).json({ error: "API key is required" });
return;
}
// Check for existing session ID
const sessionId = req.headers["mcp-session-id"];
let transport;
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: (sessionId) => {
// Store the transport by session ID
transports[sessionId] = transport;
console.log(`Streamable connection established with sessionId: ${sessionId}`);
},
// DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
// locally, make sure to set:
// enableDnsRebindingProtection: true,
// allowedHosts: ['127.0.0.1'],
});
// Clean up transport when closed
transport.onclose = () => {
if (transport.sessionId) {
delete transports[transport.sessionId];
console.log(`Streamable connection closed for sessionId: ${transport.sessionId}`);
}
};
if (!server) {
server = createMcpServer(apiKey);
}
// Connect to the MCP server
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);
});
// Reusable handler for GET and DELETE requests
const handleSessionRequest = async (req, res) => {
const sessionId = req.headers["mcp-session-id"];
if (!sessionId || !transports[sessionId]) {
res.status(400).send("Invalid or missing session ID");
return;
}
const transport = transports[sessionId];
await transport.handleRequest(req, res);
};
// Handle GET requests for server-to-client notifications via SSE
app.get("/mcp", handleSessionRequest);
// Handle DELETE requests for session termination
app.delete("/mcp", handleSessionRequest);
return app;
}
/**
* Start the SSE server with proper error handling and graceful shutdown
*/
async function startServer() {
try {
const config = getServerConfig("HTTP_PORT", "8081");
const httpStreamServer = createHttpStreamServer();
const server = setupListenServer(httpStreamServer, "HttpStream", config);
setupGracefulShutdown(server, "HttpStream");
}
catch (error) {
console.error("❌ Failed to start server:", error);
process.exit(1);
}
}
startServer().catch((error) => {
console.error("❌ Server startup failed:", error);
process.exit(1);
});
//# sourceMappingURL=index.js.map