Skip to main content
Glama
wisesight

Zocialeye MCP Server

by wisesight
index.ts10.8 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; import express from 'express'; import cors from 'cors'; const APP_NAME = 'zocialeye-mcp'; const APP_VERSION = '1.0.0'; const API_BASE_URL = process.env.ZOCIALEYE_API_URL || 'https://apix.zocialeye.com/api/v1'; const PORT = parseInt(process.env.PORT || '3000', 10); // Simple logger function log(level: string, message: string, ...args: any[]) { const timestamp = new Date().toISOString(); console.log(`[${timestamp}] [${level}] ${message}`, ...args); } // Get default date range (last 7 days) function getDefaultDateRange() { const now = Math.floor(Date.now() / 1000); const sevenDaysAgo = now - (7 * 24 * 60 * 60); return { date_start: sevenDaysAgo, date_end: now }; } // Convert date to Unix timestamp function toUnixTimestamp(dateInput?: string | number): number | undefined { if (!dateInput) return undefined; if (typeof dateInput === 'number') return dateInput; const date = new Date(dateInput); if (isNaN(date.getTime())) { throw new Error(`Invalid date: ${dateInput}. Use ISO 8601 format.`); } return Math.floor(date.getTime() / 1000); } // Create MCP server instance function createMCPServer(apiKey: string, campaignId: string) { const server = new Server( { name: APP_NAME, version: APP_VERSION }, { capabilities: { tools: {} } } ); // Define tools server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'get_campaign_wordcloud', description: 'Get word cloud data for a campaign', inputSchema: { type: 'object', properties: { date_start: { type: 'string', description: 'Start date in ISO 8601 format (default: 7 days ago)' }, date_end: { type: 'string', description: 'End date in ISO 8601 format (default: now)' }, group_by: { type: 'string', enum: ['category', 'group', 'sentiment', 'keywords', 'all'], }, filter: { type: 'object' }, }, }, }, { name: 'get_campaign_messages', description: 'Get messages/posts for a campaign', inputSchema: { type: 'object', properties: { date_start: { type: 'string', description: 'Start date in ISO 8601 format (default: 7 days ago)' }, date_end: { type: 'string', description: 'End date in ISO 8601 format (default: now)' }, total: { type: 'number', maximum: 50 }, from: { type: 'number', default: 0 }, sort_by: { type: 'object' }, group_by: { type: 'string', enum: ['category', 'group', 'sentiment', 'keywords', 'channel', 'all'], }, filter: { type: 'object' }, }, }, }, { name: 'get_campaign_summary', description: 'Get summary statistics for a campaign', inputSchema: { type: 'object', properties: { date_start: { type: 'string', description: 'Start date in ISO 8601 format (default: 7 days ago)' }, date_end: { type: 'string', description: 'End date in ISO 8601 format (default: now)' }, duration: { type: 'string', enum: ['hour', 'day'], default: 'day' }, group_by: { type: 'string', enum: ['category', 'group', 'sentiment', 'keyword', 'all'], }, filter: { type: 'object' }, }, }, }, { name: 'get_campaign_influencers', description: 'Get top influencers for a campaign', inputSchema: { type: 'object', properties: { date_start: { type: 'string', description: 'Start date in ISO 8601 format (default: 7 days ago)' }, date_end: { type: 'string', description: 'End date in ISO 8601 format (default: now)' }, group_by: { type: 'string', enum: ['category', 'group', 'sentiment', 'keyword', 'all'], }, filter: { type: 'object' }, }, }, }, { name: 'get_campaign_categories', description: 'Get all categories for a campaign', inputSchema: { type: 'object', properties: {} }, }, { name: 'get_campaign_keywords', description: 'Get all keywords for a campaign', inputSchema: { type: 'object', properties: {} }, }, ] as Tool[], })); // Make API request async function apiRequest(endpoint: string, params: any = {}, method = 'POST') { try { const response = await axios({ method, url: `${API_BASE_URL}${endpoint}`, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, }, ...(method === 'POST' ? { data: params } : { params }), timeout: 30000, }); return response.data; } catch (error: any) { throw new Error(`API Error: ${error.response?.status} ${error.response?.statusText || error.message}`); } } // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args = {} } = request.params; const defaults = getDefaultDateRange(); let data; switch (name) { case 'get_campaign_wordcloud': { const params: any = { date_start: toUnixTimestamp((args as any).date_start) || defaults.date_start, date_end: toUnixTimestamp((args as any).date_end) || defaults.date_end, }; if ((args as any).group_by) params.group_by = (args as any).group_by; if ((args as any).filter) params.filter = (args as any).filter; data = await apiRequest(`/campaigns/${campaignId}/wordcloud`, params); break; } case 'get_campaign_messages': { const params: any = { date_start: toUnixTimestamp((args as any).date_start) || defaults.date_start, date_end: toUnixTimestamp((args as any).date_end) || defaults.date_end, }; if ((args as any).total) params.total = (args as any).total; if ((args as any).from) params.from = (args as any).from; if ((args as any).sort_by) params.sort_by = (args as any).sort_by; if ((args as any).group_by) params.group_by = (args as any).group_by; if ((args as any).filter) params.filter = (args as any).filter; data = await apiRequest(`/campaigns/${campaignId}/messages`, params); break; } case 'get_campaign_summary': { const params: any = { date_start: toUnixTimestamp((args as any).date_start) || defaults.date_start, date_end: toUnixTimestamp((args as any).date_end) || defaults.date_end, duration: (args as any).duration || 'day', }; if ((args as any).group_by) params.group_by = (args as any).group_by; if ((args as any).filter) params.filter = (args as any).filter; data = await apiRequest(`/campaigns/${campaignId}/summary`, params); break; } case 'get_campaign_influencers': { const params: any = { date_start: toUnixTimestamp((args as any).date_start) || defaults.date_start, date_end: toUnixTimestamp((args as any).date_end) || defaults.date_end, }; if ((args as any).group_by) params.group_by = (args as any).group_by; if ((args as any).filter) params.filter = (args as any).filter; data = await apiRequest(`/campaigns/${campaignId}/influencers`, params); break; } case 'get_campaign_categories': data = await apiRequest(`/campaigns/${campaignId}/categories`, {}, 'GET'); break; case 'get_campaign_keywords': data = await apiRequest(`/campaigns/${campaignId}/keywords`, {}, 'GET'); break; default: throw new Error(`Unknown tool: ${name}`); } return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }], }; } catch (error: any) { return { content: [{ type: 'text' as const, text: `Error: ${error.message}` }], isError: true, }; } }); return server; } // Express app const app = express(); app.use(cors()); app.use(express.json()); // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', service: APP_NAME, version: APP_VERSION }); }); // Store MCP servers per campaign+apiKey const mcpServers = new Map<string, { server: Server; transport: StreamableHTTPServerTransport }>(); // Streamable HTTP endpoint app.use('/:campaignId/mcp', async (req, res) => { const campaignId = req.params.campaignId; const authHeader = req.headers.authorization; // Validate campaign ID is numeric if (!/^\d+$/.test(campaignId)) { return res.status(401).json({ error: 'Invalid campaign ID. Must be numeric.' }); } // Extract API key from Authorization header if (!authHeader?.startsWith('Bearer ')) { return res.status(401).json({ error: 'Missing Authorization header. Use: Authorization: Bearer <your-api-key>' }); } const apiKey = authHeader.substring(7).trim(); if (!apiKey) { return res.status(401).json({ error: 'API key cannot be empty' }); } log('INFO', `MCP request: campaign=${campaignId}, method=${req.method}`); try { // Create server instance if not exists const key = `${campaignId}:${apiKey}`; if (!mcpServers.has(key)) { const mcpServer = createMCPServer(apiKey, campaignId); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, // Stateless mode }); await mcpServer.connect(transport); mcpServers.set(key, { server: mcpServer, transport }); log('INFO', `Created MCP server instance for campaign=${campaignId}`); } // Handle request with transport const { transport } = mcpServers.get(key)!; await transport.handleRequest(req, res, req.body); } catch (error: any) { log('ERROR', `Request error: ${error.message}`); if (!res.headersSent) { res.status(500).json({ error: 'Request failed' }); } } }); // Start server app.listen(PORT, () => { log('INFO', `${APP_NAME} v${APP_VERSION} running on port ${PORT}`); log('INFO', `Endpoint: http://localhost:${PORT}/{campaign_id}/mcp`); log('INFO', `Transport: Streamable HTTP`); });

Latest Blog Posts

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/wisesight/zocialeye-mcp'

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