Torrent Search MCP Server
# Torrent Search MCP/API/WebUI
[](https://docs.astral.sh/uv/getting-started/installation/)
[](https://www.python.org/downloads/)
[](https://badge.fury.io/py/torrent-search-mcp)
[](https://github.com/philogicae/torrent-search-mcp/actions)
[](https://opensource.org/licenses/MIT)
[](https://deepwiki.com/philogicae/torrent-search-mcp)
This repository provides a Python API/WebUI and an MCP (Model Context Protocol) server to find torrents programmatically on **ThePirateBay**, **1337x**, **Nyaa**, **YTS**, **EZTV**, **FitGirl**, **SubsPlease** and **UIndex**. It allows for easy integration into other applications or services.
<div align="center" style="margin: 20px 0;">
<img src=".github/assets/cover.png" alt="Torrent Search web UI - popular torrents view" width="720" />
</div>
## Quickstart
> [How to use it with MCP Clients](#via-mcp-clients)
> [Run it with Docker to bypass common DNS issues](#for-docker)
> [Search directly from the command line](#as-cli)
```bash
uvx torrent-search-mcp --mode cli "sample show"
# MCP server over stdio (default)
uvx torrent-search-mcp --mode stdio
# MCP server over streamable HTTP (port 8000, endpoint /mcp)
uvx torrent-search-mcp --mode http
# MCP server over SSE (port 8000, endpoint /sse, legacy)
uvx torrent-search-mcp --mode sse
# Standalone API server (port 8000)
uvx torrent-search-mcp --mode api
```
## Table of Contents
- [Features](#features)
- [Supported Sources](#supported-sources)
- [Setup](#setup)
- [Prerequisites](#prerequisites)
- [Configuration](#configuration-optional)
- [Installation](#installation)
- [Install from PyPI (Recommended)](#install-from-pypi-recommended)
- [For Local Development](#for-local-development)
- [For Docker](#for-docker)
- [Usage](#usage)
- [As CLI](#as-cli)
- [As Python Wrapper](#as-python-wrapper)
- [As MCP Server](#as-mcp-server)
- [As API Server](#as-api-server)
- [Via MCP Clients](#via-mcp-clients)
- [Example with Devin](#example-with-devin)
- [Changelog](#changelog)
- [Contributing](#contributing)
- [License](#license)
## Features
- API wrapper for **ThePirateBay**, **1337x**, **Nyaa**, **YTS**, **EZTV**, **FitGirl**, **SubsPlease** and **UIndex**.
- MCP server interface (FastMCP 4) serving the `2026-07-28` protocol revision over `stdio` or streamable HTTP (`http`), with automatic negotiation of older handshake revisions and legacy transport aliases (`streamable-http`, `sse`).
- API server interface for alternative HTTP access (e.g., for direct API calls or testing).
- CLI mode for quick one-off searches directly from the terminal.
- `popular_torrents` cached for 2 minutes for Web UI delivery; searches are never cached (only identical concurrent requests are coalesced) so new results appear immediately.
- In-memory 1-hour, 5000-entry torrent cache used only to resolve magnet links via `get_torrent` without re-scraping.
- Magnet links are always stored internally; MCP hides them unless `INCLUDE_LINKS=true`.
- Configurable source filtering via environment variables.
- Telegram-gated web UI: one-time QR/deep-link pairing, forward-to-Telegram popup and (optional) server-side forwarding.
- Tools:
- Search for torrents across all available sources.
- Get the most popular torrents per source (apibay, uindex, 1337x, YTS, nyaa, EZTV).
- Get the magnet link for a specific torrent by id.
- List available sources.
- Present the web UI and its Telegram pairing access, approve pairing codes, and forward torrents to Telegram chats.
## Supported Sources
| Source | Scraping domain | Fetch method |
| ------------ | ---------------------- | --------------------- |
| ThePirateBay | `apibay.org` | JSON API |
| 1337x | `1337x.to` + mirrors | HTML search/top pages |
| Nyaa | `nyaa.si` | RSS + HTML top page |
| YTS | `yts.mx` + mirrors | JSON API |
| EZTV | `eztvx.to` | JSON API |
| FitGirl | `fitgirl-repacks.site` | RSS |
| SubsPlease | `subsplease.org` | JSON API |
| UIndex | `uindex.org` | HTML top list |
The API exposes public display domains where applicable: `apibay.org` is shown as `thepiratebay.org`, and `yts.mx` as `yts.vg`. Results may include a validated HTTP(S) `page_url` linking back to their source page.
> **Note on UIndex:** the site exposes no programmatic search endpoint (its search path is protected by a browser challenge), so queries are matched client-side against its live top list - which conveniently carries magnet links inline.
Sources can be excluded individually via the [`EXCLUDE_SOURCES`](#configuration-optional) env var.
## Setup
### Prerequisites
- Python 3.10+ (required for PyPI install). CI and Docker images use Python 3.14.
- [`uv`](https://github.com/astral-sh/uv) (for local development).
- Docker and Docker Compose (for Docker setup).
### Configuration (Optional)
The application reads configuration from environment variables. The recommended way to set them is by creating a `.env` file in your project's root directory. The application will load it automatically. See `.env.example` for all available options.
| Variable | Default | Description |
| ------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `INCLUDE_LINKS` | `false` | When `true`, include magnet links in the MCP `search_torrents` / `popular_torrents` results. Left off by default to greatly reduce token usage. |
| `EXCLUDE_SOURCES` | _(none)_ | Comma-separated list of sources to exclude from results (e.g. `nyaa.si,1337x.to`). |
| `TORRENT_SEARCH_API_URL` | _(none)_ | MCP only: base URL of a running Torrent Search REST API - tools proxy it instead of scraping locally. Unset = standalone. |
| `TELEGRAM_BOT_HANDLE` | _(none)_ | Telegram bot handle used by the Web UI torrent action. Unset = the web UI runs without the pairing gate and Telegram features stay hidden. |
| `TORRENT_SEARCH_API_KEY` | _(none)_ | Secret required to approve Web UI pairing codes (register endpoint + `authorize_webapp` MCP tool). Must match between API and MCP servers. Unset = pairing disabled (no gate). |
| `TELEGRAM_BOT_TOKEN` | _(none)_ | Bot token enabling server-side sending via `POST /forward_telegram` (non-agent mode). Unset = that endpoint replies 503 unless agent mode is configured; the Web UI forward popup still works through Telegram draft deep links. |
| `AGENT_RELAY_URL` | _(none)_ | Agent relay mode (with `AGENT_RELAY_TOKEN`, required): forward becomes a Confirm/Cancel dialog POSTing `{chat_id, sender, notice, prompt}` to the agent's HTTP relay instead of the Bot API (bots never receive bot-authored Telegram messages). |
| `AGENT_RELAY_TOKEN` | _(none)_ | Agent relay mode: shared secret sent as the `X-Relay-Token` header; must match the agent's `AGENT_RELAY_TOKEN`. |
| `TELEGRAM_AGENT_NAME` | _(none)_ | Agent relay mode: `sender` name passed to the relay (spoofed as the chat identity downstream). |
| `TELEGRAM_MSG_FORWARD` | _(none)_ | Agent relay mode: `notice` echoed into the chat by the agent before it processes the prompt. |
| `PRUNE_MAGNET_LINKS` | `false` | When `true`, magnets sent over every Telegram path (forward popup draft + `/forward_telegram`) are pruned to `magnet:?xt=urn:btih:<hash>&dn=<name>`; copy/magnet buttons keep originals. |
| `TELEGRAM_AUTH_FILE` | `./authorized_tokens.json` | Persistence file for authorized session tokens (SHA-256 hashes only); shared between API and MCP processes via mtime-based reload. |
| `WEBUI_URL` | _(none)_ | MCP only: public URL of the web UI; enables the `torrent_webapp` tool that presents the app and its pairing flow. |
### Installation
Choose one of the following installation methods.
#### Install from PyPI (Recommended)
This method is best for using the package as a library or running the server without modifying the code.
1. Install the package from PyPI:
```bash
pip install torrent-search-mcp
```
2. Create a `.env` file in the directory where you'll run the application (optional).
3. Run the MCP server (default: stdio):
```bash
python -m torrent_search
```
#### For Local Development
This method is for contributors who want to modify the source code.
Using [`uv`](https://github.com/astral-sh/uv):
1. Clone the repository:
```bash
git clone https://github.com/philogicae/torrent-search-mcp.git
cd torrent-search-mcp
```
2. Install dependencies using `uv`:
```bash
uv sync --frozen
```
3. Create your configuration file by copying the example:
```bash
cp .env.example .env
```
4. Run the MCP server (default: stdio):
```bash
uv run -m torrent_search
```
The repo also ships a `dev.sh` helper that locks/syncs deps, formats, lints, type-checks (`ty`) and runs the test suite with coverage:
```bash
./dev.sh
```
#### For Docker
This method uses Docker Compose to run **two containers**: the REST API + web UI, and an MCP server that proxies the API (no local scraping).
`compose.yaml` is configured to bypass DNS issues (using [quad9](https://quad9.net/) DNS).
| Container | Mode | Host port | Endpoints |
| -------------------- | ------ | --------- | ------------------------------------------------------------------------------------------ |
| `torrent-search-api` | `api` | `8000` | `/` (web UI), `/torrent/*`, `/sources`, `/docs` |
| `torrent-search-mcp` | `http` | `8001` | `/mcp` (MCP over streamable HTTP, `TORRENT_SEARCH_API_URL=http://torrent-search-api:8000`) |
1. Clone the repository (if you haven't already):
```bash
git clone https://github.com/philogicae/torrent-search-mcp.git
cd torrent-search-mcp
```
2. Create your configuration file by copying the example:
```bash
cp .env.example .env
```
3. Build and run the containers using Docker Compose:
```bash
docker compose up --build -d
```
4. Access container logs:
```bash
docker logs torrent-search-api -f
docker logs torrent-search-mcp -f
```
## Usage
The package exposes a single entry point, `torrent-search-mcp` (installed by `pip`/`uvx`), equivalent to `python -m torrent_search`. It supports the following `--mode` values:
| Mode | Endpoint | Description |
| ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `cli` | - | Run a single search query and print results to stdout. |
| `stdio` | - | MCP server over stdio (default). |
| `http` | `/mcp` | MCP server using streamable HTTP. Serves MCP protocol revision `2026-07-28` (sessionless) and negotiates older revisions for legacy clients. |
| `streamable-http` | `/mcp` | Alias of `http` (legacy fastmcp transport name). |
| `sse` | `/sse` | MCP server using Server-Sent Events. Legacy HTTP transport (deprecated by the MCP spec in favor of `http`). |
| `api` | `/` | Standalone API HTTP server (see [As API Server](#as-api-server)). |
The server is built on FastMCP 4 (MCP SDK v2). Clients supporting the `2026-07-28` revision negotiate it automatically (stateless requests, `server/discover`, no session IDs); older clients fall back to the previous handshake era against the same deployment.
MCP modes (`stdio`, `http`, `streamable-http`, `sse`) run **standalone** by default (tools scrape locally). Set [`TORRENT_SEARCH_API_URL`](#configuration-optional) to switch to **API mode**: the tools proxy a running Torrent Search REST API instead.
Common flags (for `http`, `streamable-http`, `sse` and `api` modes): `--host` (default `0.0.0.0`), `--port` (default `8000`), `--reload`, `--workers` (API only).
### As CLI
Run a one-off search directly from the terminal. Prints each result as `id (seeders|leechers|downloads) - filename`, then fetches the magnet/torrent for the top hit.
```bash
# Using the installed entry point
torrent-search-mcp --mode cli "sample show"
# Or via uvx without installing
uvx torrent-search-mcp --mode cli "sample show"
# Or from source
uv run -m torrent_search --mode cli "sample show"
```
### As Python Wrapper
```python
from torrent_search import torrent_search_api
results = await torrent_search_api.search_torrents("sample show")
for torrent in results:
print(
f"{torrent.filename} | {torrent.size} | {torrent.seeders} SE | {torrent.leechers} LE | {torrent.date} | {torrent.source}"
)
```
`search_torrents` is async and accepts an optional `max_items` (default `20`). `popular_torrents(per_source=20)` returns the current most popular torrents from sources with a top listing - up to `per_source` results per source (pass `per_source=None` for everything), merged and ranked by seeders + leechers. Each `Torrent` exposes `id`, `filename`, `category`, `size`, `seeders`, `leechers`, `downloads`, `date`, `source`, `uploader`, and, when available, `magnet_link` and a validated HTTP(S) `page_url`. Pass a torrent's `id` to `get_torrent()` to retrieve its magnet link.
### As MCP Server
```python
from torrent_search import torrent_search_mcp
torrent_search_mcp.run(transport="http")
```
### As API Server
This project also includes a API server as an alternative way to interact with the library via a standard HTTP API. This can be useful for direct API calls, integration with other web services, or for testing purposes.
**Running the API Server:**
```bash
# With Python
python -m torrent_search --mode api
# With uv
uv run -m torrent_search --mode api
```
- `--host <host>`: Default: `0.0.0.0`.
- `--port <port>`: Default: `8000`.
- `--reload`: Enables auto-reloading when code changes (useful for development).
- `--workers <workers>`: Default: `1`. Caching and request coalescing are in-process, so each worker keeps its own cache; use the default single worker unless per-worker caches are acceptable (a shared backend such as Redis would be needed to scale out).
The API server will then be accessible at `http://<host>:<port>`.
**Available Endpoints:**
The API server exposes similar functionalities to the MCP server. Key endpoints include:
- `GET /`: Built-in web UI (dark/light) - search, per-site popular tiles, sortable results, source-page links and magnet actions. Telegram sending requires one-time QR pairing when configured.
- `POST /torrent/search`: Search for torrents. Query params: `query` (required), `max_items` (optional, default `20`; uncapped when `per_source` is set) and `per_source` (optional, keep up to N results per source, ranked by swarm health).
- `GET /sources`: List the available torrent source domains.
- `GET /torrent/popular`: Get the most popular torrents. Query param: `per_source` (optional, default `20`).
- `GET /torrent/{torrent_id}`: Get the magnet link for a specific torrent by id. Returns the magnet URI as a JSON string.
- `GET /telegram/session`: Web UI auth state (`enabled`, `authenticated`, public bot `handle`, `prune_magnet_links`). Send the session token as `Authorization: Bearer`.
- `POST /telegram/auth/challenge`: Create a one-time 16-character alphanumeric pairing code (rate-limited). Codes expire after ~5 minutes and are shown as a QR + deep link in the pairing popup.
- `GET /telegram/auth/poll?code=`: Poll a pairing code; on approval returns the one-time session token for the browser to store.
- `DELETE /telegram/auth/challenge/{code}`: Cancel a pending pairing code.
- `POST /telegram/auth/register`: Approve a pairing code bound to a Telegram `chat_id`. Requires `Authorization: Bearer $TORRENT_SEARCH_API_KEY`.
- `POST /telegram/auth/logout`: Revoke the presented session token.
- `POST /forward_telegram`: Send torrent info to the Telegram chat bound to the presented session token; server-to-server callers may instead use `Authorization: Bearer $TORRENT_SEARCH_API_KEY` with the target `chat_id` query param. JSON body: `filename` (required), `magnet_link` (required), optional `size`, `seeders`. When `PRUNE_MAGNET_LINKS=true` the forwarded magnet is pruned; requires `TELEGRAM_BOT_TOKEN`, otherwise 503.
- `/docs`: Interactive API documentation (Swagger UI).
- `/redoc`: Alternative API documentation (ReDoc).
Environment variables are configured the same way as for the MCP server (via an `.env` file in the project root).
### Via MCP Clients
Usable with any MCP-compatible client. Available tools:
- `search_torrents(user_intent, query)`: Search for torrents across all available sources.
- `user_intent`: A short description reflecting the user's overall intention (e.g. `"latest episode of Sample Show"`).
- `query`: Optimized, lowercase, space-separated keywords (e.g. `"sample show s01e05"`). Generic/filler/technical terms should be stripped per the tool's docstring.
- By default magnet links are stripped from the response to save tokens; set `INCLUDE_LINKS=true` to include them.
- **Magnet round trips:** with `INCLUDE_LINKS` unset, results keep their `id` but no magnet. After picking the 2-5 torrents worth recommending, call `get_torrent(id)` once per pick: the server resolves it from its 1-hour torrent cache, or re-runs the search if the entry expired. Do not fetch magnets for every result.
- `popular_torrents(per_source=20)`: Get the most popular torrents right now from sources with an official top listing (apibay, uindex, 1337x, YTS, nyaa, EZTV) - up to `per_source` results each, grouped per source and pre-ranked by seeders + leechers.
- By default magnet links are stripped from the response to save tokens; set `INCLUDE_LINKS=true` to include them.
- `available_sources()`: Get the list of available torrent sources.
- `get_torrent(torrent_id)`: Get the magnet link for a specific torrent by id (the `id` returned by `search_torrents` or `popular_torrents`).
- `authorize_webapp(code, chat_id)`: Approve a Web UI pairing code bound to your Telegram chat id (the code shown in the browser pairing gate). Requires `TORRENT_SEARCH_API_KEY` and `TORRENT_SEARCH_API_URL`.
- `forward_torrent(filename, magnet_link, chat_id, size=None, seeders=None)`: Forward a torrent (filename + magnet) to your Telegram chat through the REST API (magnet pruned when `PRUNE_MAGNET_LINKS=true`). Requires `TORRENT_SEARCH_API_KEY` and `TORRENT_SEARCH_API_URL`; rate-limited per chat (20/min).
- `torrent_webapp()`: Present the web UI URL (`WEBUI_URL`) and its pairing-based access system.
#### Example with Devin
Configuration:
```json
{
"mcpServers": {
...
# with stdio (only requires uv)
"torrent-search-mcp": {
"command": "uvx",
"args": [ "torrent-search-mcp" ]
},
# with streamable-http transport (Docker compose: MCP on port 8001; standalone server: 8000)
"torrent-search-mcp": {
"serverUrl": "http://127.0.0.1:8001/mcp"
},
# with sse transport (legacy; requires running server)
"torrent-search-mcp": {
"serverUrl": "http://127.0.0.1:8001/sse"
},
...
}
}
```
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for a history of changes to this project.
## Contributing
Contributions are welcome! Please open an issue or submit a pull request.
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
TDQS
Scored across 7 tools
The search, popular, get-by-id, source-list, forwarding, and webapp auth tools each target distinct actions, and the descriptions clearly separate them. search_torrents and popular_torrents are the closest pair, but query-based search vs. official top listings is explicit. torrent_webapp and authorize_webapp are complementary steps rather than overlapping tools.
Most tools follow a verb_noun pattern (get_torrent, search_torrents, authorize_webapp, forward_torrent), but available_sources and popular_torrents use adjective_noun phrasing and torrent_webapp is a bare resource name. These deviations are readable but break the otherwise consistent command-style convention.
Seven tools is a well-scoped size for a torrent search server with an additional Telegram/webapp pairing flow. Each tool covers a distinct step without redundancy or unnecessary breadth.
The core workflow is covered: discover sources, search or list popular torrents, resolve a magnet by id, and forward it to Telegram, plus the webapp authorization flow. Minor gaps remain such as source-specific browsing or more detailed torrent metadata, but they do not create dead ends for the main use case.