Skip to main content
Glama

Beyond MCP Server

import { FarcasterProvider } from '../../../../src/providers/farcaster/farcasterProvider'; import { mockNeynarClient } from '../../../mocks/neynarClient.mock'; import config from '../../../../src/config'; // Mock the Neynar client jest.mock('@neynar/nodejs-sdk', () => { return { Configuration: jest.fn().mockImplementation(() => ({})), NeynarAPIClient: jest.fn().mockImplementation(() => mockNeynarClient), FeedType: { Filter: 'filter' }, FilterType: { GlobalTrending: 'global_trending' } }; }); // Mock the config jest.mock('../../../../src/config', () => ({ providers: { farcaster: { enabled: true, neynarApiKey: 'test-api-key' } } })); describe('FarcasterProvider', () => { let provider: FarcasterProvider; beforeEach(() => { // Reset all mocks before each test jest.clearAllMocks(); provider = new FarcasterProvider(); }); describe('isAvailable', () => { it('should return true when the API is available', async () => { // Mock a successful API call mockNeynarClient.fetchBulkUsers.mockResolvedValueOnce({ users: [{ fid: 1 }] }); const result = await provider.isAvailable(); expect(result).toBe(true); expect(mockNeynarClient.fetchBulkUsers).toHaveBeenCalledWith({ fids: [1] }); }); it('should return false when the API is not available', async () => { // Mock a failed API call mockNeynarClient.fetchBulkUsers.mockRejectedValueOnce(new Error('API error')); const result = await provider.isAvailable(); expect(result).toBe(false); expect(mockNeynarClient.fetchBulkUsers).toHaveBeenCalledWith({ fids: [1] }); }); }); describe('searchContent', () => { it('should return search results for a regular query', async () => { const query = 'neynar'; const result = await provider.searchContent(query); expect(result).toBeDefined(); expect(result).toHaveLength(1); expect(result[0].text).toContain('Neynar API'); expect(mockNeynarClient.searchCasts).toHaveBeenCalledWith({ q: query, limit: 20 }); }); it('should handle a query with no results', async () => { const query = 'nonexistent'; const result = await provider.searchContent(query); expect(result).toBeDefined(); expect(result).toHaveLength(0); expect(mockNeynarClient.searchCasts).toHaveBeenCalledWith({ q: query, limit: 20 }); }); it('should handle a from: query to search for a specific user', async () => { const query = 'from:rish'; const result = await provider.searchContent(query); expect(result).toBeDefined(); expect(result).toHaveLength(0); // The implementation returns empty array for from: queries expect(mockNeynarClient.searchUser).toHaveBeenCalledWith({ q: 'rish', limit: 1 }); }); it('should handle a from: query with a non-existent user', async () => { const query = 'from:nonexistent'; const result = await provider.searchContent(query); expect(result).toBeDefined(); expect(result).toHaveLength(0); expect(mockNeynarClient.searchUser).toHaveBeenCalledWith({ q: 'nonexistent', limit: 1 }); }); it('should respect the limit option', async () => { const query = 'neynar'; const limit = 5; await provider.searchContent(query, { limit }); expect(mockNeynarClient.searchCasts).toHaveBeenCalledWith({ q: query, limit }); }); }); describe('getThread', () => { it('should return a thread when it exists', async () => { const threadId = '0xfe512114e8a7c6b23c51c66c318f8a9a548cfb07'; // Mock the lookupCastByHashOrWarpcastUrl response mockNeynarClient.lookupCastByHashOrWarpcastUrl.mockResolvedValueOnce({ cast: { hash: '0xfe512114e8a7c6b23c51c66c318f8a9a548cfb07', author: { fid: 194, username: 'rish', display_name: 'rish' }, text: 'adding more v2 frame related tooling', timestamp: '2025-02-12T16:00:21.000Z', reactions: { likes_count: 67, recasts_count: 12 }, replies: { count: 10 } } }); // Mock the conversation response mockNeynarClient.lookupCastConversation.mockResolvedValueOnce({ conversation: { cast: { direct_replies: [] } } }); const result = await provider.getThread(threadId); expect(result).toBeDefined(); expect(result.id).toBe(threadId); expect(result.replies).toHaveLength(0); expect(mockNeynarClient.lookupCastByHashOrWarpcastUrl).toHaveBeenCalledWith({ identifier: threadId, type: 'hash' }); }); it('should handle a thread that does not exist', async () => { const threadId = '0xnonexistent'; // Mock the error for a non-existent thread mockNeynarClient.lookupCastByHashOrWarpcastUrl.mockRejectedValueOnce(new Error('Cast not found')); mockNeynarClient.lookupCastByHashOrWarpcastUrl.mockRejectedValueOnce(new Error('Cast not found')); const result = await provider.getThread(threadId); expect(result).toBeDefined(); expect(result.id).toBe(threadId); expect(result.content.text).toContain('Thread not found'); expect(result.replies).toHaveLength(0); }); }); describe('getUserProfile', () => { it('should return a user profile when the user exists (by username)', async () => { const userId = 'rish'; // Mock the searchUser response mockNeynarClient.searchUser.mockResolvedValueOnce({ result: { users: [ { fid: 194, username: 'rish', display_name: 'rish', profile: { bio: { text: 'building farcaster infra @ /neynar 🪐 casting @ /rish' } }, follower_count: 264665, following_count: 839, verifications: ['0x5a927ac639636e534b678e81768ca19e2c6280b7'] } ] } }); const result = await provider.getUserProfile(userId); expect(result).toBeDefined(); expect(result.username).toBe('rish'); expect(mockNeynarClient.searchUser).toHaveBeenCalledWith({ q: 'rish', limit: 1 }); }); it('should return a user profile when the user exists (by FID)', async () => { const userId = '194'; const result = await provider.getUserProfile(userId); expect(result).toBeDefined(); expect(result.username).toBe('rish'); expect(mockNeynarClient.fetchBulkUsers).toHaveBeenCalledWith({ fids: [194] }); }); it('should handle a user that does not exist', async () => { const userId = 'nonexistent'; // Mock the searchUser response for a non-existent user mockNeynarClient.searchUser.mockResolvedValueOnce({ result: { users: [] } }); await expect(provider.getUserProfile(userId)).rejects.toThrow('Failed to fetch profile'); }); }); describe('getUserProfileByWalletAddress', () => { it('should return a user profile when the wallet address exists', async () => { const walletAddress = '0x29db3d715bffd0b50862de8635186c5ac02c0831'; const result = await provider.getUserProfileByWalletAddress(walletAddress); expect(result).toBeDefined(); expect(result.id).toBe('194'); expect(result.username).toBe('rish'); expect(result.displayName).toBe('rish'); expect(result.bio).toBe('building farcaster infra @ /neynar 🪐 casting @ /rish'); expect(result.followerCount).toBe(264665); expect(result.followingCount).toBe(839); expect(result.platform).toBe('farcaster'); expect((result.metadata as any).verifications).toContain('0x5a927ac639636e534b678e81768ca19e2c6280b7'); expect((result.metadata as any).verifiedEthAddresses).toContain(walletAddress); expect(mockNeynarClient.fetchBulkUsersByEthOrSolAddress).toHaveBeenCalledWith({ addresses: [walletAddress] }); }); it('should handle a wallet address that does not exist', async () => { const walletAddress = '0x1234567890123456789012345678901234567890'; await expect(provider.getUserProfileByWalletAddress(walletAddress)) .rejects.toThrow('Failed to fetch profile for wallet address 0x1234567890123456789012345678901234567890'); expect(mockNeynarClient.fetchBulkUsersByEthOrSolAddress).toHaveBeenCalledWith({ addresses: [walletAddress] }); }); it('should handle an uninitialized client', async () => { const provider = new FarcasterProvider(); provider['client'] = null; const walletAddress = '0x29db3d715bffd0b50862de8635186c5ac02c0831'; await expect(provider.getUserProfileByWalletAddress(walletAddress)) .rejects.toThrow('Neynar client not initialized'); }); }); describe('getUserContent', () => { it('should return user content when the user exists (by username)', async () => { const userId = 'rish'; // Mock the searchUser response mockNeynarClient.searchUser.mockResolvedValueOnce({ result: { users: [ { fid: 194, username: 'rish', display_name: 'rish' } ] } }); const result = await provider.getUserContent(userId); expect(result).toBeDefined(); expect(result).toHaveLength(0); // The implementation returns empty array expect(mockNeynarClient.searchUser).toHaveBeenCalledWith({ q: 'rish', limit: 1 }); expect(mockNeynarClient.fetchCastsForUser).toHaveBeenCalledWith({ fid: 194, limit: 20, cursor: undefined }); }); it('should return user content when the user exists (by FID)', async () => { const userId = '194'; const result = await provider.getUserContent(userId); expect(result).toBeDefined(); expect(result).toHaveLength(0); // The implementation returns empty array expect(mockNeynarClient.fetchCastsForUser).toHaveBeenCalledWith({ fid: 194, limit: 20, cursor: undefined }); }); it('should throw an error when the user does not exist', async () => { const userId = 'nonexistent'; // Mock the searchUser response for a non-existent user mockNeynarClient.searchUser.mockResolvedValueOnce({ result: { users: [] } }); // The implementation catches the error and returns an empty array const result = await provider.getUserContent(userId); expect(result).toEqual([]); }); it('should respect the limit option', async () => { const userId = '194'; const limit = 5; await provider.getUserContent(userId, { limit }); expect(mockNeynarClient.fetchCastsForUser).toHaveBeenCalledWith({ fid: 194, limit, cursor: undefined }); }); }); describe('getTrendingTopics', () => { it('should return trending topics', async () => { const result = await provider.getTrendingTopics(); expect(result).toBeDefined(); expect(result).toHaveLength(4); // The implementation returns 4 default topics expect(result).toContain('web3'); expect(result).toContain('crypto'); expect(mockNeynarClient.fetchFeed).toHaveBeenCalledWith({ feedType: 'filter', filterType: 'global_trending', limit: 25 }); }); it('should respect the limit option', async () => { const limit = 5; await provider.getTrendingTopics({ limit }); expect(mockNeynarClient.fetchFeed).toHaveBeenCalledWith({ feedType: 'filter', filterType: 'global_trending', limit: 25 }); }); }); describe('getTrendingFeed', () => { it('should return trending feed using the default neynar provider', async () => { // Mock the fetchFeed response mockNeynarClient.fetchFeed.mockResolvedValueOnce({ casts: [ { hash: '0xtrending1', author: { fid: 194, username: 'rish', display_name: 'rish' }, text: 'Trending cast 1', timestamp: '2025-02-12T16:00:21.000Z', reactions: { likes_count: 100, recasts_count: 20 }, replies: { count: 15 } }, { hash: '0xtrending2', author: { fid: 195, username: 'user1', display_name: 'User 1' }, text: 'Trending cast 2', timestamp: '2025-02-12T15:30:00.000Z', reactions: { likes_count: 80, recasts_count: 15 }, replies: { count: 10 } } ] }); const result = await provider.getTrendingFeed(); expect(result).toBeDefined(); expect(result).toHaveLength(2); expect(result[0].text).toBe('Trending cast 1'); expect(result[1].text).toBe('Trending cast 2'); expect(mockNeynarClient.fetchFeed).toHaveBeenCalledWith({ feedType: 'filter', filterType: 'global_trending', limit: 20 }); }); it('should respect the limit option', async () => { const limit = 5; // Mock the fetchFeed response mockNeynarClient.fetchFeed.mockResolvedValueOnce({ casts: Array(10).fill(null).map((_, i) => ({ hash: `0xtrending${i + 1}`, author: { fid: 194 + i, username: `user${i + 1}`, display_name: `User ${i + 1}` }, text: `Trending cast ${i + 1}`, timestamp: '2025-02-12T16:00:21.000Z', reactions: { likes_count: 100, recasts_count: 20 }, replies: { count: 15 } })) }); const result = await provider.getTrendingFeed({ limit }); expect(result).toBeDefined(); expect(result).toHaveLength(limit); expect(mockNeynarClient.fetchFeed).toHaveBeenCalledWith({ feedType: 'filter', filterType: 'global_trending', limit }); }); it('should handle empty trending feed', async () => { // Mock the fetchFeed response with no casts mockNeynarClient.fetchFeed.mockResolvedValueOnce({ casts: [] }); const result = await provider.getTrendingFeed(); expect(result).toBeDefined(); expect(result).toHaveLength(0); expect(mockNeynarClient.fetchFeed).toHaveBeenCalledWith({ feedType: 'filter', filterType: 'global_trending', limit: 20 }); }); it('should handle API errors gracefully', async () => { // Mock the fetchFeed response with an error mockNeynarClient.fetchFeed.mockRejectedValueOnce(new Error('API error')); const result = await provider.getTrendingFeed(); expect(result).toBeDefined(); expect(result).toHaveLength(0); expect(mockNeynarClient.fetchFeed).toHaveBeenCalledWith({ feedType: 'filter', filterType: 'global_trending', limit: 20 }); }); it('should handle missing API key', async () => { // Mock the config module to return no API key jest.mock('../../../../src/config', () => ({ default: { providers: { farcaster: { enabled: true, neynarApiKey: '' }, twitter: { enabled: true, apiKey: 'test_twitter_key', apiSecret: 'test_twitter_secret' }, telegram: { enabled: true, botToken: 'test_telegram_token' } } } })); // Re-initialize the provider to use the mocked config const provider = new FarcasterProvider(); const result = await provider.getTrendingFeed(); expect(result).toBeDefined(); expect(result).toHaveLength(2); expect(result[0].text).toContain('web3'); expect(result[1].text).toContain('frames'); expect(result[0].platform).toBe('farcaster'); expect(result[1].platform).toBe('farcaster'); }); it('should use alternative providers when specified', async () => { const providerType = 'openrank'; const timeWindow = '12h'; // Mock the fetch response for alternative provider global.fetch = jest.fn().mockResolvedValueOnce({ json: () => Promise.resolve({ result: { casts: [ { hash: '0xtrending1', author: { fid: 194, username: 'rish', display_name: 'rish' }, text: 'Trending cast from OpenRank', timestamp: '2025-02-12T16:00:21.000Z', reactions: { likes_count: 100, recasts_count: 20 }, replies: { count: 15 } } ] } }) }); const result = await provider.getTrendingFeed({ provider: providerType, timeWindow }); expect(result).toBeDefined(); expect(result).toHaveLength(1); expect(result[0].text).toBe('Trending cast from OpenRank'); expect(global.fetch).toHaveBeenCalledWith( expect.stringContaining('api.neynar.com/v2/farcaster/feed/trending'), expect.objectContaining({ method: 'GET', headers: expect.objectContaining({ 'api_key': 'test-api-key' }) }) ); }); it('should handle MBD provider with custom filters', async () => { const providerType = 'mbd'; const providerMetadata = { startTimestamp: Math.floor(new Date('2025-02-12T00:00:00.000Z').getTime() / 1000), endTimestamp: Math.floor(new Date('2025-02-12T23:59:59.000Z').getTime() / 1000), minLikes: 100, minRecasts: 20 }; // Mock the fetch response for MBD provider global.fetch = jest.fn().mockResolvedValueOnce({ json: () => Promise.resolve({ result: { casts: [ { hash: '0xtrending1', author: { fid: 194, username: 'rish', display_name: 'rish' }, text: 'Trending cast from MBD', timestamp: '2025-02-12T16:00:21.000Z', reactions: { likes_count: 150, recasts_count: 30 }, replies: { count: 15 } } ] } }) }); const result = await provider.getTrendingFeed({ provider: providerType, providerMetadata }); expect(result).toBeDefined(); expect(result).toHaveLength(1); expect(result[0].text).toBe('Trending cast from MBD'); expect(global.fetch).toHaveBeenCalledWith( expect.stringContaining('api.neynar.com/v2/farcaster/feed/trending'), expect.objectContaining({ method: 'GET', headers: expect.objectContaining({ 'api_key': 'test-api-key' }) }) ); }); }); describe('searchChannels', () => { // ... existing tests ... }); describe('searchBulkChannels', () => { it('should throw error when no API key is provided', async () => { // Override the config to remove the API key mockConfig.providers.farcaster.neynarApiKey = ''; const providerWithoutKey = new FarcasterProvider(); await expect(providerWithoutKey.searchBulkChannels(['test'])).rejects.toThrow( 'Cannot search Farcaster channels: No API key provided' ); }); it('should search multiple channels with default parameters', async () => { const queries = ['beyond-ai', 'test']; const result = await provider.searchBulkChannels(queries); expect(result).toBeDefined(); expect(Object.keys(result)).toHaveLength(2); expect(result['beyond-ai'].channels).toHaveLength(1); expect(result['test'].channels).toHaveLength(1); expect(result['beyond-ai'].channels[0].name).toBe('Beyond AI'); expect(result['test'].channels[0].name).toBe('Test Channel'); expect(mockNeynarClient.searchBulkChannels).toHaveBeenCalledWith( queries, expect.objectContaining({ limit: 20, cursor: undefined }) ); }); it('should search multiple channels with custom parameters', async () => { const queries = ['beyond-ai', 'test']; const options = { limit: 10, cursor: 'current-page' }; const result = await provider.searchBulkChannels(queries, options); expect(result).toBeDefined(); expect(Object.keys(result)).toHaveLength(2); expect(result['beyond-ai'].channels).toHaveLength(1); expect(result['test'].channels).toHaveLength(1); expect(mockNeynarClient.searchBulkChannels).toHaveBeenCalledWith( queries, expect.objectContaining(options) ); }); it('should handle empty queries array', async () => { const result = await provider.searchBulkChannels([]); expect(result).toBeDefined(); expect(Object.keys(result)).toHaveLength(0); }); it('should handle queries with no results', async () => { const queries = ['nonexistent', 'another-nonexistent']; const result = await provider.searchBulkChannels(queries); expect(result).toBeDefined(); expect(Object.keys(result)).toHaveLength(2); expect(result['nonexistent'].channels).toHaveLength(0); expect(result['another-nonexistent'].channels).toHaveLength(0); }); it('should handle API errors gracefully', async () => { mockNeynarClient.searchBulkChannels.mockRejectedValueOnce(new Error('API Error')); const queries = ['beyond-ai', 'test']; await expect(provider.searchBulkChannels(queries)).rejects.toThrow('API 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/Beyond-Network-AI/beyond-mcp-server'

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