UNPKG

threads-mcp-server

Version:

Professional Threads MCP Server - Fixed API issues, enhanced setup validation, and enterprise features

1,139 lines 154 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; import { ThreadsAPIClient } from './api/client.js'; dotenv.config(); const server = new Server({ name: 'threads-mcp-server', version: '4.0.1', }, { capabilities: { tools: {}, }, }); let apiClient = null; const initializeClient = () => { const accessToken = process.env.THREADS_ACCESS_TOKEN; if (!accessToken) { throw new Error('THREADS_ACCESS_TOKEN environment variable is required'); } apiClient = new ThreadsAPIClient(accessToken); return apiClient; }; server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'get_my_profile', description: 'Get your own Threads profile information', inputSchema: { type: 'object', properties: { fields: { type: 'array', items: { type: 'string' }, description: 'Profile fields to retrieve', }, }, }, }, { name: 'get_my_threads', description: 'Get your own threads/posts', inputSchema: { type: 'object', properties: { fields: { type: 'array', items: { type: 'string' }, description: 'Thread fields to retrieve', }, limit: { type: 'number', description: 'Number of threads to retrieve', }, since: { type: 'string', description: 'ISO 8601 date for filtering', }, until: { type: 'string', description: 'ISO 8601 date for filtering', }, }, }, }, { name: 'publish_thread', description: 'Create and publish a new thread', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'The text content of the thread', }, media_type: { type: 'string', enum: ['TEXT', 'IMAGE', 'VIDEO'], description: 'Type of media (default: TEXT)', }, media_url: { type: 'string', description: 'URL of media to include (for IMAGE/VIDEO)', }, location_name: { type: 'string', description: 'Location name for location tagging', }, }, required: ['text'], }, }, { name: 'delete_thread', description: 'Delete one of your threads', inputSchema: { type: 'object', properties: { thread_id: { type: 'string', description: 'ID of the thread to delete', }, }, required: ['thread_id'], }, }, { name: 'search_my_threads', description: 'Search within your own threads using keywords', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query/keywords', }, limit: { type: 'number', description: 'Number of threads to search through', }, }, required: ['query'], }, }, { name: 'get_thread_replies', description: 'Get replies to your specific thread', inputSchema: { type: 'object', properties: { thread_id: { type: 'string', description: 'ID of your thread', }, fields: { type: 'array', items: { type: 'string' }, description: 'Reply fields to retrieve', }, }, required: ['thread_id'], }, }, { name: 'manage_reply', description: 'Hide or show replies to your threads', inputSchema: { type: 'object', properties: { reply_id: { type: 'string', description: 'ID of the reply to manage', }, hide: { type: 'boolean', description: 'Whether to hide (true) or show (false) the reply', }, }, required: ['reply_id', 'hide'], }, }, { name: 'get_my_insights', description: 'Get analytics and insights for your account', inputSchema: { type: 'object', properties: { metrics: { type: 'array', items: { type: 'string' }, description: 'Metrics to retrieve', }, period: { type: 'string', enum: ['day', 'week', 'days_28', 'month', 'lifetime'], description: 'Time period for metrics', }, since: { type: 'string', description: 'ISO 8601 start date', }, until: { type: 'string', description: 'ISO 8601 end date', }, }, required: ['metrics'], }, }, { name: 'get_thread_insights', description: 'Get performance metrics for your specific thread', inputSchema: { type: 'object', properties: { thread_id: { type: 'string', description: 'ID of your thread', }, metrics: { type: 'array', items: { type: 'string' }, description: 'Metrics to retrieve', }, period: { type: 'string', enum: ['day', 'week', 'days_28', 'month', 'lifetime'], description: 'Time period for metrics', }, }, required: ['thread_id', 'metrics'], }, }, { name: 'get_mentions', description: 'Get threads where you are mentioned', inputSchema: { type: 'object', properties: { fields: { type: 'array', items: { type: 'string' }, description: 'Fields to retrieve from mentions', }, limit: { type: 'number', description: 'Number of mentions to retrieve', }, }, }, }, { name: 'get_publishing_limit', description: 'Check your current publishing quotas and limits', inputSchema: { type: 'object', properties: {}, }, }, { name: 'create_reply', description: 'Reply to a specific thread/post', inputSchema: { type: 'object', properties: { reply_to_id: { type: 'string', description: 'ID of the thread/post to reply to', }, text: { type: 'string', description: 'Reply text content', }, media_type: { type: 'string', enum: ['TEXT', 'IMAGE', 'VIDEO'], description: 'Type of media (default: TEXT)', }, media_url: { type: 'string', description: 'URL of media to include (for IMAGE/VIDEO)', }, reply_control: { type: 'string', enum: ['everyone', 'accounts_you_follow', 'mentioned_only', 'parent_post_author_only', 'followers_only'], description: 'Who can reply to this reply', }, }, required: ['reply_to_id', 'text'], }, }, { name: 'create_thread_chain', description: 'Create a thread chain (multiple connected replies)', inputSchema: { type: 'object', properties: { parent_thread_id: { type: 'string', description: 'ID of the parent thread to start the chain', }, replies: { type: 'array', items: { type: 'object', properties: { text: { type: 'string' }, reply_control: { type: 'string', enum: ['everyone', 'accounts_you_follow', 'mentioned_only', 'parent_post_author_only', 'followers_only'] } }, required: ['text'] }, description: 'Array of reply texts to create as a chain', }, }, required: ['parent_thread_id', 'replies'], }, }, { name: 'quote_post', description: 'Quote another thread/post with your own text', inputSchema: { type: 'object', properties: { quoted_post_id: { type: 'string', description: 'ID of the post to quote', }, text: { type: 'string', description: 'Your quote text/commentary', }, media_type: { type: 'string', enum: ['TEXT', 'IMAGE', 'VIDEO'], description: 'Type of media (default: TEXT)', }, media_url: { type: 'string', description: 'URL of media to include (for IMAGE/VIDEO)', }, reply_control: { type: 'string', enum: ['everyone', 'accounts_you_follow', 'mentioned_only', 'parent_post_author_only', 'followers_only'], description: 'Who can reply to this quote', }, }, required: ['quoted_post_id', 'text'], }, }, { name: 'repost_thread', description: 'Repost/share another thread', inputSchema: { type: 'object', properties: { post_id: { type: 'string', description: 'ID of the post to repost', }, }, required: ['post_id'], }, }, { name: 'unrepost_thread', description: 'Remove a repost you previously shared', inputSchema: { type: 'object', properties: { post_id: { type: 'string', description: 'ID of the post to unrepost', }, }, required: ['post_id'], }, }, { name: 'like_post', description: 'Like a thread/post', inputSchema: { type: 'object', properties: { post_id: { type: 'string', description: 'ID of the post to like', }, }, required: ['post_id'], }, }, { name: 'unlike_post', description: 'Remove like from a thread/post', inputSchema: { type: 'object', properties: { post_id: { type: 'string', description: 'ID of the post to unlike', }, }, required: ['post_id'], }, }, { name: 'get_post_likes', description: 'Get list of users who liked a post', inputSchema: { type: 'object', properties: { post_id: { type: 'string', description: 'ID of the post to get likes for', }, limit: { type: 'number', description: 'Number of likes to retrieve', }, }, required: ['post_id'], }, }, { name: 'create_post_with_restrictions', description: 'Create post with advanced reply and audience restrictions', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'The text content of the thread', }, media_type: { type: 'string', enum: ['TEXT', 'IMAGE', 'VIDEO'], description: 'Type of media (default: TEXT)', }, media_url: { type: 'string', description: 'URL of media to include (for IMAGE/VIDEO)', }, reply_control: { type: 'string', enum: ['everyone', 'accounts_you_follow', 'mentioned_only', 'parent_post_author_only', 'followers_only'], description: 'Who can reply to this post', }, audience_control: { type: 'string', enum: ['public', 'followers_only', 'close_friends'], description: 'Who can see this post', }, location_name: { type: 'string', description: 'Location name for location tagging', }, hashtags: { type: 'array', items: { type: 'string' }, description: 'Array of hashtags to include (without #)', }, mentions: { type: 'array', items: { type: 'string' }, description: 'Array of usernames to mention (without @)', }, }, required: ['text'], }, }, { name: 'schedule_post', description: 'Schedule a post to be published at a future time', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'The text content of the thread', }, scheduled_publish_time: { type: 'string', description: 'ISO 8601 datetime when to publish (e.g., "2025-08-25T10:00:00+07:00")', }, media_type: { type: 'string', enum: ['TEXT', 'IMAGE', 'VIDEO'], description: 'Type of media (default: TEXT)', }, media_url: { type: 'string', description: 'URL of media to include (for IMAGE/VIDEO)', }, reply_control: { type: 'string', enum: ['everyone', 'accounts_you_follow', 'mentioned_only', 'parent_post_author_only', 'followers_only'], description: 'Who can reply to this post', }, location_name: { type: 'string', description: 'Location name for location tagging', }, }, required: ['text', 'scheduled_publish_time'], }, }, { name: 'search_posts', description: 'Search for posts using keywords', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search keyword or phrase', }, search_type: { type: 'string', enum: ['TOP', 'RECENT'], description: 'Search results order: TOP (popular) or RECENT (chronological)', }, limit: { type: 'number', description: 'Number of results to return (max 100, default 25)', }, since: { type: 'string', description: 'ISO 8601 date to search from', }, until: { type: 'string', description: 'ISO 8601 date to search until', }, }, required: ['query'], }, }, { name: 'search_mentions', description: 'Search for posts that mention you or specific users', inputSchema: { type: 'object', properties: { user_id: { type: 'string', description: 'User ID to search mentions for (defaults to current user)', }, limit: { type: 'number', description: 'Number of mentions to retrieve', }, since: { type: 'string', description: 'ISO 8601 date to search from', }, until: { type: 'string', description: 'ISO 8601 date to search until', }, }, }, }, { name: 'search_by_hashtags', description: 'Search for posts by hashtag or topic tags', inputSchema: { type: 'object', properties: { hashtags: { type: 'array', items: { type: 'string' }, description: 'Hashtags to search for (without #)', }, search_type: { type: 'string', enum: ['TOP', 'RECENT'], description: 'Search results order', }, limit: { type: 'number', description: 'Number of results to return', }, }, required: ['hashtags'], }, }, { name: 'search_by_topics', description: 'Search for posts by topic tags', inputSchema: { type: 'object', properties: { topics: { type: 'array', items: { type: 'string' }, description: 'Topic tags to search for', }, search_type: { type: 'string', enum: ['TOP', 'RECENT'], description: 'Search results order', }, limit: { type: 'number', description: 'Number of results to return', }, }, required: ['topics'], }, }, { name: 'get_trending_posts', description: 'Get trending/popular posts in various categories', inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'Trending category (optional)', }, limit: { type: 'number', description: 'Number of trending posts to retrieve', }, timeframe: { type: 'string', enum: ['hour', 'day', 'week'], description: 'Trending timeframe', }, }, }, }, { name: 'search_users', description: 'Search for users by username or display name', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Username or display name to search for', }, limit: { type: 'number', description: 'Number of users to return', }, }, required: ['query'], }, }, { name: 'get_user_followers', description: 'Get followers list for a user', inputSchema: { type: 'object', properties: { user_id: { type: 'string', description: 'User ID to get followers for (defaults to current user)', }, limit: { type: 'number', description: 'Number of followers to retrieve', }, }, }, }, { name: 'get_user_following', description: 'Get following list for a user', inputSchema: { type: 'object', properties: { user_id: { type: 'string', description: 'User ID to get following for (defaults to current user)', }, limit: { type: 'number', description: 'Number of following to retrieve', }, }, }, }, { name: 'follow_user', description: 'Follow a user', inputSchema: { type: 'object', properties: { user_id: { type: 'string', description: 'User ID to follow', }, }, required: ['user_id'], }, }, { name: 'unfollow_user', description: 'Unfollow a user', inputSchema: { type: 'object', properties: { user_id: { type: 'string', description: 'User ID to unfollow', }, }, required: ['user_id'], }, }, { name: 'block_user', description: 'Block a user', inputSchema: { type: 'object', properties: { user_id: { type: 'string', description: 'User ID to block', }, }, required: ['user_id'], }, }, { name: 'get_enhanced_insights', description: 'Get advanced analytics including views, clicks, shares, and demographics', inputSchema: { type: 'object', properties: { thread_id: { type: 'string', description: 'Thread ID for media insights (optional for user insights)', }, metrics: { type: 'array', items: { type: 'string' }, description: 'Metrics to retrieve: views, likes, replies, reposts, quotes, shares, clicks, followers_count, follower_demographics', }, period: { type: 'string', enum: ['day', 'week', 'days_28', 'month', 'lifetime'], description: 'Time period for insights', }, breakdown: { type: 'array', items: { type: 'string', enum: ['country', 'city', 'age', 'gender'] }, description: 'Demographic breakdown options', }, since: { type: 'string', description: 'ISO 8601 start date', }, until: { type: 'string', description: 'ISO 8601 end date', }, }, }, }, { name: 'get_audience_demographics', description: 'Get detailed audience demographic analysis', inputSchema: { type: 'object', properties: { breakdown_by: { type: 'array', items: { type: 'string', enum: ['country', 'city', 'age', 'gender'] }, description: 'Demographic categories to analyze', }, period: { type: 'string', enum: ['day', 'week', 'days_28', 'month', 'lifetime'], description: 'Time period for demographic data', }, }, }, }, { name: 'get_engagement_trends', description: 'Analyze engagement patterns and trends over time', inputSchema: { type: 'object', properties: { metrics: { type: 'array', items: { type: 'string', enum: ['views', 'likes', 'replies', 'reposts', 'quotes', 'shares', 'clicks'] }, description: 'Engagement metrics to track', }, timeframe: { type: 'string', enum: ['week', 'month', 'quarter'], description: 'Analysis timeframe', }, granularity: { type: 'string', enum: ['daily', 'weekly'], description: 'Data point frequency', }, }, }, }, { name: 'get_follower_growth_analytics', description: 'Track follower growth patterns and projections', inputSchema: { type: 'object', properties: { period: { type: 'string', enum: ['week', 'month', 'quarter', 'year'], description: 'Growth analysis period', }, include_projections: { type: 'boolean', description: 'Include growth projections based on trends', }, }, }, }, { name: 'analyze_best_posting_times', description: 'AI-driven analysis of optimal posting times based on engagement', inputSchema: { type: 'object', properties: { analysis_period: { type: 'string', enum: ['month', 'quarter', 'year'], description: 'Historical data period for analysis', }, timezone: { type: 'string', description: 'Timezone for recommendations (e.g., "America/New_York")', }, content_type: { type: 'string', enum: ['all', 'text', 'image', 'video'], description: 'Content type to analyze', }, }, }, }, { name: 'get_content_performance_report', description: 'Comprehensive performance report across all content', inputSchema: { type: 'object', properties: { report_type: { type: 'string', enum: ['summary', 'detailed', 'top_performers', 'underperformers'], description: 'Type of performance report', }, period: { type: 'string', enum: ['week', 'month', 'quarter'], description: 'Report time period', }, metrics: { type: 'array', items: { type: 'string' }, description: 'Metrics to include in report', }, include_comparisons: { type: 'boolean', description: 'Include period-over-period comparisons', }, }, }, }, // Phase 3B: Professional Content Creation & Automation { name: 'create_carousel_post', description: 'Create multi-media carousel posts with up to 20 items (September 2024 update)', inputSchema: { type: 'object', properties: { media_urls: { type: 'array', items: { type: 'string' }, description: 'Array of image/video URLs for carousel (2-20 items supported)', minItems: 2, maxItems: 20, }, text: { type: 'string', description: 'Post caption text', }, alt_texts: { type: 'array', items: { type: 'string' }, description: 'Alt text for each media item (accessibility)', }, carousel_settings: { type: 'object', properties: { auto_alt_text: { type: 'boolean', description: 'Generate alt text automatically' }, aspect_ratio: { type: 'string', enum: ['square', 'portrait', 'landscape'], description: 'Preferred aspect ratio' }, thumbnail_selection: { type: 'string', enum: ['auto', 'first', 'custom'], description: 'Thumbnail selection method' }, }, }, }, required: ['media_urls', 'text'], }, }, { name: 'schedule_post', description: 'Schedule posts with advanced automation and optimal timing', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'Post content', }, media_url: { type: 'string', description: 'Optional media URL', }, schedule_time: { type: 'string', description: 'ISO 8601 datetime for scheduling', }, automation_settings: { type: 'object', properties: { auto_optimize_time: { type: 'boolean', description: 'Automatically optimize posting time based on audience' }, recurring: { type: 'string', enum: ['none', 'daily', 'weekly', 'monthly'], description: 'Recurring schedule' }, auto_hashtags: { type: 'boolean', description: 'Automatically add relevant hashtags' }, content_variation: { type: 'boolean', description: 'Create slight variations for recurring posts' }, }, }, timezone: { type: 'string', description: 'Timezone for scheduling (e.g., America/New_York)', }, }, required: ['text'], }, }, { name: 'auto_hashtag_suggestions', description: 'AI-powered hashtag suggestions based on content analysis', inputSchema: { type: 'object', properties: { content: { type: 'string', description: 'Post content to analyze for hashtag suggestions', }, media_url: { type: 'string', description: 'Optional media URL for visual analysis', }, suggestion_settings: { type: 'object', properties: { count: { type: 'number', description: 'Number of hashtag suggestions (1-10)', minimum: 1, maximum: 10 }, style: { type: 'string', enum: ['trending', 'niche', 'branded', 'mixed'], description: 'Hashtag style preference' }, exclude_overused: { type: 'boolean', description: 'Exclude overused hashtags' }, industry_focus: { type: 'string', description: 'Industry/niche to focus on' }, }, }, }, required: ['content'], }, }, { name: 'content_optimization_analysis', description: 'Advanced content analysis with optimization recommendations', inputSchema: { type: 'object', properties: { content: { type: 'string', description: 'Content to analyze', }, analysis_type: { type: 'string', enum: ['engagement', 'reach', 'accessibility', 'seo', 'comprehensive'], description: 'Type of optimization analysis', }, target_audience: { type: 'object', properties: { demographics: { type: 'array', items: { type: 'string' }, description: 'Target demographic groups' }, interests: { type: 'array', items: { type: 'string' }, description: 'Target interests' }, timezone: { type: 'string', description: 'Primary audience timezone' }, }, }, optimization_goals: { type: 'array', items: { type: 'string', enum: ['increase_engagement', 'expand_reach', 'improve_accessibility', 'boost_shares', 'drive_traffic'] }, description: 'Optimization objectives', }, }, required: ['content'], }, }, { name: 'bulk_post_management', description: 'Manage multiple posts with bulk operations and analytics', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['analyze_performance', 'bulk_edit', 'content_audit', 'export_data'], description: 'Bulk operation to perform', }, filters: { type: 'object', properties: { date_range: { type: 'object', properties: { start: { type: 'string' }, end: { type: 'string' } } }, performance_threshold: { type: 'string', enum: ['low', 'medium', 'high'], description: 'Performance level filter' }, content_type: { type: 'string', enum: ['text', 'image', 'video', 'carousel'], description: 'Content type filter' }, engagement_range: { type: 'object', properties: { min: { type: 'number' }, max: { type: 'number' } } }, }, }, bulk_operations: { type: 'object', properties: { add_hashtags: { type: 'array', items: { type: 'string' }, description: 'Hashtags to add to filtered posts' }, update_alt_text: { type: 'boolean', description: 'Update alt text for accessibility' }, archive_low_performers: { type: 'boolean', description: 'Archive underperforming posts' }, }, }, }, required: ['action'], }, }, { name: 'website_integration_setup', description: 'Setup Threads integration for websites and external platforms', inputSchema: { type: 'object', properties: { integration_type: { type: 'string', enum: ['embed_feed', 'share_buttons', 'auto_crosspost', 'webhook_setup'], description: 'Type of integration to setup', }, website_config: { type: 'object', properties: { domain: { type: 'string', description: 'Website domain' }, platform: { type: 'string', enum: ['wordpress', 'shopify', 'custom', 'react', 'vue', 'angular'], description: 'Website platform' }, styling_preferences: { type: 'object', properties: { theme: { type: 'string', enum: ['light', 'dark', 'auto'] }, layout: { type: 'string', enum: ['grid', 'list', 'carousel'] }, post_count: { type: 'number', minimum: 1, maximum: 20 } } }, }, }, automation_settings: { type: 'object', properties: { auto_sync: { type: 'boolean', description: 'Automatically sync new posts' }, crosspost_enabled: { type: 'boolean', description: 'Enable cross-posting from website' }, webhook_url: { type: 'string', description: 'Webhook endpoint URL' }, notification_settings: { type: 'object', properties: { email: { type: 'string' }, slack_webhook: { type: 'string' } } }, }, }, }, required: ['integration_type'], }, }, // NEW: Token validation and diagnostics { name: 'validate_setup', description: 'Validate access token, check scopes, and verify business account setup', inputSchema: { type: 'object', properties: { check_scopes: { type: 'boolean', description: 'Check if all required scopes are present', default: true, }, required_scopes: { type: 'array', items: { type: 'string' }, description: 'Custom list of required scopes to check', }, }, }, }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { if (!apiClient) { apiClient = initializeClient(); } const { name, arguments: args } = request.params; try { let result; switch (name) { case 'get_my_profile': const { fields } = args; const fieldsParam = fields?.join(',') || 'id,username,name,threads_profile_picture_url,threads_biography'; result = await apiClient.get('/me', { fields: fieldsParam }); break; case 'get_my_threads': const { fields: threadFields, limit, since, until } = args; const threadsFields = threadFields?.join(',') || 'id,media_type,media_url,text,timestamp,permalink,username'; // Get current user ID first const currentUser = await apiClient.get('/me', { fields: 'id' }); result = await apiClient.paginate(`/${currentUser.id}/threads`, { fields: threadsFields, limit: limit || 25, since, until, }); break; case 'publish_thread': const { text, media_type, media_url, location_name } = args; // Get current user ID first const user = await apiClient.get('/me', { fields: 'id' }); // Build the proper container data based on media type const containerData = { media_type: media_type || 'TEXT', }; // Add text if provided if (text) { containerData.text = text; } // Handle different media types with correct parameter names if (media_type === 'IMAGE' && media_url) { containerData.image_url = media_url; // Use image_url for images } else if (media_type === 'VIDEO' && media_url) { containerData.video_url = media_url; // Use video_url for videos } else if (media_url && !media_type) { // Auto-detect media type from URL if (media_url.match(/\.(jpg|jpeg|png|gif|webp)$/i)) { containerData.media_type = 'IMAGE'; containerData.image_url = media_url; } else if (media_url.match(/\.(mp4|mov|av