Whoishiring MCP Server
Fetches job postings from Hacker News 'Who is hiring?' threads via the Algolia search API.
Scrapes job board pages from Greenhouse to extract structured job details.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Whoishiring MCP Serverscan HN and YC job posts and rank by fit with my resume"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
HN "Who's Hiring" Job Scanner
An automated job discovery and tracking pipeline that scrapes multiple sources, applies a multi-stage filter cascade, and exposes a 24-tool MCP server so Claude Desktop can analyze job fit against a resume — all without requiring an API key.
What It Does
Aggregates job postings from two sources (Hacker News "Who is hiring?" threads via Algolia API, and YC's Work at a Startup board via Playwright headless browser), runs them through a shared multi-stage filter pipeline, maintains a bounded tracking system with backlog promotion, and provides a full MCP (Model Context Protocol) tool interface for an LLM to analyze, rank, and manage a job search pipeline end-to-end.
Related MCP server: JobSpy MCP Server
Architecture
Algolia API ──→ hn_jobs.py ──┐
├──→ filters.py ──→ mcp_server.py ──→ Claude Desktop
Playwright ──→ waas.py ──┘ │ │
Shared filter 24 MCP tools
pipeline 8 JSON stores
Description cacheMulti-Source Ingestion
HN threads: Algolia search API → parallelized comment fetch (20-worker ThreadPoolExecutor) → HTML comment parsing to extract company, location, remote status, emails, job board URLs
WAAS: Playwright headless browser with authenticated session → infinite-scroll scraping → structured field extraction (title, salary, batch, company size, seniority)
Both sources feed into the same
filters.pypipeline — keyword scoring, negative filters, seniority estimation, job type classification, and location detection share a single implementation
Filter Pipeline (filters.py)
Five-stage filter cascade, each with configurable behavior:
Weighted keyword scoring — Three categories scored by relevance (AI tooling: 3, Systems: 2, General AI+SWE: 1). Scoring is per-category, not per-keyword — multiple hits in one category don't stack. All matching uses compiled
\bword-boundary regex, case-insensitive.Negative keyword filter — Detects senior/management titles (staff, principal, director, VP) and high experience thresholds (10+, 15+ years). Matched posts aren't silently dropped — they go to a "Filtered Out" section so nothing is lost.
Seniority estimation — Infers seniority from job title keywords and description experience-year requirements. Maps to a level scale (intern → junior → mid → senior → staff+). Configurable max level — jobs above the threshold are filtered. Unknown seniority is never filtered (benefit of the doubt).
Job type classification — Classifies roles as coding (engineer, developer, SRE, etc.) vs non-coding (PM, designer, sales, recruiter). Engineering management is classified as non-coding. Unknown types are kept.
Location filter — Regex matching against known US and non-US cities/countries. Non-US, non-remote jobs are filtered. No detected location = kept (benefit of the doubt).
Bounded Job Tracking System
Eight JSON files managed atomically (read-modify-write) by the MCP server:
scan_waas ──→ [all new jobs] ──→ backlog_jobs.json (overflow, ranked by score)
│
▼ (top N promoted)
tracked_jobs.json (max N active, default 20)
│
┌──────────────┼──────────────┐
▼ ▼ ▼
applied_jobs dismissed_jobs longshot_jobs
(permanent) (validated) (validated)
│
┌─────┴─────┐
▼ ▼
rejected_jobs accepted_jobs
(outcome) (outcome)Tracked — Bounded active pipeline (max N, configurable). Only the highest-scoring jobs from backlog fill these slots. Every
mark_applied,mark_dismissed, ormark_longshotcall frees a slot and auto-promotes the top backlog entry, returning the promoted job in the response to avoid redundantget_tracked_jobscalls.Backlog — Unbounded overflow, sorted by keyword score. Jobs that pass all filters but don't make the top N cut. Promotion happens automatically when tracked slots open.
Applied — Permanent record. Not validated against WAAS (job might be filled but the application record matters).
Dismissed / Longshot — Validated periodically against WAAS — dead listings are pruned automatically.
Rejected / Accepted — Outcome tracking for applied jobs.
mark_rejectedandmark_acceptedmove jobs from applied to their respective stores. Both can be reverted to tracked viamark_open.
Description Caching
Full job descriptions are cached to disk during scanning (job_descriptions.json). get_job_details reads from cache first, falls back to live HTTP + BeautifulSoup parsing. Cache is auto-pruned — descriptions are removed when jobs leave all active stores (tracked, backlog, applied, longshot, rejected, accepted). Applied/rejected/accepted jobs retain their descriptions permanently.
MCP Server (24 Tools)
Stdio-transport MCP server for Claude Desktop. Claude Desktop acts as the LLM ranker, eliminating the need for an API key. The server owns all state — Claude never writes to JSON files directly, only through tool calls.
Scanning: scan_jobs, scan_waas, scan_all, get_job_details
scan_waasreturns only run metadata (counts, timing, active filters) — not job data. Job data flows through the tracking system.scan_allcombines HN + WAAS with cross-source dedup by company name (case-insensitive, whitespace-stripped). HN takes priority.get_job_detailsserves from disk cache, falls back to live fetch with structured extraction (JSON-LD → Open Graph → meta tags → title tag).
Tracking: get_tracked_jobs, get_applied_jobs, get_dismissed_jobs, get_longshot_jobs, get_rejected_jobs, get_accepted_jobs, update_job_analysis, mark_applied, mark_dismissed, mark_longshot, mark_rejected, mark_accepted, mark_open, swap_role, validate_tracked_jobs, reset_tracking
swap_rolereplaces a tracked job with an alternate role URL from the same company (e.g., a better-fit position discovered throughother_roles), clearing stale analysis.validate_tracked_jobschecks open/dismissed/longshot jobs against WAAS, removes dead listings, and backfills from backlog.mark_applied/mark_dismissed/mark_longshotfree a tracked slot and return the newly promoted backlog job inline, enabling a dismiss-and-analyze loop without re-fetching the full tracked list.mark_rejected/mark_acceptedmove applied jobs to outcome tracking. Both can be reverted viamark_open.
Config: get_resume, get_preferences, get_config, update_config, get_latest_results
get_resumeextracts text from a configured PDF via PyMuPDF.update_configwrites toconfig.yamlfor runtime filter/preference changes.
Daily Workflow (Automated via MCP Prompt)
validate_tracked_jobs— prune dead listings from tracked/dismissed/longshot, backfill from backlogscan_waas— discover new jobs, auto-track top N by score, overflow to backlogget_tracked_jobs→ for each unanalyzed job:get_job_details→update_job_analysis(ormark_dismissed/mark_longshotwith inline backfill loop)Render tracked/applied/dismissed/longshot/rejected/accepted into a React artifact
Deduplication
Within-source:
seen_posts.json(HN comment IDs),seen_waas.json(WAAS job URLs). Auto-pruned after 6 months.Cross-source:
scan_alldeduplicates by company name (case-insensitive, stripped). HN takes priority since it has richer context.First-run backfill: Empty seen files trigger a 3-month HN backfill scan.
Tech Stack
Component | Technology | Purpose |
HN ingestion |
| Parallelized comment fetching (20-worker ThreadPoolExecutor) |
WAAS scraping |
| Headless browser with auth, infinite scroll handling |
HTML parsing |
| Job board scraping (Greenhouse, Lever, Ashby) |
Resume parsing |
| PDF text extraction |
Config |
| YAML config with runtime updates |
LLM integration |
| Resume-based job ranking |
MCP server |
| 20-tool stdio server for Claude Desktop |
Filter pipeline |
| Shared across both sources |
State management | JSON files (atomic read-modify-write) | 8 tracking stores + description cache |
Setup
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp config.yaml.example config.yaml # edit with your preferencesEnvironment Variables (.env, gitignored)
Variable | Purpose |
| Claude API (CLI ranking mode only) |
| YC account for full WAAS access (~30 jobs without) |
| Email delivery (Gmail app password) |
| Directory for tracking JSON files (keeps paths out of git) |
Claude Desktop MCP Config
{
"mcpServers": {
"hn-jobs": {
"type": "stdio",
"command": "wsl",
"args": [
"bash", "-c",
"set -a; source /path/to/.env; set +a; /path/to/.venv/bin/python3 /path/to/mcp_server.py"
]
}
}
}CLI Usage
python3 hn_jobs.py --dry-run --no-rank # HTML preview, no ranking
python3 hn_jobs.py --no-email # Terminal output
python3 hn_jobs.py --dry-run --resume resume.pdf # Rank against resume
python3 hn_jobs.py # Full scan + email deliveryThis server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/nickw409/Whoishiring'
If you have feedback or need assistance with the MCP directory API, please join our Discord server