Skip to main content
Glama

qbt-kit

CI status Latest PyPI version Supported Python versions MIT licence

A complete, typed Python client for the qBittorrent WebUI API — with a CLI that covers every endpoint one-to-one, and an MCP server so coding agents can drive it without deleting your data.

$ qbt coverage

qbt-kit coverage — target release-5.2.3

  scope               n   spec  progress
  torrents           61     61  ████████████████████  61/61
  app                16     16  ████████████████████  16/16
  rss                14     14  ████████████████████  14/14
  search             11     11  ████████████████████  11/11
  transfer            9      9  ████████████████████  9/9
  torrentcreator      4      4  ████████████████████  4/4
  auth                2      2  ████████████████████  2/2
  clientdata          2      2  ████████████████████  2/2
  log                 2      2  ████████████████████  2/2
  sync                2      2  ████████████████████  2/2
                   ----
  TOTAL             123    123  ████████████████████  100.0%

Highlights

  • All 123 endpoints of qBittorrent 5.2.3, each one a spec entry that generates the client method, the CLI command and the docs together.

  • A verifiable coverage claim. The endpoint list is extracted from the qBittorrent C++ source per release tag and committed, so CI fails both when the spec misses an endpoint and when it invents one.

  • An MCP server with 15 tools — 13 task-shaped, plus qbt_call and list_endpoints — and safety gates that hold even through the escape hatch.

  • Compatibility measured, not assumed — tested against real 4.6, 5.0, 5.1 and 5.2 containers, with a rename table so torrents/stop resolves to pause on servers that predate the rename.

  • Typed throughout, no runtime dependency beyond httpx and typer.

That coverage claim is worth a sentence of context. The wiki page most clients are written against documents 78 endpoints. qBittorrent 5.2.3 ships 123, and two whole scopes — torrentcreator and clientdata — appear in no wiki page at all.

Related MCP server: qBittorrent MCP Server

Installation

PyPI's qbt name belongs to an unrelated package, so the CLI is run via --from:

uvx --from qbt-kit qbt doctor
uvx --from "qbt-kit[mcp]" qbt-mcp --help   # the server lives in the `mcp` extra

Both run via --from because the qbt name on PyPI belongs to an unrelated package, and qbt-mcp is a script inside this one rather than a package of its own — uvx qbt-mcp resolves to nothing.

Or add it to a project:

uv add qbt-kit              # library and CLI
uv add "qbt-kit[mcp]"       # with the MCP server

As a Claude Code plugin

This repository is also a plugin marketplace, so one line installs the MCP server and the skill together:

/plugin install qbt-kit --marketplace manueltarouca/qbt-kit

The bundled MCP server runs from the plugin's own checkout, so it needs uv but not a published package, and it always matches the version you installed. Point it at your server with QBT_URL, and see Configuration for credentials.

Quickstart

qbt doctor                          # version, auth mode, endpoint availability
qbt torrents info --category tv     # the API calls this `info`, not `list`
qbt torrents add --urls <magnet> --category tv --auto-tmm
qbt health                          # swarm health across the queue
qbt coverage                        # the endpoint coverage ledger
qbt call torrents/pieceStates -p hash=<h>    # raw, reaches all 123

Scope commands mirror the API exactly — torrents info, not a friendlier list — so anything you read in qBittorrent's own docs translates directly. --json works everywhere, so every command composes with jq.

qbt ready is also there, but it inspects the local filesystem to decide whether a download has really settled, so it only means anything when the CLI runs on the same machine as qBittorrent.

MCP server

{
  "mcpServers": {
    "qbittorrent": {
      "command": "uvx",
      "args": ["--from", "qbt-kit[mcp]", "qbt-mcp", "--url", "http://127.0.0.1:8080"]
    }
  }
}

Fifteen tools rather than 123 — a tool list that long costs context on every turn and measurably worsens which tool a model picks. qbt_call keeps every endpoint reachable and list_endpoints lets a model discover them, so the small surface stays complete.

Configuration

Flags first, then the environment, then the macOS Keychain:

variable

meaning

QBT_URL

base URL, default http://127.0.0.1:8080

QBT_API_KEY

API key (qBittorrent 5.2+); preferred when present

QBT_USERNAME

WebUI username, for cookie login on any version

QBT_PASSWORD

WebUI password

QBT_READ_ONLY

1/true/yes to refuse everything that changes state

Keychain fallback uses service qbittorrent, account api-key for a key and webui for the password:

security add-generic-password -s qbittorrent -a api-key -w '<key>'

qBittorrent has no OAuth, so there is no token flow to delegate and the secret is long-lived. The reference MCP servers put credentials straight into the client's env block — GITHUB_PERSONAL_ACCESS_TOKEN is the canonical example — but that file is plaintext and frequently committed. Prefer the Keychain, and if you do use env, reference a variable instead of pasting the secret:

"env": {
  "QBT_URL": "${QBT_URL:-http://127.0.0.1:8080}",
  "QBT_API_KEY": "${QBT_API_KEY:-}"      // empty falls through to the Keychain
}

The :- default matters. Claude Code passes an unset ${VAR} through as the literal text ${VAR} rather than as empty, which otherwise surfaces as a confusing 401 much later — so qbt-mcp detects that case at startup and says so plainly instead of starting.

On startup it reports, on stderr, where the credentials came from and never what they are:

qbt-mcp: http://127.0.0.1:8080 · WebAPI 2.15.1 · auth=bearer via macOS Keychain

Safety

An agent driving a torrent client can destroy data that took weeks to collect, so the gates are code rather than guidance:

  • Destructive endpoints require confirm; delete_files is never implied.

  • --read-only (or QBT_READ_ONLY=1) blocks mutations, including via qbt_call.

  • app/setPreferences refuses the WebUI, auth and network keys that would sever the connection or weaken security. There is no override.

  • Refusals return a structured cause and remedy, so an agent can correct itself instead of seeing an opaque tool error.

qbt-mcp speaks stdio by default, which keeps it a child process with nothing listening. The sse and streamable-http transports expose an unauthenticated control surface holding your qBittorrent credentials — bound to loopback, but do not forward those ports. See SECURITY.md.

Documentation

docs/TRACEABILITY.md

how the coverage claim is built and gated

docs/TESTING.md

the container harness, and behaviours that cost real time to learn

docs/PLAN.md

the design and the reasoning behind it

docs/ROADMAP.md

what is done and what is not

docs/REVIEW-1.md

an independent review of the plan, with every finding dispositioned

docs/dependencies.md

every dependency, and why it is justified

SECURITY.md

threat model, credential handling, how to report a vulnerability

A Claude skill ships in skills/qbt-kit/ covering CLI usage and the swarm-health rule.

Field notes

Things that cost us time, documented so they cost you none.

  • Login success cannot be read from the status code. qBittorrent ≤5.1 answers a failed login with HTTP 200 and the body Fails.; 5.2 returns an empty 204. The only rule that holds across versions is whether a session cookie was issued.

  • app/webapiVersion requires authentication. Only auth/login is public, so you cannot read the version to decide how to authenticate. Probe instead.

  • The session cookie is not always SID. On 5.2 it is QBT_SID_<port>; on 5.0/5.1 it is user-configurable. Never match on the name.

  • A bare 401 is often Host-header validation, not bad credentials. It is on by default and rejects any request whose Host port differs from the port qBittorrent listens on — which is what happens behind mapped container ports, SSH tunnels and most reverse proxies.

  • API keys exist only from qBittorrent 5.2.0. Sending Authorization: Bearer to 5.0/5.1 gets a 403 that looks like a credentials problem.

  • pause/resume were removed in 5.0, not aliased; they 404 there.

  • progress: 1.0 does not mean every file is on disk. Progress is computed over the selected files, so a torrent with files at priority 0 reports 100% having never fetched them. size < total_size is the cheap flag, from the same listing call.

  • A category alone does not place a torrent. qBittorrent only applies a category's save path under automatic torrent management, so torrents/add with a category but without autoTMM files it in the global save path.

  • A stalled torrent is usually not slow but stuck. availability is how many complete copies exist across reachable peers; when it does not exceed progress, no peer has the missing pieces. But a queued torrent reports availability 0 simply for not being connected — judging those as dead is how you delete healthy downloads.

Contributing

See CONTRIBUTING.md. The short version: endpoints are added to the spec under src/qbtkit/spec/, never by hand to the CLI or MCP, and tools/build_coverage.py --strict has to stay green.

Acknowledgements

  • qBittorrent — and specifically its src/webui/api/*controller.cpp, which is the only complete and accurate description of this API that exists.

  • qbittorrent-api is the mature, well-maintained Python library for this API, and if a library is all you need, use it. qbt-kit exists for the CLI, the MCP server and the verifiable coverage claim, not because anything is wrong with it.

Licence

MIT — see LICENSE.

Available Tools

15 tools
add_torrentC

Add a torrent by magnet or URL, or several at once.

Inputs: urls (required), stopped, auto_tmm, category, +1 more. Returns: result

categoriesC

List, create, edit or remove categories.

Inputs: name, action, confirm, save_path. Returns: result

control_torrentsA

Start, stop, recheck or reannounce torrents. Note that force_start makes a torrent ignore share limits, so a stop-on-complete rule stops applying to it.

Inputs: action (required), hashes. Returns: result

delete_torrentsA

Remove torrents. delete_files is never implied: without it the data stays on disk. Requires confirm=True because it cannot be undone.

Inputs: hashes (required), confirm, delete_files. Returns: result

list_endpointsA

List every endpoint qbt_call can reach, with its shape.

Inputs: scope, mutating_only. Returns: result

list_torrentsC

List torrents, with the fields that usually matter.

Inputs: tag, full, sort, limit, +2 more. Returns: result

move_torrentsA

Move a torrent's data to a new location. qBittorrent performs the move and the torrent stays valid; the call returns before the move finishes. A recheck afterwards is optional because it rehashes everything.

Inputs: hashes (required), location (required), recheck. Returns: result

preferencesA

Read preferences, or write a subset. Keys that would sever this connection or weaken the server's security (WebUI port, password, host-header validation and similar) are refused outright, with no override.

Inputs: keys, action, values. Returns: result

qbt_callA

Call any qBittorrent endpoint directly as scope/name, for anything the tools above do not cover. Parameters are validated against the endpoint spec, and the same safety gates apply: destructive endpoints need confirm, and read-only mode refuses anything that mutates state.

Inputs: path (required), params, confirm. Returns: result

server_infoB

Server version, auth mode and endpoint availability.

Inputs: no named parameters. Returns: result

swarm_healthA

Judge whether each torrent can actually finish. availability is how many complete copies exist across reachable peers; when it does not exceed progress, no peer holds the missing pieces and the download can never complete however long it waits. Queued torrents are excluded because they report availability 0 simply for not being connected yet. progress is relative to the selected files, so partial_selection marks torrents that are 100% yet never fetched their deselected files.

Inputs: include_complete. Returns: result

tagsC

List, create, delete, add or remove tags.

Inputs: names, action, hashes, confirm. Returns: result

torrent_detailB

Properties, trackers and files for one torrent.

Inputs: hash (required). Returns: result

transfer_statusB

Global transfer rates, limits and connection status.

Inputs: no named parameters. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 15 tools

    Disambiguation4/5

    Each tool targets a distinct resource or concern (list vs detail vs swarm health vs control vs move vs delete), and the status/config tools are clearly separated. The main soft overlap is qbt_call acting as an escape hatch for anything the others cover, but it is explicitly framed as a fallback, so the boundaries stay readable.

    Naming Consistency3/5

    There is a mix of verb_noun tools (list_torrents, add_torrent, delete_torrents, move_torrents, control_torrents), noun-phrase tools (torrent_detail, transfer_status, server_info, swarm_health), and bare-noun names (categories, tags, preferences, search). Plural bulk nouns versus a singular add_torrent and the multi-action bundle names break a uniform pattern.

    Tool Count5/5

    15 tools sits at the top of the well-scoped range and each maps to a real qBittorrent capability. Multi-action tools (categories, tags, control_torrents, preferences) sensibly bundle related operations instead of exploding the count.

    Completeness4/5

    Core lifecycle is covered: list, detail, add, control, move, delete, plus transfer/server status, categories, tags, preferences, and search. Gaps exist for RSS feed/automation-rule management and per-torrent file/peer priority, though qbt_call provides a raw fallback for those.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      A
      quality
      D
      maintenance
      Enables interaction with qBittorrent through its Web API to search for torrents using search plugins and manage downloads. Supports torrent searching, downloading via URLs/magnet links, and torrent management operations like pause, resume, and delete.
      7
      3
      -
    • A
      license
      B
      quality
      C
      maintenance
      MCP server that exposes qui's JSON REST API as tools for monitoring and managing qBittorrent instances, torrents, automations, cross-seeding, RSS, backups, and related services.
      12
      MIT
    • A
      license
      A
      quality
      C
      maintenance
      MCP server exposing qBittorrent's WebUI API v2 as tools, enabling LLMs to manage torrents, categories, tags, RSS feeds, search plugins, and application settings.
      90
      3
      MIT