MCP server for M-Team (馒头) private torrent tracker - search, detail, and download
The server implements 3 tools with reasonable verb-noun naming (search_torrents, get_torrent_detail, download_torrent) and present descriptions. However, multiple critical gaps limit production readiness: (1) parameter descriptions are embedded in docstrings rather than properly surfaced in JSON schemas; (2) no enum constraints for the 'mode' parameter despite 9 discrete valid values; (3) output formats are unstructured strings rather than typed objects; (4) no documented error handling or recovery guidance; (5) pagination support exists but is minimally documented; (6) the download_torrent tool has no dry-run or confirmation pattern despite being destructive (writes to disk). Tool naming is clear and follows verb-noun convention, but schema completeness and LLM-optimized descriptions are significantly below production baseline.
Download a .torrent file for the given torrent ID. This tool first obtains a temporary download token from the M-Team API, then downloads the .torrent file and saves it to the configured download directory (default: ./seed/). Args: torrent_id: The numeric torrent ID (e.g. "1125330"). Returns: The absolute path to the saved .torrent file on success, or an error message on failure.
Get detailed information for a specific torrent by its ID. Args: torrent_id: The numeric torrent ID (e.g. "1125330"). Returns: A formatted string with full torrent details including name, size, description, ratings, file count, seeder/leecher counts, etc.
Search torrents on M-Team by keyword. Args: keyword: Search keyword (supports Chinese / English). mode: Search mode. One of: normal, adult, movie, music, tvshow, waterfall, rss, rankings, all. Defaults to "normal". page_number: Page number (1-based). Defaults to 1. page_size: Number of results per page (max 100). Defaults to 20. Returns: A formatted string listing matching torrents with their IDs, names, sizes, seeder/leecher counts, labels, ratings, and discount info.
Parameter 'mode' in search_torrents lacks enum constraint, free-form string invites hallucinated values. Valid modes are: normal, adult, movie, music, tvshow, waterfall, rss, rankings, all. Constraint only in docstring, not in JSON schema.
All three tools return unstructured strings rather than typed objects. No output schemas documented. Agents must parse free-text responses, wasting tokens and increasing hallucination risk. E.g., search_torrents returns formatted multi-line string instead of JSON array of {id, name, size, seeders, leechers, discount, labels, ratings}.
download_torrent is destructive (writes to filesystem) but lacks confirmation/dry-run pattern. No idempotency guarantees. No permission-gating or audit logging. Agents could repeatedly call this and accumulate duplicate .torrent files without recourse.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | D | 54 | 2026-07-28+ | v2 |
| 2026-03-09 | F | 41 | - | v1 |
Parameter descriptions are embedded in Python docstrings, not in JSON schema objects. LLMs cannot access docstring text during tool selection, they only see the schema and brief tool description. Format constraints ('numeric', 'max 100', '2-10 chars') are invisible to LLMs.
No error handling or recovery guidance in any tool. Stack traces or raw API errors would be returned to LLM with no actionable next steps. E.g., 'API error 403: Unauthorized' tells agent nothing, should be 'MTEAM_API_KEY missing or invalid. Set it in .env file as MTEAM_API_KEY=<your-key>.'
API_KEY, BASE_URL, and DOWNLOAD_DIR are server config (good), but no validation at startup. If MTEAM_API_KEY is missing, the error only surfaces when a tool is called, wasting a turn. Server should validate secrets on startup and fail fast.
search_torrents pagination support present (page_number, page_size) but no 'total' or 'has_next' field documented in return. Agents cannot determine if more results exist without counting items vs page_size. Response should include {results: [...], total: N, has_next: bool} or next_cursor.
search_torrents returns unstructured formatted string with variable-length output. For large result sets (page_size=100), response could exceed 10KB of text, bloating context window. No enforced result limit; max=100 is only a constraint, not capped in code.
Tool descriptions do not state WHEN to use each tool over others. search_torrents vs get_torrent_detail, when does agent choose one vs the other? Lack of usage context forces agent to guess or try both.
get_torrent_detail accepts only numeric torrent_id as string. If agent has 'torrent_id' field from search_torrents response, the type must match exactly. No name-based lookup alternative. Asymmetry forces agent to always use search before detail.