Skip to main content
Glama
README.md
# tor-mcp

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![Tests](https://github.com/rushikeshmore/tor-mcp/actions/workflows/tests.yml/badge.svg)](https://github.com/rushikeshmore/tor-mcp/actions/workflows/tests.yml)

> Every web request your LLM tool makes leaks your IP. tor-mcp routes them through Tor, with selective per-URL routing so things that block Tor still work.

An MCP server for Claude Code, Claude Desktop, Cursor, Windsurf, and any other MCP-speaking client. Drop in, get anonymous web fetches as a tool.

## Why this exists

When an LLM agent uses a tool like `fetch` or `web_search`, the request goes straight from your machine. Your IP, your ISP, your geo, your fingerprint. For OSINT lookups, breach checks, threat-intel APIs, or just "I don't want this corner of the web to know who's asking," that's a leak by design.

tor-mcp slots in as an MCP server so the agent's web requests go through your local Tor SOCKS5 proxy with DNS-safe routing (`socks5h://`, no DNS leaks). It's not trying to anonymize your browser — it's trying to anonymize the web traffic an AI tool makes on your behalf.

## What you get

Four MCP tools, all with both JSON and Markdown output:

| Tool | What it does |
|---|---|
| `tor_private_fetch` | Fetch a URL. Routes via Tor or direct based on URL pattern matching, or force one with `force_tor` / `force_direct` (mutually exclusive). |
| `tor_check_anonymity` | Verify Tor is active. Compares the Tor exit IP with your real IP via a multi-provider fallback chain. |
| `tor_new_identity` | Rotate Tor circuit. Get a new exit IP via the Tor control port. Rate-limited to once per 10s by Tor itself. |
| `tor_privacy_status` | Full health check: connection, exit IP, control port, routing rules, config. |

### Sample output

`tor_private_fetch(url="https://api.ipify.org?format=json")` →

```json
{
  "status_code": 200,
  "url": "https://api.ipify.org?format=json",
  "content_type": "application/json",
  "content": "{\"ip\":\"185.220.101.20\"}",
  "routed_through": "tor",
  "routing": {"route": "tor", "reason": "default route: tor"}
}
```

`tor_check_anonymity(response_format="markdown")` →

```markdown
## Anonymity Check
- **Status**: Anonymous
- **Tor Exit IP**: 185.220.101.20
- **Direct IP**: 106.213.80.181
- **Tor Verified**: Yes
```

## Quickstart

### 1. Install Tor

```bash
# macOS
brew install tor && brew services start tor

# Ubuntu/Debian
sudo apt install tor && sudo systemctl start tor

# Verify
curl --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip
```

### 2. Clone and add to your MCP client

```bash
git clone https://github.com/rushikeshmore/tor-mcp.git
cd tor-mcp
uv sync
```

<details>
<summary>Claude Code</summary>

```bash
claude mcp add tor-mcp -- uv --directory $(pwd) run tor-mcp
```

</details>

<details>
<summary>Claude Desktop</summary>

In `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "tor-mcp": {
      "command": "uv",
      "args": ["--directory", "/absolute/path/to/tor-mcp", "run", "tor-mcp"]
    }
  }
}
```

</details>

<details>
<summary>Cursor / Windsurf / generic MCP clients</summary>

```json
{
  "mcpServers": {
    "tor-mcp": {
      "command": "uv",
      "args": ["--directory", "/absolute/path/to/tor-mcp", "run", "tor-mcp"],
      "env": {
        "TOR_SOCKS_PORT": "9050",
        "TOR_DEFAULT_ROUTE": "tor"
      }
    }
  }
}
```

</details>

You can also launch the server with `python -m tor_mcp` if it's installed in your environment.

### 3. Optional: enable circuit rotation

To use `tor_new_identity`, edit your `torrc` (`/usr/local/etc/tor/torrc` on macOS, `/etc/tor/torrc` on Linux):

```
ControlPort 9051
CookieAuthentication 1
```

Restart Tor afterwards.

## Routing

URL → route is decided in this order:

| Priority | Rule | Route |
|---|---|---|
| 1 | `.onion` domains | Tor (always) |
| 2 | `localhost`, `127.0.0.1`, `*.local` | Direct (always) |
| 3 | User `TOR_PATTERNS` | Tor |
| 4 | User `DIRECT_PATTERNS` | Direct |
| 5 | Default OSINT/security domains (Shodan, HIBP, Censys, urlscan, VirusTotal, OTX, crt.sh) | Tor |
| 6 | Default Tor-blocking domains (GitHub, OpenAI, Anthropic, Google) | Direct |
| 7 | Everything else | `TOR_DEFAULT_ROUTE` (default: `tor`) |

URL inputs without a scheme get one added automatically — `https://` for remote hosts, `http://` for `localhost` / `127.0.0.1` / `*.local` so TLS doesn't blow up against a plain dev server.

## Configuration

All via environment variables:

| Variable | Default | Description |
|---|---|---|
| `TOR_SOCKS_PORT` | `9050` | Tor SOCKS5 proxy port |
| `TOR_CONTROL_PORT` | `9051` | Tor control port (for circuit rotation) |
| `TOR_CONTROL_PASSWORD` |  | Control port password (if set in torrc) |
| `TOR_DEFAULT_ROUTE` | `tor` | Fallback route: `tor` or `direct` |
| `TOR_TIMEOUT` | `30` | HTTP request timeout in seconds |
| `TOR_PATTERNS` |  | Comma-separated hostnames to route through Tor (supports `*.example.com`) |
| `DIRECT_PATTERNS` |  | Comma-separated hostnames to route direct |

## Threat model

What tor-mcp **does** protect against:

- Origin-IP exposure on outbound HTTP(S) requests made by your LLM tool. Traffic exits from a Tor relay, not your home IP.
- DNS leaks. SOCKS5h forces remote DNS resolution at the exit; your resolver never sees the target hostname.
- Trivial geo-fencing and IP-rate-limiting on a per-source basis (use `tor_new_identity` to rotate).

What it **does not** protect against:

- TLS / JA3 / HTTP-header fingerprinting. The traffic is over Tor, but it's still recognizable as Python `httpx`. If a target compares fingerprints, they can tell.
- Traffic correlation by a global adversary. Standard Tor caveat.
- Account-level deanonymization. If you log in, the target knows who you are regardless of route.
- Browser fingerprinting. Not applicable — there's no browser here, just an HTTP client.
- OS / process / clipboard / filesystem metadata. tor-mcp only handles HTTP traffic.
- Malicious or surveilling exits. Use HTTPS-only targets, treat exit-served content as untrusted.

The Tor control port without authentication assumes a single-user machine. On shared or multi-user hosts, set `TOR_CONTROL_PASSWORD` and a hashed password in `torrc`.

This is a privacy tool, not an evasion tool. Don't use it for fraud, abuse, or anything you wouldn't be comfortable explaining. Tor exit operators get a lot of flak from people doing exactly that, and bad actors hurt the network for everyone.

## Tools (parameters)

### `tor_private_fetch`

- `url` (string, required): Target URL.
- `method` (string, optional): `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`, `OPTIONS`. Default `GET`.
- `force_tor` (bool, optional): Force Tor regardless of routing rules.
- `force_direct` (bool, optional): Force direct regardless of routing rules.
  Setting both `force_tor=true` and `force_direct=true` is rejected as invalid input.
- `response_format` (string, optional): `json` or `markdown`. Default `json`.

### `tor_check_anonymity`

- `response_format` (string, optional): `json` or `markdown`. Default `json`.

Compares the Tor exit IP against the direct IP via a multi-provider chain (ipify → httpbin → ifconfig.co) so a single provider outage doesn't break the comparison.

### `tor_new_identity`

- `verify` (bool, optional): Check whether the IP actually changed. Default `true`.
- `response_format` (string, optional): `json` or `markdown`. Default `json`.

### `tor_privacy_status`

- `response_format` (string, optional): `json` or `markdown`. Default `json`.

## Development

```bash
git clone https://github.com/rushikeshmore/tor-mcp.git
cd tor-mcp
uv sync --dev

uv run python -m pytest        # run tests (no Tor needed, all mocked)
uv run python -m ruff check src/ tests/
uv run tor-mcp                 # start MCP server on stdio
```

Tests are fully mocked — you don't need Tor running to develop. Live verification (against a running Tor daemon) is documented in `CLAUDE.md`.

## License

[MIT](LICENSE).

TDQS

A4.8/5.0

Scored across 4 tools

Disambiguation5/5

Each of the four tools has a clearly distinct purpose: anonymity check, circuit renewal, status diagnostics, and privacy-aware URL fetching. The descriptions include explicit 'Use when' and 'Don't use when' cues that eliminate ambiguity.

Naming Consistency5/5

All tool names follow a consistent `tor_verb_noun` pattern (e.g., `tor_check_anonymity`, `tor_new_identity`), making the set predictable and easy to navigate.

Tool Count5/5

With exactly 4 tools, the server is well-scoped for its purpose. Each tool covers an essential operation (verification, identity change, status, fetching) without unnecessary bloat or fragmentation.

Completeness5/5

The tool set covers the full lifecycle of Tor usage: verifying anonymity, requesting a new identity, diagnosing the overall privacy status, and fetching URLs with intelligent routing. No obvious gaps exist for the stated domain.

Maintenance

ActivityInactive
ResponsivenessNo issues