@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
142 lines • 5.84 kB
JavaScript
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
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 createHttpCombinedServer() {
const app = express();
const transports = {
streamable: {},
sse: {},
};
setupExpressApp(app);
setupHealthCheck(app, transports);
// Handle POST requests for client-to-server communication
app.all("/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.streamable[sessionId]) {
// Reuse existing transport
transport = transports.streamable[sessionId];
}
else if (!sessionId && isInitializeRequest(req.body)) {
// New initialization request
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sessionId) => {
// Store the transport by session ID
if (transport instanceof StreamableHTTPServerTransport) {
transports.streamable[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.streamable[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.streamable[sessionId]) {
res.status(400).send("Invalid or missing session ID");
return;
}
const transport = transports.streamable[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);
// Legacy SSE endpoint for older clients
app.get("/sse", async (req, res) => {
// Create SSE transport for legacy clients
const transport = new SSEServerTransport("/messages", res);
transports.sse[transport.sessionId] = transport;
const apiKey = req.query.apiKey || process.env.APOLLO_IO_API_KEY;
if (!apiKey) {
res.status(400).json({ error: "API key is required" });
return;
}
if (!server) {
server = createMcpServer(apiKey);
}
res.on("close", () => {
delete transports.sse[transport.sessionId];
console.log(`SSE connection established with sessionId: ${transport.sessionId}`);
});
// Connect to the MCP server
await server.connect(transport);
console.log(`SSE connection established with sessionId: ${transport.sessionId}`);
});
// Legacy message endpoint for older clients
app.post("/messages", async (req, res) => {
const sessionId = req.query.sessionId;
const transport = transports.sse[sessionId];
if (transport) {
await transport.handlePostMessage(req, res, req.body);
}
else {
res.status(400).send("No transport found for sessionId");
}
});
return app;
}
/**
* Start the SSE server with proper error handling and graceful shutdown
*/
async function startServer() {
try {
const config = getServerConfig("HTTP_PORT", "8082");
const httpCombinedServer = createHttpCombinedServer();
const server = setupListenServer(httpCombinedServer, "HttpCombined", config);
setupGracefulShutdown(server, "HttpCombined");
}
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