Skip to main content
Glama
a7512cs

mcp-server-104

by a7512cs

mcp-server-104

MCP-сервер для тайваньской биржи труда 104. Позволяет Claude (или любому MCP-клиенту) напрямую искать актуальные вакансии 104.

Подходит ли вам этот инструмент?

Ваша ситуация

Лучший инструмент

Изредка ищете работу сами

Просто откройте сайт 104

Хотите написать разовый скрипт-парсер для сбора данных

Достаточно скрипта на Playwright / cycletls, MCP не нужен

Хотите, чтобы Claude анализировал/сравнивал/собирал/автоматизировал вакансии

Этот MCP

Related MCP server: job-source-mcp

Установка

Выберите один из трёх вариантов, в зависимости от вашего клиента:

A: клиент с быстрыми командами — одна строка, настройка записывается автоматически:

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: клиент с ручной вставкой настроек — вставьте настройки в MCP-конфиг вашего клиента:

Claude Desktop / Cursor / Windsurf (JSON):

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

OpenAI Codex CLI (старая версия, ~/.codex/config.toml):

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

A и B делают одно и то же: говорят клиенту «запусти этот сервер через npx». Ядро везде одно — npx -y mcp-server-104, разница только в том, как каждый клиент его регистрирует.

⚠️ Веб/десктопная версия ChatGPT не подключается к таким локальным (stdio) серверам — она поддерживает только MCP с удалённым URL, а в её облаке нет вашего компьютера, где можно запустить npx.

C: разработчик, хотите менять код — после клонирования этого репозитория:

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

Повседневные команды и стратегия тестирования — см. раздел «Разработка» ниже.

Как получаются данные

Поисковый API 104 скрыт за защитой Cloudflare от ботов. curl или Node fetch (даже с Referer / User-Agent) блокируются — возвращается 403 или страница-челлендж Cloudflare "Just a moment...".

Ключ не в заголовках, а в TLS-отпечатке. Cloudflare проверяет отпечаток TLS-рукопожатия (JA3); отпечаток обычной программы сразу видно, что это не браузер, и запрос блокируется.

Этот проект использует cycletls, чтобы подделать TLS-отпечаток Chrome и заставить Cloudflare думать, что запрос идёт от настоящего браузера → пропускает. Так не нужно открывать браузер (на порядок легче Playwright / Selenium, быстрее, проще в развёртывании) — чистый HTTP, и вы получаете настоящий JSON.

Под капотом cycletls — дочерний процесс TLS-клиента, написанный на Go; запускается один раз при старте сервера и используется всё время.

Что уже есть

Tool

Статус

Описание

search_jobs

✅ реальные данные

Поиск вакансий по ключевому слову + множеству фильтров, с пагинацией

get_job_detail

✅ реальные данные

Полные детали одной вакансии: полное JD, зарплата, место, требования к образованию/опыту, навыки, языки, льготы, отрасль

get_company_jobs

✅ реальные данные

Список всех открытых вакансий компании (с пагинацией)

Параметры search_jobs

Параметр

Обязателен

Описание

keyword

Ключевое слово должности, например Rust 工程師

area

Название региона, например 台北市, 新竹 (автоматически преобразуется в официальный код региона 104 для запроса). Если имя совпадает в нескольких местах (например, «信義區» есть и в Тайбэе, и в Цзилуне), поиск не выполняется напрямую — возвращается список кандидатов ambiguousArea, чтобы модель уточнила у вас

salaryMin

Нижняя граница месячной зарплаты (NTD), например 60000. Вакансии с зарплатой явно ниже отфильтровываются; «по договорённости» по умолчанию сохраняются

excludeNegotiable

true — исключить вакансии «по договорённости». По умолчанию false

excludeFeatured

true — исключить платные рекламные вакансии 104 (те, что с featured=true). По умолчанию false

jobCategory

Название категории должности, например 軟體工程師 (автоматически преобразуется в официальный код категории 104 для запроса)

remote

Удалёнка: full полностью удалённо / partial частично / any любая

jobType

Тип занятости: fulltime полная / parttime частичная

experience

Требуемый стаж: under-1y / 1-3y / 3-5y / 5-10y / over-10y

page

Номер страницы (20 записей на страницу), по умолчанию 1. Хотите больше — листайте дальше

limit

Максимум записей на этой странице, до 20, по умолчанию 5

Нюансы реализации параметров фильтрации (все получены наблюдением за реальными запросами UI сайта 104 + замером metadata.total на практике):

  1. Для salaryMin нужно отправлять scmin + sctp=M + scstrict=1 одновременно; без scstrict фильтр зарплаты полностью игнорируется.

  2. Значение зарплаты «по договорённости» — 0, 104 по умолчанию сохраняет такие (по договорённости может быть очень высокая). excludeNegotiable исключает их.

  3. Верхний предел зарплаты 9,999,999 — это сентинел-значение 104 «без ограничения», на стороне сервера нормализуется в «от N». Префикс зарплаты помечается по исходному типу s10 (10=по договорённости, 30=почасовая, 40=подённая, 50=месячная, 60=годовая) — у частичной занятости чаще почасовая, не читайте как месячную.

  4. remoteWork=1 полностью/2 частично, ro=1 полная/2 частичная занятость, jobexp=1/3/5/10/99 (взаимоисключающие диапазоны стажа).

  5. Регионы/категории используют древовидную таблицу кодов + отсечение ветвей: при попадании в родительский узел (например, «新竹縣市») используется родительский код, без разворачивания в кучу дочерних — слишком много кодов заставит 104 вернуть 400. При одинаковых именах в разных местах (например, «信義區») не делается ни объединение, ни поиск — возвращается ambiguousArea, чтобы модель уточнила у пользователя (объединять географически несвязанные места бессмысленно); при множественных попаданиях категорий объединение сохраняется (искать связанные категории вместе обычно и нужно).

  6. Обнаружение рекламы: 104 вставляет рекламу в начало результатов (исходное поле jobType=1), и она игнорирует ключевое слово (например, в поиске медсестёр вылезает «продажи люксовых товаров COACH»). Каждая запись помечается флагом featured, excludeFeatured=true отфильтровывает их пачкой. jobType=2 (платное приоритетное место) всё же соответствует ключевому слову и считается валидным результатом без пометки. Список поиска намеренно не содержит полного JD (компактнее и чтобы модель при составлении списка не перепутала URL одной записи с другой) — полное содержимое берите через get_job_detail.

Именование полей единообразно во всех трёх инструментах (везде соответствует семантике исходных полей 104, чтобы избежать одинаковых имён для разных вещей):

Понятие

search_jobs

get_job_detail

get_company_jobs

Код вакансии (slug, можно скормить обратно в get_job_detail)

jobId

jobId

jobId

URL вакансии

url

url

url

Регион (уровень района)

area

area

area

Полный адрес (район + улица)

location

Требуемый стаж

experience

experience

Инструменты/языки (C++, Linux)

skills

skills

Профессиональные навыки (уровень категории, например «разработка программных инженерных систем»)

jobSkills

URL страницы компании (скормить в get_company_jobs)

companyUrl

companyUrl

Рекламное ли место (jobType=1)

featured

Дата обновления («MM/DD更新» на странице)

appearDate

appearDate

jobId всегда slug (например, 7uqyj), а не внутренний номер 104 — только slug можно скормить обратно в get_job_detail. skills везде означает «конкретные технологии». appearDate везде в формате YYYY/MM/DD. Даты в вакансиях компании намеренно не возвращаются: исходный API компании даёт только формат без года, вроде 8/20, и давно не обновлявшиеся «зомби-вакансии» всегда выглядят свежими (на практике среди них попадаются вакансии 2025 года) — тихое введение в заблуждение через границу года; хотите дату конкретной записи — скормите её jobId в get_job_detail и получите полную.

Параметры get_job_detail

Параметр

Обязателен

Описание

jobUrlOrId

URL или код вакансии, например https://www.104.com.tw/job/7uqyj или 7uqyj (используйте url из ответа search_jobs)

Параметры get_company_jobs

Параметр

Обязателен

Описание

companyUrlOrId

URL или код компании, например https://www.104.com.tw/company/1a2x6blghh или 1a2x6blghh

page

Номер страницы (20 записей на страницу), по умолчанию 1

limit

Максимум записей на этой странице, до 20, по умолчанию 10

Как связаны три инструмента:

  • search_jobs / get_job_detail в каждой записи возвращают два URL: url (вакансия) и companyUrl (компания).

  • Хотите полное содержимое вакансии → скормите её url в get_job_detail.

  • Хотите узнать «какие ещё вакансии есть у этой компании» → скормите companyUrl в get_company_jobs (это список вакансий конкретной компании, а не поиск по ключевому слову).

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

Справочник внутреннего API 104

Основной endpoint:

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

Обязательные заголовки: Referer: https://www.104.com.tw/jobs/search/, Accept-Language: zh-TW

Часто используемые параметры запроса (проект сейчас использует только часть, остальные — на будущее):

Параметр

Значение

Пример

keyword

Ключевое слово

произвольный текст

kwop

Операция по ключевому слову

7 (все совпадения)

order

Сортировка

15 релевантность (по умолчанию) · 16 новизна · 13 зарплата

page / pagesize

Пагинация

pagesize рекомендуется 20

area

Код региона (через запятую)

см. Area.json (ниже)

jobcat

Код категории (через запятую)

см. JobCat.json

scmin + scstrict=1

Минимальная зарплата

целое число

remoteWork

Удалёнка

1 полностью · 2 частично · 1,2 любая (проверено)

ro

Полная/частичная занятость

1 полная · 2 частичная (проверено; часть значений wt даёт 400, не использовать)

jobexp

Стаж

1/3/5/10/99 = до 1 года/1-3/3-5/5-10/от 10 лет (взаимоисключающие диапазоны, проверено)

edu

Образование

4,5,6 высшее и выше и т.д.

Таблицы кодов регионов/категорий (лежат на static.104.com.tw, без защиты Cloudflare, обычный fetch их достанет):

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

Прочие endpoints:

  • Детали вакансии: GET https://www.104.com.tw/job/ajax/content/{slug} (Referer указывает на /job/{slug})

  • Вакансии компании: GET https://www.104.com.tw/api/companies/{code}/jobs?page=1&pageSize=20 (возвращает list.topJobs + list.normalJobs)

Структура файлов

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      代碼表樹狀比對 + 剪枝

Разработка

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

После изменения кода нужно npm run build, затем перезапустить Claude Code (или reconnect через /mcp) — клиент забирает список инструментов только один раз при старте сессии.

Стратегия тестирования: вся чистая логика (normalize, сборка URL, фильтрация) вынесена в types.ts / query.ts и тестируется встроенным в Node node --test — быстро и без сети, поломку видно сразу. Части, работающие с сетью (job104.ts / httpClient.ts), проверяются smoke-тестами против реального 104.

⚠️ Отказ от ответственности

  • У 104 нет публичного официального API. Проект использует неофициальные внутренние endpoints веб-фронтенда, которые могут в любой момент перестать работать из-за обновлений 104.

  • Автоматизированный доступ может нарушать условия обслуживания 104. Проект предназначен только для личного, низкочастотного, учебного использования.

  • Не используйте для высокочастотного сбора, массового парсинга или развёртывания как публичного сервиса — легко получить блокировку, плюс юридические риски.

  • В проект уже встроено вежливое ограничение частоты (случайный интервал 1.5~3.5 секунды между запросами), не удаляйте и не уменьшайте его.

  • Любые последствия использования этого проекта — на ответственности пользователя.

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