Skip to content

Navigation Menu

Sign in
Sign up

Latest commit

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

job-source-mcp

MCP server that searches job listings from Taiwanese job boards and returns normalized results.

Supported sources:

  • 104 — uses curl_cffi Chrome TLS impersonation; no login required
  • Yourator — uses Playwright headless browser; no login required
  • CakeResume — uses curl_cffi Chrome TLS impersonation; no login required
  • LinkedIn — uses the public guest job-search API via curl_cffi; no login required

Removed source: Meet.jobs was supported until the service permanently shut down on 2026年06月30日 (the site now serves only a closure announcement). The adapter was removed in July 2026.

Installation

python -m venv .venv
source .venv/bin/activate
pip install -e .
playwright install chromium

To also enable Chrome cookie injection for Yourator (improves result quality on accounts with browsing history):

pip install -e ".[cookies]"

Usage with Claude Code

Add to your .mcp.json:

{
 "mcpServers": {
 "job-source": {
 "type": "stdio",
 "command": "/path/to/.venv/bin/job-source-mcp"
 }
 }
}

MCP tools

ping

Health check. Returns {"ok": true}.

session_status

Returns readiness of each source. All sources work without login.

search_jobs

Search job listings across one or more sources.

{
 "keyword": "golang backend",
 "source": "all",
 "page": 1,
 "limit": 20,
 "location": "台北市"
}

source accepts: "all", "104", "yourator", "cakeresume", "linkedin".

Response:

{
 "keyword": "golang backend",
 "source": "all",
 "count": 12,
 "jobs": [
 {
 "source": "104",
 "id": "abc123",
 "title": "Golang Backend Engineer",
 "company": "Acme Corp",
 "location": "台北市信義區",
 "salary": "80,000–120,000",
 "url": "https://www.104.com.tw/job/abc123",
 "posted_at": "20260601",
 "tags": ["Go", "Kafka", "Redis"],
 "description": "..."
 }
 ],
 "rate_limited": [],
 "errors": []
}

rate_limited lists any source that returned HTTP 429 even after backing off (each entry: {"source", "retry_after"}). It exists so a throttled source is never confused with one that simply found nothing: if count is 0 and rate_limited is empty, the search genuinely matched no jobs; if a source appears in rate_limited, its 0 results mean "couldn't fetch", not "no matches". Rate-limited sources also appear in errors with "type": "rate_limited" (other failures use "type": "error").

How it works

104 — Direct API call to https://www.104.com.tw/jobs/search/api/jobs using curl_cffi with impersonate="chrome110". This bypasses Cloudflare bot detection by presenting a real Chrome TLS fingerprint. No login or session cookie required.

Yourator — Playwright launches a headless Chromium browser, navigates to https://www.yourator.co/jobs?term=<keyword>, and intercepts the backend API response (GET /api/v4/jobs?term=<keyword>). The browser's persistent profile is stored in ~/.config/job-source-mcp/profiles/yourator/ so it is reused across runs.

If browser-cookie3 is installed, Yourator also injects cookies from your local Chrome profile, which may improve result relevance for logged-in users.

CakeResume — Fetches the search results page https://www.cakeresume.com/jobs?q=<keyword> (filtered to zh-TW) with curl_cffi using impersonate="chrome110", then parses the embedded Next.js __NEXT_DATA__ JSON blob to extract listings. No login or session cookie required. CakeResume caps each page at ~10 results. Returned url uses the cakeresume.com/jobs/<slug> path; the canonical clickable form is cake.me/companies/<company-slug>/jobs/<slug>.

LinkedIn — Calls the public guest job-search endpoint https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search with curl_cffi (impersonate="chrome110") and parses the returned HTML job cards with BeautifulSoup. No login or session cookie required. Pagination uses an offset (start = (page - 1) * 10); each request yields ~10 cards. When location is omitted it defaults to Taiwan. Guest cards do not include a job description (left empty) and rarely include salary. LinkedIn is the most rate-limit-sensitive source, so requests use an adaptive low-frequency limiter (see Rate limiting).

Configuration

Environment variable Default Description
JOB_SOURCE_DIR ~/.config/job-source-mcp Base directory for Playwright browser profiles

Rate limiting

The 104, Yourator, and CakeResume adapters include a random delay (1.5–4 s) per request to simulate human browsing speed.

LinkedIn uses an adaptive process-wide rate limiter (job_source_mcp/throttle.py). It serializes outbound requests with a minimum spacing (≥ 8 s, plus jitter) so the server never calls this API at high frequency. When a source returns HTTP 429, the limiter escalates the interval and keeps it escalated (doubling, up to 120 s for LinkedIn) and backs off before a single retry — the response to throttling is to call less often, not to retry harder. The interval only relaxes gradually after sustained success.

When searching multiple keywords, call search_jobs sequentially rather than in parallel.

Development

Install with the dev extras, then run the linter and tests:

pip install -e ".[dev]"
ruff check . # lint
pytest # tests (no network access required)

The test suite mocks the network layer (curl_cffi sessions) and drives the adapters' parsers with fixtures, so it runs offline in well under a second. CI (.github/workflows/ci.yml) runs the same ruff check + pytest across Python 3.11–3.13 on every push and pull request.

License

MIT

About

MCP server for searching job listings from 104 and Yourator (Taiwan job boards)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /