Skip to main content
Glama
Bob-lance

Instagram Engagement MCP

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
NameRequiredDescriptionDefault
accountOrPostUrlYesInstagram account handle or post URL to analyze
sampleSizeNoNumber of users to sample for demographic analysis (default: 50)

Implementation Reference

  • 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}`);
      }
    }
  • 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);
  • 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;
      }
    }

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/Bob-lance/instagram-engagement-mcp'

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