Torn MCP Server
by Yoonutz
README.md
# Torn MCP Server
[](LICENSE)
[](https://workers.cloudflare.com)
[](https://modelcontextprotocol.io)
A remote [Model Context Protocol](https://modelcontextprotocol.io) server for the [Torn City](https://www.torn.com) API v2, running on **Cloudflare Workers**. Connect from VS Code (or any MCP client) anywhere — no local install. You supply your Torn API key via the `X-Torn-Api-Key` header; it is never stored, never shown to the model, and never read from the URL (see [Security & privacy](#security--privacy)).
**Tools:** one grouped tool per Torn tag (`torn_user`, `torn_faction`, `torn_torn`, `torn_company`, `torn_market`, `torn_racing`, `torn_forum`, `torn_property`, `torn_key`) covering all 187 endpoints across 234 operations of the spec via an `endpoint` argument — plus **12 intelligence tools** that aggregate endpoints into structured summaries (`analyze_player`, `war_readiness_report`, `find_profitable_items`, …) and `torn_list_endpoints` for discovery.
> [!NOTE]
> Read-only. The Torn API v2 exposes only `GET` endpoints — this server can never modify your account or take in-game actions.
## Install
Live endpoint (hosted instance):
```
https://torn-mcp.yoonutz.workers.dev/mcp
```
**One-click install:**
[](https://insiders.vscode.dev/redirect/mcp/install?name=torn&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Ftorn-mcp.yoonutz.workers.dev%2Fmcp%22%2C%22headers%22%3A%7B%22X-Torn-Api-Key%22%3A%22YOUR_TORN_API_KEY%22%7D%7D)
[](https://insiders.vscode.dev/redirect/mcp/install?name=torn&quality=insiders&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Ftorn-mcp.yoonutz.workers.dev%2Fmcp%22%2C%22headers%22%3A%7B%22X-Torn-Api-Key%22%3A%22YOUR_TORN_API_KEY%22%7D%7D)
[](https://cursor.com/install-mcp?name=torn&config=eyJ1cmwiOiJodHRwczovL3Rvcm4tbWNwLnlvb251dHoud29ya2Vycy5kZXYvbWNwIiwiaGVhZGVycyI6eyJYLVRvcm4tQXBpLUtleSI6IllPVVJfVE9STl9BUElfS0VZIn19)
Click a badge → the client opens with the server pre-filled. It installs with a `YOUR_TORN_API_KEY` placeholder — replace it with your real key ([get one](#get-a-torn-api-key)) in the client's MCP settings after install.
**Setup guides** (these clients have no one-click protocol — badge links to their MCP docs; use the config blocks below):
[](https://docs.claude.com/en/docs/claude-code/mcp)
[](https://modelcontextprotocol.io/quickstart/user)
[](https://docs.windsurf.com/windsurf/cascade/mcp)
[](https://learn.microsoft.com/en-us/visualstudio/ide/mcp-servers)
[](https://docs.continue.dev/customize/deep-dives/mcp)
Or set it up manually — pick your client below. Prefer to self-host? See [Deploy](#deploy).
<details>
<summary><b>Claude Code</b> (CLI)</summary>
```bash
claude mcp add --transport http torn https://torn-mcp.yoonutz.workers.dev/mcp \
--header "X-Torn-Api-Key: YOUR_TORN_API_KEY"
```
Check it: `claude mcp list`. Remove: `claude mcp remove torn`.
</details>
<details>
<summary><b>VS Code</b> (CLI)</summary>
macOS / Linux (bash, zsh):
```bash
code --add-mcp '{"name":"torn","type":"http","url":"https://torn-mcp.yoonutz.workers.dev/mcp","headers":{"X-Torn-Api-Key":"YOUR_TORN_API_KEY"}}'
```
Windows PowerShell — escape the inner quotes with `\"` (the `code.cmd` shim strips plain quotes otherwise):
```powershell
code --add-mcp '{\"name\":\"torn\",\"type\":\"http\",\"url\":\"https://torn-mcp.yoonutz.workers.dev/mcp\",\"headers\":{\"X-Torn-Api-Key\":\"YOUR_TORN_API_KEY\"}}'
```
Insiders: use `code-insiders`. Then open **Copilot Chat → Agent mode → 🛠️ tools** and enable `torn`. (If quoting still fights you, use the manual `mcp.json` below — no escaping needed.)
Manual alternative — user `mcp.json` (Command Palette → _MCP: Open User Configuration_):
```json
{
"servers": {
"torn": {
"type": "http",
"url": "https://torn-mcp.yoonutz.workers.dev/mcp",
"headers": { "X-Torn-Api-Key": "YOUR_TORN_API_KEY" }
}
}
}
```
</details>
<details>
<summary><b>Cursor</b></summary>
Edit `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):
```json
{
"mcpServers": {
"torn": {
"url": "https://torn-mcp.yoonutz.workers.dev/mcp",
"headers": { "X-Torn-Api-Key": "YOUR_TORN_API_KEY" }
}
}
}
```
</details>
<details>
<summary><b>Windsurf</b></summary>
Edit `~/.codeium/windsurf/mcp_config.json`:
```json
{
"mcpServers": {
"torn": {
"serverUrl": "https://torn-mcp.yoonutz.workers.dev/mcp",
"headers": { "X-Torn-Api-Key": "YOUR_TORN_API_KEY" }
}
}
}
```
</details>
<details>
<summary><b>Claude Desktop</b></summary>
Settings → Developer → Edit Config (`claude_desktop_config.json`). Recent versions accept a remote URL directly:
```json
{
"mcpServers": {
"torn": {
"url": "https://torn-mcp.yoonutz.workers.dev/mcp",
"headers": { "X-Torn-Api-Key": "YOUR_TORN_API_KEY" }
}
}
}
```
If your version only supports stdio servers (the connection fails), use the `mcp-remote` bridge instead:
```json
{
"mcpServers": {
"torn": {
"command": "npx",
"args": [
"mcp-remote",
"https://torn-mcp.yoonutz.workers.dev/mcp",
"--header",
"X-Torn-Api-Key:YOUR_TORN_API_KEY"
]
}
}
}
```
Restart Claude Desktop.
</details>
<details>
<summary><b>Visual Studio</b> (2022 17.14+)</summary>
Add a `.mcp.json` to your solution or `%USERPROFILE%\.mcp.json`:
```json
{
"servers": {
"torn": {
"type": "http",
"url": "https://torn-mcp.yoonutz.workers.dev/mcp",
"headers": { "X-Torn-Api-Key": "YOUR_TORN_API_KEY" }
}
}
}
```
</details>
<details>
<summary><b>Continue</b></summary>
Edit `~/.continue/config.yaml`:
```yaml
mcpServers:
- name: torn
type: streamable-http
url: https://torn-mcp.yoonutz.workers.dev/mcp
requestOptions:
headers:
X-Torn-Api-Key: YOUR_TORN_API_KEY
```
</details>
> The **Claude Code** and **VS Code** CLI commands above are tested. The other clients use the same endpoint + `X-Torn-Api-Key` header (verified working), but their config key names and file paths can change between versions — check the client's own MCP docs if a connection fails.
> Security: putting the key inline lands it in config files / shell history. Where the client supports it (e.g. VS Code `${input:...}` prompts), prefer that over a plaintext key.
## Get a Torn API Key
Torn → **Settings → [API Keys](https://www.torn.com/preferences.php#tab=api)** → create a key. A **Limited** or **Minimal** key covers most tools; faction/market selections may need broader scope. Paste it into the `X-Torn-Api-Key` header.
## Tools
One grouped tool per Torn tag, each covering all of that tag's endpoints (table generated from the committed spec by `npm run generate`; counts are checked by the contract test):
<!-- tools:start -->
| Tool | Endpoints | Example `endpoint` values |
| --------------------- | --------- | ------------------------------------------------------------------------------------------- |
| `torn_user` | 69 | `ammo`, `attacks`, `attacksfull`, `bars`, `basic`, `battlestats` |
| `torn_faction` | 42 | `applications`, `attacks`, `attacksfull`, `balance`, `basic`, `chain` |
| `torn_torn` | 38 | `attacklog`, `bank`, `bounties`, `calendar`, `cards`, `cityshops` |
| `torn_company` | 10 | `applications`, `employees`, `news`, `companies`, `profile`, `search` |
| `torn_market` | 9 | `auctionhouselisting`, `auctionhouse`, `bazaar`, `itemmarket`, `properties`, `pointsmarket` |
| `torn_racing` | 8 | `cars`, `carupgrades`, `races`, `race`, `records`, `tracks` |
| `torn_forum` | 6 | `categories`, `posts`, `thread`, `threads`, `lookup`, `timestamp` |
| `torn_property` | 3 | `property`, `lookup`, `timestamp` |
| `torn_key` | 2 | `log`, `info` |
| `torn_list_endpoints` | — | discovery: lists every endpoint per tag |
<!-- tools:end -->
Each tool takes:
- `endpoint` (required) — which data type to fetch (full list per tool, or call `torn_list_endpoints`).
- `id` (optional) — entity id; used when the endpoint is entity-scoped or requires one. Item endpoints (`market/itemmarket`, `market/bazaar`, `torn/items`) also accept an item **name**; `ids` endpoints accept a comma-separated numeric list; `torn/itemdetails` and `torn/itemstats` take item **uids**, never names.
- `params` (optional) — extra query options (`limit`, `from`, `to`, `sort`, `cat`, …). Only the params the called variant accepts are allowed; anything else is rejected with the accepted list, so a typo cannot be silently ignored by Torn.
The Torn key is **not** a tool parameter — it comes from the request header, so it never enters model context or client transcripts.
### Errors and non-JSON responses
- Torn reports every error as **HTTP 200** with a JSON envelope `{ "error": { "code": N, "error": "..." } }`; the OpenAPI spec documents only the 200 success shape. The server detects the envelope on every endpoint and returns it as a tool error `Torn API error N: message`.
- Three endpoints (`user/snapshot`, `faction/snapshot`, `company/snapshot`) answer **CSV**, not JSON (the spec documents `text/csv`). They are marked `[csv]` in the tool description and return `{ "csv": "<text>", "format": "text/csv" }` in the structured channel with the raw CSV as text — no pagination, no enrichment.
- Any other non-JSON body is reported as `Torn API returned a non-JSON response.`
### Intelligence tools
Higher-level tools that aggregate multiple endpoints and return structured summaries instead:
| Tool | Aggregates | Returns |
| ------------------------- | --------------------------------- | -------------------------------------- |
| `analyze_player` | user/profile + personalstats | status, activity, life, social, stats |
| `summarize_player` | user/profile | condensed one-glance snapshot |
| `compare_players` | user/profile ×N | side-by-side + level gap to top |
| `summarize_faction` | faction/basic + members | counts by position and activity |
| `faction_member_activity` | faction/members | online / idle / offline buckets |
| `war_readiness_report` | faction/members | availability-based readiness score |
| `territory_summary` | faction + torn territory | holdings + global sample |
| `crime_analysis` | faction/crimes | status/difficulty counts, success rate |
| `summarize_company` | company/profile + employees | profile + headcount |
| `item_market_analysis` | market/itemmarket + torn/items | depth, price band, market value |
| `market_analysis` | market/itemmarket ×N | items ranked by spread |
| `find_profitable_items` | market/itemmarket + torn/items ×N | items ranked by margin |
> War readiness is availability-based (okay/hospital/traveling, online, on-wall, in-OC) — per-member battlestats are not exposed by the Torn API.
## Security & privacy
- Key supplied via the `X-Torn-Api-Key` header **only** — never a tool parameter, and never read from the URL. A `?key=` query parameter is ignored (the request fails with a "send the header" error), and the query string is not forwarded past the Worker's front door.
- Never stored, never returned in error messages.
- **Logging, precisely:** this code writes no logs. Cloudflare Workers invocation logs (which would record `<Method> <URL>` per request) are switched off in `wrangler.toml` (`[observability.logs] invocation_logs = false`), so a request URL is not persisted even if a misconfigured client puts a key in it. What this repository cannot control: Cloudflare's own edge/analytics data, and the upstream leg — the key reaches `api.torn.com` as the `key` query parameter Torn documents, which is Torn's log surface, not ours.
- Upstream calls are pinned to `https://api.torn.com` (SSRF guard) with `User-Agent: torn-mcp`.
- Per-key rate limiting (~100 req/min, Torn's cap) via a Durable Object — returns a clear error instead of hammering Torn.
### Dependencies
The deployed Worker's runtime dependencies are `@modelcontextprotocol/sdk` and `zod`. Dev/build tooling advisories are pinned to patched versions via `overrides` in `package.json` (`esbuild`, `js-yaml`, `ws`); `npm audit` is part of the release check — see [SECURITY.md](SECURITY.md).
## Scope & roadmap
Covered: all 9 Torn tags — every documented data endpoint (the counts in the table above are derived from the committed spec) as raw JSON, plus 12 intelligence tools and a discovery tool. The spec is re-synced from Torn weekly; see [docs/ROADMAP.md](docs/ROADMAP.md) for what shipped and what is proposed (MCP resources, prompts, richer per-endpoint output typing).
## License
[MIT](LICENSE) — `// @license MIT` headers are included in all source files.
This server cannot be deployed
Maintenance
ActivityActive
ResponsivenessNo issues