nicotine-mcp
SOULSEEK · FOR CLAUDE · GPL-3.0
№ 01

SEARCH SOULSEEK. QUEUE ALBUMS. MANAGE DOWNLOADS.

Lets Claude (Claude Code, or Claude Desktop) drive a running Nicotine+ client: search Soulseek, inspect results, queue files or whole folders, and manage downloads.
Tested against Nicotine+ 3.3.10, 3.3.11 and 3.4.0.dev2, with MCP Python SDK 2.x.
CLAUDE CODE / DESKTOP
──stdio──▶
nicotine-mcp · uv script
──unix socket──▶
MCP Bridge plugin
──▶
NICOTINE+ CORE
INSTALL.
$ sudo pacman -S --needed nicotine+ uv $ git clone https://github.com/h-3303/nicotine-mcp && cd nicotine-mcp $ ./install.sh
The installer does four things:
Xcopies plugin/mcp_bridge into ~/.local/share/nicotine/plugins/ (or the Flatpak data directory, if that’s your install);
Xinstalls the server as ~/.local/bin/nicotine-mcp;
Xpre-fetches its dependencies;
Xruns claude mcp add --scope user nicotine -- uv run --script ~/.local/bin/nicotine-mcp.
After that, open Nicotine+ → Preferences → Plugins, enable plugins, and tick MCP Bridge. The Nicotine+ log should show “MCP bridge listening on …”. Then claude mcp list should report nicotine as connected.
For Claude Desktop or any other MCP client, register the command uv run --script ~/.local/bin/nicotine-mcp as a stdio server.
THE TOOLS.
ELEVEN · READ-ONLY / WRITE / DESTRUCTIVE
nicotine_status READ-ONLY
Connection state, logged-in username, download folder, download counts by status, tracked searches, and recent folder-download requests.
e.g.  nicotine_status()
Search Soulseek and return results grouped by (user, folder), best candidates first. Soulseek matches every word against the full file path; use artist + album words, avoid punctuation. Results keep arriving after this returns; call get_search_results later with the same search_id for more. mode="user" needs usernames; mode="rooms" needs room. Filters (lossless_only, extensions like ["flac"], min_bitrate in kbps for lossy files, free_slot_only) only affect what is returned, not what is collected.
PARAMTYPEDEFAULT
querystrrequired
wait_secondsint12
modeglobal | buddies | rooms | user"global"
usernameslist[str]
roomstr
lossless_onlyboolfalse
extensionslist[str]
min_bitrateint (kbps)
free_slot_onlyboolfalse
max_foldersint15
files_per_folderint25
e.g.  search(query="mingus ah um", lossless_only=true)
get_search_results READ-ONLY
Re-read (and re-filter) an existing search. path_contains = space-separated words that must all appear in the path (case-insensitive); username restricts to one peer, handy for seeing a full folder.
PARAMTYPEDEFAULT
search_idintrequired
lossless_onlyboolfalse
extensionslist[str]
min_bitrateint (kbps)
free_slot_onlyboolfalse
usernamestr
path_containsstr
max_foldersint15
files_per_folderint25
e.g.  get_search_results(search_id=41, username="vinylrip")
list_searches READ-ONLY
Searches currently tracked by the bridge (id, query, age, file/user counts). The oldest are dropped past the configured limit.
e.g.  list_searches()
Stop collecting results for a search and discard them (also closes its tab in Nicotine+).
PARAMTYPEDEFAULT
search_idintrequired
e.g.  stop_search(search_id=41)
download_files WRITE
Queue specific files from a search by their result ids. With keep_folder_structure, files land in <download folder>/<remote parent folder name>/, which keeps album tracks together.
PARAMTYPEDEFAULT
search_idintrequired
result_idslist[int]required
keep_folder_structurebooltrue
e.g.  download_files(search_id=41, result_ids=[3, 4, 5])
download_folder WRITE
Queue an entire remote folder (e.g. an album) from a user. folder_path is the ‘folder’ value from search results. The peer is asked for the folder listing first, so files appear in list_downloads a few seconds later. include_subfolders also grabs e.g. CD1/CD2 or Scans subfolders.
PARAMTYPEDEFAULT
usernamestrrequired
folder_pathstrrequired
include_subfoldersboolfalse
e.g.  download_folder(username="vinylrip", folder_path="Music\\Mingus Ah Um (1959) [FLAC]")
list_downloads READ-ONLY
List downloads with status, progress (0–1), speed and queue position, each with a download_id. Most recent last.
PARAMTYPEDEFAULT
statuseslist[status]
usernamestr
limitint50
statuses: Queued · Getting status · Transferring · Paused · Cancelled · Filtered · Finished · User logged off · Connection closed · Connection timeout · Download folder error · Local file error
e.g.  list_downloads(statuses=["Transferring"])
cancel_downloads DESTRUCTIVE
Cancel downloads by download_id (from list_downloads). Partial files stay in the incomplete folder.
PARAMTYPEDEFAULT
download_idslist[str]required
e.g.  cancel_downloads(download_ids=["9f2c01ab34de"])
retry_downloads WRITE
Retry failed, paused or cancelled downloads by download_id.
PARAMTYPEDEFAULT
download_idslist[str]required
e.g.  retry_downloads(download_ids=["9f2c01ab34de"])
clear_downloads DESTRUCTIVE
Remove entries from the download list (does not delete finished files from disk). Give download_ids, statuses (e.g. ["Finished"]), or both — it refuses to clear everything.
PARAMTYPEDEFAULT
download_idslist[str]
statuseslist[status]
e.g.  clear_downloads(statuses=["Finished"])
PLUGIN SETTINGS.
Preferences → Plugins → MCP Bridge
Socket path
Leave empty for $XDG_RUNTIME_DIR/nicotine-mcp.sock. Flatpak installs automatically use $XDG_RUNTIME_DIR/app/org.nicotine_plus.Nicotine/nicotine-mcp.sock. If you set a custom path, export the same path as NICOTINE_MCP_SOCKET for the server, for example with claude mcp add --env NICOTINE_MCP_SOCKET=…
Allow downloads
Switch this off for search-only access.
Max results / max searches
Max results per search and max searches kept are memory caps; the oldest searches are removed first.
SECURITY MODEL.
Access control: the socket file is 0600, and the plugin also rejects any peer whose UID (from SO_PEERCRED) doesn’t match its own. There is no TCP listener, so browsers and other local users can’t reach it.
Stability: all requests are run on Nicotine+’s main loop. Errors are caught before they reach Nicotine+’s event bus, because an uncaught exception in a main-thread callback makes Nicotine+ quit.
0600 · SAME UID · NO TCP
NOTES.
XSearch results: they trickle in from peers for a minute or more. Call get_search_results again for a fuller picture.
XFolder downloads: these ask the peer for the folder listing first, so the files appear in list_downloads a few seconds later. Requests that get no answer are reported as timed out in nicotine_status after 3 minutes.
XNicotine+ 3.3 large folders: for folders over 100 files, Nicotine+ also shows its own confirmation dialog. The bridge queues the files regardless, and duplicate queue entries are ignored.
XHeadless use: nicotine --headless runs Nicotine+ without a GUI. Enable the plugin once from the GUI first, since the setting persists.
XProtocol: one JSON request per connection, of the form {"method": ..., "params": {...}}. See the HANDLERS table in the plugin to add methods.
A TRANSCRIPT.
find me Mingus — Ah Um, lossless if you can…
search(query="mingus ah um", lossless_only=true)
214 files from 37 users · best folder: “vinylrip” — 12 files matched, flac 16bit 44.1kHz, free slot, queue 0
download_folder(username="vinylrip", folder_path="Music\\Jazz\\Mingus Ah Um (1959) [FLAC]")
requested — files are queued once the user replies; check status or list_downloads
list_downloads(statuses=["Transferring"])
12 downloads · 3 transferring at 1,240 kbps · 9 queued
“Queued the 1959 album in FLAC — twelve tracks on their way to your download folder. I’ll check list_downloads again in a minute.”
✦ ✦ ✦ ✦ ✦ ✦ ✦ ✦ ✦
GPL-3.0 · github.com/h-3303/nicotine-mcp · One socket · Pass it on