Skip to main content
Glama
KaungHtunThant

j-hunt-mcp

README.md
# j-hunt-mcp

A local **job-hunting MCP server** built on the official Python SDK (`mcp` / FastMCP).
It runs over **stdio** for Claude Desktop / Claude Code and helps with the full loop:

- **Discover jobs** across pluggable web sources — including **geo / map-region search**.
- **Track applications** through a status lifecycle.
- **Store a profile & resume** the assistant can reference.
- **Tailor resumes / cover letters** via reusable prompts (the client LLM does the writing).

## Architecture

Strict, one-directional layering keeps business logic out of the MCP glue:

```
tools/ • resources.py • prompts.py   (thin MCP adapters — no logic)
            │
            ▼
        services/         (all business logic; constructor-injected deps)
            │
   ┌────────┼─────────────┐
   ▼        ▼             ▼
repositories/  scraping/   geo.py        (SQLite • job sources • Google Maps)
   │
   ▼
models/   (pure Pydantic domain types, reused by every layer)
```

| Layer | Location | Responsibility |
|-------|----------|----------------|
| Models | `src/jhunt_mcp/models/` | Pure Pydantic domain types (one file per domain) |
| Repositories | `src/jhunt_mcp/repositories/` | SQLite persistence; map rows ↔ models (SQLAlchemy tables kept separate) |
| Services | `src/jhunt_mcp/services/` | Business logic (search, tracking, profile, geo) |
| Scraping | `src/jhunt_mcp/scraping/` | Pluggable `JobSource` registry + sources |
| Tools/Resources/Prompts | `src/jhunt_mcp/tools/`, `resources.py`, `prompts.py` | Thin MCP adapters over services |

## Setup

Requires Python ≥ 3.10 and [`uv`](https://docs.astral.sh/uv/).

```bash
uv sync                      # install deps
cp .env.example .env         # optional: add GOOGLE_MAPS_API_KEY for geo search
uv run pytest                # run the test suite (offline)
```

### Run / develop

```bash
uv run j-hunt-mcp                          # run the stdio server directly
uv run mcp dev src/jhunt_mcp/server.py     # open the MCP Inspector (needs Node/npx)
```

### Register with Claude Desktop

```bash
uv run mcp install src/jhunt_mcp/server.py --name "Job Hunt" \
    -v GOOGLE_MAPS_API_KEY=your_key_here
```

Restart Claude Desktop; the **Job Hunt** server's tools then appear.

## Capabilities

**Tools**

| Tool | Purpose |
|------|---------|
| `search_jobs` | Keyword search; optional `location` + `radius_km` for nearby jobs |
| `search_jobs_in_region` | Search inside a map-selected bounding box (NE/SW corners) |
| `geocode_location` | Resolve a place name → coordinates + formatted address |
| `save_job` / `list_saved_jobs` | Persist and list jobs |
| `log_application` | Record an application (by saved `job_id` or ad-hoc `url`) |
| `update_application_status` | Move an application through its lifecycle (validated) |
| `set_next_action` | Set a follow-up reminder on an application |
| `list_applications` | List applications, optionally filtered by status |
| `get_profile` / `update_profile` / `set_resume` | Manage the stored profile & resume |

**Resources:** `profile://me`, `resume://current`, `jobs://saved`, `applications://{status}` (use `all`).

**Prompts:** `tailor_resume`, `draft_cover_letter`, `application_followup_email`.

### Geo / map-region search

There is no map UI in the server itself — a client passes the *result* of a map
selection as parameters. Three shapes are supported, all requiring `GOOGLE_MAPS_API_KEY`:

- a **place string** + `radius_km` (`search_jobs`),
- a **bounding box** (`search_jobs_in_region`) — what a map rectangle yields,
- (internally) a **center + radius**.

Geocoding results are cached in SQLite to conserve API quota. Without a key, plain
keyword search still works; geo paths return a clear error.

## Job sources & scraping note

> Major boards (LinkedIn, Indeed) actively block scraping and forbid it in their ToS.
> v1 therefore ships sources that expose **public JSON/RSS** and are scraping-tolerant:
> **RemoteOK**, **WeWorkRemotely**, **Hacker News "Who is hiring?"**. Requests are
> rate-limited per host. Add a board by implementing `scraping/base.py:JobSource`.

## Secrets

Never commit credentials. The only secret today is `GOOGLE_MAPS_API_KEY`, read from a
gitignored `.env` via `pydantic-settings`. When authenticated boards are added later,
use the OS keyring for passwords and the SQLite DB for session cookies — never JSON in
the repo. `mcp install ... -v KEY=value` injects env vars without writing them to source.

## Data

The SQLite database lives at `data/jhunt.db` by default (override with `JHUNT_DB_PATH`).
The `data/` directory is gitignored.

TDQS

A3.5/5.0

Scored across 12 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: geocoding, profile management, job searching with two variants, saving jobs, logging applications, tracking status, setting follow-up actions, and updating resume. No overlap or ambiguity.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern in snake_case (e.g., list_applications, save_job, update_application_status). The only minor variant, search_jobs_in_region, still adheres to the pattern with a qualifier.

Tool Count5/5

12 tools cover the core workflows of a job hunt assistant: searching, saving, applying, tracking, profile/resume management, and geolocation. The count is well-scoped without redundancy.

Completeness4/5

The tool surface covers search, save, apply, status updates, follow-up actions, and profile management. Minor gaps exist (no delete operations for saved jobs or applications), but these are non-critical for the domain.

Maintenance

ActivityStale
ResponsivenessNo issues