Skip to main content
Glama

Twitter MCP Server

user.handlers.ts8.33 kB
import { TwitterClient } from '../client/twitter.js'; import { UserV2, TTweetv2UserField } from 'twitter-api-v2'; import { HandlerResponse, TwitterHandler, UserHandlerArgs, GetAuthenticatedUserArgs } from '../types/handlers.js'; import { createResponse } from '../utils/response.js'; import { createMissingTwitterApiKeyResponse, formatTwitterError } from '../utils/twitter-response.js'; interface GetUserInfoArgs extends UserHandlerArgs { fields?: TTweetv2UserField[]; } interface GetUserTimelineArgs extends UserHandlerArgs { maxResults?: number; tweetFields?: string[]; } interface GetFollowersArgs extends UserHandlerArgs { maxResults?: number; userFields?: string[]; } interface GetFollowingArgs extends UserHandlerArgs { maxResults?: number; userFields?: string[]; } export const handleGetUserInfo: TwitterHandler<GetUserInfoArgs> = async ( client: TwitterClient | null, { username, fields }: GetUserInfoArgs ): Promise<HandlerResponse> => { if (!client) { return createMissingTwitterApiKeyResponse('getUserInfo'); } try { const user = await client.v2.userByUsername( username, { 'user.fields': fields || ['description', 'public_metrics', 'profile_image_url', 'verified'] as TTweetv2UserField[] } ); if (!user.data) { throw new Error(`User not found: ${username}`); } return createResponse(`User info: ${JSON.stringify(user.data, null, 2)}`); } catch (error) { if (error instanceof Error) { throw new Error(formatTwitterError(error, 'getting user info')); } throw error; } }; export const handleFollowUser: TwitterHandler<UserHandlerArgs> = async ( client: TwitterClient | null, { username }: UserHandlerArgs ): Promise<HandlerResponse> => { if (!client) { return createMissingTwitterApiKeyResponse('followUser'); } try { const userId = await client.v2.me().then((response: any) => response.data.id); const targetUser = await client.v2.userByUsername(username); if (!targetUser.data) { throw new Error(`User not found: ${username}`); } await client.v2.follow(userId, targetUser.data.id); return createResponse(`Successfully followed user: ${username}`); } catch (error) { if (error instanceof Error) { throw new Error(formatTwitterError(error, 'following user')); } throw error; } }; export const handleUnfollowUser: TwitterHandler<UserHandlerArgs> = async ( client: TwitterClient | null, { username }: UserHandlerArgs ): Promise<HandlerResponse> => { if (!client) { return createMissingTwitterApiKeyResponse('unfollowUser'); } try { const userId = await client.v2.me().then((response: any) => response.data.id); const targetUser = await client.v2.userByUsername(username); if (!targetUser.data) { throw new Error(`User not found: ${username}`); } await client.v2.unfollow(userId, targetUser.data.id); return createResponse(`Successfully unfollowed user: ${username}`); } catch (error) { if (error instanceof Error) { throw new Error(formatTwitterError(error, 'unfollowing user')); } throw error; } }; export const handleGetFollowers: TwitterHandler<GetFollowersArgs> = async ( client: TwitterClient | null, { username, maxResults, userFields }: GetFollowersArgs ): Promise<HandlerResponse> => { if (!client) { return createMissingTwitterApiKeyResponse('getFollowers'); } try { const user = await client.v2.userByUsername(username); if (!user.data) { throw new Error(`User not found: ${username}`); } const followers = await client.v2.followers(user.data.id, { max_results: maxResults, 'user.fields': userFields?.join(',') || 'description,public_metrics' }); if (!followers.data || !Array.isArray(followers.data) || followers.data.length === 0) { return createResponse(`No followers found for user: ${username}`); } const responseData = { followers: followers.data, meta: followers.meta }; return createResponse(`Followers for ${username}: ${JSON.stringify(responseData, null, 2)}`); } catch (error) { if (error instanceof Error) { if (error.message.includes('403')) { throw new Error(`Get followers functionality requires elevated permissions. This endpoint may require Pro tier access ($5,000/month) or special permission approval from X. Current Basic tier ($200/month) has limited access to follower data for privacy reasons. Contact X Developer Support or consider upgrading at https://developer.x.com/en/portal/products/pro`); } throw new Error(formatTwitterError(error, 'getting followers')); } throw error; } }; export const handleGetFollowing: TwitterHandler<GetFollowingArgs> = async ( client: TwitterClient | null, { username, maxResults, userFields }: GetFollowingArgs ): Promise<HandlerResponse> => { if (!client) { return createMissingTwitterApiKeyResponse('getFollowing'); } try { const user = await client.v2.userByUsername(username); if (!user.data) { throw new Error(`User not found: ${username}`); } const following = await client.v2.following(user.data.id, { max_results: maxResults, 'user.fields': userFields?.join(',') || 'description,profile_image_url,public_metrics,verified' }); if (!following.data || !Array.isArray(following.data) || following.data.length === 0) { return createResponse(`User ${username} is not following anyone`); } const responseData = { following: following.data, meta: following.meta }; return createResponse(`Users followed by ${username}: ${JSON.stringify(responseData, null, 2)}`); } catch (error) { if (error instanceof Error) { if (error.message.includes('403')) { throw new Error(`Get following functionality requires elevated permissions. This endpoint may require Pro tier access ($5,000/month) or special permission approval from X. Current Basic tier ($200/month) has limited access to following data for privacy reasons. Contact X Developer Support or consider upgrading at https://developer.x.com/en/portal/products/pro`); } throw new Error(formatTwitterError(error, 'getting following')); } throw error; } }; /** * Get the authenticated user's own profile information */ export const handleGetAuthenticatedUser: TwitterHandler<GetAuthenticatedUserArgs> = async ( client: TwitterClient | null, { userFields }: GetAuthenticatedUserArgs ): Promise<HandlerResponse> => { if (!client) { return createMissingTwitterApiKeyResponse('getAuthenticatedUser'); } try { const me = await client.v2.me({ 'user.fields': (userFields as TTweetv2UserField[]) || ['id', 'username', 'name', 'description', 'public_metrics', 'verified', 'profile_image_url', 'created_at'] as TTweetv2UserField[] }); if (!me.data) { throw new Error('Unable to retrieve authenticated user information'); } return createResponse(`Authenticated user info: ${JSON.stringify(me.data, null, 2)}`); } catch (error) { if (error instanceof Error) { if (error.message.includes('401')) { throw new Error(`Authentication failed. Please check your API credentials and tokens. This endpoint requires valid OAuth 1.0a User Context or OAuth 2.0 Authorization Code with PKCE authentication.`); } if (error.message.includes('429')) { throw new Error(`Rate limit exceeded. Please wait before making another request.`); } throw new Error(formatTwitterError(error, 'getting authenticated user')); } throw error; } };

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/crazyrabbitLTC/mcp-twitter-server'

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