jobwise
by HadarAlfasi
README.md
# JobWise
[](https://github.com/HadarAlfasi/jobwise/actions/workflows/ci.yml)
Job search with an agent-native interface. One engine, two surfaces: an
[MCP](https://modelcontextprotocol.io) server any AI agent can drive, and — next
— a web app.
JobWise reads open roles from public ATS APIs, scores them against your resume,
and tracks your applications. The scoring explains itself, and nothing is
scraped.
> **Status:** Phase 0 complete — core engine and MCP server. Web app is next.
> See [ROADMAP.md](ROADMAP.md).
## Why MCP
Job tools are built for a human clicking through a UI. That makes them
invisible to the agents people increasingly work through.
JobWise exposes its engine over the Model Context Protocol, so you can ask your
assistant *"what should I apply to this week?"* and it queries your real
pipeline — no browser, no copy-paste, no separate app to remember to open.
## Quickstart
```bash
git clone https://github.com/HadarAlfasi/jobwise.git
cd jobwise
npm ci
npm run build
npm run demo
```
`npm run demo` scans the bundled boards, filters and ranks them against a sample
resume, and prints the top matches. No configuration, no API keys, nothing to
sign up for.
Then wire it into your agent:
```bash
claude mcp add jobwise -s user -e JOBWISE_DIR="$PWD" -- node "$PWD/packages/mcp/build/index.js"
```
Restart your client and ask it what you should apply to.
## What a session looks like
```
You: What should I apply to this week?
→ search_roles { limit: 4 }
42 matching role(s), showing 4:
71 Payoneer — Team Lead R&D, CLM
Ramat HaSharon, Tel Aviv District, Israel · posted 2026-08-19
https://www.payoneer.com/careers/position/8146...
· Title matches "Team Lead"
· Seniority matches your target
· Mentions 8 of your 31 skills: agile, go, kafka, microservices…
70 Riskified — Software Engineering Manager
Tel Aviv-Yafo, Gush Dan, Israel · posted 2026-08-11
· Title matches "Software Engineer"
· Seniority matches your target
· Mentions 8 of your 31 skills: agile, aws, ci/cd, kubernetes…
You: Anything I should be chasing?
→ needs_attention { silentForDays: 7 }
2 application(s) quiet for 7+ days, longest first:
30d Northwind — Senior Backend Engineer [applied] id=app-northwind
11d Meridian Labs — Platform Team Lead [applied] id=app-meridian
You: Meridian got back to me, I have a phone screen.
→ record_event { applicationId: "app-meridian", type: "interview",
note: "phone screen booked" }
Recorded "interview" on Meridian Labs — Platform Team Lead.
Status is now interview with 2 events on record.
```
*(Roles above are real listings from the bundled boards; the applications are
from the sample tracker in `fixtures/`.)*
## Tools
| Tool | What it answers |
|---|---|
| `search_roles` | "What should I apply to?" — scans, filters, ranks, with reasons |
| `pipeline_status` | "Where does my search stand?" — counts by stage, what moved last |
| `needs_attention` | "What should I chase?" — applications gone quiet, terminal ones excluded |
| `record_event` | "Mark this as rejected" — append-only status change |
Plus a `jobwise://pipeline` resource: every application with its full history.
`record_event` only writes to your local store. **JobWise never submits an
application, sends a message, or contacts an employer.**
## Configuration
Two files, both plain YAML.
**`config/boards.yml`** — the company boards to scan. One entry per board;
adding a company is one line and needs no code change.
```yaml
boards:
- { provider: greenhouse, slug: catonetworks, company: Cato Networks }
- { provider: ashby, slug: finout, company: Finout }
```
It ships seeded with Israeli tech employers verified to run a scannable board.
Replace them with yours — find the slug in a company's careers URL
(`job-boards.greenhouse.io/SLUG`, `jobs.lever.co/SLUG`, `jobs.ashbyhq.com/SLUG`).
**`config/profile.yml`** — your preferences. Copy the example:
```bash
cp config/profile.example.yml config/profile.yml
```
Set your location rules, target titles, and the path to your resume.
`config/profile.yml` and `my-resume.*` are gitignored, so your real job search
never lands in version control.
> One trap the example warns about, because it cost real applications: location
> `alwaysAllow` is evaluated **before** `block`. Put a country there and every
> block rule silently stops applying — you get roles from cities you explicitly
> excluded, with nothing telling you why. Keep it to your home city. A
> regression test pins the behaviour.
## Coverage, honestly
JobWise reads **Greenhouse, Lever and Ashby** through their public JSON APIs.
No key, no scraping, no rate-limit games.
That is not everything. Many large employers — Comeet-hosted startups, and
companies on Workday, iCIMS or a bespoke careers page — expose no scannable API,
and **no tool of this kind can see them.** Support for more ATS platforms is on
the [roadmap](ROADMAP.md); the ones already listed there are all plain HTTP.
The ATS platforms also publish no index of who uses them, so "scan everything"
is a discovery problem rather than a fetching one. See the roadmap for the
approach.
### No scraping, by design
JobWise does not scrape job boards, and will not. It is against their terms,
it breaks constantly, and on a tool aimed at recruiters it is the wrong signal.
Fetching a single public posting a user explicitly pastes is a different act and
is planned; harvesting a board is not.
## How it works
```
Greenhouse ┐
Lever ├──▶ @jobwise/core ──┬──▶ MCP server (agents drive it)
Ashby ┘ fetch · normalize └──▶ web app (next)
your resume ─▶ match · track
```
All the logic lives in `@jobwise/core`. The surfaces are thin adapters that
translate and format — if behaviour appears in one of them, it belongs in the
core.
**Scoring is deterministic and explainable.** Five weighted components — title,
skill overlap, seniority, recency, home location — each contributing points with
a human-readable reason. No model call, no API key, same inputs always give the
same score. That is deliberate: it is the baseline any future LLM-based scoring
has to beat *measurably*, rather than being assumed to be better.
A component with no data **abstains** rather than scoring zero, so a board that
publishes no dates does not quietly rank below a chattier one.
**Application history is append-only.** Status is derived from the event log
rather than stored, so history can never disagree with current state. A
correction is another event, never an edit.
## Development
```bash
npm run build # compile both packages
npm run typecheck # includes test files
npm test # 204 tests, no network
npm run demo # end-to-end against live boards
```
The suite blocks `fetch` entirely (`test/no-network.ts`), so an ATS outage can
never turn the build red. Provider tests run against committed fixtures captured
from real responses — refresh them with `node scripts/capture-fixtures.mjs`.
**stdout is the JSON-RPC channel.** A `console.log` anywhere in `core` or `mcp`
corrupts the MCP stream, and the web app would tolerate the same line happily. A
test scans the source and fails the build on it, and the integration test spawns
the real server and asserts every line it writes to stdout is valid JSON-RPC.
## Credits
The tracker importer reads the file format of
[career-ops](https://github.com/santifer/career-ops) by Santiago Fernández de
Valderrama, so its users can seed JobWise with existing history in one command.
JobWise is separate work and shares no code with it.
## License
MIT © Hadar Alfasi
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing