Analyze listening patterns, build playlists, find missing albums, discover similar artists via Last.fm, fetch synced lyrics, and explore global radio. Play it all through your speakers via mpv, with a built-in web UI that makes any device with a brower act as a remote. Gives full control of your Navidrome library using natural language.
Drift inferred · capture-to-capture
tool count over time
tools
-
add_tracks_to_playlist
Add songs, albums, artist discographies, or specific discs in one operation
-
clear_saved_queue
Clear the Navidrome saved queue
-
click_station
Register a play click for popularity metrics
-
create_playlist
Create a new playlist
-
create_radio_station
Create one or more stations (JSON array, optional validateBeforeAdd)
-
delete_playlist
Delete a playlist
-
delete_radio_station
Delete a station
-
discover_radio_stations
Find stations globally via Radio Browser
-
get_album
Detailed album metadata by ID
-
get_album_info
Album detail: tracklist with durations, year and type, genres, wiki summary, popularity, and library membership. Works for albums you don't own
-
get_artist
Detailed artist metadata by ID
-
get_artist_albums
Full discography with release types and years (MusicBrainz), genres and popularity (Last.fm), and an in-library flag per album. Answers "what albums by X am I missing?"
-
get_artist_info
Artist biography and tags
-
get_filter_options
Discover available filter values for search operations
-
get_lyrics
Lyrics for one song, by Navidrome song ID or by LRCLIB record ID. Time-synced (LRC) lines when the source carries them
-
get_playlist
Get playlist metadata by ID
-
get_playlist_tracks
Get playlist contents (JSON or M3U)
-
get_radio_filters
Available filter values (tags, countries, languages, codecs)
-
get_radio_station
Detailed info for a station by ID
-
get_saved_queue
Read the Navidrome saved queue (web UI sync)
-
get_similar_artists
Find artists similar to a given artist
-
get_similar_tracks
Find tracks similar to a given track
-
get_song
Detailed song metadata by ID
-
get_song_playlists
List all playlists containing a given song
-
get_station_by_uuid
Detailed Radio Browser station info
-
get_tag_distribution
Tag usage counts across the library
-
get_top_tracks_by_artist
Top tracks for an artist
-
get_trending_music
Trending artists, tracks, and tags from Last.fm charts
-
get_user_details
User profile, available libraries, and active-library status
-
list_most_played
Most-played songs, albums, or artists
-
list_playlists
View all accessible playlists
-
list_radio_stations
List all saved Navidrome radio stations
-
list_recently_played
Recent listening activity with optional time-range filter
-
list_starred_items
View starred songs, albums, or artists
-
list_top_rated
View highest-rated items
-
pause
Pause playback (position preserved)
-
play_albums
Play one or many albums. mode plus shuffle: 'none' \
-
play_albums_search
Search and play albums in one step. Accepts all search_albums filters plus mode and shuffle
-
play_playlist
Load a playlist's tracks into the queue by playlistId. Supports mode and shuffle
-
play_radio_station
Play a saved Navidrome radio station. Replaces the queue, since radio cannot mix with songs or albums
-
play_songs
Play one or many songs. mode: 'replace' \
-
play_songs_search
Search and play songs in one step. Accepts all search_songs filters plus mode and shuffle
-
remove_tracks_from_playlist
Remove tracks by position
-
reorder_playlist_track
Move a track to a new position
-
resume
Resume playback
-
save_queue
Save a queue to Navidrome for web UI sync
-
search_albums
Search albums with advanced filters and sorting
-
search_all
Search across artists, albums, and songs with filters and sorting
-
search_artists
Search artists with advanced filters and sorting
-
search_by_tags
Search by tag values (genre, releasetype, media, etc.)
-
search_lyrics
Search LRCLIB by title and artist. Returns candidate records plus the matching library song. Requires the LRCLIB provider, set in the settings page
-
search_songs
Search songs with advanced filters and sorting
-
set_active_libraries
Set which libraries are active for all search/list operations
-
set_rating
Set a 0-5 star rating
-
star_item
Star a song, album, or artist
-
test_connection
Verify Navidrome connectivity and report feature/tool availability
-
unstar_item
Remove a star
-
update_playlist
Update name, description, or visibility
-
validate_radio_stream
Test an http(s) stream URL for accessibility and audio content
-
vote_station
Vote for a station
analyzed commit 8ab5e86 · analyzer v33 · 3w ago
skills & prompt files 2
- agent-rules Blakeem-Navidrome-MCP-8ab5e86/CLAUDE.md
- agent-rules Blakeem-Navidrome-MCP-8ab5e86/tests/CLAUDE.md
danger signals1
- suspicious endpoint 188.40.97.185 Blakeem-Navidrome-MCP-8ab5e86/tests/unit/tools/radio-validation.test.ts :691
url: 'http://188.40.97.185:8179/stream',
code files: 223
filesystem 18
- fs (weak) Blakeem-Navidrome-MCP-8ab5e86/scripts/build-webui.mjs :12
import { cpSync, existsSync, mkdirSync } from 'node:fs'; - fs (weak) Blakeem-Navidrome-MCP-8ab5e86/scripts/make-launcher.mjs :51
} from 'node:fs'; - fs (weak) Blakeem-Navidrome-MCP-8ab5e86/scripts/playback-poc.ts :27
import { existsSync } from 'node:fs'; - fs Blakeem-Navidrome-MCP-8ab5e86/src/config-app/server.ts :20
import { existsSync } from 'node:fs'; - fs Blakeem-Navidrome-MCP-8ab5e86/src/config/seed.ts :19
import { readFileSync } from 'node:fs'; - fs Blakeem-Navidrome-MCP-8ab5e86/src/config/store.ts :29
} from 'node:fs'; - fs Blakeem-Navidrome-MCP-8ab5e86/src/services/playback/mpv-process.ts :20
import { accessSync, constants } from 'node:fs'; - fs Blakeem-Navidrome-MCP-8ab5e86/src/services/playback/playback-engine.ts :20
import { existsSync } from 'node:fs'; - fs Blakeem-Navidrome-MCP-8ab5e86/src/utils/version.ts :5
import { readFileSync } from 'node:fs'; - fs Blakeem-Navidrome-MCP-8ab5e86/src/web/main.ts :34
import { appendFileSync, mkdirSync } from 'node:fs'; - fs Blakeem-Navidrome-MCP-8ab5e86/src/web/spawn.ts :20
import { existsSync } from 'node:fs'; - fs Blakeem-Navidrome-MCP-8ab5e86/src/webui/routes/static-files.ts :19
import { existsSync } from 'node:fs'; - fs (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/integration/coordination/helpers.ts :16
import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; - fs (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/meta/dead-code-detection.test.ts :9
import { readFileSync, readdirSync, existsSync } from 'fs'; - fs (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/config-app/settings-server.test.ts :11
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; - fs (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/config/config-resolve.test.ts :7
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; - fs (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/config/seed.test.ts :11
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; - fs (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/config/store.test.ts :6
import { mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
shell / exec 10
- shell (weak) Blakeem-Navidrome-MCP-8ab5e86/scripts/make-launcher.mjs :42
import { spawnSync } from 'node:child_process'; - shell (weak) Blakeem-Navidrome-MCP-8ab5e86/scripts/playback-poc.ts :25
import { spawn, type ChildProcess } from 'node:child_process'; - shell Blakeem-Navidrome-MCP-8ab5e86/src/services/playback/mpv-process.ts :19
import { spawn, execSync, type ChildProcess } from 'node:child_process'; - shell Blakeem-Navidrome-MCP-8ab5e86/src/services/playback/playback-engine.ts :19
import type { ChildProcess } from 'node:child_process'; - shell Blakeem-Navidrome-MCP-8ab5e86/src/utils/open-browser.ts :19
import { spawn } from 'node:child_process'; - shell Blakeem-Navidrome-MCP-8ab5e86/src/web/spawn.ts :19
import { spawn } from 'node:child_process'; - shell (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/integration/coordination/fixtures/ipc-parent.mjs :8
import { spawn } from 'node:child_process'; - shell (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/integration/coordination/helpers.ts :15
import { type ChildProcess, spawn } from 'node:child_process'; - shell (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/meta/dead-code-detection.test.ts :11
import { execSync } from 'child_process'; - shell (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/services/playback/mpv-process.test.ts :94
const actual = await vi.importActual<typeof import('node:child_process')>('node:child_process');
network 45
- net (weak) Blakeem-Navidrome-MCP-8ab5e86/scripts/playback-poc.ts :26
import { createConnection, type Socket } from 'node:net'; - net Blakeem-Navidrome-MCP-8ab5e86/src/client/navidrome-client.ts :279
// to GET in fetch(). - net Blakeem-Navidrome-MCP-8ab5e86/src/config-app/public/app.js :188
const res = await fetch(path, { - net Blakeem-Navidrome-MCP-8ab5e86/src/config-app/routes.ts :19
import type { IncomingMessage, ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/config-app/server.ts :19
import { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/services/filter-cache-manager.ts :80
// fetch (the whole point of cache-disabled mode is freshness). - net Blakeem-Navidrome-MCP-8ab5e86/src/services/playback/mpv-ipc.ts :19
import { createConnection, type Socket } from 'node:net'; - net Blakeem-Navidrome-MCP-8ab5e86/src/services/playback/playback-engine.ts :22
import { createConnection } from 'node:net'; - net Blakeem-Navidrome-MCP-8ab5e86/src/tools/radio.ts :501
* an uncached fetch (the post-create id-resolution path). - net Blakeem-Navidrome-MCP-8ab5e86/src/tools/user-preferences.ts :219
// `minRating` is applied client-side AFTER the fetch (Navidrome's REST list - net Blakeem-Navidrome-MCP-8ab5e86/src/transport/http.ts :19
import { createServer, type Server as HttpServer, type IncomingMessage, type ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/utils/fetch-with-timeout.ts :162
* fetch() wrapper that: - net Blakeem-Navidrome-MCP-8ab5e86/src/utils/network-safety.ts :20
import { Agent, buildConnector, fetch as undiciFetch } from 'undici'; - net Blakeem-Navidrome-MCP-8ab5e86/src/web/acquire.ts :19
import { get as httpGet, type Server } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/web/main.ts :35
import type { Server } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/broadcaster.ts :19
import type { ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/http-helpers.ts :19
import type { IncomingMessage, ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/loopback.ts :19
import type { IncomingMessage } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/public/app.js :148
const res = await fetch(path, opts); - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/routes/controls.ts :19
import type { IncomingMessage, ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/routes/cover.ts :19
import type { ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/routes/events.ts :19
import type { ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/routes/health.ts :19
import type { IncomingMessage, ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/routes/network-info.ts :19
import type { ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/routes/player.ts :19
import type { IncomingMessage, ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/routes/playlists.ts :19
import type { IncomingMessage, ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/routes/snapshot.ts :19
import type { ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/routes/static-files.ts :21
import type { ServerResponse } from 'node:http'; - net Blakeem-Navidrome-MCP-8ab5e86/src/webui/server.ts :24
} from 'node:http'; - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/integration/coordination/helpers.ts :17
import { createConnection } from 'node:net'; - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/integration/coordination/lifecycle.test.ts :54
await fetch(`http://127.0.0.1:${port}/api/shutdown`, { method: 'POST' }).catch(() => undefined); - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/integration/coordination/port-as-lock.test.ts :10
import { createServer as createHttpServer } from 'node:http'; - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/client/auth-manager.test.ts :67
it('5 concurrent authenticate() calls trigger only 1 fetch (single-flight)', async () => { - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/config-app/settings-server.test.ts :52
fetch(`${base}/api/settings`, { - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/tools/radio-discovery.test.ts :278
// Only one fetch (for codecs), not four - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/tools/radio-validation.test.ts :619
// Only one fetch (HEAD) — audio sampling skipped because headers are conclusive. - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/tools/search-artist-filters.test.ts :7
* /api/artist sub-fetch (Navidrome silently ignores them; sending them is a - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/tools/search.test.ts :403
it.skipIf(shouldSkipLiveTests())('searchAll offset paginates each sub-fetch (no longer hardcoded _start=0)', async () => { - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/transport/http-transport.test.ts :18
import { request as httpRequest } from 'node:http'; - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/utils/network-safety.test.ts :7
import { createServer, type Server } from 'node:http'; - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/web/acquire.test.ts :10
import type { Server } from 'node:http'; - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/webui/broadcaster.test.ts :11
import type { ServerResponse } from 'node:http'; - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/webui/http-helpers.test.ts :9
import type { IncomingMessage } from 'node:http'; - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/webui/routes.test.ts :21
import type { IncomingMessage, ServerResponse } from 'node:http'; - net (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/webui/static-files.test.ts :12
import type { ServerResponse } from 'node:http';
secrets 5
- secrets (weak) Blakeem-Navidrome-MCP-8ab5e86/scripts/ci-test-env.js :19
process.env.NAVIDROME_PASSWORD = 'ci-dummy-password'; - secrets (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/helpers/env-detection.ts :30
process.env.NAVIDROME_PASSWORD - secrets (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/config-app/settings-server.test.ts :99
process.env['NAVIDROME_PASSWORD'] = 'firstrunpass'; - secrets (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/config/config-resolve.test.ts :220
process.env['NAVIDROME_PASSWORD'] = 'env-pass'; - secrets (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/config/seed.test.ts :57
process.env['NAVIDROME_PASSWORD'] = 'envpass';
install hooks 1
- prepare Blakeem-Navidrome-MCP-8ab5e86/package.json :25
pnpm build
declared dependencies 20
- @modelcontextprotocol/sdk@^1.17.4
- file-type@^19.5.0
- undici@^7.28.0
- zod@^4.1.4
- @eslint-community/eslint-plugin-eslint-comments@^4.7.2
- @eslint/js@^9.34.0
- @types/node@^24.3.0
- @typescript-eslint/eslint-plugin@^8.41.0
- @typescript-eslint/parser@^8.41.0
- @vitest/coverage-v8@^3.2.4
- @vitest/ui@^3.2.4
- eslint@^9.34.0
- eslint-config-prettier@^9.1.0
- eslint-plugin-unicorn@^64.0.0
- nodemon@^3.1.10
- prettier@^3.6.2
- ts-unused-exports@^11.0.1
- tsx@^4.20.5
- typescript@^5.9.2
- vitest@^3.2.4
perm:untrusted 1
- untrusted (weak) Blakeem-Navidrome-MCP-8ab5e86/tests/integration/coordination/lifecycle.test.ts :67
const list = (await (await fetch(`http://127.0.0.1:${port}/api/playlists`)).json()) as {
transport_posture 2
- binds all interfaces (0.0.0.0) Blakeem-Navidrome-MCP-8ab5e86/tests/unit/transport/http-transport.test.ts :260
host: '0.0.0.0', - network-listening, binds all interfaces, no auth detected Blakeem-Navidrome-MCP-8ab5e86/tests/unit/webui/routes.test.ts :147
handleHealth(fakeReq(LAN_PEER), cap.res, configWith({ expose: false, host: '0.0.0.0' }));