Skip to main content
Glama
Neoo-Blue
by Neoo-Blue
README.md
# seerr-mcp

A remote MCP server that lets an AI assistant search your media catalog and add
movies and shows to [Overseerr](https://overseerr.dev) or
[Jellyseerr](https://github.com/fallenbagel/jellyseerr). Ask for a title in
plain language, the assistant finds it, confirms the match, and requests it.
Radarr and Sonarr take it from there.

It runs as a single Cloudflare Worker with no database and no build step. Any
MCP client that speaks Streamable HTTP can connect: Claude Code, Claude Desktop,
a hosted voice agent that answers a phone number, or your own code.

```
MCP client  ->  POST /mcp on your Worker      (bearer gated by MCP_TOKEN)
            ->  Overseerr / Jellyseerr        (/api/v1, X-Api-Key)
            ->  Radarr / Sonarr add
```

## Tools

| Tool | What it does |
|---|---|
| `search_media` | Search the catalog. Returns up to five matches with title, year, type, current availability, and a tmdb_id |
| `request_media` | Add a confirmed movie or show. TV defaults to all seasons. Approved automatically unless you turn that off |
| `check_status` | Report whether a title is already available, downloading, or already requested |

## What you need

* A running Overseerr or Jellyseerr that this Worker can reach over the public
  internet, plus an admin api key from Settings, General, API Key.
* A Cloudflare account. The free plan is enough.
* Node 18 or newer, for `npx wrangler`.

The api key should belong to an admin. Requests then approve themselves, and the
availability lookups that stop duplicate requests are allowed to run.

## Setup

```bash
git clone https://github.com/Neoo-Blue/seerr-mcp.git
cd seerr-mcp
npm install
```

Generate a bearer token. This is the only thing standing between the open
internet and write access to your media stack, so make it a real random value
and keep it out of shell history you share:

```bash
openssl rand -hex 24
```

Set the three secrets. Wrangler prompts for each value and never writes it to
disk:

```bash
npx wrangler secret put MCP_TOKEN      # the value you just generated
npx wrangler secret put SEERR_URL      # e.g. https://seerr.example.com
npx wrangler secret put SEERR_APIKEY   # Seerr Settings, General, API Key
```

Deploy:

```bash
npx wrangler deploy
```

You get back a url like `https://seerr-mcp.your-subdomain.workers.dev`. That is
already public https, so it works as is. To serve it from your own hostname
instead, uncomment the `routes` block in `wrangler.jsonc` and set your zone.

## Check it works

```bash
export SEERR_MCP=https://seerr-mcp.your-subdomain.workers.dev
export MCP_TOKEN=the-token-you-generated

# health, no auth needed
curl $SEERR_MCP/

# list the tools
curl -s -X POST $SEERR_MCP/mcp \
  -H "Authorization: Bearer $MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# search the catalog
curl -s -X POST $SEERR_MCP/mcp \
  -H "Authorization: Bearer $MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search_media","arguments":{"query":"Dune"}}}'
```

A wrong token returns `unauthorized` with a 401. If `tools/list` works but
`search_media` says it could not reach the library, the Worker cannot see your
`SEERR_URL` or the api key is wrong.

## Connect a client

### Claude Code

```bash
claude mcp add --transport http seerr https://seerr-mcp.your-subdomain.workers.dev/mcp \
  --header "Authorization: Bearer YOUR_MCP_TOKEN"
```

Then just ask: "add Dune to the library" or "is The Bear available yet".

### Claude Desktop and other MCP clients

Add a remote MCP server with the url `https://your-worker/mcp` and an
`Authorization: Bearer YOUR_MCP_TOKEN` header. Clients that only speak stdio can
reach it through [mcp-remote](https://www.npmjs.com/package/mcp-remote):

```json
{
  "mcpServers": {
    "seerr": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://seerr-mcp.your-subdomain.workers.dev/mcp",
        "--header", "Authorization: Bearer YOUR_MCP_TOKEN"
      ]
    }
  }
}
```

### A voice agent on a phone number

The same three tools turn this into a phone line people can call to add a movie.
See [docs/voice-agent.md](docs/voice-agent.md) for the wiring and a ready made
concierge prompt.

## Local development

```bash
cp .dev.vars.example .dev.vars   # fill in the three values
npm run dev
```

`.dev.vars` is gitignored. Point your curl calls at `http://localhost:8787`.

## Configuration

| Name | Where | Meaning |
|---|---|---|
| `MCP_TOKEN` | secret | Bearer every client must present on `/mcp`. Required. With none set, the endpoint rejects everything |
| `SEERR_URL` | secret | Base url of your Overseerr or Jellyseerr, no trailing path |
| `SEERR_APIKEY` | secret | Admin api key, sent as `X-Api-Key` |
| `SEERR_AUTOAPPROVE` | var in `wrangler.jsonc` | `1` approves requests immediately. `0` leaves them pending for review in Seerr |

## Notes on behaviour

* Overseerr accepts repeat requests for titles that are already available or
  already downloading, with no error, which quietly creates duplicate rows.
  `request_media` therefore checks real availability first and only posts a
  request when the title is genuinely absent. If it is already there, it says so
  instead of pretending it started a download.
* `search_media` returns the tmdb_id in its text so the model can pass it to
  `request_media`, and the tool description tells the model never to read that
  number out loud. Worth keeping if you edit the prompts.
* The Worker never exposes your Seerr UI. It only calls `/api/v1` server side,
  so `SEERR_URL` and the api key stay on Cloudflare and never reach the client.

## Security

Anyone holding `MCP_TOKEN` can add anything to your library. Treat it like a
password: rotate it with `npx wrangler secret put MCP_TOKEN` if it ever lands in
a chat log, a screenshot, or a repo. Set `SEERR_AUTOAPPROVE` to `0` if you would
rather approve each request by hand.

## License

MIT. See [LICENSE](LICENSE).