feat(qbit): add speed limit tool#22
Conversation
Adds a new MCP tool for reading and setting qBittorrent global speed limits and the built-in alternative-rate scheduler. - Get current dl/ul limits, alt limits, active mode, and scheduler state - Set limits in KiB/s (0 = unlimited) with >= 0 validation - Toggle alt speed mode immediately via use_alt_limits - Configure scheduler time window and day pattern (all|weekdays|weekends|monday–sunday, matching all 10 qBittorrent API values) - schedule_enabled validated as bool before forwarding to API Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @koinsaari's task in 2m 23s —— View job Review: Add
|
… write Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @koinsaari's task in 2m 17s —— View job
Review: Add
|
| Check | Status |
|---|---|
Errors return mcp.NewToolResultError(...), no panics |
✅ All 12 error paths return MCP errors |
| No credentials/secrets in response content | ✅ Response contains only speed limits, scheduler config, mode |
| HTTP client has explicit timeout | ✅ Uses shared QBitClient (no direct HTTP calls) |
| Input validated before downstream API calls | ✅ All fields validated; use_alt_limits checked before SetPreferencesCtx |
Tool registered in server.go |
✅ s.AddTool(tools.QBitSpeedLimits(qc)) |
🟡 Nit
Unknown set fields silently ignored but echoed as "applied" — internal/tools/qbittorrent.go:804
If a caller passes a misspelled field (e.g. "donwload_limit"), it's silently skipped during processing but still appears in the "applied" map in the response. The caller sees {"mode":"write","applied":{"donwload_limit":100}} and may believe the field took effect. Not a correctness bug — just potentially misleading for AI agents that don't cross-check.
Summary
Clean implementation. The previous review's blocking issue (partial write on invalid use_alt_limits) is fixed and verified by a dedicated test. All CLAUDE.md invariants hold. 16 test cases cover read, write, toggle, day mappings, and all validation error paths. Ready to merge.
|
Claude finished @koinsaari's task in 3m 16s —— View job
Review: Add
|
| Check | Status |
|---|---|
Errors return mcp.NewToolResultError(...), no panics |
✅ All error paths return MCP errors |
| No credentials/secrets in response content | ✅ Response contains only speed limits, scheduler config, mode |
| HTTP client has explicit timeout | ✅ Uses shared QBitClient (no direct HTTP calls) |
| Input validated before downstream API calls | ✅ All fields validated; use_alt_limits checked before SetPreferencesCtx |
Tool registered in server.go |
✅ s.AddTool(tools.QBitSpeedLimits(qc)) |
🟡 Nit
applied response uses internal API keys and byte values — internal/tools/qbittorrent.go:793-799
The applied map echoes the qBittorrent API's internal key names and raw byte values rather than the tool's user-facing field names and KiB/s values. A caller that sends {"download_limit": 1024} gets back {"applied": {"dl_limit": 1048576}} — both the key and value differ from what was submitted. For AI agent callers, a response that mirrors the input schema would be easier to correlate.
Summary
Clean implementation. All three findings from prior reviews are fixed and verified by dedicated tests. 19 test cases cover read, write, toggle, day mappings, validation errors, SDK errors, and the unknown-field edge case. Ready to merge.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
15df0f7 to
1e808c1
Compare
|
Claude finished @koinsaari's task in 2m 15s —— View job
Review: Add
|
| Check | Status |
|---|---|
Errors return mcp.NewToolResultError(...), no panics |
✅ All 12 error paths return MCP errors |
| No credentials/secrets in response content | ✅ Response contains only speed limits, scheduler config, mode |
| HTTP client has explicit timeout | ✅ Uses shared QBitClient (no direct HTTP calls) |
| Input validated before downstream API calls | ✅ All fields validated; use_alt_limits checked before SetPreferencesCtx |
Tool registered in server.go |
✅ s.AddTool(tools.QBitSpeedLimits(qc)) |
🟡 Nit
Toggle race between read and toggle — internal/tools/qbittorrent.go:781-790
The use_alt_limits toggle does a read-then-conditional-toggle: it calls GetAlternativeSpeedLimitsModeCtx, checks if the current state differs from the desired state, then calls ToggleAlternativeSpeedLimitsCtx. If another client toggles the mode between the read and the toggle, the result is the opposite of what was requested. This is inherent to the qBittorrent API (no "set" endpoint, only toggle), so there's no fix — just noting the limitation.
Summary
Clean implementation. All three findings from prior reviews are fixed and covered by dedicated tests. 19 test cases cover read, write, toggle, day mappings, validation errors, type errors, SDK errors, and the unknown-field edge case. All CLAUDE.md invariants hold. Ready to merge.
Adds
qbit_speed_limits— a single tool for reading and setting qBittorrent global speed limits and the built-in alternative-rate scheduler.What it does
Read (no
setargument): returns current state — dl/ul limits, alt limits, whether alt mode is active, and full scheduler config.Write (pass a
setobject with only the fields you want to change):download_limit/upload_limit— global limits in KiB/s,0= unlimitedalt_download_limit/alt_upload_limit— alternative rate limits in KiB/suse_alt_limits— switch to alt limits immediately (read-then-toggle, no-op if already in desired state)schedule_enabled,schedule_from_hour,schedule_to_hour,schedule_days— configure the automatic time-based switchschedule_dayssupports all 10 qBittorrent API values:all,weekdays,weekends,monday–sunday.Validation
schedule_enabledvalidated as bool before forwarding to API (previously any type would pass through)schedule_daysvalues return a descriptive error listing valid optionsTests
16 new test cases covering read, write, toggle behaviour, all day mappings, and all validation error paths.
🤖 Co-authored with Claude Sonnet 4.6