Skip to main content
Glama

Audius MCP Server

# TODO Implementation Guide This document provides detailed implementation guidance for adding missing SDK functionality to the Audius MCP server. ## High Priority Tasks ### 1. Albums Module Implementation **Files to create:** - `src/tools/albums.ts` - Album-specific tools - Update `src/toolsets/index.ts` - Add album toolset registration **Step 1: Create src/tools/albums.ts** ```typescript import { z } from 'zod'; import { AudiusClient } from '../sdk-client.js'; import { RequestHandlerExtra } from '../types/index.js'; import { createTextResponse, createMixedResponse, createResourceResponse } from '../utils/response.js'; // Schema for get-album tool export const getAlbumSchema = { type: 'object', properties: { albumId: { type: 'string', description: 'Album ID (albums are playlists with is_album=true)', }, }, required: ['albumId'], }; // Schema for get-album-tracks tool export const getAlbumTracksSchema = { type: 'object', properties: { albumId: { type: 'string', description: 'Album ID to get tracks for', }, }, required: ['albumId'], }; // Schema for get-user-albums tool export const getUserAlbumsSchema = { type: 'object', properties: { userId: { type: 'string', description: 'User ID to get albums for', }, offset: { type: 'number', description: 'Offset for pagination (default: 0)', }, limit: { type: 'number', description: 'Maximum number of albums to return (default: 20)', }, }, required: ['userId'], }; // Handler for get-album export async function getAlbum( args: { albumId: string }, extra?: RequestHandlerExtra ): Promise<any> { try { const client = AudiusClient.getInstance(); // Albums are playlists with is_album=true const response = await client.playlists.getPlaylist({ playlistId: args.albumId }); if (!response.data?.isAlbum) { return createTextResponse('The specified playlist is not an album', true); } const album = response.data; const albumInfo = `Album: ${album.playlistName} Artist: ${album.user.name} Track Count: ${album.trackCount} Total Duration: ${album.totalPlayTime}s Release Date: ${album.releaseDate || 'N/A'} Description: ${album.description || 'N/A'}`; return createMixedResponse( albumInfo, createResourceResponse(`audius://album/${album.id}`, album.playlistName, album) ); } catch (error: any) { return createTextResponse(`Error fetching album: ${error.message}`, true); } } // Handler for get-album-tracks export async function getAlbumTracks( args: { albumId: string }, extra?: RequestHandlerExtra ): Promise<any> { try { const client = AudiusClient.getInstance(); const response = await client.playlists.getPlaylistTracks({ playlistId: args.albumId }); if (!response.data || response.data.length === 0) { return createTextResponse('No tracks found in this album', true); } const tracks = response.data; let trackList = 'Album Tracks:\n\n'; tracks.forEach((track: any, index: number) => { trackList += `${index + 1}. ${track.title} Duration: ${Math.floor(track.duration / 60)}:${(track.duration % 60).toString().padStart(2, '0')} Play Count: ${track.playCount} ID: ${track.id}\n\n`; }); return createTextResponse(trackList); } catch (error: any) { return createTextResponse(`Error fetching album tracks: ${error.message}`, true); } } // Handler for get-user-albums export async function getUserAlbums( args: { userId: string; offset?: number; limit?: number }, extra?: RequestHandlerExtra ): Promise<any> { try { const client = AudiusClient.getInstance(); const offset = args.offset || 0; const limit = args.limit || 20; // Get user's playlists and filter for albums const response = await client.users.getUserPlaylists({ id: args.userId, offset, limit: limit * 2 // Request more to filter }); if (!response.data) { return createTextResponse('No playlists found for this user', true); } // Filter for albums only const albums = response.data.filter((p: any) => p.isAlbum).slice(0, limit); if (albums.length === 0) { return createTextResponse('No albums found for this user'); } let albumList = `User Albums (${albums.length}):\n\n`; albums.forEach((album: any, index: number) => { albumList += `${index + 1}. ${album.playlistName} Track Count: ${album.trackCount} Release Date: ${album.releaseDate || 'N/A'} ID: ${album.id}\n\n`; }); return createTextResponse(albumList); } catch (error: any) { return createTextResponse(`Error fetching user albums: ${error.message}`, true); } } ``` **Step 2: Update src/toolsets/index.ts** Add these imports after the existing playlist imports (around line 21): ```typescript import { getAlbum as getAlbumNew, getAlbumSchema as getAlbumNewSchema, getAlbumTracks, getAlbumTracksSchema, getUserAlbums, getUserAlbumsSchema } from '../tools/albums.js'; ``` Create the albums toolset after the playlists toolset (around line 166): ```typescript // 4.5 Albums Toolset (new) const albumTools = new Toolset('albums', 'Audius Album-specific tools'); albumTools.addReadTools( createServerTool('get-album-details', getAlbumNewSchema, getAlbumNew, true, 'Get album details'), createServerTool('get-album-tracks', getAlbumTracksSchema, getAlbumTracks, true, 'Get tracks in an album'), createServerTool('get-user-albums', getUserAlbumsSchema, getUserAlbums, true, 'Get albums for a user') ); ``` Add the toolset to the group (around line 313): ```typescript toolsetGroup.addToolset(albumTools); ``` ### 2. OAuth Module Implementation **Files to create:** - `src/tools/oauth.ts` - OAuth authentication tools - `src/toolsets/oauth.ts` - OAuth toolset registration **Implementation notes:** - OAuth requires browser-based flow, may need special handling for MCP context - Consider storing tokens securely - May need to implement a callback handler ### 3. Core Functionality #### 3.1 Add Track Stream URL Support **File to modify:** `src/tools/tracks.ts` Add this schema and handler: ```typescript // Schema for get-track-stream-url tool export const getTrackStreamUrlSchema = { type: 'object', properties: { trackId: { type: 'string', description: 'Track ID to get stream URL for', }, }, required: ['trackId'], }; // Handler for get-track-stream-url export async function getTrackStreamUrl( args: { trackId: string }, extra?: RequestHandlerExtra ): Promise<any> { try { const client = AudiusClient.getInstance(); // Get track details first to verify it exists const trackResponse = await client.tracks.getTrack({ trackId: args.trackId }); if (!trackResponse.data) { return createTextResponse('Track not found', true); } const track = trackResponse.data; // Check if track requires premium access if (track.isPremium || track.streamConditions) { return createTextResponse( `This track requires special access conditions. Stream conditions: ${JSON.stringify(track.streamConditions)}`, true ); } // Construct stream URL based on API pattern const baseUrl = 'https://discoveryprovider.audius.co/v1'; const streamUrl = `${baseUrl}/tracks/${args.trackId}/stream`; return createTextResponse(`Stream URL: ${streamUrl}\n\nNote: This URL requires proper authentication headers if the track has access restrictions.`); } catch (error: any) { return createTextResponse(`Error getting stream URL: ${error.message}`, true); } } ``` Update the toolset registration in `src/toolsets/index.ts`: ```typescript // In the tracks toolset section, add: trackTools.addReadTools( // ... existing tools ... createServerTool('get-track-stream-url', getTrackStreamUrlSchema, getTrackStreamUrl, true, 'Get stream URL for a track') ); ``` #### 3.2 Add Resolve Functionality **File to create:** `src/tools/core.ts` ```typescript import { z } from 'zod'; import { AudiusClient } from '../sdk-client.js'; import { RequestHandlerExtra } from '../types/index.js'; import { createTextResponse, createMixedResponse, createResourceResponse } from '../utils/response.js'; // Schema for resolve tool export const resolveSchema = { type: 'object', properties: { url: { type: 'string', description: 'Audius URL to resolve (e.g., audius.co/user/track)', }, }, required: ['url'], }; // Handler for resolve export async function resolve( args: { url: string }, extra?: RequestHandlerExtra ): Promise<any> { try { const client = AudiusClient.getInstance(); // Extract the path from the URL let path = args.url; if (path.includes('audius.co/')) { path = path.split('audius.co/')[1]; } // Make API call to resolve endpoint const apiUrl = `https://discoveryprovider.audius.co/v1/resolve?url=${encodeURIComponent(path)}`; const response = await fetch(apiUrl); if (!response.ok) { throw new Error(`Failed to resolve URL: ${response.statusText}`); } const data = await response.json(); if (!data.data) { return createTextResponse('Could not resolve the provided URL', true); } // Format response based on entity type const entity = data.data; let entityType = 'unknown'; let entityInfo = ''; if (entity.track) { entityType = 'track'; const track = entity.track; entityInfo = `Track: ${track.title} Artist: ${track.user.name} Duration: ${Math.floor(track.duration / 60)}:${(track.duration % 60).toString().padStart(2, '0')} Play Count: ${track.playCount} ID: ${track.id}`; } else if (entity.user) { entityType = 'user'; const user = entity.user; entityInfo = `User: ${user.name} Handle: @${user.handle} Followers: ${user.followerCount} Tracks: ${user.trackCount} ID: ${user.id}`; } else if (entity.playlist) { entityType = 'playlist'; const playlist = entity.playlist; entityInfo = `${playlist.isAlbum ? 'Album' : 'Playlist'}: ${playlist.playlistName} Owner: ${playlist.user.name} Track Count: ${playlist.trackCount} ID: ${playlist.id}`; } return createMixedResponse( entityInfo, createResourceResponse(`audius://${entityType}/${entity.id}`, entity.name || entity.title || entity.playlistName, entity) ); } catch (error: any) { return createTextResponse(`Error resolving URL: ${error.message}`, true); } } // Schema for get-sdk-version tool export const getSdkVersionSchema = { type: 'object', properties: {}, }; // Handler for get-sdk-version export async function getSdkVersion( args: {}, extra?: RequestHandlerExtra ): Promise<any> { try { // Read from package.json const packageJson = require('../../package.json'); const sdkVersion = packageJson.dependencies['@audius/sdk']; const mcpVersion = packageJson.version; return createTextResponse(`Audius SDK Version: ${sdkVersion}\nMCP Server Version: ${mcpVersion}`); } catch (error: any) { return createTextResponse(`Error getting SDK version: ${error.message}`, true); } } ``` Create a new core toolset in `src/toolsets/index.ts`: ```typescript // Import at the top import { resolve, resolveSchema, getSdkVersion, getSdkVersionSchema } from '../tools/core.js'; // Add after other toolsets (around line 300) // 14. Core Toolset const coreTools = new Toolset('core', 'Core Audius functionality'); coreTools.addReadTools( createServerTool('resolve', resolveSchema, resolve, true, 'Resolve Audius URLs to entities'), createServerTool('get-sdk-version', getSdkVersionSchema, getSdkVersion, true, 'Get SDK and server version info') ); // Add to toolset group toolsetGroup.addToolset(coreTools); ``` ### 4. Missing Types Definition **File to create:** - `src/types/index.ts` ```typescript // src/types/index.ts export interface RequestHandlerExtra { signal?: AbortSignal; [key: string]: any; } export interface ToolHandler<T = any> { (params: T, extra?: RequestHandlerExtra): Promise<any>; } ``` ## Medium Priority Tasks ### 5. Bulk Operations #### 5.1 Bulk Tracks **File to modify:** `src/tools/tracks.ts` Add this schema and handler: ```typescript // Schema for get-bulk-tracks tool export const getBulkTracksSchema = { type: 'object', properties: { trackIds: { type: 'array', items: { type: 'string' }, description: 'Array of track IDs to fetch', }, }, required: ['trackIds'], }; // Handler for get-bulk-tracks export async function getBulkTracks( args: { trackIds: string[] }, extra?: RequestHandlerExtra ): Promise<any> { try { const client = AudiusClient.getInstance(); const tracks = []; // SDK may not have bulk endpoint, so we fetch in parallel const trackPromises = args.trackIds.map(id => client.tracks.getTrack({ trackId: id }).catch(err => ({ error: true, trackId: id, message: err.message })) ); const results = await Promise.all(trackPromises); let successCount = 0; let errorCount = 0; let trackInfo = 'Bulk Track Results:\n\n'; results.forEach((result, index) => { if (result.error) { errorCount++; trackInfo += `❌ Track ${args.trackIds[index]}: ${result.message}\n`; } else if (result.data) { successCount++; const track = result.data; trackInfo += `✅ ${track.title} by ${track.user.name} (ID: ${track.id})\n`; tracks.push(track); } }); trackInfo += `\nTotal: ${successCount} found, ${errorCount} errors`; return createTextResponse(trackInfo); } catch (error: any) { return createTextResponse(`Error fetching bulk tracks: ${error.message}`, true); } } ``` #### 5.2 Bulk Users **File to modify:** `src/tools/users.ts` ```typescript // Schema for get-bulk-users tool export const getBulkUsersSchema = { type: 'object', properties: { userIds: { type: 'array', items: { type: 'string' }, description: 'Array of user IDs to fetch', }, }, required: ['userIds'], }; // Handler for get-bulk-users export async function getBulkUsers( args: { userIds: string[] }, extra?: RequestHandlerExtra ): Promise<any> { try { const client = AudiusClient.getInstance(); const userPromises = args.userIds.map(id => client.users.getUser({ id }).catch(err => ({ error: true, userId: id, message: err.message })) ); const results = await Promise.all(userPromises); let userInfo = 'Bulk User Results:\n\n'; results.forEach((result) => { if (result.error) { userInfo += `❌ User ${result.userId}: ${result.message}\n`; } else if (result.data) { const user = result.data; userInfo += `✅ ${user.name} (@${user.handle}) - ${user.followerCount} followers (ID: ${user.id})\n`; } }); return createTextResponse(userInfo); } catch (error: any) { return createTextResponse(`Error fetching bulk users: ${error.message}`, true); } } ``` ### 6. Parameter Naming Convention Fix **Example migration for a single tool:** Current (snake_case): ```typescript export const getUserTracksSchema = { type: 'object', properties: { user_id: { type: 'string', description: 'User ID' }, track_count: { type: 'number', description: 'Number of tracks' } } }; ``` Updated (camelCase): ```typescript export const getUserTracksSchema = { type: 'object', properties: { userId: { type: 'string', description: 'User ID' }, trackCount: { type: 'number', description: 'Number of tracks' } } }; // Update handler to match: export async function getUserTracks( args: { userId: string; trackCount?: number }, extra?: RequestHandlerExtra ): Promise<any> { // Convert to SDK expected format if needed const response = await client.users.getUserTracks({ id: args.userId, limit: args.trackCount }); } ``` **Files that need parameter updates:** - All files in `src/tools/*.ts` - Focus on commonly used parameters: - `user_id` → `userId` - `track_id` → `trackId` - `playlist_id` → `playlistId` - `track_count` → `trackCount` - `play_count` → `playCount` - `follower_count` → `followerCount` ### 7. User Library Methods **File to modify:** `src/tools/users.ts` ```typescript // Schema for get-user-library-tracks export const getUserLibraryTracksSchema = { type: 'object', properties: { userId: { type: 'string', description: 'User ID to get library tracks for', }, offset: { type: 'number', description: 'Offset for pagination', }, limit: { type: 'number', description: 'Number of tracks to return', }, }, required: ['userId'], }; // Handler for get-user-library-tracks export async function getUserLibraryTracks( args: { userId: string; offset?: number; limit?: number }, extra?: RequestHandlerExtra ): Promise<any> { try { const client = AudiusClient.getInstance(); // Get user's favorited tracks as library const response = await client.users.getUserFavorites({ id: args.userId, offset: args.offset || 0, limit: args.limit || 20 }); if (!response.data || response.data.length === 0) { return createTextResponse('No tracks in user library'); } let libraryInfo = `User Library Tracks (${response.data.length}):\n\n`; response.data.forEach((track: any, index: number) => { libraryInfo += `${index + 1}. ${track.title} by ${track.user.name} Duration: ${Math.floor(track.duration / 60)}:${(track.duration % 60).toString().padStart(2, '0')} Added: ${track.favoriteCreatedAt || 'Unknown'}\n\n`; }); return createTextResponse(libraryInfo); } catch (error: any) { return createTextResponse(`Error fetching library tracks: ${error.message}`, true); } } // Similar patterns for getUserLibraryAlbums and getUserLibraryPlaylists ``` ## Low Priority Tasks ### 8. Additional User Methods - getAuthorizedApps - getConnectedWallets - getDeveloperApps ### 9. Upload Enhancement - Add buffer upload support (currently only URL upload is supported) ### 10. SDK Version Method - Add getSdkVersion() to return current SDK version ## Implementation Order 1. Fix missing types (RequestHandlerExtra) - blocks other implementations 2. Implement Albums module - high user value 3. Add core resolve() functionality - enables URL-based access 4. Add streaming URL support - essential for playback 5. Fix parameter naming convention - improves SDK compatibility 6. Implement bulk operations - improves efficiency 7. Add OAuth module - enables authenticated operations 8. Implement remaining methods by priority ## Testing Each Implementation After implementing each feature: 1. Run `npm run build` to ensure TypeScript compiles 2. Run `npm run lint` to check for linting errors 3. Test with the test client: `npm run test-client` 4. Update CHANGELOG.md with new features 5. Update README.md if new toolsets are added ## Summary of Missing SDK Methods by Priority ### Critical Missing Features (High Priority) 1. **Albums Module** - Entire module missing - getAlbum, getAlbumTracks, getUserAlbums 2. **OAuth Authentication** - Required for protected endpoints - login, verifyToken, makeRequestWithAuth 3. **Core Features** - resolve() - URL resolution - getTrackStreamUrl() - Streaming support ### Important Missing Features (Medium Priority) 1. **Bulk Operations** - Performance optimization - getBulkTracks, getBulkUsers, getBulkPlaylists 2. **User Library Methods** - Common user features - getLibraryTracks, getLibraryAlbums, getLibraryPlaylists 3. **Parameter Naming** - SDK compatibility - Convert snake_case to camelCase ### Nice-to-Have Features (Low Priority) 1. **Additional User Methods** - getAuthorizedApps, getConnectedWallets, getDeveloperApps 2. **Buffer Upload Support** 3. **SDK Version Method** ## Estimated Implementation Time - High Priority: 2-3 days - Medium Priority: 2-3 days - Low Priority: 1 day - Testing & Documentation: 1 day **Total: ~1 week for full SDK parity** ## Next Steps 1. Start with creating the missing types file (✅ Done) 2. Implement Albums module as it's a complete feature set 3. Add core resolve() and streaming functionality 4. Work through medium priority items for better SDK compatibility 5. Address low priority items as time permits

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/glassBead-tc/audius-mcp-atris'

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