Skip to main content
Glama
index.ts.backup53 kB
/** * Navidrome MCP Server - Tool Registry * Copyright (C) 2025 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ import type { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { NavidromeClient } from '../client/navidrome-client.js'; import type { Config } from '../config.js'; import { ListToolsRequestSchema, CallToolRequestSchema, type Tool, } from '@modelcontextprotocol/sdk/types.js'; import { testConnection } from './test.js'; import { listSongs } from './library.js'; import { listAlbums, listArtists, listGenres, getSong, getAlbum, getArtist, getSongPlaylists, } from './media-library.js'; import { listPlaylists, getPlaylist, createPlaylist, updatePlaylist, deletePlaylist, getPlaylistTracks, addTracksToPlaylist, removeTracksFromPlaylist, reorderPlaylistTrack, batchAddTracksToPlaylist, } from './playlist-management.js'; import { searchAll, searchSongs, searchAlbums, searchArtists, } from './search.js'; import { starItem, unstarItem, setRating, listStarredItems, listTopRated, } from './user-preferences.js'; import { getQueue, setQueue, clearQueue, } from './queue-management.js'; import { listRecentlyPlayed, listMostPlayed, } from './listening-history.js'; import { getSimilarArtists, getSimilarTracks, getArtistInfo, getTopTracksByArtist, getTrendingMusic, } from './lastfm-discovery.js'; import { validateRadioStream } from './radio-validation.js'; import { listRadioStations, createRadioStation, deleteRadioStation, getRadioStation, playRadioStation, getCurrentRadioInfo, batchCreateRadioStations, } from './radio.js'; import { listTags, getTag, searchByTags, getTagDistribution, listUniqueTags, } from './tags.js'; import { discoverRadioStations, getRadioFilters, getStationByUuid, clickStation, voteStation, } from './radio-discovery.js'; import { getLyrics } from './lyrics.js'; import { DEFAULT_VALUES } from '../constants/defaults.js'; /** * Helper function to create a standardized tool response * Reduces duplication across tool handlers */ function createToolResponse(result: unknown): { content: Array<{ type: string; text: string }> } { return { content: [{ type: 'text', text: JSON.stringify(result, null, 2), }], }; } export function registerTools(server: Server, client: NavidromeClient, config: Config): void { // Use feature flags from config instead of direct environment access const hasLastFm = config.features.lastfm; const hasRadioBrowser = config.features.radioBrowser; const hasLyrics = config.features.lyrics; // Debug logging for disabled features if (!hasLastFm && config.debug) { console.warn('[DEBUG] Last.fm tools disabled: LASTFM_API_KEY not configured'); } if (!hasRadioBrowser && config.debug) { console.warn('[DEBUG] Radio Browser discovery tools disabled: RADIO_BROWSER_USER_AGENT not configured'); } if (!hasLyrics && config.debug) { console.warn('[DEBUG] Lyrics tools disabled: LYRICS_PROVIDER and LRCLIB_USER_AGENT must be configured'); } // Define core tools (always available) const tools: Tool[] = [ { name: 'test_connection', description: 'Test the connection to the Navidrome server', inputSchema: { type: 'object', properties: { includeServerInfo: { type: 'boolean', description: 'Include detailed server information in the response', default: false, }, }, }, }, { name: 'list_songs', description: 'List songs from the Navidrome music library with clean, LLM-friendly data (filtering and pagination supported)', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of songs to return (1-500)', minimum: 1, maximum: 500, default: DEFAULT_VALUES.SONGS_LIMIT, }, offset: { type: 'number', description: 'Number of songs to skip for pagination', minimum: 0, default: 0, }, sort: { type: 'string', description: 'Field to sort by', enum: ['title', 'artist', 'album', 'year', 'duration', 'playCount', 'rating'], default: 'title', }, order: { type: 'string', description: 'Sort order', enum: ['ASC', 'DESC'], default: 'ASC', }, starred: { type: 'boolean', description: 'Filter for starred songs only', }, }, }, }, { name: 'list_albums', description: 'List albums from the Navidrome music library with clean, LLM-friendly data', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of albums to return (1-500)', minimum: 1, maximum: 500, default: DEFAULT_VALUES.ALBUMS_LIMIT, }, offset: { type: 'number', description: 'Number of albums to skip for pagination', minimum: 0, default: 0, }, sort: { type: 'string', description: 'Field to sort by', default: 'name', }, order: { type: 'string', description: 'Sort order', enum: ['ASC', 'DESC'], default: 'ASC', }, }, }, }, { name: 'list_artists', description: 'List artists from the Navidrome music library with clean, LLM-friendly data', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of artists to return (1-500)', minimum: 1, maximum: 500, default: 100, }, offset: { type: 'number', description: 'Number of artists to skip for pagination', minimum: 0, default: 0, }, sort: { type: 'string', description: 'Field to sort by', default: 'name', }, order: { type: 'string', description: 'Sort order', enum: ['ASC', 'DESC'], default: 'ASC', }, }, }, }, { name: 'list_genres', description: 'List all genres from the Navidrome music library', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of genres to return (1-500)', minimum: 1, maximum: 500, default: 100, }, offset: { type: 'number', description: 'Number of genres to skip for pagination', minimum: 0, default: 0, }, sort: { type: 'string', description: 'Field to sort by', default: 'name', }, order: { type: 'string', description: 'Sort order', enum: ['ASC', 'DESC'], default: 'ASC', }, }, }, }, { name: 'get_song', description: 'Get detailed information about a specific song by ID', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the song', }, }, required: ['id'], }, }, { name: 'get_album', description: 'Get detailed information about a specific album by ID', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the album', }, }, required: ['id'], }, }, { name: 'get_artist', description: 'Get detailed information about a specific artist by ID', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the artist', }, }, required: ['id'], }, }, { name: 'get_song_playlists', description: 'Get all playlists that contain a specific song', inputSchema: { type: 'object', properties: { songId: { type: 'string', description: 'The unique ID of the song', }, }, required: ['songId'], }, }, { name: 'list_playlists', description: 'List all playlists accessible to the user with clean, LLM-friendly data', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of playlists to return (1-500)', minimum: 1, maximum: 500, default: 100, }, offset: { type: 'number', description: 'Number of playlists to skip for pagination', minimum: 0, default: 0, }, sort: { type: 'string', description: 'Field to sort by', default: 'name', }, order: { type: 'string', description: 'Sort order', enum: ['ASC', 'DESC'], default: 'ASC', }, }, }, }, { name: 'get_playlist', description: 'Get detailed information about a specific playlist by ID', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the playlist', }, }, required: ['id'], }, }, { name: 'create_playlist', description: 'Create a new playlist with a name, optional description, and visibility setting', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'The name of the playlist', }, comment: { type: 'string', description: 'Optional description or comment for the playlist', }, public: { type: 'boolean', description: 'Whether the playlist should be public', default: false, }, }, required: ['name'], }, }, { name: 'update_playlist', description: 'Update a playlist\'s metadata (name, description, visibility)', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the playlist to update', }, name: { type: 'string', description: 'New name for the playlist', }, comment: { type: 'string', description: 'New description or comment', }, public: { type: 'boolean', description: 'New public visibility setting', }, }, required: ['id'], }, }, { name: 'delete_playlist', description: 'Delete a playlist (owner or admin only)', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the playlist to delete', }, }, required: ['id'], }, }, { name: 'get_playlist_tracks', description: 'Get all tracks in a playlist (supports JSON or M3U export)', inputSchema: { type: 'object', properties: { playlistId: { type: 'string', description: 'The unique ID of the playlist', }, limit: { type: 'number', description: 'Maximum number of tracks to return (1-500)', minimum: 1, maximum: 500, default: 100, }, offset: { type: 'number', description: 'Number of tracks to skip for pagination', minimum: 0, default: 0, }, format: { type: 'string', description: 'Output format: json for structured data, m3u for playlist file', enum: ['json', 'm3u'], default: 'json', }, }, required: ['playlistId'], }, }, { name: 'add_tracks_to_playlist', description: 'Add tracks to a playlist (supports song IDs, album IDs, artist IDs, or specific discs)', inputSchema: { type: 'object', properties: { playlistId: { type: 'string', description: 'The unique ID of the playlist', }, ids: { type: 'array', items: { type: 'string' }, description: 'Array of song IDs to add', }, albumIds: { type: 'array', items: { type: 'string' }, description: 'Array of album IDs to add (all tracks from albums)', }, artistIds: { type: 'array', items: { type: 'string' }, description: 'Array of artist IDs to add (all tracks from artists)', }, discs: { type: 'array', items: { type: 'object', properties: { albumId: { type: 'string' }, discNumber: { type: 'number' }, }, required: ['albumId', 'discNumber'], }, description: 'Array of specific discs to add', }, }, required: ['playlistId'], }, }, { name: 'remove_tracks_from_playlist', description: 'Remove tracks from a playlist by track position IDs', inputSchema: { type: 'object', properties: { playlistId: { type: 'string', description: 'The unique ID of the playlist', }, trackIds: { type: 'array', items: { type: 'string' }, description: 'Array of track position IDs to remove', minItems: 1, }, }, required: ['playlistId', 'trackIds'], }, }, { name: 'reorder_playlist_track', description: 'Reorder a track within a playlist to a new position', inputSchema: { type: 'object', properties: { playlistId: { type: 'string', description: 'The unique ID of the playlist', }, trackId: { type: 'string', description: 'The track position ID to move', }, insert_before: { type: 'number', description: 'New position (0-based index) to insert the track before', minimum: 0, }, }, required: ['playlistId', 'trackId', 'insert_before'], }, }, { name: 'batch_add_tracks_to_playlist', description: 'Batch add multiple sets of tracks to a playlist', inputSchema: { type: 'object', properties: { playlistId: { type: 'string', description: 'The unique ID of the playlist', }, trackSets: { type: 'array', description: 'Array of track sets to add to the playlist', items: { type: 'object', properties: { ids: { type: 'array', items: { type: 'string' }, description: 'Array of song IDs to add', }, albumIds: { type: 'array', items: { type: 'string' }, description: 'Array of album IDs to add (all tracks)', }, artistIds: { type: 'array', items: { type: 'string' }, description: 'Array of artist IDs to add (all tracks)', }, discs: { type: 'array', description: 'Array of specific discs to add', items: { type: 'object', properties: { albumId: { type: 'string' }, discNumber: { type: 'number' }, }, required: ['albumId', 'discNumber'], }, }, }, }, }, }, required: ['playlistId', 'trackSets'], }, }, { name: 'search_all', description: 'Search across all content types (artists, albums, songs) using a single query', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search terms to look for', }, artistCount: { type: 'number', description: 'Maximum number of artists to return', minimum: 0, maximum: 100, default: DEFAULT_VALUES.SEARCH_ALL_LIMIT, }, albumCount: { type: 'number', description: 'Maximum number of albums to return', minimum: 0, maximum: 100, default: DEFAULT_VALUES.SEARCH_ALL_LIMIT, }, songCount: { type: 'number', description: 'Maximum number of songs to return', minimum: 0, maximum: 100, default: DEFAULT_VALUES.SEARCH_ALL_LIMIT, }, }, required: ['query'], }, }, { name: 'search_songs', description: 'Search for songs by title, artist, or album', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search terms to look for in song titles, artists, or albums', }, limit: { type: 'number', description: 'Maximum number of songs to return', minimum: 1, maximum: 100, default: 100, }, }, required: ['query'], }, }, { name: 'search_albums', description: 'Search for albums by name or artist', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search terms to look for in album names or artists', }, limit: { type: 'number', description: 'Maximum number of albums to return', minimum: 1, maximum: 100, default: 100, }, }, required: ['query'], }, }, { name: 'search_artists', description: 'Search for artists by name', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search terms to look for in artist names', }, limit: { type: 'number', description: 'Maximum number of artists to return', minimum: 1, maximum: 100, default: 100, }, }, required: ['query'], }, }, { name: 'star_item', description: 'Star/favorite a song, album, or artist', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the item to star', }, type: { type: 'string', description: 'The type of item to star', enum: ['song', 'album', 'artist'], }, }, required: ['id', 'type'], }, }, { name: 'unstar_item', description: 'Unstar/unfavorite a song, album, or artist', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the item to unstar', }, type: { type: 'string', description: 'The type of item to unstar', enum: ['song', 'album', 'artist'], }, }, required: ['id', 'type'], }, }, { name: 'set_rating', description: 'Set a rating (0-5 stars) for a song, album, or artist', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the item to rate', }, type: { type: 'string', description: 'The type of item to rate', enum: ['song', 'album', 'artist'], }, rating: { type: 'number', description: 'Rating from 0-5 stars (0 removes rating)', minimum: 0, maximum: 5, }, }, required: ['id', 'type', 'rating'], }, }, { name: 'list_starred_items', description: 'List starred/favorited songs, albums, or artists', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Type of starred items to list', enum: ['songs', 'albums', 'artists'], }, limit: { type: 'number', description: 'Maximum number of items to return (1-500)', minimum: 1, maximum: 500, default: 100, }, offset: { type: 'number', description: 'Number of items to skip for pagination', minimum: 0, default: 0, }, }, required: ['type'], }, }, { name: 'list_top_rated', description: 'List top-rated songs, albums, or artists', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Type of items to list', enum: ['songs', 'albums', 'artists'], }, minRating: { type: 'number', description: 'Minimum rating to include (1-5)', minimum: 1, maximum: 5, default: 4, }, limit: { type: 'number', description: 'Maximum number of items to return (1-500)', minimum: 1, maximum: 500, default: 100, }, offset: { type: 'number', description: 'Number of items to skip for pagination', minimum: 0, default: 0, }, }, required: ['type'], }, }, { name: 'get_queue', description: 'Get the current playback queue', inputSchema: { type: 'object', properties: {}, }, }, { name: 'set_queue', description: 'Set the playback queue with specified songs', inputSchema: { type: 'object', properties: { songIds: { type: 'array', items: { type: 'string' }, description: 'Array of song IDs to add to queue', }, current: { type: 'number', description: 'Index of current track (0-based)', minimum: 0, default: 0, }, position: { type: 'number', description: 'Playback position in seconds', minimum: 0, default: 0, }, }, required: ['songIds'], }, }, { name: 'clear_queue', description: 'Clear the playback queue', inputSchema: { type: 'object', properties: {}, }, }, { name: 'list_recently_played', description: 'List recently played tracks with time filtering', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of tracks to return (1-500)', minimum: 1, maximum: 500, default: 100, }, offset: { type: 'number', description: 'Number of tracks to skip for pagination', minimum: 0, default: 0, }, timeRange: { type: 'string', description: 'Time range for recently played tracks', enum: ['today', 'week', 'month', 'all'], default: 'all', }, }, }, }, { name: 'list_most_played', description: 'List most played songs, albums, or artists', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Type of items to list', enum: ['songs', 'albums', 'artists'], default: 'songs', }, limit: { type: 'number', description: 'Maximum number of items to return (1-500)', minimum: 1, maximum: 500, default: 100, }, offset: { type: 'number', description: 'Number of items to skip for pagination', minimum: 0, default: 0, }, minPlayCount: { type: 'number', description: 'Minimum play count to include', minimum: 1, default: 1, }, }, }, }, { name: 'list_radio_stations', description: 'List all internet radio stations from Navidrome', inputSchema: { type: 'object', properties: {}, }, }, { name: 'create_radio_station', description: 'Create a new internet radio station in Navidrome', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Station name (required)', }, streamUrl: { type: 'string', description: 'Stream URL (required) - must be valid HTTP/HTTPS URL', }, homePageUrl: { type: 'string', description: 'Optional homepage URL for the station', }, validateBeforeAdd: { type: 'boolean', description: 'Validate stream URL before adding (default: false)', }, }, required: ['name', 'streamUrl'], }, }, { name: 'delete_radio_station', description: 'Delete an internet radio station by ID', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the radio station to delete', }, }, required: ['id'], }, }, { name: 'get_radio_station', description: 'Get detailed information about a specific radio station by ID', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the radio station', }, }, required: ['id'], }, }, { name: 'play_radio_station', description: 'Start playing a radio station by setting it in the playback queue', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the radio station to play', }, }, required: ['id'], }, }, { name: 'get_current_radio_info', description: 'Get information about currently playing radio station and stream metadata', inputSchema: { type: 'object', properties: {}, }, }, { name: 'batch_create_radio_stations', description: 'Batch create multiple internet radio stations in Navidrome', inputSchema: { type: 'object', properties: { stations: { type: 'array', description: 'Array of radio stations to create', items: { type: 'object', properties: { name: { type: 'string', description: 'Station name (required)', }, streamUrl: { type: 'string', description: 'Stream URL (required)', }, homePageUrl: { type: 'string', description: 'Optional homepage URL', }, }, required: ['name', 'streamUrl'], }, }, validateBeforeAdd: { type: 'boolean', description: 'Validate all stream URLs before adding (default: false)', }, }, required: ['stations'], }, }, { name: 'list_tags', description: 'List all metadata tags with optional filtering by tag name and pagination', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of tags to return (1-500)', minimum: 1, maximum: 500, default: 100, }, offset: { type: 'number', description: 'Number of tags to skip for pagination', minimum: 0, default: 0, }, sort: { type: 'string', description: 'Field to sort by', enum: ['tagName', 'tagValue', 'albumCount', 'songCount'], default: 'tagName', }, order: { type: 'string', description: 'Sort order', enum: ['ASC', 'DESC'], default: 'ASC', }, tagName: { type: 'string', description: 'Filter by specific tag name (e.g., "genre", "composer", "label")', }, }, }, }, { name: 'get_tag', description: 'Get detailed information about a specific tag by ID', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The unique ID of the tag', }, }, required: ['id'], }, }, { name: 'search_by_tags', description: 'Search for songs/albums by specific tag criteria (e.g., find all songs with genre "Jazz" or composer "Bach")', inputSchema: { type: 'object', properties: { tagName: { type: 'string', description: 'Tag name to search by (e.g., "genre", "composer", "label")', }, tagValue: { type: 'string', description: 'Optional tag value to match exactly', }, limit: { type: 'number', description: 'Maximum number of matching tags to return', minimum: 1, maximum: 100, default: 100, }, }, required: ['tagName'], }, }, { name: 'get_tag_distribution', description: 'Analyze tag usage patterns and distribution across the music library', inputSchema: { type: 'object', properties: { tagNames: { type: 'array', items: { type: 'string' }, description: 'Specific tag names to analyze (if omitted, analyzes all)', }, limit: { type: 'number', description: 'Maximum number of tag names to analyze', minimum: 1, maximum: 50, default: 10, }, distributionLimit: { type: 'number', description: 'Maximum number of tag values to show in distribution (prevents huge output)', minimum: 1, maximum: 100, default: 20, }, }, }, }, { name: 'list_unique_tags', description: 'List all unique tag names with statistics (how many unique values, total usage)', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of tag names to return', minimum: 1, maximum: 100, default: 100, }, minUsage: { type: 'number', description: 'Minimum song count to include a tag name', minimum: 1, default: 1, }, }, }, }, { name: 'validate_radio_stream', description: 'Tests if a radio stream URL is valid, accessible, and streams audio content. Checks HTTP response, content type, streaming headers, and attempts to verify audio data.', inputSchema: { type: 'object', properties: { url: { type: 'string', format: 'uri', description: 'The radio stream URL to validate (required)', }, timeout: { type: 'number', description: 'Timeout in milliseconds (default: 8000, max: 30000)', minimum: 1000, maximum: 30000, default: 8000, }, followRedirects: { type: 'boolean', description: 'Follow HTTP redirects (default: true)', default: true, }, }, required: ['url'], }, }, ]; // Add conditional tools based on configuration if (hasLastFm) { tools.push( { name: 'get_similar_artists', description: 'Get similar artists using Last.fm API', inputSchema: { type: 'object', properties: { artist: { type: 'string', description: 'Name of the artist to find similar artists for', }, limit: { type: 'number', description: 'Maximum number of similar artists to return (1-100)', minimum: 1, maximum: 100, default: 100, }, }, required: ['artist'], }, }, { name: 'get_similar_tracks', description: 'Get similar tracks using Last.fm API', inputSchema: { type: 'object', properties: { artist: { type: 'string', description: 'Name of the track artist', }, track: { type: 'string', description: 'Name of the track', }, limit: { type: 'number', description: 'Maximum number of similar tracks to return (1-100)', minimum: 1, maximum: 100, default: 100, }, }, required: ['artist', 'track'], }, }, { name: 'get_artist_info', description: 'Get detailed artist information from Last.fm', inputSchema: { type: 'object', properties: { artist: { type: 'string', description: 'Name of the artist to get information for', }, lang: { type: 'string', description: 'Language for the biography (ISO 639 code)', default: 'en', }, }, required: ['artist'], }, }, { name: 'get_top_tracks_by_artist', description: 'Get top tracks for an artist from Last.fm', inputSchema: { type: 'object', properties: { artist: { type: 'string', description: 'Name of the artist', }, limit: { type: 'number', description: 'Maximum number of top tracks to return (1-50)', minimum: 1, maximum: 50, default: 10, }, }, required: ['artist'], }, }, { name: 'get_trending_music', description: 'Get trending music charts from Last.fm', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Type of chart to get', enum: ['artists', 'tracks', 'tags'], }, limit: { type: 'number', description: 'Maximum number of items to return (1-100)', minimum: 1, maximum: 100, default: 100, }, page: { type: 'number', description: 'Page number for pagination', minimum: 1, default: 1, }, }, required: ['type'], }, } ); } if (hasRadioBrowser) { tools.push( { name: 'discover_radio_stations', description: 'Discover internet radio stations worldwide via Radio Browser API. Search by genre/tag, country, language, quality, and more. Returns validated streams with metadata, sorted by popularity by default.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for station names (e.g., "BBC", "Classic FM", "Jazz FM")', }, tag: { type: 'string', description: 'Filter by music genre/tag (e.g., "jazz", "rock", "classical", "electronic", "hip-hop", "country", "reggae", "latin")', }, countryCode: { type: 'string', description: 'ISO 2-letter country code (e.g., "US"=United States, "GB"=United Kingdom, "FR"=France, "DE"=Germany, "JP"=Japan, "AU"=Australia)', }, language: { type: 'string', description: 'Broadcast language (e.g., "english", "spanish", "french", "german", "japanese", "portuguese", "italian")', }, codec: { type: 'string', description: 'Audio codec preference (e.g., "MP3" for best compatibility, "AAC" for better quality, "OGG" for open standard)', }, bitrateMin: { type: 'number', description: 'Minimum audio quality in kbps (e.g., 128 for standard quality, 256 for high quality, 320 for maximum quality)', minimum: 0, }, isHttps: { type: 'boolean', description: 'Require secure HTTPS streams (recommended for security)', }, order: { type: 'string', description: 'Sort results by: "votes"=popularity, "name"=alphabetical, "clickcount"=most played, "bitrate"=quality, "lastcheckok"=reliability, "random"=shuffle', enum: ['name', 'votes', 'clickcount', 'bitrate', 'lastcheckok', 'random'], default: 'votes', }, reverse: { type: 'boolean', description: 'Reverse sort order (true=descending/best first, false=ascending)', default: true, }, offset: { type: 'number', description: 'Skip first N results for pagination', minimum: 0, }, limit: { type: 'number', description: 'Maximum number of stations to return (15=quick discovery, 50=extensive search, 500=maximum)', minimum: 1, maximum: 500, default: DEFAULT_VALUES.RADIO_DISCOVERY_LIMIT, }, hideBroken: { type: 'boolean', description: 'Hide stations that failed recent connectivity checks (recommended: true)', default: true, }, }, }, }, { name: 'get_radio_filters', description: 'Get available filter options for radio station discovery (tags, countries, languages, codecs)', inputSchema: { type: 'object', properties: { kinds: { type: 'array', description: 'Filter types to retrieve', items: { type: 'string', enum: ['tags', 'countries', 'languages', 'codecs'], }, default: ['tags', 'countries', 'languages', 'codecs'], }, }, }, }, { name: 'get_station_by_uuid', description: 'Get detailed information about a specific radio station by its UUID', inputSchema: { type: 'object', properties: { stationUuid: { type: 'string', description: 'The unique UUID of the radio station', }, }, required: ['stationUuid'], }, }, { name: 'click_station', description: 'Register a play click for a radio station (helps with popularity metrics). Call this when starting playback.', inputSchema: { type: 'object', properties: { stationUuid: { type: 'string', description: 'The unique UUID of the radio station', }, }, required: ['stationUuid'], }, }, { name: 'vote_station', description: 'Vote for a radio station to increase its popularity', inputSchema: { type: 'object', properties: { stationUuid: { type: 'string', description: 'The unique UUID of the radio station', }, }, required: ['stationUuid'], }, } ); } if (hasLyrics) { tools.push({ name: 'get_lyrics', description: 'Get lyrics for a song (both synced and unsynced). Returns timed lyrics for karaoke-style display when available.', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Song title', }, artist: { type: 'string', description: 'Artist name', }, album: { type: 'string', description: 'Album name (improves match accuracy)', }, durationMs: { type: 'number', description: 'Song duration in milliseconds (improves match accuracy)', minimum: 0, }, id: { type: 'string', description: 'LRCLIB record ID if known', }, }, required: ['title', 'artist'], }, }); } // Register list tools handler server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools, })); // Register call tool handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; // Simple tool handlers using the helper function if (name === 'test_connection') { const result = await testConnection(client, config, args ?? {}); return createToolResponse(result); } if (name === 'list_songs') { const result = await listSongs(client, args ?? {}); return createToolResponse(result); } if (name === 'list_albums') { const result = await listAlbums(client, args ?? {}); return createToolResponse(result); } if (name === 'list_artists') { const result = await listArtists(client, args ?? {}); return createToolResponse(result); } if (name === 'list_genres') { const result = await listGenres(client, config, args ?? {}); return createToolResponse(result); } if (name === 'get_song') { const result = await getSong(client, args ?? {}); return createToolResponse(result); } if (name === 'get_album') { const result = await getAlbum(client, args ?? {}); return createToolResponse(result); } if (name === 'get_artist') { const result = await getArtist(client, args ?? {}); return createToolResponse(result); } if (name === 'get_song_playlists') { const result = await getSongPlaylists(client, args ?? {}); return createToolResponse(result); } if (name === 'list_playlists') { const result = await listPlaylists(client, args ?? {}); return createToolResponse(result); } if (name === 'get_playlist') { const result = await getPlaylist(client, args ?? {}); return createToolResponse(result); } if (name === 'create_playlist') { const result = await createPlaylist(client, args ?? {}); return createToolResponse(result); } if (name === 'update_playlist') { const result = await updatePlaylist(client, args ?? {}); return createToolResponse(result); } if (name === 'delete_playlist') { const result = await deletePlaylist(client, args ?? {}); return createToolResponse(result); } if (name === 'get_playlist_tracks') { const result = await getPlaylistTracks(client, args ?? {}); return createToolResponse(result); } if (name === 'add_tracks_to_playlist') { const result = await addTracksToPlaylist(client, args ?? {}); return createToolResponse(result); } if (name === 'remove_tracks_from_playlist') { const result = await removeTracksFromPlaylist(client, args ?? {}); return createToolResponse(result); } if (name === 'reorder_playlist_track') { const result = await reorderPlaylistTrack(client, args ?? {}); return createToolResponse(result); } if (name === 'batch_add_tracks_to_playlist') { const result = await batchAddTracksToPlaylist(client, args ?? {}); return createToolResponse(result); } if (name === 'search_all') { const result = await searchAll(config, args ?? {}); return createToolResponse(result); } if (name === 'search_songs') { const result = await searchSongs(config, args ?? {}); return createToolResponse(result); } if (name === 'search_albums') { const result = await searchAlbums(config, args ?? {}); return createToolResponse(result); } if (name === 'search_artists') { const result = await searchArtists(config, args ?? {}); return createToolResponse(result); } if (name === 'star_item') { const result = await starItem(client, config, args ?? {}); return createToolResponse(result); } if (name === 'unstar_item') { const result = await unstarItem(client, config, args ?? {}); return createToolResponse(result); } if (name === 'set_rating') { const result = await setRating(client, config, args ?? {}); return createToolResponse(result); } if (name === 'list_starred_items') { const result = await listStarredItems(client, args ?? {}); return createToolResponse(result); } if (name === 'list_top_rated') { const result = await listTopRated(client, args ?? {}); return createToolResponse(result); } if (name === 'get_queue') { const result = await getQueue(client, args ?? {}); return createToolResponse(result); } if (name === 'set_queue') { const result = await setQueue(client, args ?? {}); return createToolResponse(result); } if (name === 'clear_queue') { const result = await clearQueue(client, args ?? {}); return createToolResponse(result); } if (name === 'list_recently_played') { const result = await listRecentlyPlayed(client, args ?? {}); return createToolResponse(result); } if (name === 'list_most_played') { const result = await listMostPlayed(client, args ?? {}); return createToolResponse(result); } if (name === 'get_similar_artists' && hasLastFm) { const result = await getSimilarArtists(config, args ?? {}); return createToolResponse(result); } if (name === 'get_similar_tracks' && hasLastFm) { const result = await getSimilarTracks(config, args ?? {}); return createToolResponse(result); } if (name === 'get_artist_info' && hasLastFm) { const result = await getArtistInfo(config, args ?? {}); return createToolResponse(result); } if (name === 'get_top_tracks_by_artist' && hasLastFm) { const result = await getTopTracksByArtist(config, args ?? {}); return createToolResponse(result); } if (name === 'get_trending_music' && hasLastFm) { const result = await getTrendingMusic(config, args ?? {}); return createToolResponse(result); } if (name === 'list_radio_stations') { const result = await listRadioStations(config, args ?? {}); return createToolResponse(result); } if (name === 'create_radio_station') { const result = await createRadioStation(config, args ?? {}); return createToolResponse(result); } if (name === 'delete_radio_station') { const result = await deleteRadioStation(config, args ?? {}); return createToolResponse(result); } if (name === 'get_radio_station') { const result = await getRadioStation(config, args ?? {}); return createToolResponse(result); } if (name === 'play_radio_station') { const result = await playRadioStation(config, args ?? {}); return createToolResponse(result); } if (name === 'get_current_radio_info') { const result = await getCurrentRadioInfo(config, args ?? {}); return createToolResponse(result); } if (name === 'batch_create_radio_stations') { const result = await batchCreateRadioStations(config, args ?? {}); return createToolResponse(result); } if (name === 'list_tags') { const result = await listTags(client, args ?? {}); return createToolResponse(result); } if (name === 'get_tag') { const result = await getTag(client, args ?? {}); return createToolResponse(result); } if (name === 'search_by_tags') { const result = await searchByTags(client, args ?? {}); return createToolResponse(result); } if (name === 'get_tag_distribution') { const result = await getTagDistribution(client, args ?? {}); return createToolResponse(result); } if (name === 'list_unique_tags') { const result = await listUniqueTags(client, args ?? {}); return createToolResponse(result); } if (name === 'validate_radio_stream') { const result = await validateRadioStream(client, args ?? {}); return createToolResponse(result); } if (name === 'discover_radio_stations' && hasRadioBrowser) { const result = await discoverRadioStations(config, client, args ?? {}); return createToolResponse(result); } if (name === 'get_radio_filters' && hasRadioBrowser) { const result = await getRadioFilters(config, args ?? {}); return createToolResponse(result); } if (name === 'get_station_by_uuid' && hasRadioBrowser) { const result = await getStationByUuid(config, args ?? {}); return createToolResponse(result); } if (name === 'click_station' && hasRadioBrowser) { const result = await clickStation(config, args ?? {}); return createToolResponse(result); } if (name === 'vote_station' && hasRadioBrowser) { const result = await voteStation(config, args ?? {}); return createToolResponse(result); } if (name === 'get_lyrics' && hasLyrics) { const result = await getLyrics(config, args ?? {}); return createToolResponse(result); } throw new Error(`Unknown tool: ${name}`); }); }

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/Blakeem/Navidrome-MCP'

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