extract_demographics
Extract demographic insights from users engaged with Instagram posts or accounts to understand audience composition and target marketing efforts.
Instructions
Extract demographic insights from users engaged with a post or account
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| accountOrPostUrl | Yes | Instagram account handle or post URL to analyze | |
| sampleSize | No | Number of users to sample for demographic analysis (default: 50) |
Implementation Reference
- src/index.ts:428-528 (handler)The main execution function for the extract_demographics tool. Handles input validation, determines if input is post or account, fetches sample users (likers for posts, followers for accounts), performs basic demographic analysis (e.g., private/verified ratios), and returns results.private async handleExtractDemographics(args: ExtractDemographicsArgs) { console.error('[Tool] handleExtractDemographics called with args:', args); const { accountOrPostUrl, sampleSize = 50 } = args; // Determine if it's a post URL or username let targetId: string; let targetType: 'account' | 'post'; if (isValidPostUrl(accountOrPostUrl)) { targetType = 'post'; const mediaId = await this.getMediaIdFromUrl(accountOrPostUrl); if (!mediaId) { throw new McpError(ErrorCode.InvalidParams, 'Could not extract media ID from post URL.'); } targetId = mediaId; console.error(`[Tool] Extracting demographics from post: ${targetId}`); } else if (isValidUsername(accountOrPostUrl)) { targetType = 'account'; try { const userId = await this.ig.user.getIdByUsername(accountOrPostUrl); targetId = String(userId); // Convert number userId to string for targetId console.error(`[Tool] Extracting demographics from account followers: ${accountOrPostUrl} (ID: ${targetId})`); } catch(e: any) { if (e.name === 'IgNotFoundError') { throw new McpError(ErrorCode.InvalidParams, `Account ${accountOrPostUrl} not found.`); } throw new McpError(ErrorCode.InternalError, `Failed to get user ID for ${accountOrPostUrl}: ${e.message}`); } } else { throw new McpError(ErrorCode.InvalidParams, 'Invalid input. Provide a valid Instagram username or post URL.'); } try { let users: any[] = []; if (targetType === 'post') { // Get likers or commenters as sample console.error(`[Tool Debug] Attempting to fetch likers for media ID: ${targetId}`); const likersResponse = await this.ig.media.likers(targetId); let fetchedUsers: any[] = likersResponse.users || []; // Access users from the response object // Manual pagination simulation (if needed and possible, likers might not support feed pagination) // The private API might not offer easy pagination for likers beyond the initial batch. // For simplicity, we'll just use the first batch returned. /* let fetchedUsers: any[] = []; do { // Feed results are usually directly items const items = await likersFeed.items(); fetchedUsers = fetchedUsers.concat(items); if (fetchedUsers.length >= sampleSize) break; await new Promise(resolve => setTimeout(resolve, 200 + Math.random() * 300)); } while (likersFeed.isMoreAvailable()); */ users = fetchedUsers.slice(0, sampleSize); console.error(`[Tool] Fetched ${users.length} likers from post ${targetId}`); } else { // targetType === 'account' const userIdNum = parseInt(targetId, 10); const followersFeed = this.ig.feed.accountFollowers(userIdNum); let fetchedUsers: any[] = []; do { const items = await followersFeed.items(); fetchedUsers = fetchedUsers.concat(items); if (fetchedUsers.length >= sampleSize) break; await new Promise(resolve => setTimeout(resolve, 200 + Math.random() * 300)); } while (followersFeed.isMoreAvailable()); users = fetchedUsers.slice(0, sampleSize); console.error(`[Tool] Fetched ${users.length} followers from account ${accountOrPostUrl}`); } if (users.length === 0) { return { results: { message: 'No users found to analyze (post might have no likes/comments, or account has no followers/is private).', demographics: {} } }; } // Placeholder for actual demographic analysis const demographics = { sampleAnalyzed: users.length, commonLocationsGuess: ['Unknown'], genderDistributionGuess: { male: 0.4, female: 0.4, unknown: 0.2 }, accountTypes: { private: users.filter(u => u.is_private).length / users.length, verified: users.filter(u => u.is_verified).length / users.length, }, sampleUserProfiles: users.slice(0, 5).map(u => ({ username: u.username, fullName: u.full_name, isPrivate: u.is_private, })) }; return { results: { demographics } }; } catch (error: any) { console.error(`[API Error] Failed to extract demographics for ${accountOrPostUrl}:`, error.message || error); if (error.name === 'IgNotFoundError') { throw new McpError(ErrorCode.InvalidParams, `${targetType === 'post' ? 'Post' : 'Account'} not found or access denied.`); } throw new McpError(ErrorCode.InternalError, `Failed to fetch users for demographic analysis: ${error.message}`); } }
- src/index.ts:38-41 (schema)TypeScript interface defining the expected input parameters for the extract_demographics tool.interface ExtractDemographicsArgs { accountOrPostUrl: string; sampleSize?: number; }
- src/index.ts:172-189 (registration)Registration of the extract_demographics tool in the MCP ListTools handler, defining name, description, and JSON input schema.{ 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'], }, },
- src/index.ts:270-271 (registration)Dispatch case in the CallToolRequestSchema handler that routes calls to the extract_demographics handler function.case 'extract_demographics': return await this.handleExtractDemographics(args as unknown as ExtractDemographicsArgs);
- src/index.ts:777-824 (helper)Helper function to resolve Instagram post URLs to media IDs, crucial for handling post inputs in the demographics extraction.private async getMediaIdFromUrl(url: string): Promise<string | null> { try { // Extract shortcode first const shortcode = extractPostIdFromUrl(url); if (!shortcode) return null; // Getting the numeric media PK (required by many feed functions) from URL/shortcode is unreliable. // Option 1: Use a library method if exists (e.g., getIdFromUrl - hypothetical) // Option 2: Use media.info(pk) - but we don't have pk! // Option 3: Use media.getByUrl(url) - might exist in some versions // Option 4: Return the shortcode and hope feed functions accept it (sometimes works) // Option 5: Oembed (public, might give ID) let mediaId: string | null = null; try { // Try using getByUrl if it exists in the installed library version // @ts-ignore // Ignore potential TS error if method doesn't exist on type const mediaInfo = await this.ig.media.getByUrl(url); if (mediaInfo && mediaInfo.pk) { console.log(`[Helper] Found media PK ${mediaInfo.pk} using getByUrl for ${url}`); mediaId = mediaInfo.pk; // pk is the numeric ID } else { console.warn(`[Helper Warn] ig.media.getByUrl did not return expected info for ${url}.`); } } catch(lookupError: any) { console.warn(`[Helper Warn] Failed to get media PK using getByUrl for ${url}: ${lookupError.message}.`); // If getByUrl fails or doesn't exist, fall back to using the shortcode directly. // Note: Some feeds (like mediaComments) require the numeric PK and will fail with the shortcode. mediaId = shortcode; console.log(`[Helper] Falling back to using shortcode ${shortcode} as media ID for ${url}`); } if (!mediaId) { console.error(`[Helper Error] Could not resolve media ID for shortcode: ${shortcode}`); return null; } return mediaId; } catch (error: any) { console.error(`[Helper Error] Failed to get media ID from URL ${url}:`, error.message); if (error.name === 'IgNotFoundError') { return null; } return null; } }