# webshare-api Node.js/Express proxy that exposes [Webshare.cz](https://webshare.cz) as protocols *arr apps understand, and as an [MCP](https://modelcontextprotocol.io) server for AI agents. Official Webshare HTTP API documentation: [https://webshare.cz/apidoc/](https://webshare.cz/apidoc/) | Role | Endpoints | Purpose | |------|-----------|---------| | **Torznab indexer** | `GET /api?t=caps\|search\|tvsearch\|movie` | Search → Torznab RSS/XML | | **Fake torrent** | `GET /download/:ident` | Minimal `.torrent` whose webseed points at `/stream/:ident` | | **Stream** | `GET /stream/:ident` | Fresh Webshare `file_link` + HTTP 302 to CDN | | **Fake qBittorrent** | `/api/v2/*` | Download client API; streams the HTTPS file to disk | | **Debug** | `GET /resolve/:ident` | JSON with the resolved CDN URL | | **Web UI** | `GET /` | Two-tab UI: search files and add to queue; queue view with progress, speed, and cancel | | **MCP server** | `POST /mcp` (Streamable HTTP) | Tools for AI agents: search, resolve links, login check | Webshare download links expire after roughly ten minutes, so the torrent never embeds a CDN URL—only a path on this service that resolves a fresh link at fetch time. ## Web UI Light monochrome TUI-style page at `http://:3001/` — **Search** and **Queue** tabs (keys `1` / `2`, `/` focuses search). Queue shares the same download engine as the fake qBittorrent client. ![Queue tab with multiple Iron Man downloads](docs/ui-queue.png) ## Layout ``` src/ # source (CommonJS modules) ui/ # Web UI source (index.html, styles.css, scripts.js) scripts/build.mjs # esbuild: single server.js + minified single-file UI dist/ # build output (gitignored; produced by npm run build / Docker) server.js # one file: app + all npm dependencies mcp.js # optional MCP-only entry ui/index.html # CSS+JS inlined __tests__/ compose.yaml Dockerfile # multi-stage: build with esbuild, runtime = Node + dist only ``` ## Build (smaller image / single JS) ```bash npm install npm run build # → dist/server.js (~2 MB, all deps bundled) + dist/ui/index.html npm start # node dist/server.js npm run start:dev # unbundled src/ for local hacking ``` **esbuild** bundles the server (and optional `mcp` entry) into a **single `.js` file** with dependencies inlined — no `node_modules` needed at runtime. The Web UI is minified into **one** `dist/ui/index.html` (CSS and JS inlined). Docker uses a multi-stage build: final image copies only `dist/`, not source or `node_modules`, which keeps the image much smaller than `npm ci --omit=dev`. ## Configuration Environment variables (required unless noted): | Variable | Default | Description | |----------|---------|-------------| | `WEBSHARE_USERNAME` | _(empty)_ | Webshare username or email. **Optional** — if empty (with password), runs in **guest** mode | | `WEBSHARE_PASSWORD` | _(empty)_ | Webshare password (plain; hashed client-side). Optional with username for VIP/session downloads | | `PORT` | `3001` | HTTP listen port | | `BASE_URL` | `http://localhost:$PORT` | Public base URL Sonarr must reach (use the Compose service hostname when linking containers) | | `DOWNLOAD_PATH` | `/downloads/webshare` | Staging dir for UI + *arr (Sonarr/Radarr import from here into Shows/Movies) | | `MEDIA_ROOT` | `/data` | Media root (path reporting) | | `DOWNLOAD_UID` / `DOWNLOAD_GID` | `1000` | chown finished files when the container runs as root | | `MAX_CONCURRENT_DOWNLOADS` | `2` | Parallel stream downloads | | `SONARR_URL` | `http://sonarr:8989` | Optional; Sonarr base URL | | `SONARR_API_KEY` | _(empty)_ | Optional; enables periodic missing-episode search | | `SONARR_SEARCH_INTERVAL_HOURS` | `2` | Interval for that search | | `DOWNLOAD_HOST_PATH` | `./data/webshare` | Host path mounted at `DOWNLOAD_PATH` in Compose | | `MCP_ENABLED` | `true` | Set to `0`/`false` to disable the MCP HTTP endpoint | | `MCP_PATH` | `/mcp` | HTTP path for the Streamable HTTP MCP endpoint | Copy `.env.example`. Credentials are **optional**: without them the app uses Webshare **guest** `file_link` (no `wst`) — search and downloads work, free-tier CDN (`free.*.dl.wsfiles.cz`) is typically much slower than an authenticated account. Do not commit a filled `.env`. ## Run with Compose ```bash cp .env.example .env # set WEBSHARE_USERNAME and WEBSHARE_PASSWORD docker compose up -d --build ``` See `compose.yaml`. Override ports, `BASE_URL`, Sonarr settings, and volume mounts via `.env` as needed. ## Run without Compose ```bash npm install npm run build export WEBSHARE_USERNAME=... export WEBSHARE_PASSWORD=... npm start # or: npm run start:dev # no build step, uses src/ ``` ## Sonarr / Radarr 1. **Indexer** (Torznab): `http://:3001/api` 2. **Download client** (qBittorrent): host/port of this service; any username/password (login accepts all). When both run on the same Compose network, use the service name, e.g. `http://webshare-api:3001`. Sonarr hands `.torrent` files to the fake qBittorrent API. This service extracts the webseed, streams the file into `DOWNLOAD_PATH`, and reports progress via `/api/v2/torrents/*`. ## MCP (AI agents) Exposes Webshare to MCP-compatible clients over **Streamable HTTP** (not stdio). With `npm start` (or Compose), MCP is available on the same process/port: ``` POST http://:3001/mcp ``` Optional MCP-only process (no Torznab/qBittorrent): ```bash export WEBSHARE_USERNAME=... export WEBSHARE_PASSWORD=... npm run mcp # listens on PORT, serves POST /mcp ``` ### Tools | Tool | Description | |------|-------------| | `webshare_search` | Search files (`query`, optional `limit` / `offset` / `sort` / `category`) | | `webshare_get_link` | Resolve a temporary CDN URL for a file `ident` | | `webshare_login_check` | Verify credentials against the Webshare API | ### Client configuration example **HTTP / Streamable HTTP** (preferred): ```toml [mcp_servers.webshare] url = "http://127.0.0.1:3001/mcp" ``` ```bash grok mcp add --transport http webshare http://127.0.0.1:3001/mcp ``` Credentials stay on the server process (`WEBSHARE_*` env); clients only need the URL. The transport is **stateless** Streamable HTTP (POST only). ## Tests ```bash npm test ``` ## License [MIT](LICENSE) You need a valid Webshare account. This project only speaks their documented HTTP API ([apidoc](https://webshare.cz/apidoc/)). Use at your own risk.