From e011c0f63e8adb05e058049ce2204deb09a24f0c Mon Sep 17 00:00:00 2001 From: Deivid Soto Date: Mon, 9 Feb 2026 17:46:24 +0100 Subject: [PATCH] fix: correct README inconsistencies and add TORRENTCLAW_ALLOW_PRIVATE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename get_torrent_download_url → get_torrent_url in README tools table - Add TORRENTCLAW_ALLOW_PRIVATE env var to bypass SSRF checks for self-hosted setups (localhost, private IPs) - Update self-hosted config example with TORRENTCLAW_ALLOW_PRIVATE=true - Add Prompts section to README - Add 3 tests for ALLOW_PRIVATE behavior (88 tests total) --- README.md | 15 +++++++++++++-- src/config.ts | 13 ++++++++----- tests/config.test.ts | 32 +++++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4bf117c..81a621b 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ No API key required. | `get_recent` | Get recently added content | | `get_watch_providers` | Streaming availability by country (Netflix, Disney+, etc.) | | `get_credits` | Cast and director for a title | -| `get_torrent_download_url` | Get .torrent file download URL from info hash | +| `get_torrent_url` | Get .torrent file download URL from info hash | ## Resources @@ -27,6 +27,15 @@ No API key required. |-----|-------------| | `torrentclaw://stats` | Catalog statistics (content/torrent counts by source) | +## Prompts + +| Prompt | Description | +|--------|-------------| +| `search_movie` | Search for a movie by title and get torrents + streaming | +| `search_show` | Search for a TV show by title and get torrents | +| `whats_new` | Discover recently added movies and TV shows | +| `where_to_watch` | Find where to stream, rent, or buy a title | + ## Configuration ### Claude Desktop @@ -70,7 +79,8 @@ Point to your own TorrentClaw instance: "command": "npx", "args": ["-y", "torrentclaw-mcp"], "env": { - "TORRENTCLAW_API_URL": "http://localhost:3030" + "TORRENTCLAW_API_URL": "http://localhost:3030", + "TORRENTCLAW_ALLOW_PRIVATE": "true" } } } @@ -82,6 +92,7 @@ Point to your own TorrentClaw instance: | Variable | Default | Description | |----------|---------|-------------| | `TORRENTCLAW_API_URL` | `https://torrentclaw.com` | Base URL of the TorrentClaw API | +| `TORRENTCLAW_ALLOW_PRIVATE` | `false` | Set to `true` to allow private/localhost URLs (for self-hosted setups) | ## Development diff --git a/src/config.ts b/src/config.ts index 6a37a6f..4a615d9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -24,11 +24,14 @@ export function validateApiUrl(raw: string): string { ); } - const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); - if (PRIVATE_IP_PATTERNS.some((re) => re.test(hostname))) { - throw new Error( - `Invalid TORRENTCLAW_API_URL: private/reserved addresses not allowed`, - ); + const allowPrivate = process.env.TORRENTCLAW_ALLOW_PRIVATE === "true"; + if (!allowPrivate) { + const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); + if (PRIVATE_IP_PATTERNS.some((re) => re.test(hostname))) { + throw new Error( + `Invalid TORRENTCLAW_API_URL: private/reserved addresses not allowed. Set TORRENTCLAW_ALLOW_PRIVATE=true for self-hosted setups.`, + ); + } } return raw; diff --git a/tests/config.test.ts b/tests/config.test.ts index df2e0fd..b0b996f 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -1,7 +1,20 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { validateApiUrl } from "../src/config.js"; describe("validateApiUrl", () => { + const originalEnv = process.env.TORRENTCLAW_ALLOW_PRIVATE; + + beforeEach(() => { + delete process.env.TORRENTCLAW_ALLOW_PRIVATE; + }); + + afterEach(() => { + if (originalEnv !== undefined) { + process.env.TORRENTCLAW_ALLOW_PRIVATE = originalEnv; + } else { + delete process.env.TORRENTCLAW_ALLOW_PRIVATE; + } + }); it("accepts valid https URL", () => { expect(validateApiUrl("https://torrentclaw.com")).toBe( "https://torrentclaw.com", @@ -82,4 +95,21 @@ describe("validateApiUrl", () => { it("rejects IPv6 loopback ::1", () => { expect(() => validateApiUrl("http://[::1]")).toThrow("private/reserved"); }); + + it("allows localhost when TORRENTCLAW_ALLOW_PRIVATE=true", () => { + process.env.TORRENTCLAW_ALLOW_PRIVATE = "true"; + expect(validateApiUrl("http://localhost:3030")).toBe( + "http://localhost:3030", + ); + }); + + it("allows 192.168.x.x when TORRENTCLAW_ALLOW_PRIVATE=true", () => { + process.env.TORRENTCLAW_ALLOW_PRIVATE = "true"; + expect(validateApiUrl("http://192.168.1.1")).toBe("http://192.168.1.1"); + }); + + it("still rejects ftp even when TORRENTCLAW_ALLOW_PRIVATE=true", () => { + process.env.TORRENTCLAW_ALLOW_PRIVATE = "true"; + expect(() => validateApiUrl("ftp://localhost")).toThrow("only http/https"); + }); });