UI grabs write Movies/<Title (Year)>/; chown to 1000:1000; CORS and safer /api/queue handling for the Web UI.
webshare-api
Node.js/Express proxy that exposes Webshare.cz as protocols *arr apps understand, and as an MCP server for AI agents.
Official Webshare HTTP API documentation: 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://<host>:3001/ — Search and Queue tabs
(keys 1 / 2, / focuses search). Queue shares the same download engine as the fake
qBittorrent client.
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)
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 |
Where the fake qBittorrent writes files |
MEDIA_ROOT |
/data |
Media root (used for path reporting) |
MOVIES_PATH |
$MEDIA_ROOT/Movies |
UI / direct grabs write into this library tree (Jellyfin folders) |
DOWNLOAD_UID / DOWNLOAD_GID |
1000 |
chown finished files when the container runs as root |
MOVIES_PATH |
$MEDIA_ROOT/Movies |
UI / direct grabs write into this library tree (Jellyfin folders) |
DOWNLOAD_UID / DOWNLOAD_GID |
1000 |
chown finished files when 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
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
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
- Indexer (Torznab):
http://<host>:3001/api - 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://<host>:3001/mcp
Optional MCP-only process (no Torznab/qBittorrent):
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):
[mcp_servers.webshare]
url = "http://127.0.0.1:3001/mcp"
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
npm test
License
You need a valid Webshare account. This project only speaks their documented HTTP API (apidoc). Use at your own risk.
