Work Researcher MCP
This server is an MCP application-management and job-search assistant for a single candidate (Andrey Remnev), combining job discovery, CV/application workflow, and browser automation.
Profiles & status: list or switch candidate profiles; check health/config, active profile, CV directory, database stats, providers, API keys, and saved searches.
Job search & discovery: run UK job searches with ranking, deduplication, location/remote filters, salary/date limits, training-ad exclusion, and configurable response sizes.
Job details & enrichment: fetch full job descriptions via browser (especially for parser gaps), detect closed vacancies, re-run requirements checks, and store descriptions.
Stored-job querying: search the local job database by company, title, location, source, or freshness without hitting job boards.
External observations: submit jobs found via a browser (Indeed, LinkedIn, etc.) into the same dedup and ranking store.
CV management: list indexed CVs, rescan the active profile's local CV folder, and get per-CV recommendation scores for a job.
Application workflow: plan and start applications with chosen CV, applicant profile, step lists, and caution flags; refuse duplicate applications across boards.
Application tracking: record statuses (planned, applied, submitted, interview, offer, rejected, etc.), notes, evidence screenshots, and cover letters; list full application history.
Duplicate/blocklist memory: check if a job/URL/company was already applied to; maintain employer/keyword blocklists that hide jobs and block applications.
Cover letters: generate DOCX cover letters ready for upload.
Browser automation: login to job boards (Google auth), open pages, snapshot/interact with forms, click, fill, upload files, press keys, wait, screenshot, evaluate JS, and manage tabs — all in a persistent browser session.
Enables searching and tracking job postings from Indeed through the agent's browser, with support for starting and completing applications on Indeed via automated form filling.
Click on "Deploy 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., "@Work Researcher MCP find junior Data Analyst jobs within 25 miles and flag any I'm not qualified for"
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.
Work Researcher Bot
Серверный агент поиска работы для одного кандидата — Andrey Remnev. Каждый
вечер в 20:00 по времени United Kingdom он обновляет четыре профильных CV из
публичной папки Google Drive, ищет entry-level вакансии, фильтрует агентства и
неподходящую географию, оценивает вакансии через glm-5.3-flash и отправляет
ранжированный HTML-отчёт в Telegram.
Четыре карьерные линии
Data Engineering
Geospatial Data Engineering
Analytics → Data Engineering
Software Engineering → Data Platform
Ищутся junior, trainee, graduate, associate и Level 1 роли без требования значительного подтверждённого опыта. AI/MLOps остаётся последующей надстройкой, а не отдельной пятой линией.
Related MCP server: swissdevjobs
Политика отбора
только прямые работодатели; агентства и recruitment consultancies исключаются;
remote — вся Великобритания;
on-site — Blackpool, Preston и ближайшие населённые пункты;
hybrid — расширенная зона до Manchester и сопоставимых городов;
senior/lead/manager роли, требования 3+ лет и платные training/course ads исключаются;
GLM ранжирует, проверяет обязательные и желательные требования, специальные условия, соответствие выбранному CV и формирует русское резюме; после пакетной проверки отдельный проход сравнивает весь shortlist на единой шкале; модель ищет явные entry-сигналы (обучение, mentoring, приглашение кандидатов, которые соответствуют не всем критериям); неизвестный автор объявления допускается только после подтверждения моделью прямого работодателя;
вакансии с явной пометкой closed/expired или истёкшим указанным closing date исключаются до отчёта, а близкий deadline повышает срочность рассмотрения;
уже доставленные вакансии хранятся в SQLite, но остаются кандидатами, пока актуальны: новые варианты получают преимущество, а сильные повторные заполняют топ и помечаются как ранее показанные;
линия определяется по названию и содержанию вакансии, независимо от поискового запроса; только после этого выбирается соответствующий CV;
Reed учитывает локальную географию, фильтр direct employers и пагинацию; Indeed UK собирается отдельным поддерживаемым адаптером, а Totaljobs имеет публичный reader fallback вместо прежнего server-side timeout. Мягкие квоты в shortlist и топе сохраняют разнообразие источников/линий, но не оставляют пустые места, если других подходящих вариантов нет.
Runtime
Перед GLM бот заново открывает все прошедшие фильтры объявления и цепочки
ссылок подачи (только GET; без отправки заявок). Срок работодателя приоритетнее
срока размещения на площадке; сохраняются источник, время проверки и расхождения.
Проверяются validThrough, даты/время по Europe/London, 404/410 и closed/expired.
Год, отсутствующий в объявлении, принимается текущим и явно помечается в отчёте.
CAPTCHA, недоступная или JS-only страница не считаются подтверждением приёма:
такие варианты помечаются предупреждением, а при сроке закрытия сегодня
удерживаются из отчёта до подтверждения. GLM не может переписать эти факты.
Перед отправкой срок повторно сравнивается с текущим временем.
В каждой Telegram-карточке (включая краткие 6–10) есть строки «Опубликовано» и
«Дедлайн». Для дублей сравниваются даты на найденных площадках и связанных
страницах работодателя: основной становится самая ранняя известная публикация.
Отдельная таблица vacancy_publications хранит доказательства по каждому URL,
поэтому перепубликация и более длинное описание не затирают раннюю дату.
first_seen, дата скачивания и dateModified/Jooble updated публикацией не
считаются. Приблизительные даты и отсутствующий год помечаются; при равенстве
дат без времени не заявляется, какая площадка была первой в течение дня.
Это самая ранняя дата среди найденных источников, а не доказательство
первой публикации во всём интернете. Если старая копия закрыта, она остаётся
ссылкой-источником даты, а основная ссылка ведёт на актуальную копию.
Повторная проверка сохранённого отчёта без GLM и Telegram:
uv run python scripts/verify_report_freshness.py /path/to/nightly-runs/report.json20:00 Europe/London
-> public Google Drive: download + validate exactly four non-geology CVs
-> стабильные запросы + GLM-план дополнительных названий ролей из каждого CV
-> отдельные national, remote UK и Blackpool/Preston/Manchester проходы
across configured providers, включая Indeed, Totaljobs, GOV.UK Find a job
и Civil Service Careers
-> детерминированные фильтры только для явных senior/agency/training/geo отказов
-> advert + application-link freshness verification
-> GLM-5.3-Flash full-description assessment
-> one global comparative rerank of every hard-filtered candidate
-> Telegram HTML header + one detailed vacancy card per message
-> delivery state saved only after Telegram succeedsTelegram-отчёт содержит максимум 10 вакансий: первые 5 приходят полными
карточками с требованиями и анализом CV, позиции 6–10 — сокращёнными карточками
с ключевыми данными и ссылкой.
Точная копия сформированного отчёта, распределение источников и Telegram message IDs
сохраняются в data/nightly-runs/ для последующего аудита качества.
Еженедельное исследование рынка
Каждую пятницу в 20:00 UK time отдельный work-researcher-market.timer
исследует рынок по матрице 4 карьерных направления × 3 уровня: Entry, Middle и
High-paying (£80k+). GLM-5.3-Flash подтверждает релевантность и уровень роли,
а воспроизводимый Python-анализ рассчитывает спрос на технологии, salary
coverage, P25/P50/P75, распределение по квартилям и наиболее частые/дорогие
пары и тройки технологий. Поиск использует 90-дневное rolling window и
расширенную сетку из 90 role-запросов. История сохраняет до 104 недель спроса
и медианной зарплаты для каждой технологии и комбинации.
Последний snapshot и история хранятся вне release в
/var/lib/work-researcher-bot/market. Dashboard публикуется существующим Nginx:
https://devbot.remart.ovh/jobs/.
Локальные команды
uv sync --extra dev
uv run work-researcher doctor
uv run work-researcher sync-drive
uv run work-researcher run-once --dry-run
uv run work-researcher weekly-market
uv run pytest -qСкопируйте config.example.toml в config.toml. Секреты задаются только через
переменные окружения: ZAI_API_KEY, TELEGRAM_BOT_TOKEN,
TELEGRAM_CHAT_ID и необязательные API-ключи job boards.
Доставка
Push в main запускает .github/workflows/deploy.yml: тесты, сборку release,
SSH-загрузку на сервер и атомарное переключение /opt/work-researcher-bot/current.
Systemd timer использует Europe/London, поэтому BST/GMT учитываются
автоматически. Подробности — в SETUP.md.
Отделённый application-контур
Унаследованные MCP-инструменты для браузера и подачи заявок сохранены в
server.py, но nightly-сервис их не импортирует и не запускает. Их можно
использовать вручную/локально через work-researcher serve; автоматическая
подача заявок на сервере не входит в этот runtime.
Основные компоненты
src/work_researcher/bot.py nightly orchestration
src/work_researcher/drive.py public Drive CV snapshot
src/work_researcher/career.py hard eligibility filters
src/work_researcher/llm.py GLM structured assessment
src/work_researcher/market.py weekly market statistics and publication
src/work_researcher/telegram.py Telegram HTML reports
dashboard/index.html static interactive market dashboard
src/work_researcher/providers/ vacancy sources
src/work_researcher/server.py separate local MCP/application surface
deploy/ systemd configuration and activationLicense: MIT.
Available Tools
29 toolsbrowser_clickA
Click element #n → returns the FRESH snapshot (popups become the active tab automatically).
Inputs: n (required), timeout_ms.
browser_closeA
Close the browser (the login profile persists on disk).
Inputs: no named parameters.
browser_evalA
Evaluate JS on the page (extract hidden JSON, scroll, dismiss cookie banners).
Inputs: js (required).
browser_formA
The dominant form with human labels per field (n, tag, type, label, required, options) + likely submit buttons. Use once per application page, then browser_set fields by number.
Inputs: no named parameters.
browser_loginA
Ensure we're signed in on a job board before applying. Opens the site; if signed out, walks 'Continue with Google' and picks the pre-approved account (active profile auth.google_account — the user allows this WITHOUT asking). Returns logged_in; needs_user=true on 2FA/ captcha/consent — then stop and ask the user to finish in the window.
Inputs: url (required).
browser_openA
Open a URL in the persistent application browser; returns the first snapshot. Logins survive between runs. Headed by default so the user can handle 2FA/captcha.
Inputs: url (required), headless.
browser_pressA
Press a page-level key (Enter, Tab, Escape) → fresh snapshot.
Inputs: key (required).
browser_screenshotA
Save a PNG to data/screenshots — pass the path as evidence to record_application.
Inputs: name, full_page.
browser_setA
Set element #n to a value — one verb for every control: inputs → fill; selects → pick option value; checkboxes/radios → true/false → returns fresh snapshot.
Inputs: n (required), value (required).
browser_snapshotA
Look at the page: numbered interactive elements + text. focus: 'inputs'|'buttons'|'links'; filter_text narrows by name (find 'Apply'); text_chars=0 → elements only, 6000 → reading mode. modal_only=true → ONLY the active dialog/wizard (question text + controls, hidden templates excluded) — use for apply wizards, then browser_set/click by number.
Inputs: focus, modal_only, text_chars, filter_text.
browser_tabsA
Tabs: list / select / close. Click-opened popups become active automatically — use this to switch back.
Inputs: index, action.
browser_typeA
Type into #n key-by-key (fires JS handlers); submit=true presses Enter → fresh snapshot. Use for search boxes and chat fields.
Inputs: n (required), text (required), submit.
browser_uploadA
Click upload control #n and send a local file (the CV path from start_application) → fresh snapshot.
Inputs: n (required), file_path (required).
browser_waitB
Wait for time / text to appear ('Application submitted') / text to disappear (spinners) → returns a snapshot.
Inputs: text, seconds, text_gone.
check_appliedA
'Have we already applied here?' — by job_id, exact URL, or fuzzy title+company (catches the same vacancy re-found on another board). ALWAYS call before a manual browser application.
Inputs: url, title, job_id, company.
fetch_job_descriptionA
Fetch FULL job descriptions by opening pages in the browser. Accepts 1-10 job_ids (BATCH - prefer batching to save round-trips). Use for jobs where the parser returned null/short descriptions (common on Totaljobs/Reed). Stores descriptions back and re-runs the requirements check against your CVs. Also detects closed vacancies ("no longer accepting applications").
Inputs: job_ids (required).
get_jobA
Details for selected jobs with adaptive batch sizing. Pass the same context_window/response_profile used for search_jobs. Compact returns up to 5 summaries or 2 full descriptions; balanced 12/5; wide 30/12. Request full descriptions only for plausible finalists. No job is deleted when a supplied job_ids list is truncated; request the remainder later.
Inputs: job_ids (required), context_window, response_profile, include_description.
get_statusA
Health/config snapshot: active candidate, local CV directory, DB stats, providers, API keys and saved searches. Call first in a session.
Inputs: no named parameters.
list_applicationsB
Application history (job, company, status, dates) — the long-term memory of everything already applied to.
Inputs: limit, status.
list_cvsA
CVs indexed from the active profile's local folder. Pass job_id to get per-CV recommendation scores for that job.
Inputs: job_id.
list_stored_jobsA
Search the LOCAL job database by criteria (no board calls). Use INSTEAD of Bash/sqlite: find all jobs from a company, filter by title keyword, location, source board, or freshness. Returns job_ids + titles + requirements status - then get_job / fetch_job_description for detail.
Inputs: limit, query, source, company, +2 more.
make_cover_letterA
Write a cover letter as a DOCX file into CV_collection and return its path — ready for browser_upload as a supporting file. Boards like Totaljobs reject files smaller than 8KB, so the document is padded through metadata if the text alone is too short. Prefer DOCX over PDF here (PDFs from minimal text are usually under the limit).
Inputs: text (required), name.
manage_blocklistA
Employer/recruiter exclusion memory. When the user says 'never apply to ' → action=add, kind=company, value=, reason=. kind=keyword blocks any vacancy whose title/description contains it. action: add | remove | list. Blocked jobs are hidden from search results and start_application refuses them until removed.
Inputs: kind, value, action, reason.
manage_profilesA
List candidate profiles or switch the active candidate.
``switch`` persists [general].active_profile in config.toml and takes
effect immediately. It closes the old candidate's browser first, then
changes the isolated CV folder, database/application history and
browser-login folder. Always list/verify profiles before a
search or application task for a named person.
Inputs: action, profile.
record_applicationA
Update an application: status planned|applying|submitted|interview| offer|rejected|withdrawn|failed; notes; evidence {screenshot: path,…}; cover_letter text.
Inputs: status (required), application_id (required), notes, evidence, +1 more.
search_jobsA
Run a UK job search OR page an earlier one. Fresh search: pass query ('Data Analyst'…) or profile ('data_analytics'/'field_geologist'). Paging: pass search_id (+offset from next_offset), preferably by copying the returned next_page object. Response sizing: pass context_window and auto selects compact (<=80k), balanced (80k-300k), or wide (>=300k). Local Qwen 3.8 27B uses context_window=78000. You may instead pass response_profile explicitly. Optional limit requests a page size within that profile's safety cap (8/20/50). With no sizing signal the default is balanced (12). All remaining results stay stored. Results are ranked, cross-board duplicates merged (sources[]), with memory flags (already_applied, application_status) and location intelligence (work_mode, distance_miles from the active profile's home, location_status ok|mismatch|caution|unknown). PAID TRAINING/COURSE ADS (where you pay them, e.g. Netcom-style 'trainee' course marketing) are excluded automatically — training_offers_skipped shows how many; set include_training=true only if the user explicitly wants courses. location_policy: 'auto' (default), 'uk_wide', 'commute_only'. work_from_home=true restricts to remote.
Inputs: limit, query, offset, profile, +15 more.
start_applicationA
Begin applying to a job. Returns the full plan: URL, apply method + site playbook, chosen CV (+alternatives), applicant profile values, step list, cautions (location mismatch, LinkedIn ToS…). REFUSES to create a second application for a job that already has one — that is the anti-double-apply memory (works across boards via dedup).
Inputs: job_id (required), cv_id, notes.
submit_job_observationsA
Feed jobs found via the harness's own browser (Indeed, CV-Library, LinkedIn, Glassdoor) into the same dedup + ranking store. Each observation: {source, url, title, company, location_text, salary_raw, description, posted_at?}. Returns stored count + duplicates merged.
Inputs: observations (required), task_id, search_id, observation_type.
sync_cvsA
Index CV files manually copied into the active profile's cv_dir. Unchanged files are skipped unless force=true. There is no cloud sync.
Inputs: force.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 29 tools
Each tool maps to a distinct job-search, application, or browser action, and search_jobs/get_job/fetch_job_description are separated by source and purpose. The main boundary that needs care is browser_type vs browser_set, but their descriptions steer usage to different control types.
Domain tools consistently use verb_noun (search_jobs, start_application, record_application), and browser tools share a predictable browser_ prefix. A few noun-style names like browser_tabs, browser_snapshot, and browser_form break the otherwise verb-first pattern.
At 29 tools, this exceeds the 25-tool threshold and feels heavy for a single MCP server. The browser_* primitives are individually distinct, but bundling a full browser automation suite with the job-application domain layer makes the surface larger than necessary.
The tool set covers the full workflow: searching and deduplicating jobs, fetching fuller descriptions, managing blocklists and CVs, checking prior applications, starting and recording applications, and driving the browser through manual forms. There are no obvious dead ends; truncated descriptions have a dedicated fetch tool and form interactions have snapshot/form/set/click/upload primitives.
Maintenance
Related MCP Connectors
Auto-apply to jobs: matches your CV, tailors a fresh CV per posting, and applies for you.
Tailor resumes, score ATS fit, find hiring leads, and send cold outreach, from your agent.
Automate your job search: find jobs, upload a resume, apply automatically, and track applications.
Tailored, graded job applications: a CV, cover letter and form answers built per vacancy.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables AI assistants to evaluate job postings under a UK visa constraint, including sponsor-licence checks with confidence grades, screening for seniority and hard requirements, and role searches that exclude recruitment agencies. It also tracks application history to help avoid duplicate applications.472 npmMIT
- FlicenseNot gradedqualityAmaintenanceEnables an AI agent to search nine job boards across eight countries for salary-transparent tech roles, filter by pay, remote work, tech stack, and seniority, and apply directly while tracking already-applied postings so duplicates never resurface.-
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to discover postings from watched company boards ranked by keyword fit, tailor and fact-check resumes against a source-cited profile, render ATS-readable PDFs, submit applications through the user's own browser, and track replies and follow-ups. All ranking, claim checking and email classification run as plain code, so LLM tokens are spent only on writing.1Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to autonomously search multiple job portals, score candidate-to-job fit with a dynamic 4-tier ATS engine, tailor resumes and cover letters, prepare applications, and export Word documents locally and privately.1MIT