Skip to main content
Glama

Beehiiv Analytics MCP Server

by ousepachn
server.js13.5 kB
#!/usr/bin/env node console.error("=== BEEHIIV MCP SERVER STARTING ==="); // Check API key const apiKey = process.env.BEEHIIV_API_KEY; if (!apiKey) { console.error("❌ BEEHIIV_API_KEY environment variable is required"); process.exit(1); } console.error("✓ API key found, length:", apiKey.length); // HTTP request helper function makeRequest(method, url, headers = {}) { return new Promise((resolve, reject) => { console.error(`Making ${method} request to:`, url); const urlObj = new URL(url); const options = { hostname: urlObj.hostname, port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80), path: urlObj.pathname + urlObj.search, method: method, headers: { 'Content-Type': 'application/json', 'User-Agent': 'Beehiiv-MCP-Server/1.0.0', ...headers }, timeout: 30000 }; const req = (urlObj.protocol === 'https:' ? require('https') : require('http')).request(options, (res) => { console.error(`Response status: ${res.statusCode}`); let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { console.error(`Response data length: ${data.length} bytes`); try { const parsed = JSON.parse(data); if (res.statusCode >= 200 && res.statusCode < 300) { resolve(parsed); } else { reject(new Error(`HTTP ${res.statusCode}: ${parsed.message || 'Unknown error'}`)); } } catch (error) { reject(new Error(`Failed to parse response: ${error.message}`)); } }); }); req.on('error', (error) => { console.error(`Request error:`, error.message); reject(error); }); req.on('timeout', () => { console.error(`Request timeout`); req.destroy(); reject(new Error('Request timeout')); }); req.end(); }); } // Beehiiv API client class BeehiivAPI { constructor(apiKey) { this.apiKey = apiKey; this.baseUrl = 'https://api.beehiiv.com/v2'; this.headers = { 'Authorization': `Bearer ${apiKey}` }; console.error("✓ BeehiivAPI initialized"); } async getPublications() { return await makeRequest('GET', `${this.baseUrl}/publications`, this.headers); } async getPublicationDetails(publicationId) { return await makeRequest('GET', `${this.baseUrl}/publications/${publicationId}`, this.headers); } async getPosts(publicationId, limit = 10) { return await makeRequest('GET', `${this.baseUrl}/publications/${publicationId}/posts?limit=${limit}`, this.headers); } async getPostDetails(publicationId, postId) { return await makeRequest('GET', `${this.baseUrl}/publications/${publicationId}/posts/${postId}`, this.headers); } async getSubscribers(publicationId, limit = 10) { return await makeRequest('GET', `${this.baseUrl}/publications/${publicationId}/subscribers?limit=${limit}`, this.headers); } async getSubscriberDetails(publicationId, subscriberId) { return await makeRequest('GET', `${this.baseUrl}/publications/${publicationId}/subscribers/${subscriberId}`, this.headers); } async getSegments(publicationId) { return await makeRequest('GET', `${this.baseUrl}/publications/${publicationId}/segments`, this.headers); } async getSegmentDetails(publicationId, segmentId) { return await makeRequest('GET', `${this.baseUrl}/publications/${publicationId}/segments/${segmentId}`, this.headers); } } const client = new BeehiivAPI(apiKey); let isInitialized = false; function sendResponse(id, result, error = null) { const response = { jsonrpc: "2.0", id: id }; if (error) { response.error = error; } else { response.result = result; } console.error("Sending response:", JSON.stringify(response).substring(0, 100) + "..."); console.log(JSON.stringify(response)); } function sendNotification(method, params = {}) { const notification = { jsonrpc: "2.0", method: method, params: params }; console.error("Sending notification:", method); console.log(JSON.stringify(notification)); } process.stdin.on('data', (chunk) => { const data = chunk.toString(); console.error("Received:", data.substring(0, 100) + "..."); try { const request = JSON.parse(data); console.error("Parsed request:", request.method, "ID:", request.id); switch (request.method) { case 'initialize': console.error("Handling initialize request"); sendResponse(request.id, { protocolVersion: "2025-06-18", capabilities: { tools: {}, prompts: {}, resources: {} }, serverInfo: { name: "beehiiv-mcp-server", version: "1.0.0" } }); break; case 'notifications/initialized': console.error("Handling notifications/initialized - marking server as initialized"); // Mark server as initialized immediately isInitialized = true; // Send our initialized notification immediately sendNotification("initialized"); console.error("✓ Server initialized successfully"); // Don't send a response - notifications don't get responses return; case 'tools/list': console.error("Handling tools/list request"); if (!isInitialized) { sendResponse(request.id, null, { code: -32002, message: "Server not initialized" }); return; } sendResponse(request.id, { tools: [ { name: "get_publications", description: "Get list of publications", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get_publication_details", description: "Get detailed information about a specific publication", inputSchema: { type: "object", properties: { publication_id: { type: "string", description: "The ID of the publication" } }, required: ["publication_id"] } }, { name: "get_posts", description: "Get posts for a publication", inputSchema: { type: "object", properties: { publication_id: { type: "string", description: "The ID of the publication" }, limit: { type: "number", description: "Number of posts to return (default: 10)", default: 10 } }, required: ["publication_id"] } }, { name: "get_post_details", description: "Get detailed information about a specific post", inputSchema: { type: "object", properties: { publication_id: { type: "string", description: "The ID of the publication" }, post_id: { type: "string", description: "The ID of the post" } }, required: ["publication_id", "post_id"] } }, { name: "get_subscribers", description: "Get subscribers for a publication", inputSchema: { type: "object", properties: { publication_id: { type: "string", description: "The ID of the publication" }, limit: { type: "number", description: "Number of subscribers to return (default: 10)", default: 10 } }, required: ["publication_id"] } }, { name: "get_subscriber_details", description: "Get detailed information about a specific subscriber", inputSchema: { type: "object", properties: { publication_id: { type: "string", description: "The ID of the publication" }, subscriber_id: { type: "string", description: "The ID of the subscriber" } }, required: ["publication_id", "subscriber_id"] } }, { name: "get_segments", description: "Get segments for a publication", inputSchema: { type: "object", properties: { publication_id: { type: "string", description: "The ID of the publication" } }, required: ["publication_id"] } }, { name: "get_segment_details", description: "Get detailed information about a specific segment", inputSchema: { type: "object", properties: { publication_id: { type: "string", description: "The ID of the publication" }, segment_id: { type: "string", description: "The ID of the segment" } }, required: ["publication_id", "segment_id"] } } ] }); break; case 'prompts/list': console.error("Handling prompts/list request"); if (!isInitialized) { sendResponse(request.id, null, { code: -32002, message: "Server not initialized" }); return; } sendResponse(request.id, { prompts: [] }); break; case 'resources/list': console.error("Handling resources/list request"); if (!isInitialized) { sendResponse(request.id, null, { code: -32002, message: "Server not initialized" }); return; } sendResponse(request.id, { resources: [] }); break; case 'tools/call': console.error("Handling tools/call request"); if (!isInitialized) { sendResponse(request.id, null, { code: -32002, message: "Server not initialized" }); return; } const { name, arguments: args } = request.params; console.error(`Executing tool: ${name} with args:`, args); // Handle all 8 tools with real API calls (async) (async () => { try { let result; switch (name) { case 'get_publications': result = await client.getPublications(); break; case 'get_publication_details': result = await client.getPublicationDetails(args.publication_id); break; case 'get_posts': result = await client.getPosts(args.publication_id, args.limit || 10); break; case 'get_post_details': result = await client.getPostDetails(args.publication_id, args.post_id); break; case 'get_subscribers': result = await client.getSubscribers(args.publication_id, args.limit || 10); break; case 'get_subscriber_details': result = await client.getSubscriberDetails(args.publication_id, args.subscriber_id); break; case 'get_segments': result = await client.getSegments(args.publication_id); break; case 'get_segment_details': result = await client.getSegmentDetails(args.publication_id, args.segment_id); break; default: throw new Error(`Unknown tool: ${name}`); } sendResponse(request.id, { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }); } catch (error) { console.error(`Tool execution error:`, error.message); sendResponse(request.id, null, { code: -32603, message: `Tool execution failed: ${error.message}` }); } })(); break; default: console.error("Unknown method:", request.method); sendResponse(request.id, null, { code: -32601, message: "Method not found" }); } } catch (error) { console.error("Error:", error.message); } }); process.stdin.on('end', () => { console.error("=== STDIN ENDED ==="); process.exit(0); }); console.error("=== SERVER READY ===");

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ousepachn/beehiivanalyticsMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server