UNPKG

instagram-engagement-mcp

Version:

Instagram Engagement Analysis MCP Server for analyzing engagement metrics and identifying leads

591 lines (590 loc) 24.1 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { IgApiClient } from 'instagram-private-api'; import * as dotenv from 'dotenv'; // Load environment variables dotenv.config(); // Instagram API credentials const INSTAGRAM_USERNAME = process.env.INSTAGRAM_USERNAME; const INSTAGRAM_PASSWORD = process.env.INSTAGRAM_PASSWORD; // Validate required environment variables if (!INSTAGRAM_USERNAME || !INSTAGRAM_PASSWORD) { console.error('[Error] Missing required environment variables: INSTAGRAM_USERNAME and INSTAGRAM_PASSWORD'); process.exit(1); } // Utility function to validate post URL const isValidPostUrl = (url) => { return /^https:\/\/(www\.)?instagram\.com\/p\/[A-Za-z0-9_-]+\/?/.test(url); }; // Utility function to extract post ID from URL const extractPostIdFromUrl = (url) => { const match = url.match(/\/p\/([A-Za-z0-9_-]+)/); return match ? match[1] : ''; }; // Utility function to validate Instagram username const isValidUsername = (username) => { return /^[A-Za-z0-9._]+$/.test(username); }; class InstagramEngagementServer { constructor() { this.isLoggedIn = false; console.error('[Setup] Initializing Instagram Engagement MCP server...'); this.server = new Server({ name: 'instagram-engagement-server', version: '0.1.0', }, { capabilities: { tools: {}, }, }); this.ig = new IgApiClient(); this.setupToolHandlers(); // Error handling this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } async loginToInstagram() { if (this.isLoggedIn) return true; try { console.error('[Auth] Attempting to log in to Instagram...'); this.ig.state.generateDevice(INSTAGRAM_USERNAME); await this.ig.account.login(INSTAGRAM_USERNAME, INSTAGRAM_PASSWORD); this.isLoggedIn = true; console.error('[Auth] Successfully logged in to Instagram'); return true; } catch (error) { console.error('[Auth Error] Failed to log in to Instagram:', error); return false; } } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'analyze_post_comments', description: 'Analyze comments on an Instagram post to identify sentiment, themes, and potential leads', inputSchema: { type: 'object', properties: { postUrl: { type: 'string', description: 'URL of the Instagram post to analyze', }, maxComments: { type: 'number', description: 'Maximum number of comments to analyze (default: 100)', }, }, required: ['postUrl'], }, }, { name: 'compare_accounts', description: 'Compare engagement metrics across different Instagram accounts', inputSchema: { type: 'object', properties: { accounts: { type: 'array', items: { type: 'string', }, description: 'List of Instagram account handles to compare', }, metrics: { type: 'array', items: { type: 'string', enum: ['followers', 'engagement', 'posts', 'comments', 'likes'], }, description: 'Metrics to compare (default: all)', }, }, required: ['accounts'], }, }, { name: 'extract_demographics', description: 'Extract demographic insights from users engaged with a post or account', inputSchema: { type: 'object', properties: { accountOrPostUrl: { type: 'string', description: 'Instagram account handle or post URL to analyze', }, sampleSize: { type: 'number', description: 'Number of users to sample for demographic analysis (default: 50)', }, }, required: ['accountOrPostUrl'], }, }, { name: 'identify_leads', description: 'Identify potential leads based on engagement patterns', inputSchema: { type: 'object', properties: { accountOrPostUrl: { type: 'string', description: 'Instagram account handle or post URL to analyze', }, criteria: { type: 'object', properties: { minComments: { type: 'number', description: 'Minimum number of comments from a user', }, minFollowers: { type: 'number', description: 'Minimum number of followers a user should have', }, keywords: { type: 'array', items: { type: 'string', }, description: 'Keywords to look for in user comments or bio', }, }, description: 'Criteria for identifying leads', }, }, required: ['accountOrPostUrl'], }, }, { name: 'generate_engagement_report', description: 'Generate a comprehensive engagement report for an Instagram account', inputSchema: { type: 'object', properties: { account: { type: 'string', description: 'Instagram account handle', }, startDate: { type: 'string', description: 'Start date for the report (YYYY-MM-DD)', }, endDate: { type: 'string', description: 'End date for the report (YYYY-MM-DD)', }, }, required: ['account'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { console.error(`[Tool] Request to execute tool: ${request.params.name}`); // Ensure we're logged in to Instagram const loggedIn = await this.loginToInstagram(); if (!loggedIn) { throw new McpError(ErrorCode.InternalError, 'Failed to authenticate with Instagram API'); } const args = request.params.arguments || {}; switch (request.params.name) { case 'analyze_post_comments': return this.handleAnalyzePostComments(args); case 'compare_accounts': return this.handleCompareAccounts(args); case 'extract_demographics': return this.handleExtractDemographics(args); case 'identify_leads': return this.handleIdentifyLeads(args); case 'generate_engagement_report': return this.handleGenerateReport(args); default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); } }); } async handleAnalyzePostComments(args) { console.error('[Tool] Analyzing post comments:', args); if (!isValidPostUrl(args.postUrl)) { return { content: [ { type: 'text', text: 'Invalid Instagram post URL. Please provide a valid URL in the format: https://www.instagram.com/p/CODE/', }, ], isError: true, }; } const maxComments = args.maxComments || 100; const postId = extractPostIdFromUrl(args.postUrl); try { // In a real implementation, we would use the Instagram API to fetch comments // For this example, we'll simulate the response // Simulated analysis results const analysisResults = { postUrl: args.postUrl, totalComments: 245, analyzedComments: Math.min(245, maxComments), sentiment: { positive: 65, neutral: 25, negative: 10, }, commonThemes: [ { theme: 'Product quality', mentions: 42 }, { theme: 'Customer service', mentions: 28 }, { theme: 'Price', mentions: 15 }, ], topKeywords: [ { word: 'love', count: 37 }, { word: 'great', count: 25 }, { word: 'awesome', count: 18 }, ], potentialLeads: [ { username: 'user123', engagementScore: 8.5, comments: 3 }, { username: 'influencer456', engagementScore: 7.9, comments: 2 }, ], }; return { content: [ { type: 'text', text: JSON.stringify(analysisResults, null, 2), }, ], }; } catch (error) { console.error('[Error] Failed to analyze post comments:', error); return { content: [ { type: 'text', text: `Error analyzing post comments: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } async handleCompareAccounts(args) { console.error('[Tool] Comparing accounts:', args); // Validate account handles const invalidAccounts = args.accounts.filter(account => !isValidUsername(account)); if (invalidAccounts.length > 0) { return { content: [ { type: 'text', text: `Invalid Instagram account handles: ${invalidAccounts.join(', ')}`, }, ], isError: true, }; } try { // In a real implementation, we would use the Instagram API to fetch account data // For this example, we'll simulate the response // Simulated comparison results const comparisonResults = { accounts: args.accounts, metrics: { followers: { [args.accounts[0]]: 15420, [args.accounts[1]]: 8750, }, engagement: { [args.accounts[0]]: 3.2, [args.accounts[1]]: 4.7, }, posts: { [args.accounts[0]]: 342, [args.accounts[1]]: 187, }, comments: { [args.accounts[0]]: 28, [args.accounts[1]]: 35, }, likes: { [args.accounts[0]]: 420, [args.accounts[1]]: 380, }, }, insights: [ `${args.accounts[1]} has a higher engagement rate despite fewer followers`, `${args.accounts[0]} posts more frequently but receives fewer comments per post`, ], }; return { content: [ { type: 'text', text: JSON.stringify(comparisonResults, null, 2), }, ], }; } catch (error) { console.error('[Error] Failed to compare accounts:', error); return { content: [ { type: 'text', text: `Error comparing accounts: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } async handleExtractDemographics(args) { console.error('[Tool] Extracting demographics:', args); const sampleSize = args.sampleSize || 50; try { // In a real implementation, we would use the Instagram API to fetch user data // For this example, we'll simulate the response // Simulated demographics results const demographicsResults = { source: args.accountOrPostUrl, sampleSize: sampleSize, demographics: { age: { '18-24': 35, '25-34': 42, '35-44': 15, '45+': 8, }, gender: { 'female': 62, 'male': 36, 'other': 2, }, location: { 'United States': 45, 'United Kingdom': 12, 'Canada': 8, 'Australia': 7, 'Other': 28, }, interests: [ { category: 'Fashion', percentage: 48 }, { category: 'Technology', percentage: 35 }, { category: 'Travel', percentage: 32 }, { category: 'Fitness', percentage: 28 }, ], }, insights: [ 'Predominantly female audience in the 25-34 age range', 'Strong interest in fashion and technology', 'Significant engagement from North America', ], }; return { content: [ { type: 'text', text: JSON.stringify(demographicsResults, null, 2), }, ], }; } catch (error) { console.error('[Error] Failed to extract demographics:', error); return { content: [ { type: 'text', text: `Error extracting demographics: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } async handleIdentifyLeads(args) { console.error('[Tool] Identifying leads:', args); try { // In a real implementation, we would use the Instagram API to fetch user data // For this example, we'll simulate the response // Default criteria if not provided const criteria = args.criteria || { minComments: 2, minFollowers: 1000, keywords: ['interested', 'buy', 'price'], }; // Simulated leads results const leadsResults = { source: args.accountOrPostUrl, criteria: criteria, leads: [ { username: 'potential_customer1', fullName: 'John Smith', followers: 2340, comments: 3, relevantKeywords: ['interested', 'price'], engagementScore: 8.7, contactInfo: 'Email in bio: john@example.com', }, { username: 'business_account2', fullName: 'Sarah Johnson', followers: 5620, comments: 2, relevantKeywords: ['buy'], engagementScore: 7.5, contactInfo: 'Website in bio: www.example.com', }, ], insights: [ '2 high-quality leads identified based on criteria', 'Both leads have shown direct purchase intent', 'Combined reach of identified leads: 7,960 followers', ], }; return { content: [ { type: 'text', text: JSON.stringify(leadsResults, null, 2), }, ], }; } catch (error) { console.error('[Error] Failed to identify leads:', error); return { content: [ { type: 'text', text: `Error identifying leads: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } async handleGenerateReport(args) { console.error('[Tool] Generating engagement report:', args); if (!isValidUsername(args.account)) { return { content: [ { type: 'text', text: `Invalid Instagram account handle: ${args.account}`, }, ], isError: true, }; } // Parse dates or use defaults const endDate = args.endDate ? new Date(args.endDate) : new Date(); const startDate = args.startDate ? new Date(args.startDate) : new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 days before end date try { // In a real implementation, we would use the Instagram API to fetch account data // For this example, we'll simulate the response // Simulated report results const reportResults = { account: args.account, period: { start: startDate.toISOString().split('T')[0], end: endDate.toISOString().split('T')[0], }, summary: { totalPosts: 12, totalLikes: 8750, totalComments: 420, totalShares: 135, totalSaves: 89, followerGrowth: 320, engagementRate: 3.8, }, postPerformance: [ { date: '2025-02-15', type: 'image', likes: 950, comments: 48, shares: 15, saves: 12, engagementRate: 4.2, }, { date: '2025-02-22', type: 'video', likes: 1250, comments: 72, shares: 28, saves: 19, engagementRate: 5.7, }, ], topPerformingContent: [ { postUrl: 'https://www.instagram.com/p/example1/', type: 'video', engagementRate: 5.7, insights: 'Product demonstration with call to action', }, { postUrl: 'https://www.instagram.com/p/example2/', type: 'carousel', engagementRate: 4.9, insights: 'User testimonials with product benefits', }, ], audienceGrowth: { newFollowers: 320, unfollows: 45, netGrowth: 275, growthRate: 2.8, }, recommendations: [ 'Video content consistently outperforms images', 'Posts with product demonstrations generate the most engagement', 'Optimal posting time appears to be between 6-8pm on weekdays', 'User-generated content receives more comments than branded content', ], }; return { content: [ { type: 'text', text: JSON.stringify(reportResults, null, 2), }, ], }; } catch (error) { console.error('[Error] Failed to generate engagement report:', error); return { content: [ { type: 'text', text: `Error generating engagement report: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } async run() { console.error('[Setup] Starting Instagram Engagement MCP server...'); const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('[Setup] Instagram Engagement MCP server running on stdio'); } } const server = new InstagramEngagementServer(); server.run().catch(console.error);