qui-mcp
This server exposes qui's REST API as MCP tools for monitoring and managing qBittorrent instances, torrents, and related services.
Authentication & API Keys: Check setup, get current user, validate sessions, change passwords, manage general and client API keys.
External Programs: List, create, update, delete, and execute external programs.
Notifications: List events; create, update, delete, and test notification targets.
ARR Integrations: Manage ARR instances (list/create/get/update/delete/test), test connections, resolve ARR data; manage tracker customizations.
Dashboard & UI: Get/update dashboard settings, filter views, custom themes, tracker icons.
Logging & System Info: Log settings/exclusions, list log files; version/application info.
Torrents & Cross-Seeding: List cross-instance torrents; cross-seed operations: status, apply, webhook checks, analyze, search, apply search; manage settings, triggers, blocklist, runs, completion, season packs.
Directory Scanning: Get/update settings; manage scan directories; trigger/cancel scans; view status, runs, injections, files; reset/requeue no-match.
Torznab: Manage indexers, discover, get health and tracker domains.
Provides tools for monitoring and managing qBittorrent instances, torrents, categories, tags, preferences, and automations.
Provides tools for managing RSS feeds and automation related to torrent fetching and cross-seeding.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@qui-mcp What torrents are currently downloading?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
qui-mcp
Part of the arr-mcps collection. MCP server exposing qui's JSON REST API as tools for monitoring and managing qBittorrent instances, torrents, automations, cross-seeding, RSS, backups, and related services.
Built with FastMCP. The initial release mirrors the full JSON API route surface as one MCP tool per endpoint.
Install
uv tool install qui_mcp-*.whlRegister with Claude Code:
claude mcp add qui \
--env QUI_URL=https://your-qui-host \
--env QUI_API_KEY=<api-key> \
-- qui-mcpFrom source:
uv sync
claude mcp add qui \
--env QUI_URL=https://your-qui-host \
--env QUI_API_KEY=<api-key> \
-- uv run --directory /path/to/qui-mcp qui-mcpRelated MCP server: qbittorrent-mcp
Configuration
Env var | Required | Description |
| yes | qui host, optionally including a reverse-proxy base path; |
| no | API key sent as |
Create API keys in qui under Settings -> API Keys. The server never logs the key and sends no authentication header when it is unset.
Tools
12 resource-scoped tools, each covering multiple qui JSON endpoints (214
total) via an operation parameter: instances, torrents, categories, tags,
preferences, automations, RSS, backups, orphan scans, cross-seed, Torznab,
ARR integrations, notifications, API-key management, logs, and application
metadata. Call a tool with operation set to one of its listed routes and an
arguments object matching that route's parameters — the tool's own
description (visible to your MCP client) lists every route and its method +
path. This keeps the full REST surface available while costing a fraction of
the context budget of registering all 214 routes as separate tools.
Tool | Routes |
| 55 |
| 29 |
| 24 |
| 20 |
| 17 |
| 15 |
| 14 |
| 12 |
| 10 |
| 7 |
| 7 |
| 4 |
Streaming endpoints and binary downloads are intentionally omitted because MCP
tool results are structured JSON values. Session-creation endpoints
(/auth/setup, /auth/login, and /auth/logout) are also omitted; use qui's
web UI for those flows.
Every tool accepts operation (the route name, e.g. qui_list_torrents) plus
one optional arguments object:
{
"operation": "qui_list_torrents",
"arguments": {
"instanceID": 1,
"hash": "torrent-info-hash",
"params": {"filter": "downloading"},
"body": {"value": "request payload"}
}
}Path variables use their documented names. Query values belong in
arguments.params and JSON request payloads belong in arguments.body.
Development
make help
make sync
make test
make buildmake test uses only httpx.MockTransport. Live smoke tests require
QUI_URL and can be run with make test-integration.
The release workflow builds a wheel and source distribution when a v* tag is
pushed. Start at version 0.0.0; use make bump-patch, commit, tag, and push
for the first release.
Available Tools
12 toolsqui_automationsB
qui automations operations on qui. Pass operation and an arguments object; path variables go directly in arguments, query values in arguments.params, and a JSON request body in arguments.body.
qui_apply_automations: POST /instances/{instanceID}/automations/apply
qui_create_automation: POST /instances/{instanceID}/automations/
qui_delete_automation: DELETE /instances/{instanceID}/automations/{ruleID}
qui_delete_automation_activity: DELETE /instances/{instanceID}/automations/activity
qui_dry_run_automations: POST /instances/{instanceID}/automations/dry-run
qui_get_automation_activity_run: GET /instances/{instanceID}/automations/activity/{activityId}
qui_list_automation_activity: GET /instances/{instanceID}/automations/activity
qui_list_automations: GET /instances/{instanceID}/automations/
qui_preview_automation_delete: POST /instances/{instanceID}/automations/preview
qui_reorder_automations: PUT /instances/{instanceID}/automations/order
qui_update_automation: PUT /instances/{instanceID}/automations/{ruleID}
qui_validate_automation_regex: POST /instances/{instanceID}/automations/validate-regex
Inputs: operation (required), arguments. Returns: result
qui_backupsB
qui backups operations on qui. Pass operation and an arguments object; path variables go directly in arguments, query values in arguments.params, and a JSON request body in arguments.body.
qui_delete_all_backup_runs: DELETE /instances/{instanceID}/backups/runs
qui_delete_backup_run: DELETE /instances/{instanceID}/backups/runs/{runID}
qui_execute_backup_restore: POST /instances/{instanceID}/backups/runs/{runID}/restore
qui_get_backup_manifest: GET /instances/{instanceID}/backups/runs/{runID}/manifest
qui_get_backup_settings: GET /instances/{instanceID}/backups/settings
qui_import_backup_manifest: POST /instances/{instanceID}/backups/import
qui_list_backup_runs: GET /instances/{instanceID}/backups/runs
qui_preview_backup_restore: POST /instances/{instanceID}/backups/runs/{runID}/restore/preview
qui_trigger_backup: POST /instances/{instanceID}/backups/run
qui_update_backup_settings: PUT /instances/{instanceID}/backups/settings
Inputs: operation (required), arguments. Returns: result
qui_categories_tagsB
qui categories tags operations on qui. Pass operation and an arguments object; path variables go directly in arguments, query values in arguments.params, and a JSON request body in arguments.body.
qui_create_category: POST /instances/{instanceID}/categories
qui_create_tags: POST /instances/{instanceID}/tags
qui_delete_tags: DELETE /instances/{instanceID}/tags
qui_edit_category: PUT /instances/{instanceID}/categories
qui_get_categories: GET /instances/{instanceID}/categories
qui_get_tags: GET /instances/{instanceID}/tags
qui_remove_categories: DELETE /instances/{instanceID}/categories
Inputs: operation (required), arguments. Returns: result
qui_cross_seedA
qui cross seed operations on qui. Pass operation and an arguments object; path variables go directly in arguments, query values in arguments.params, and a JSON request body in arguments.body.
qui_add_cross_seed_blocklist_entry: POST /cross-seed/blocklist/
qui_analyze_cross_seed_torrent: GET /cross-seed/torrents/{instanceID}/{hash}/analyze
qui_apply_cross_seed_search: POST /cross-seed/torrents/{instanceID}/{hash}/apply
qui_apply_cross_seed_season_pack: POST /cross-seed/season-pack/apply
qui_cancel_cross_seed_automation: POST /cross-seed/run/cancel
qui_cancel_cross_seed_search: POST /cross-seed/search/run/cancel
qui_check_cross_seed_season_pack: POST /cross-seed/season-pack/check
qui_cross_seed_apply: POST /cross-seed/apply
qui_cross_seed_webhook_check: POST /cross-seed/webhook/check
qui_delete_cross_seed_blocklist_entry: DELETE /cross-seed/blocklist/{instanceID}/{infohash}
qui_get_cross_seed_async_status: GET /cross-seed/torrents/{instanceID}/{hash}/async-status
qui_get_cross_seed_completion: GET /cross-seed/completion/{instanceID}
qui_get_cross_seed_local_matches: GET /cross-seed/torrents/{instanceID}/{hash}/local-matches
qui_get_cross_seed_search_settings: GET /cross-seed/search/settings
qui_get_cross_seed_search_status: GET /cross-seed/search/status
qui_get_cross_seed_settings: GET /cross-seed/settings
qui_get_cross_seed_status: GET /cross-seed/status
qui_get_instance_cross_seed_status: GET /instances/{instanceID}/cross-seed/status
qui_list_cross_seed_blocklist: GET /cross-seed/blocklist/
qui_list_cross_seed_runs: GET /cross-seed/runs
qui_list_cross_seed_search_runs: GET /cross-seed/search/runs
qui_list_cross_seed_season_pack_runs: GET /cross-seed/season-pack/runs
qui_patch_cross_seed_search_settings: PATCH /cross-seed/search/settings
qui_patch_cross_seed_settings: PATCH /cross-seed/settings
qui_run_cross_seed_automation: POST /cross-seed/run
qui_search_cross_seed_torrent: POST /cross-seed/torrents/{instanceID}/{hash}/search
qui_start_cross_seed_search: POST /cross-seed/search/run
qui_update_cross_seed_completion: PUT /cross-seed/completion/{instanceID}
qui_update_cross_seed_settings: PUT /cross-seed/settings
Inputs: operation (required), arguments. Returns: result
qui_dir_scanA
qui dir scan operations on qui. Pass operation and an arguments object; path variables go directly in arguments, query values in arguments.params, and a JSON request body in arguments.body.
qui_cancel_directory_scan: DELETE /dir-scan/directories/{directoryID}/scan
qui_create_scan_directory: POST /dir-scan/directories/
qui_delete_scan_directory: DELETE /dir-scan/directories/{directoryID}
qui_get_dir_scan_settings: GET /dir-scan/settings
qui_get_scan_directory: GET /dir-scan/directories/{directoryID}
qui_get_scan_directory_status: GET /dir-scan/directories/{directoryID}/status
qui_list_scan_directories: GET /dir-scan/directories/
qui_list_scan_directory_files: GET /dir-scan/directories/{directoryID}/files
qui_list_scan_directory_runs: GET /dir-scan/directories/{directoryID}/runs
qui_list_scan_run_injections: GET /dir-scan/directories/{directoryID}/runs/{runID}/injections
qui_requeue_scan_directory_no_match: POST /dir-scan/directories/{directoryID}/requeue-no-match
qui_reset_scan_directory_files: POST /dir-scan/directories/{directoryID}/reset-files
qui_scan_directory: POST /dir-scan/directories/{directoryID}/scan
qui_update_dir_scan_settings: PATCH /dir-scan/settings
qui_update_scan_directory: PATCH /dir-scan/directories/{directoryID}
Inputs: operation (required), arguments. Returns: result
qui_instancesB
qui instances operations on qui. Pass operation and an arguments object; path variables go directly in arguments, query values in arguments.params, and a JSON request body in arguments.body.
qui_create_instance: POST /instances/
qui_delete_instance: DELETE /instances/{instanceID}/
qui_get_active_trackers: GET /instances/{instanceID}/trackers
qui_get_alternative_speed_limits: GET /instances/{instanceID}/alternative-speed-limits
qui_get_directory_content: GET /instances/{instanceID}/getDirectoryContent
qui_get_instance_app_info: GET /instances/{instanceID}/app-info
qui_get_instance_capabilities: GET /instances/{instanceID}/capabilities
qui_get_instance_mediainfo: GET /instances/{instanceID}/mediainfo
qui_get_instance_transfer_info: GET /instances/{instanceID}/transfer-info
qui_get_preferences: GET /instances/{instanceID}/preferences
qui_list_instances: GET /instances/
qui_test_instance: POST /instances/{instanceID}/test
qui_toggle_alternative_speed_limits: POST /instances/{instanceID}/alternative-speed-limits/toggle
qui_update_instance: PUT /instances/{instanceID}/
qui_update_instance_order: PUT /instances/order
qui_update_instance_status: PUT /instances/{instanceID}/status
qui_update_preferences: PATCH /instances/{instanceID}/preferences
Inputs: operation (required), arguments. Returns: result
qui_orphan_scanC
qui orphan scan operations on qui. Pass operation and an arguments object; path variables go directly in arguments, query values in arguments.params, and a JSON request body in arguments.body.
qui_cancel_orphan_scan: DELETE /instances/{instanceID}/orphan-scan/runs/{runID}
qui_confirm_orphan_deletion: POST /instances/{instanceID}/orphan-scan/runs/{runID}/confirm
qui_get_orphan_scan_run: GET /instances/{instanceID}/orphan-scan/runs/{runID}
qui_get_orphan_scan_settings: GET /instances/{instanceID}/orphan-scan/settings
qui_list_orphan_scan_runs: GET /instances/{instanceID}/orphan-scan/runs
qui_trigger_orphan_scan: POST /instances/{instanceID}/orphan-scan/scan
qui_update_orphan_scan_settings: PUT /instances/{instanceID}/orphan-scan/settings
Inputs: operation (required), arguments. Returns: result
qui_rssA
qui rss operations on qui. Pass operation and an arguments object; path variables go directly in arguments, query values in arguments.params, and a JSON request body in arguments.body.
qui_add_rss_feed: POST /instances/{instanceID}/rss/feeds
qui_add_rss_folder: POST /instances/{instanceID}/rss/folders
qui_get_rss_items: GET /instances/{instanceID}/rss/items
qui_get_rss_rules: GET /instances/{instanceID}/rss/rules
qui_mark_rss_article_read: POST /instances/{instanceID}/rss/articles/read
qui_move_rss_item: POST /instances/{instanceID}/rss/items/move
qui_preview_rss_rule_matches: GET /instances/{instanceID}/rss/rules/{ruleName}/preview
qui_refresh_rss_item: POST /instances/{instanceID}/rss/items/refresh
qui_remove_rss_item: DELETE /instances/{instanceID}/rss/items
qui_remove_rss_rule: DELETE /instances/{instanceID}/rss/rules/{ruleName}
qui_rename_rss_rule: PUT /instances/{instanceID}/rss/rules/{ruleName}/rename
qui_reprocess_rss_rules: POST /instances/{instanceID}/rss/rules/reprocess
qui_set_rss_feed_url: PUT /instances/{instanceID}/rss/feeds/url
qui_set_rss_rule: POST /instances/{instanceID}/rss/rules
Inputs: operation (required), arguments. Returns: result
qui_systemD
qui system operations on qui. Pass operation and an arguments object; path variables go directly in arguments, query values in arguments.params, and a JSON request body in arguments.body.
qui_activate_license: POST /license/activate
qui_change_password: PUT /auth/change-password
qui_check_setup: GET /auth/check-setup
qui_create_api_key: POST /api-keys/
qui_create_arr_instance: POST /arr/instances
qui_create_client_api_key: POST /client-api-keys/
qui_create_external_program: POST /external-programs/
qui_create_filter_view: POST /filter-views/
qui_create_notification_target: POST /notifications/targets
qui_create_tracker_customization: POST /tracker-customizations/
qui_delete_api_key: DELETE /api-keys/{id}
qui_delete_arr_instance: DELETE /arr/instances/{id}
qui_delete_client_api_key: DELETE /client-api-keys/{id}
qui_delete_external_program: DELETE /external-programs/{id}
qui_delete_filter_view: DELETE /filter-views/{id}
qui_delete_license: DELETE /license/{licenseKey}
qui_delete_notification_target: DELETE /notifications/targets/{id}
qui_delete_tracker_customization: DELETE /tracker-customizations/{id}
qui_execute_external_program: POST /external-programs/execute
qui_get_application_info: GET /application/info
qui_get_arr_instance: GET /arr/instances/{id}
qui_get_current_user: GET /auth/me
qui_get_dashboard_settings: GET /dashboard-settings
qui_get_latest_version: GET /version/latest
qui_get_licensed_status: GET /license/licensed
qui_get_log_exclusions: GET /log-exclusions
qui_get_log_settings: GET /log-settings
qui_get_tracker_icons: GET /tracker-icons
qui_get_version: GET /version
qui_list_api_keys: GET /api-keys/
qui_list_arr_instances: GET /arr/instances
qui_list_client_api_keys: GET /client-api-keys/
qui_list_custom_themes: GET /themes/custom
qui_list_external_programs: GET /external-programs/
qui_list_filter_views: GET /filter-views/
qui_list_licenses: GET /license/licenses
qui_list_log_files: GET /logs/files
qui_list_notification_events: GET /notifications/events
qui_list_notification_targets: GET /notifications/targets
qui_list_tracker_customizations: GET /tracker-customizations/
qui_refresh_licenses: POST /license/refresh
qui_resolve_arr: POST /arr/resolve
qui_test_arr_connection: POST /arr/test
qui_test_arr_instance: POST /arr/instances/{id}/test
qui_test_notification_target: POST /notifications/targets/{id}/test
qui_update_arr_instance: PUT /arr/instances/{id}
qui_update_dashboard_settings: PUT /dashboard-settings
qui_update_external_program: PUT /external-programs/{id}
qui_update_filter_view: PUT /filter-views/{id}
qui_update_log_exclusions: PUT /log-exclusions
qui_update_log_settings: PUT /log-settings
qui_update_notification_target: PUT /notifications/targets/{id}
qui_update_tracker_customization: PUT /tracker-customizations/{id}
qui_validate_license: POST /license/validate
qui_validate_session: GET /auth/validate
Inputs: operation (required), arguments. Returns: result
qui_torrent_creatorB
qui torrent creator operations on qui. Pass operation and an arguments object; path variables go directly in arguments, query values in arguments.params, and a JSON request body in arguments.body.
qui_create_torrent: POST /instances/{instanceID}/torrent-creator/
qui_delete_torrent_creation_task: DELETE /instances/{instanceID}/torrent-creator/{taskID}
qui_get_active_torrent_creation_count: GET /instances/{instanceID}/torrent-creator/count
qui_get_torrent_creation_status: GET /instances/{instanceID}/torrent-creator/status
Inputs: operation (required), arguments. Returns: result
qui_torrentsB
qui torrents operations on qui. Pass operation and an arguments object; path variables go directly in arguments, query values in arguments.params, and a JSON request body in arguments.body.
qui_add_peers: POST /instances/{instanceID}/torrents/add-peers
qui_add_torrent: POST /instances/{instanceID}/torrents/
qui_add_torrent_trackers: POST /instances/{instanceID}/torrents/{hash}/trackers
qui_ban_peers: POST /instances/{instanceID}/torrents/ban-peers
qui_check_duplicate_torrents: POST /instances/{instanceID}/torrents/check-duplicates
qui_edit_torrent_tracker: PUT /instances/{instanceID}/torrents/{hash}/trackers
qui_get_reannounce_activity: GET /instances/{instanceID}/reannounce/activity
qui_get_reannounce_candidates: GET /instances/{instanceID}/reannounce/candidates
qui_get_torrent_field: POST /instances/{instanceID}/torrents/field
qui_get_torrent_file_mediainfo: GET /instances/{instanceID}/torrents/{hash}/files/{fileIndex}/mediainfo
qui_get_torrent_files: GET /instances/{instanceID}/torrents/{hash}/files
qui_get_torrent_peers: GET /instances/{instanceID}/torrents/{hash}/peers
qui_get_torrent_pieces: GET /instances/{instanceID}/torrents/{hash}/pieces
qui_get_torrent_properties: GET /instances/{instanceID}/torrents/{hash}/properties
qui_get_torrent_trackers: GET /instances/{instanceID}/torrents/{hash}/trackers
qui_get_torrent_webseeds: GET /instances/{instanceID}/torrents/{hash}/webseeds
qui_list_cross_instance_torrents: GET /torrents/cross-instance
qui_list_torrents: GET /instances/{instanceID}/torrents/
qui_remove_torrent_trackers: DELETE /instances/{instanceID}/torrents/{hash}/trackers
qui_rename_torrent: PUT /instances/{instanceID}/torrents/{hash}/rename
qui_rename_torrent_file: PUT /instances/{instanceID}/torrents/{hash}/rename-file
qui_rename_torrent_folder: PUT /instances/{instanceID}/torrents/{hash}/rename-folder
qui_set_torrent_file_priority: PUT /instances/{instanceID}/torrents/{hash}/files
qui_torrent_bulk_action: POST /instances/{instanceID}/torrents/bulk-action
Inputs: operation (required), arguments. Returns: result
qui_torznabD
qui torznab operations on qui. Pass operation and an arguments object; path variables go directly in arguments, query values in arguments.params, and a JSON request body in arguments.body.
qui_create_torznab_indexer: POST /torznab/indexers/
qui_delete_torznab_indexer: DELETE /torznab/indexers/{indexerID}
qui_discover_torznab_indexers: POST /torznab/indexers/discover
qui_get_torznab_activity: GET /torznab/activity
qui_get_torznab_all_health: GET /torznab/indexers/health
qui_get_torznab_indexer: GET /torznab/indexers/{indexerID}
qui_get_torznab_indexer_errors: GET /torznab/indexers/{indexerID}/errors
qui_get_torznab_indexer_health: GET /torznab/indexers/{indexerID}/health
qui_get_torznab_indexer_stats: GET /torznab/indexers/{indexerID}/stats
qui_get_torznab_search_cache: GET /torznab/search/cache/
qui_get_torznab_search_history: GET /torznab/search/history
qui_get_torznab_tracker_domains: GET /torznab/indexers/tracker-domains
qui_list_recent_torznab_searches: GET /torznab/search/recent
qui_list_torznab_indexers: GET /torznab/indexers/
qui_search_torznab: POST /torznab/search
qui_search_torznab_cross_seed: POST /torznab/cross-seed/search
qui_sync_torznab_indexer_caps: POST /torznab/indexers/{indexerID}/caps/sync
qui_test_torznab_indexer: POST /torznab/indexers/{indexerID}/test
qui_update_torznab_indexer: PUT /torznab/indexers/{indexerID}
qui_update_torznab_search_cache_settings: PUT /torznab/search/cache/settings
Inputs: operation (required), arguments. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 12 tools
The 12 tools are organized around fairly distinct functional domains (torrents, RSS, torznab, backups, etc.), so misselection is rare. The main ambiguity is `qui_system`, which acts as a catch-all for auth, licenses, arr instances, notifications, and settings, and could be confused with `qui_instances` when an agent is looking for instance management.
All tools follow a consistent lowercase `qui_<domain>` pattern, and the underlying operations use a uniform CRUD-style naming convention such as `create_*`, `list_*`, `update_*`, and `delete_*`. There is no mixing of naming styles or unpredictable casing.
Twelve tools is within the ideal 3-15 range, and each tool maps to a meaningful resource area of the qui application. Even though several tools expose a large number of sub-operations, the tool count itself is well-scoped for the server's broad purpose.
The surface covers CRUD and lifecycle operations across most major domains: instances, torrents, cross-seed, torznab, RSS, automations, backups, scanning, categories, and tags. A few direct actions like explicit torrent deletion are only available through generic bulk-action endpoints, but overall the coverage is strong with few dead ends.
Maintenance
Related MCP Connectors
MCP server for OpenAI API (chat completions, image generation, embeddings) via AceDataCloud
An MCP server that let you interact with Cycloid.io Internal Development Portal and Platform
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
MCP server for progressive tool usage at any scale (see https://klavis.ai)
Related MCP Servers
- AlicenseBqualityBmaintenanceMCP server for rTorrent BitTorrent automation, enabling torrent management (add/list/pause/delete), multi-source searching (Nyaa, Pirate Bay, etc.), and post-processing workflows via natural language.71MIT
- AlicenseAqualityCmaintenanceMCP server exposing qBittorrent's WebUI API v2 as tools, enabling LLMs to manage torrents, categories, tags, RSS feeds, search plugins, and application settings.903MIT
- AlicenseAqualityCmaintenanceMCP server exposing Prowlarr's API v1 as tools for managing indexers, applications, download clients, and more, including cross-indexer searches and system commands.10MIT
- AlicenseAqualityCmaintenanceMCP server exposing qbit_manage's Web API as tools, so an LLM can read and manage a qbit_manage instance: run maintenance commands on qBittorrent torrents, manage config files, scheduler, logs, security settings, and system state.6MIT