Metadata-Version: 2.4
Name: searchsteward-mcp
Version: 0.5.3
Summary: MCP server for SearchSteward: set up a job search by chat, search and triage your matches, track applications, and prep interviews and negotiations from Claude or any MCP client.
Project-URL: Homepage, https://searchsteward.com
Project-URL: Repository, https://github.com/SearchSteward/searchsteward-mcp
Project-URL: Issues, https://github.com/SearchSteward/searchsteward-mcp/issues
Author: SearchSteward
License: MIT
License-File: LICENSE
Keywords: claude,job-search,mcp,searchsteward
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: mcp<2,>=1.2
Provides-Extra: test
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# searchsteward-mcp

An [MCP](https://modelcontextprotocol.io) server that connects [SearchSteward](https://searchsteward.com) to Claude Desktop, Claude Code, and any other MCP client. Set up a job search by chat, search and triage your matches, track every application, and prep interviews and negotiations — from inside Claude.

**Any SearchSteward account can use it — free or paid.** You mint an API key from any plan and it applies *your plan's own limits*, exactly as the web app does: free keys reach the search, tracking, and triage tools; **Radar** unlocks the negotiation playbook and the full-depth match feed. Your key hits the paywall in precisely the same place your account does. (See [Free vs Radar](#free-vs-paid) below.)

---

## Getting started

### 1. Create a SearchSteward account

Sign up at **[searchsteward.com/signup](https://searchsteward.com/signup?utm_source=github&utm_medium=readme&utm_campaign=mcp_launch_2026_11)**. It's free. We send a verification email; verifying is only needed for emailed alerts, not for the MCP.

Then tell it what you're looking for, either way:
- **on the web:** the setup steps right after signup; or
- **from Claude:** skip the web setup, connect the MCP (steps 2-4), and say *"Set up my SearchSteward search: senior data analyst in Denver, hybrid is fine, $120k+."* `set_up_search` shows you the criteria first and saves only after you say yes. It works on a **new** account only and never overwrites a search you already have.

### 2. Make an API key

In SearchSteward: **Settings → the Billing & plan tab → the Integrations section → Connect to Claude** (or go straight to [searchsteward.com/settings?tab=billing](https://searchsteward.com/settings?tab=billing)). The separate *Integrations* tab does not hold this card.

1. Type a key name (optional) and your **current password**, then click **Create Key**.
2. Copy the `ss_pat_…` token. **It is shown once: "Save this token now — you won't see it again."** The card also shows ready-to-paste Claude Code and Claude Desktop setup with your key filled in.
3. You can have **3 active keys** at a time. To revoke one, use the same card; revocation is immediate.

Keys made on the card have `full` scope. See [Key scopes](#key-scopes) for a read-only key.

### 3. Install

**First install [uv](https://docs.astral.sh/uv/getting-started/installation/)** (it provides `uvx`, and fetches a suitable Python for you). One line:

```bash
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
```

```powershell
# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

Then open a **new** terminal so `uvx` is on your PATH, and check it installs. It starts and then waits silently for an MCP client, which is normal: press Ctrl+C to stop. (pip works too if you prefer, with Python 3.10 or newer.)

```bash
# uvx (recommended): runs the server without a manual install
uvx searchsteward-mcp@latest

# or pip
pip install searchsteward-mcp
```

You don't need to start it yourself. Your MCP client starts it (step 4). The commands above are only a check that it installs; press Ctrl+C to stop. The client configs below use the plain `searchsteward-mcp`, which reuses the copy `uvx` cached here; `@latest` only matters when you want a newer release (see [Troubleshooting](#troubleshooting)).

### 4. Connect it to Claude

The server reads your key from the `SEARCHSTEWARD_API_KEY` environment variable, which you set in your client's MCP config. It never reads a key from any other file.

**Claude Code**: the command first, `-e` last:

```bash
claude mcp add searchsteward uvx searchsteward-mcp -e SEARCHSTEWARD_API_KEY=ss_pat_...
claude mcp list
```

> **Why command-first, `-e` last?** Claude Code's `-e/--env` flag is variadic. If it comes *before* the command, it swallows `uvx searchsteward-mcp` as extra env values. The Anthropic docs show an `-e KEY=val -- uvx …` form, but **Windows PowerShell** strips the `--` before it reaches the CLI. Putting the command first and `-e` last works on PowerShell, cmd and bash alike.

**Claude Desktop**: open **Settings → Developer → Edit Config**. That opens `claude_desktop_config.json`:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

Add:

```json
{
  "mcpServers": {
    "searchsteward": {
      "command": "uvx",
      "args": ["searchsteward-mcp"],
      "env": { "SEARCHSTEWARD_API_KEY": "ss_pat_..." }
    }
  }
}
```

If you installed with pip instead, use `"command": "searchsteward-mcp"` and `"args": []` (if Claude Desktop can't find it, use the full path from `where.exe searchsteward-mcp` on Windows (in PowerShell, plain `where` is a different command and prints nothing) or `which searchsteward-mcp` on macOS). Then **quit Claude Desktop completely and reopen it**.

The key sits in that config file, so treat the file like a password.

### 5. First things to try

Start a **new** chat (MCP servers load when a session starts) and ask:

- *"Set up my SearchSteward search: product manager in Austin or remote, $140k+."* (new accounts)
- *"Anything new in my SearchSteward matches today?"*
- *"Why is my feed so thin?"*
- *"Save the first one, and hide the third, it's really a sales role."*
- *"Run a mock interview for my top match."* Or pick the `practice_interview` prompt from your client's prompt menu.

A short example (a new account, abridged):

> **You:** Set up my SearchSteward search: senior data analyst in Denver, hybrid is fine, $120k+.
>
> **Claude:** *(calls `set_up_search`, preview)* Here's what I'd save: **Senior Data Analyst, within 30 miles of Denver, CO (hybrid or remote), $120,000 pay floor: jobs whose pay tops out below it are hidden.** Save it?
>
> **You:** Yes.
>
> **Claude:** *(calls `set_up_search` with `confirm=True`)* Saved. Your first matches are being scored now. *(a minute later, `search_matches`)* You have 23 matches. The top three are…
>
> **You:** Hide the second one, it's really a sales role. And run a mock interview for the first.
>
> **Claude:** *(calls `dismiss_match` with `not_relevant`, then the `practice_interview` prompt)* Done. First question: …

<a name="free-vs-paid"></a>
## Free vs Radar

A key uses **your plan's limits — identical to the web app**; a free account mints a working key. What each tier reaches:

| | Free | Radar (paid) |
|---|---|---|
| Search, read, résumé (`search_matches`, `get_job`, `get_resume`) | ✅ | ✅ |
| Track & triage (`log_application`, `save_match`, `dismiss_match`, …) | ✅ | ✅ |
| Question bank & match-quality review | ✅ | ✅ |
| Set up, read & change search preferences (`set_up_search`, `why_is_my_feed_thin`, `get_preferences`, `update_preferences`) | ✅ | ✅ |
| `check_new_matches` (manual pull) | ✅ | ✅ |
| Full match-feed depth (beyond the free cap) | capped + upgrade hint | ✅ full |
| `get_negotiation_playbook` | ❌ 402 | ✅ |

Your key hits the paywall in exactly the place your account does: on a free key, `get_negotiation_playbook` returns a **402**, and the feed stops at the free cap with an upgrade hint. `check_new_matches` is **free**: it's a pull, you ask and it looks. Radar's **push** alerts (an email the moment a new Strong Match appears) are the paid part.

## Troubleshooting

**`error: missing required argument 'commandOrUrl'`** — the variadic `-e` ate your command, or PowerShell stripped a `--`. Use the command-first form above (`claude mcp add searchsteward uvx searchsteward-mcp -e KEY=…`).

**`Invalid input` from `claude mcp add-json`** — your Claude Code version wants a `type` field. Prefer the plain `claude mcp add` command-first form above instead.

**Tool returns a 401 / "Invalid or revoked API key"** — the key was revoked or mistyped. Mint a fresh key in Settings.

**Tool returns a 402 / "entitlement_denied"** — that capability is Radar-only (e.g. the negotiation playbook). Your key uses your plan's limits, the same as the web app.

**Tool returns a 403 / "This endpoint is not available to API keys"** — expected: API keys can only reach the tools above, nothing else.

**Tool returns a 403 / "This API key's scope does not permit…"** — your key is a `read_no_pii` key and the tool needs résumé/offer access or writes. Mint a `full` key (see [Key scopes](#key-scopes)) if you need it.

**Tool returns a 409 / `criteria_exists`**: `set_up_search` only sets up a **new** account and never overwrites. Change an existing search with `update_preferences`, or in Settings.

**"Maximum 3 active API keys per user"**: revoke an old key on the Connect to Claude card, then create a new one.

**`uvx: command not found`** (or `'uvx' is not recognized`): uv isn't installed, or the terminal was opened before you installed it. Install uv ([step 3](#3-install)) and open a new terminal.

**`spawn uvx ENOENT` in the Claude Desktop logs**: Claude Desktop doesn't see the PATH your terminal does, so it can't find `uvx`. Put the full path in `"command"`:
- macOS: run `which uvx` in Terminal (usually `/Users/<you>/.local/bin/uvx`) and use that, e.g. `"command": "/Users/<you>/.local/bin/uvx"`;
- Windows: run `where.exe uvx` in PowerShell (usually `C:\Users\<you>\.local\bin\uvx.exe`) and use it with doubled backslashes, e.g. `"command": "C:\\Users\\<you>\\.local\\bin\\uvx.exe"`.

Then quit and reopen Claude Desktop completely.

**The server doesn't show up in Claude**:
- quit and reopen Claude Desktop completely (or start a new `claude` session);
- check the config file's JSON syntax;
- check that `uvx` (or `searchsteward-mcp` after a pip install) runs in a terminal;
- read the logs: `mcp.log` and `mcp-server-searchsteward.log` in `~/Library/Logs/Claude` (macOS) or `%APPDATA%\Claude\logs` (Windows).

For Claude Code, `claude mcp list` shows the status. A project-scope server needs a one-time approval the next time you run `claude` in that folder.

**A new release isn't showing up** — `uvx` keeps using the copy it cached the first time and doesn't check for updates on its own. Run `uvx searchsteward-mcp@latest` once (or use `searchsteward-mcp@latest` as the command in your MCP config) to pick up the newest version.

**Don't paste keys into a shell command line** — `-e` values land in your shell history. If you must, revoke and re-mint afterward.

## Privacy: what a key can read and change

A key acts as you, inside the limits above. With a **`full`** key, Claude can:

- **read** your matches and applications, your **résumé text** (`get_resume`), **offer and compensation details** (`get_offer`), and your **search settings**, including your home ZIP, radius, salary floor and target titles (`get_preferences`, `why_is_my_feed_thin`). Stored credentials (e.g. a connected Gmail) are returned **masked**, never in the clear;
- **change** your account through the write tools listed under [Key scopes](#key-scopes). `update_preferences` re-scores your feed.

A key **cannot** mint or revoke keys, reach admin routes, delete your account, or read another user's data. Every route outside the tools above returns 403.

What Claude does with the data is governed by your Claude account's own settings, not by SearchSteward. If a key leaks, revoke it in **Settings → Connect to Claude**; revocation is immediate.

---

## Reference

### Key scopes

A key can be **scoped** to limit what it can reach — useful for a key you'll paste into a shared config, commit near, or hand to a script:

| Scope | Reaches | Good for |
|-------|---------|----------|
| `full` (default) | Every tool below | Your own everyday use |
| `read_no_pii` | Search and read tools, **except** anything that returns personal data; **no writes** | A key you paste somewhere shared — a leak can't read your résumé, offer, location or salary settings, or change your account |

Scope is **set once when the key is minted and can never be widened** — mint a new key to change it. Enforcement is server-side. A `read_no_pii` key gets a **403** on:

- **personal data:** `get_resume`, `get_offer`, `get_preferences`, `why_is_my_feed_thin` (it includes your target titles);
- **every write:** `set_up_search`, `update_preferences`, `log_application`, `track_external_application`, `update_application`, `save_match`, `dismiss_match`, `restore_match`, `save_question`, `submit_match_verdict`, `get_negotiation_playbook`.

The **Settings → Connect to Claude** card mints `full` keys. A `read_no_pii` key is minted through the API (`POST /api/v1/api-keys` with `"scope": "read_no_pii"` and your `current_password`) from a signed-in session.

### Tools

23 tools in six groups, plus one prompt.

**Discover & analyze**
| Tool | What it does |
|------|--------------|
| `search_matches` | Search your job matches (score-ranked; each row carries a `score`). Page size capped at 25. |
| `check_new_matches` | Pull the new matches discovered in the last N hours (default 48), by the app's match band: Strong + Good (default) or Strong only; each row is labelled. Scans the top page only — see the tool's own note. |
| `get_job` | Full detail for one match — score breakdown, ghost-listing signal, description. |
| `get_resume` | Your résumé text, so Claude can reason about fit and tailor it natively. |

**Track**
| Tool | What it does |
|------|--------------|
| `list_applications` | List your tracked applications. |
| `get_application` | Full detail for one application (status, notes, dates + offer if present). |
| `log_application` | Mark a **feed** job as applied (promotes a match to a tracked application). |
| `track_external_application` | Track a job you applied to **elsewhere** (LinkedIn, a recruiter, a company site) — it doesn't need to be in your feed. |
| `update_application` | Change an application's status and/or add a note. |

**Triage**
| Tool | What it does |
|------|--------------|
| `save_match` | Save a feed job to watch later (no application yet). |
| `dismiss_match` | Hide a match (with a reason) — sharpens future scoring. |
| `restore_match` | Undo a dismiss. |

**Prep & negotiate**
| Tool | What it does |
|------|--------------|
| `list_questions` | Your interview/application question bank. |
| `save_question` | Save a drafted answer back to the bank. |
| `get_offer` | Offer/compensation details for an application. |
| `get_negotiation_playbook` | SearchSteward's offer-negotiation playbook (**Radar**; runs an LLM job). |

**Tune your search**
| Tool | What it does |
|------|--------------|
| `why_is_my_feed_thin` | The app's own thin-feed diagnosis: feed and Strong counts, and the one saved filter that narrows your feed most, with how many jobs it excludes. Read-only; needs a `full`-scope key (it includes your target titles). |
| `set_up_search` | Set up a new account's search from one sentence ("senior data analyst in Denver, open to remote, $120k+"). Shows the criteria first and saves only when called again with `confirm=True` after you agree. Never overwrites existing criteria. |
| `get_preferences` | Read the settings that decide which jobs reach your feed — location (ZIP, radius, policy), salary floor, target titles, excluded keywords, thresholds. |
| `update_preferences` | Change those settings and re-rank the feed. ⚠ **Re-scores your whole feed — jobs can disappear as well as appear.** Read `get_preferences` first and send only the keys you're changing. |

**Audit match quality**
| Tool | What it does |
|------|--------------|
| `review_candidates` | Review whether the scorer ranked right — reaches the whole corpus, including roles it never surfaced for you. |
| `submit_match_verdict` | Record ground truth on one job (`should_surface` / `should_not_surface` / `unsure`). Distinct from `dismiss_match`. |
| `review_summary` | Count of the verdicts you've submitted, by verdict. |

**Prompts**
| Prompt | What it does |
|--------|--------------|
| `practice_interview(job_id)` | A mock interview for one of your matches: Claude reads the job, your résumé and your saved questions, asks one question at a time, gives feedback on each answer, and ends with your weak spots. Runs entirely in your Claude; no extra SearchSteward usage. |

## Configuration

| Env var | Required | Default |
|---------|----------|---------|
| `SEARCHSTEWARD_API_KEY` | yes | — |
| `SEARCHSTEWARD_API_BASE` | no | `https://searchsteward.com` |

`SEARCHSTEWARD_API_BASE` must be HTTPS (localhost is exempt for local development) — the server refuses to start otherwise, since the key would otherwise travel in cleartext.

---

## Notes

- Job descriptions returned by `get_job` are untrusted web content — treat them as data, not instructions.
- The write tools are the ones listed under [Key scopes](#key-scopes); everything else only reads.
- `why_is_my_feed_thin` reads the same diagnosis the app's matches page shows, and records the same view event a page load does.

## Development

```bash
pip install -e ".[test]"
pytest
```

Issues and contributions: [github.com/SearchSteward/searchsteward-mcp](https://github.com/SearchSteward/searchsteward-mcp).
