Cairn is an MCP server that turns any company's careers page into structured, queryable job data for AI agents. Point it at a Greenhouse board, a Lever listing, a Workday portal, or a plain HTML careers page — Cairn scrapes all postings without data loss, normalises them into a consistent schema, and exposes them as MCP tools that Claude (or any MCP-compatible agent) can call directly. No manual copy-pasting, no brittle spreadsheets: just clean job intelligence on demand.
git clone https://github.com/chalshik/cairn.git
cd cairnpython -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activatepip install mcp httpx beautifulsoup4 playwrightplaywright install chromiumclaude mcp add cairn -- python main.pyVerify it appears:
claude mcp list| Tool | Description |
|---|---|
scrape_jobs(url) |
Scrape all job postings from a careers page. Returns a JSON array. |
filter_jobs(jobs, keyword) |
Filter the array by keyword (title / department / location / description). |
get_job_detail(job_url) |
Fetch the full description of a single posting by its URL. |
{
"title": "Senior Backend Engineer",
"department": "Engineering",
"location": "Remote – US",
"url": "https://boards.greenhouse.io/stripe/jobs/12345",
"description": "Full plain-text description...",
"posted_date": "2024-03-01T00:00:00Z"
}| Platform | Method |
|---|---|
| Greenhouse | Public API (boards-api.greenhouse.io) — fast and lossless |
| Lever | Public API (api.lever.co) |
| Workday / custom JS boards | Playwright (headless Chromium) with scroll + pagination |
| Static HTML pages | httpx + BeautifulSoup heuristic extraction |
Show me all open engineering roles at Stripe.
→ Use scrape_jobs("https://boards.greenhouse.io/stripe")
Find remote senior product manager roles at Notion.
→ Use scrape_jobs on Notion's careers page, then filter_jobs with "senior product manager remote"
What does Figma's Staff Designer role require?
→ Use get_job_detail("<job-url from scrape_jobs output>")
Compare backend engineering openings across Vercel and Linear.
→ scrape_jobs for each, then filter_jobs with "backend engineer" on both results
List all jobs in Berlin at Contentful.
→ scrape_jobs("https://www.contentful.com/careers/") then filter_jobs with "berlin"
Inside the container Cairn runs as an HTTP+SSE server on port 8000, so it stays alive and accepts connections like a real service.
docker compose up --buildThe server will be available at http://localhost:8000/sse.
docker build -t cairn .
docker run -p 8000:8000 cairnclaude mcp add --transport sse cairn http://localhost:8000/sseIf you prefer the original stdio mode (process managed by Claude Code):
claude mcp add cairn -- docker run -i --rm -e CAIRN_TRANSPORT=stdio cairncairn/
├── main.py # MCP server — tool registration and entry point
├── scraper.py # Careers page scraper (Greenhouse API, Lever API, httpx, Playwright)
├── parser.py # Normalisation, deduplication, grouping, and filtering
├── Dockerfile
├── requirements.txt
├── .dockerignore
└── README.md
- Python 3.11+
mcp— MCP Python SDKhttpx— async-capable HTTP clientbeautifulsoup4— HTML parsingplaywright— headless browser for JS-rendered pages