Skip to main content
Glama
a7512cs

mcp-server-104

by a7512cs

mcp-server-104

An MCP server for Taiwan's 104 Job Bank (104人力銀行). Lets Claude (or any MCP client) search 104's live job listings directly.

Is this tool right for you?

Your situation

Best tool

Occasionally job hunting on your own

Just open the 104 website

Want to write a one-off crawler to scrape data

A Playwright / cycletls script is enough, no MCP needed

Want Claude to analyze/compare/summarize/automate job listings

This MCP

Related MCP server: job-source-mcp

Installation

Pick one of the following three, depending on which client you use:

A: Clients with quick-command support — one line, config written automatically:

claude mcp add job104 -- npx -y mcp-server-104   # Claude Code
codex mcp add job104 -- npx -y mcp-server-104    # OpenAI Codex CLI(新版才有;舊版走 B 的 TOML)

B: Clients where you paste config manually — paste the config into that client's MCP config file:

Claude Desktop / Cursor / Windsurf (JSON):

{
  "mcpServers": {
    "job104": { "command": "npx", "args": ["-y", "mcp-server-104"] }
  }
}

OpenAI Codex CLI legacy (~/.codex/config.toml):

[mcp_servers.job104]
command = "npx"
args = ["-y", "mcp-server-104"]

A and B do the same thing: tell the client "start this server with npx". The core is npx -y mcp-server-104 everywhere; the only difference is how each client registers it.

⚠️ ChatGPT web/desktop cannot connect to this kind of local (stdio) server — it only supports remote URL-based MCP, and there's no machine of yours on its cloud to run npx.

C: Developers who want to modify the code — after cloning this repo:

npm install && npm run build
claude mcp add job104 -- node /你的路徑/104-mcp-server/dist/index.js

See "Development" below for daily commands and testing strategy.

How data is fetched

104's search API sits behind Cloudflare bot protection. Using curl or Node fetch (even with Referer / User-Agent headers) gets blocked — returning 403 or Cloudflare's "Just a moment..." challenge page.

The key isn't the header, it's the TLS fingerprint. Cloudflare checks the TLS handshake fingerprint (JA3); a normal program's fingerprint clearly isn't a browser and gets blocked outright.

This project uses cycletls to impersonate Chrome's TLS fingerprint, making Cloudflare think the request comes from a real browser → let through. This way no browser is needed (an order of magnitude lighter, faster, and easier to deploy than Playwright / Selenium), and real JSON is obtained over plain HTTP.

cycletls is backed by a Go-based TLS client subprocess, started once when the server launches and shared for the entire session.

What's available now

Tool

Status

Description

search_jobs

✅ Real data

Search jobs by keyword + multiple filters, with pagination

get_job_detail

✅ Real data

Get full details for a single job: complete JD, salary, location, education/experience requirements, skills, language abilities, benefits, industry

get_company_jobs

✅ Real data

List all open positions at a given company (paginated)

search_jobs parameters

Parameter

Required

Description

keyword

Job title keyword, e.g. Rust 工程師

area

Work location name, e.g. 台北市, 新竹 (auto-resolved to 104's official area codes for querying). Same-name multi-location areas (e.g. "信義區" exists in both Taipei and Keelung) are not searched directly; instead an ambiguousArea candidate list is returned for the model to confirm with you

salaryMin

Minimum monthly salary (TWD), e.g. 60000. Listings clearly below this are filtered out; "negotiable" is kept by default

excludeNegotiable

Set true to exclude "negotiable" listings. Default false

excludeFeatured

Set true to exclude 104's paid ad listings (those with featured=true). Default false

jobCategory

Job category name, e.g. 軟體工程師 (auto-resolved to 104's official job class codes for querying)

remote

Remote: full fully remote / partial partially remote / any either

jobType

Employment type: fulltime full-time / parttime part-time

experience

Years of experience required: under-1y / 1-3y / 3-5y / 5-10y / over-10y

page

Page number (20 per page), default 1. Flip further for more results

limit

Max results returned on this page, up to 20, default 5

Implementation details of filter parameters (all derived from observing 104's official site UI requests + verifying against metadata.total):

  1. salaryMin must be sent together with scmin + sctp=M + scstrict=1; without scstrict the salary filter is completely ignored.

  2. "Negotiable" salary value is 0, and 104 keeps these by default (negotiable could be very high). excludeNegotiable removes them.

  3. The salary cap 9,999,999 is 104's "no upper limit" sentinel value, normalized server-side to "N and above". The salary prefix follows the original s10 type (10=negotiable, 30=hourly, 40=daily, 50=monthly, 60=annual) — part-time jobs are mostly hourly, don't read them as monthly.

  4. remoteWork=1 full/2 partial, ro=1 full-time/2 part-time, jobexp=1/3/5/10/99 (mutually exclusive experience bands).

  5. Area/job class use a tree code table + pruning: when a parent node matches (e.g. "新竹縣市"), the parent code is used rather than expanding into a bunch of child codes — expanding too much makes 104 return 400. For areas with the same name in multiple places (e.g. "信義區"), neither union nor search is performed; ambiguousArea is returned for the model to confirm with the user (unioning geographically unrelated places is meaningless). For job classes with multiple matches, union is kept (searching related job classes together is usually what's wanted).

  6. Ad detection: 104 stuffs ads at the top of results (raw field jobType=1), which ignore the keyword (e.g. a nurse search surfaces "COACH 精品銷售"). Each result carries a featured flag marking it, and excludeFeatured=true filters the whole batch out. jobType=2 (paid priority slots) still matches the keyword and is treated as a valid result without the flag. Search listings deliberately omit the full JD (kept lean, to avoid the model mismatching a listing's URL to another entry when summarizing lists); use get_job_detail for full content.

Field naming is consistent across all three tools (all mapped to 104's original field semantics, avoiding same-name-different-meaning):

Concept

search_jobs

get_job_detail

get_company_jobs

Job code (slug, can be fed back to get_job_detail)

jobId

jobId

jobId

Job URL

url

url

url

Area (district level)

area

area

area

Full address (district + street)

location

Years of experience required

experience

experience

Proficient tools/languages (C++, Linux)

skills

skills

Job skills (job-class level, e.g. "軟體工程系統開發")

jobSkills

Company page URL (feed to get_company_jobs)

companyUrl

companyUrl

Whether it's an ad slot (jobType=1)

featured

Update date (the page's "MM/DD更新")

appearDate

appearDate

jobId is always a slug (e.g. 7uqyj), not 104's internal number — only the slug can be fed back to get_job_detail. skills always means "specific technologies". appearDate is uniformly YYYY/MM/DD. Company job listings deliberately omit dates: the company API's raw data only has year-less formats like 8/20, so stale zombie listings always look recently updated (testing found 2025 listings mixed in), silently misleading across year boundaries — if you want a specific listing's date, feed its jobId to get_job_detail for the full version.

get_job_detail parameters

Parameter

Required

Description

jobUrlOrId

Job URL or code, e.g. https://www.104.com.tw/job/7uqyj or 7uqyj (use the url returned by search_jobs)

get_company_jobs parameters

Parameter

Required

Description

companyUrlOrId

Company URL or code, e.g. https://www.104.com.tw/company/1a2x6blghh or 1a2x6blghh

page

Page number (20 per page), default 1

limit

Max results returned on this page, up to 20, default 10

How the three tools chain together:

  • Every result from search_jobs / get_job_detail returns two URLs: url (job) and companyUrl (company).

  • Want the full content of a listing → feed its url to get_job_detail.

  • Want to see "what else this company has open" → feed companyUrl to get_company_jobs (it's a specific company's job list, not a keyword search).

search_jobs ─ url ──────→ get_job_detail
      │                        │
      └─ companyUrl ───────────┴──→ get_company_jobs

104 internal API reference

Main endpoint:

GET https://www.104.com.tw/jobs/search/api/jobs

Required headers: Referer: https://www.104.com.tw/jobs/search/, Accept-Language: zh-TW

Common query parameters (this project currently only uses a subset; the rest are for future expansion):

Parameter

Meaning

Example value

keyword

Keyword

Free text

kwop

Keyword operator

7 (match all)

order

Sort

15 relevance (default) · 16 newest · 13 salary

page / pagesize

Pagination

pagesize recommended 20

area

Area code (comma-separated)

Look up Area.json (see below)

jobcat

Job class code (comma-separated)

Look up JobCat.json

scmin + scstrict=1

Minimum salary

Integer

remoteWork

Remote

1 fully remote · 2 partial · 1,2 either (verified)

ro

Full/part-time

1 full-time · 2 part-time (verified; some wt values return 400, don't use)

jobexp

Experience

1/3/5/10/99 = under 1y/1-3/3-5/5-10/10y+ (mutually exclusive bands, verified)

edu

Education

4,5,6 bachelor's and above, etc.

Area / job class code tables (hosted on static.104.com.tw, no Cloudflare protection, retrievable with a plain fetch):

https://static.104.com.tw/category-tool/json/Area.json
https://static.104.com.tw/category-tool/json/JobCat.json

Other endpoints:

  • Job detail: GET https://www.104.com.tw/job/ajax/content/{slug} (Referer points to /job/{slug})

  • Company jobs: GET https://www.104.com.tw/api/companies/{code}/jobs?page=1&pageSize=20 (returns list.topJobs + list.normalJobs)

File structure

src/
  index.ts            進入點:建 server、掛 tool、接 stdio、處理關閉
  config.ts           所有設定 / 魔術數字(JA3 指紋、endpoint、節流區間…)
  types.ts            乾淨型別 + normalizeJob / JobDetail / CompanyJob(防腐層)
  query.ts            純函式:組查詢網址、client 端過濾、enum 對照
  slug.ts             從 104 網址取出職缺 slug / 公司碼(types/query 共用)
  codes.ts            地區/職類「名稱→官方代碼」解析(樹狀比對+剪枝,快取代碼表)
  api/
    httpClient.ts     cycletls 單例(TLS 指紋偽裝)
    throttle.ts       禮貌性隨機節流 1.5~3.5s
    job104.ts         104 抓取層:組 URL → 打 API → 重試 → 正規化
  tools/
    searchJobs.ts     search_jobs
    getJobDetail.ts   get_job_detail
    getCompanyJobs.ts get_company_jobs
scripts/
  smoke-test.mjs      手動發 JSON-RPC 驗證,不用開 Claude 也能測
test/
  types.test.mjs      normalize 邏輯(薪資格式、面議、哨兵值…)
  query.test.mjs      組網址 / slug / 公司碼 / 過濾 / enum 對照
  codes.test.mjs      代碼表樹狀比對 + 剪枝

Development

npm run build                 # 編譯 src → dist
npm test                      # 跑單元測試(先 build 再 node --test,零額外依賴)
node scripts/smoke-test.mjs   # 煙霧測試(連真實 104)
npm run inspect               # 開 MCP Inspector GUI 除錯

After changing code, run npm run build, then restart Claude Code (or use /mcp reconnect) for changes to take effect — the client only fetches the tool list once at session startup.

Testing strategy: pure logic (normalize, URL building, filtering) is extracted into types.ts / query.ts and tested with Node's built-in node --test — fast and no network needed, so breakage is caught immediately. Network-touching parts (job104.ts / httpClient.ts) are verified against the real 104 via smoke tests.

⚠️ Disclaimer

  • 104 has no public official API. This project uses unofficial internal endpoints from the web frontend, which may stop working at any time due to 104's changes.

  • Automated access may violate 104's terms of service. This project is for personal, low-frequency, learning purposes only.

  • Do not use it for high-frequency scraping, large-scale crawling, or hosting as a public service — you risk being blocked and face legal exposure.

  • This project has built-in polite rate limiting (random 1.5~3.5 second interval between requests); do not remove or lower it.

  • Users are solely responsible for any consequences of using this project.

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables users to search LinkedIn's public job listings with advanced filters like location, salary, and experience level. It allows MCP-compatible clients to retrieve real-time job opportunities without requiring LinkedIn authentication or API keys.
    1
    2
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Searches 104 job listings with natural-language filters and retrieves full postings via MCP tools.
    3
    22
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables job search on LinkedIn through MCP tools, including keyword and location search, filtering by remote, easy apply, experience level, job type, and date, and retrieving job details.

View all related MCP servers

Related MCP Connectors

  • Search live startup jobs from Claude, Cursor, or ChatGPT via MCP. Free, no account needed.

  • Job search and interview prep MCP. 11 tools, OAuth 2.1, cross-LLM. four-leaf.ai.

  • Search remote and onsite jobs through the public Corvi Careers MCP server.

View all MCP Connectors

Latest Blog Posts

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/a7512cs/104-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server