Skip to main content
Glama

X (Twitter) MCP Server - Enhanced Edition

by mbelinky
mcp-handlers.test.ts7.92 kB
import { jest } from '@jest/globals'; import { TwitterServer } from '../../src/index.js'; import { TwitterError } from '../../src/types.js'; import type { Config } from '../../src/types.js'; // Mock the Twitter API jest.mock('../../src/auth/factory.js', () => ({ createTwitterClient: jest.fn(() => ({ v1: { uploadMedia: jest.fn(), }, v2: { tweet: jest.fn(), search: jest.fn(), }, })), })); // Mock the MCP SDK const mockServer = { setRequestHandler: jest.fn(), onerror: null, close: jest.fn(), connect: jest.fn() }; jest.mock('@modelcontextprotocol/sdk/server/index.js', () => ({ Server: jest.fn(() => mockServer) })); jest.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({ StdioServerTransport: jest.fn() })); describe('MCP Handlers Integration', () => { const mockConfig: Config = { apiKey: 'test-key', apiSecretKey: 'test-secret', accessToken: 'test-token', accessTokenSecret: 'test-token-secret', authType: 'oauth1' }; let twitterServer: TwitterServer; let postTweetHandler: any; let searchTweetsHandler: any; let listToolsHandler: any; beforeEach(() => { jest.clearAllMocks(); twitterServer = new TwitterServer(mockConfig); // Extract handlers from the mock calls const setRequestHandlerCalls = mockServer.setRequestHandler.mock.calls; // Find the handlers for (const call of setRequestHandlerCalls) { const [schema, handler] = call; if (schema.properties?.name?.const === 'list_tools') { listToolsHandler = handler; } else if (schema.properties?.name?.const === 'call_tool') { // This will be the call tool handler postTweetHandler = handler; searchTweetsHandler = handler; } } }); describe('Tool Listing', () => { it('should list available tools', async () => { const response = await listToolsHandler(); expect(response.tools).toHaveLength(2); expect(response.tools[0].name).toBe('post_tweet'); expect(response.tools[1].name).toBe('search_tweets'); // Check that post_tweet supports media const postTweetTool = response.tools[0]; expect(postTweetTool.inputSchema.properties.media).toBeDefined(); expect(postTweetTool.inputSchema.properties.media.items.properties.media_type.enum) .toEqual(['image/jpeg', 'image/png', 'image/gif']); }); }); describe('Post Tweet Handler', () => { beforeEach(() => { // Mock the underlying Twitter client const mockTwitterClient = (twitterServer as any).client; mockTwitterClient.postTweetWithMedia = jest.fn(); }); it('should handle simple tweet posting', async () => { const mockTwitterClient = (twitterServer as any).client; mockTwitterClient.postTweetWithMedia.mockResolvedValue({ id: '123', text: 'Hello world' }); const request = { params: { name: 'post_tweet', arguments: { text: 'Hello world' } } }; const response = await postTweetHandler(request); expect(response.content[0].text).toContain('Tweet posted successfully'); expect(response.content[0].text).toContain('123'); expect(mockTwitterClient.postTweetWithMedia).toHaveBeenCalledWith( 'Hello world', undefined, undefined ); }); it('should handle tweet with reply', async () => { const mockTwitterClient = (twitterServer as any).client; mockTwitterClient.postTweetWithMedia.mockResolvedValue({ id: '124', text: 'Reply tweet' }); const request = { params: { name: 'post_tweet', arguments: { text: 'Reply tweet', reply_to_tweet_id: '123' } } }; await postTweetHandler(request); expect(mockTwitterClient.postTweetWithMedia).toHaveBeenCalledWith( 'Reply tweet', '123', undefined ); }); it('should handle tweet with media', async () => { const mockTwitterClient = (twitterServer as any).client; mockTwitterClient.postTweetWithMedia.mockResolvedValue({ id: '125', text: 'Tweet with media' }); const request = { params: { name: 'post_tweet', arguments: { text: 'Tweet with media', media: [{ data: Buffer.from('fake-image').toString('base64'), media_type: 'image/jpeg' }] } } }; await postTweetHandler(request); expect(mockTwitterClient.postTweetWithMedia).toHaveBeenCalledWith( 'Tweet with media', undefined, [{ data: Buffer.from('fake-image').toString('base64'), media_type: 'image/jpeg' }] ); }); it('should handle validation errors', async () => { const request = { params: { name: 'post_tweet', arguments: { text: '' // Empty text should fail validation } } }; await expect(postTweetHandler(request)) .rejects .toThrow('Invalid parameters'); }); it('should handle Twitter API errors', async () => { const mockTwitterClient = (twitterServer as any).client; mockTwitterClient.postTweetWithMedia.mockRejectedValue( new TwitterError('Rate limit exceeded', 'rate_limit_exceeded', 429) ); const request = { params: { name: 'post_tweet', arguments: { text: 'This will fail' } } }; const response = await postTweetHandler(request); expect(response.content[0].text).toContain('Rate limit exceeded'); expect(response.content[0].isError).toBe(true); }); }); describe('Search Tweets Handler', () => { beforeEach(() => { // Mock the underlying Twitter client const mockTwitterClient = (twitterServer as any).client; mockTwitterClient.searchTweets = jest.fn(); }); it('should handle tweet search', async () => { const mockTwitterClient = (twitterServer as any).client; const mockTweets = [ { id: '1', text: 'Tweet about AI', authorId: 'user1', metrics: { likes: 10, retweets: 5 }, createdAt: '2024-01-01T00:00:00Z' } ]; const mockUsers = [ { id: 'user1', username: 'testuser', name: 'Test User', verified: false } ]; mockTwitterClient.searchTweets.mockResolvedValue({ tweets: mockTweets, users: mockUsers }); const request = { params: { name: 'search_tweets', arguments: { query: '#AI', count: 15 } } }; const response = await searchTweetsHandler(request); expect(response.content[0].text).toContain('#AI'); expect(response.content[0].text).toContain('Tweet about AI'); expect(mockTwitterClient.searchTweets).toHaveBeenCalledWith('#AI', 15); }); it('should handle search validation errors', async () => { const request = { params: { name: 'search_tweets', arguments: { query: '#AI', count: 5 // Below minimum of 10 } } }; await expect(searchTweetsHandler(request)) .rejects .toThrow('Invalid parameters'); }); }); describe('Unknown Tool Handler', () => { it('should handle unknown tool requests', async () => { const request = { params: { name: 'unknown_tool', arguments: {} } }; await expect(postTweetHandler(request)) .rejects .toThrow('Unknown tool'); }); }); });

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/mbelinky/x-mcp-server'

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