Skip to content

Repository files navigation

Česky  |  English

MCP-Jobs

MCP server pro tržní inteligenci nad českými pracovními portály — 6 providerů, boolean matching, 3-vrstvý dedup, PostgreSQL perzistence, MCP rozhraní pro AI agenty, Streamlit dashboard, skills catalog (119 IT skills, v2 scoring). Nástupce legacy scrapers — 5.8× rychlejší, config-driven, 231 testů (včetně contract tests), hardening pro produkci.

Co to je: autonomně vyvinutý, iterativně tvrzený systém — od scraping adapteru přes normalizaci, dedup a perzistenci až po AI-facing MCP protokol. Každá vrstva (HTTP → scraping → normalizace → matcher → dedup → persistence → MCP) je oddělena přes stabilní rozhraní, takže přidání nového portálu nevyžaduje přepis centrální pipeline.

Key capabilities

  • 6 portálů — jobs.cz, prace.cz, bazos.cz, jenprace.cz, profesia.cz, volnamista.cz (LMC network, ManpowerGroup, Seznam.cz) — 4 různé anti-bot/layout obrany

  • 8 konfigurovatelných query — python_ai_engineer, ai_llm_engineer, mcp_agentic, data_engineering, devops_ci_cd, prumyslova_automatizace, cnc_cam_automation, reverse_engineering

  • Boolean matching — plná AND/OR/NOT/parens logika s AST parserem + diakritika + LRU cache (8000→8 parsování)

  • 3-vrstvý dedup — URL canonicalizace → fuzzy in-memory → DB-level cross-portal

  • Exclude listy — word-boundary na title, substring na description (české skloňování)

  • Location & salary filter — substring location match, regex salary extraction

  • Rate limiting — 0.5s mezi requesty (ToS compliance, clamp min 0.2s) + pages guard (max 50)

  • Auto-validace boolean výrazů — fail-fast při malformed configu

  • PostgreSQL persistence — URL UNIQUE + ON CONFLICT dedup, run audit, graceful degradation

  • SSRF hardening — domain allowlist (SEC-001), regression testy na dangerous targets

  • Structured logging — per-card skip count, 0-ads alert, žádné silent failures

  • Streamlit dashboard — v2 layout (table-first, on_select row selection, inline detail+status, sidebar KPI), modulární frontend, shared filters, pure SQL metrics, contract tests

  • Skills catalog — 119 IT skills, 27 kategorií, v2 scoring formule (coverage 60pts + log_count 30pts + diversity 10pts), per-skill weights kalibrované pro CZ trh, PostgreSQL JSONB + GIN index

Architektura

src/mcp_jobs/
├── config.py          # UserConfig → PortalConfig → CategoryConfig → QueryConfig
├── models.py          # Ad dataclass (title, url, portal, desc, company, ...)
├── http.py            # HTTP klient s retry, timeout, rate limiting
├── matcher.py         # Boolean AST evaluator + LRU cache + exclude filter + strip_diacritics
├── utils.py           # normalize_url, fuzzy_key — centrální dedup normalizace
├── pipeline.py        # SearchPipeline orchestrator (scrape → filter → results → persist)
├── storage.py         # Unified output (etl_{PROFILE}_{ts}.{json,md,html}) + dedup
├── db.py              # PostgreSQL persistence (schema.sql, upsert_ads batched, run audit)
├── skills_catalog.py  # SkillsCatalog — v2 scoring (coverage + log_count + diversity)
├── report.py          # Report rendering (markdown/html, priority flags, match tags)
├── report_style.py    # Report CSS/HTML šablony
├── providers/         # Portal-specific scrapers
│   ├── base.py        # BaseScraper ABC + detail extraction helper
│   ├── bazos.py       # Bazos.cz s params podporou (hlokalita, humkreis)
│   ├── jobs.py        # Jobs.cz (LMC network)
│   ├── pracecz.py     # Prace.cz (LMC network)
│   ├── jenprace.py    # Jenprace.cz
│   ├── profesia.py    # Profesia.cz (ManpowerGroup layout varianta)
│   └── volnamista.py  # Volnamista.cz (Seznam.cz — anti-bot headers, JSON-LD/NEXT_DATA)
├── server.py          # FastMCP instance + tool/resource/prompt registrace + async job runner
└── cli.py             # CLI entry point (stdio MCP transport)
data/
├── schema.sql         # DDL pro PostgreSQL (ads, pipeline_runs, UNIQUE url, fuzzy sloupce)
├── query_store.json   # Persistence query výstupů
└── _archived_bazos/   # Archivované staré bazos/nyx JSON (v1 data)
output/                # ETL výstupy etl_{PROFILE}_{ts}.{json,md,html}
dashboard/             # Streamlit frontend (v2 — table-first, on_select, sidebar KPI)
├── app.py             # Auth, sidebar gatekeeper + KPI, tab routing
├── filters.py         # Shared sidebar filters + WHERE builder
├── metrics.py         # Pure SQL analytics (12 functions, 0 Streamlit calls)
├── theme.css          # Dark mode production theme
├── components/
│   ├── kpi.py         # KPI cards (main + sidebar compact)
│   ├── gatekeeper.py  # Pipeline status (sidebar compact)
│   └── styling.py     # Shared styling utilities
└── tabs/
    ├── ads.py         # Table-first, on_select row selection, inline detail + status change
    ├── analysis.py    # Skills analytika, top ads, histogram
    └── runs.py        # Pipeline history
scripts/
├── run_etl.py           # Základní ETL runner
├── run_etl_metrics.py   # ETL runner s per-provider timingem
├── run_livetests.py     # Live test runner (structured results do data/)
├── backfill_skills.py   # Batch backfill skills (--force pro prepis v1→v2)
├── db.ps1               # DB operace (restart, psql, logy, dedup check)
└── healthcheck.py       # Healthcheck (dry-mode bez DB)
docker-compose.yml     # PostgreSQL 16 (volume, schema.sql init, healthcheck)

Engineering highlights

Systém je navržen tak, aby selhání byla viditelná a měřitelná, ne tichá:

  • HTTP 200 dokazuje transportní úspěch, ne sémantický. Provider telemetrie sleduje řetězec HTTP success → cards parsed → valid ads → detail fetch → final output. Stránka s 0 kartami na page>1 = end-of-list (INFO), na page 1 = layout change (ERROR) — obojí logované rozlišitelně.

  • Dedup trade-off je observovatelný. Fuzzy dedup je heuristika (title+company+location se normalizují na identitu) — kolize se logují, neschovávají se.

  • Cache ≠ persistence. Detail cache zabraňuje opakovanému HTTP work; PostgreSQL zachovává stav. Runtime job state je ztracen restartem, persisted data přežívají.

  • Bounded concurrency. Background executor izoluje long-running scraping od MCP request timeoutu; job-state model pending → running → done/error.

  • Evidenčně řízená iterace. Regresní testy vznikají kolem objevených failure mode: identifikuj concrete failure mode → patch boundary → regression test → preserve invariant. SSRF, malformed config, dedup kolize, bot-detekce, test-ordering pollution.

Deep reasoning, dedup audit a provider resilience case studies jsou v docs/ (viz níže).

Quick start

\# Instalace  
python -m venv .venv  
.venv\\Scripts\\activate  
pip install -e ".\[dev\]"  
  
\# Konfigurace  
copy config.yaml config\_local.yaml  
\# Uprav portály, query, exclude dle potřeby (viz sekce Konfigurace)  
  
\# PostgreSQL (volitelné, pro perzistenci)  
docker compose up -d  
copy .env.example .env   \# nastav DATABASE\_URL=postgres://mcpjobs:mcpjobs@localhost:5432/mcpjobs  
\# nebo: scripts\\db.ps1 (restart, psql, logy)  
  
\# Testy (231 testů; DB testy běží na mcpjobs_test, jinak se skipnou + 9 contract tests)  
pytest tests/ -v  
  
\# ETL pipeline  
python scripts\\run\_etl.py  
\# Fallback profil (legacy manual query):  
python scripts\\run\_etl.py --config config\_legacy\_manual.yaml

Konfigurace

Všechna personalizace je v config.yaml — žádná osobní data v kódu:

user: "default"  
profile: "AI-NATIVE"  
  
pipeline:  
  max\_workers: 3  
  request\_delay: 0.5  
  
portals:  
  jobs:  
    enabled: true  
    categories:  
      - url: "https://www.jobs.cz/prace/praha/"  
        pages: 20  
  profesia:  
    enabled: true  
    categories:  
      - url: "https://www.profesia.cz/prace/praha/"  
        pages: 10  
  
queries:  
  python\_ai\_engineer:  
    boolean: "(python AND (ai OR ml OR data OR vyvojar OR programator OR inzenyr OR engineer OR developer)) NOT (lektor OR kurz OR skoleni)"  
    exclude: \["agentura", "nabizim", "hledam praci"\]  
    locations: \["praha", "remote", "home office", "domova", "hybrid", "dálkově"\]  
    portals: \["jobs", "pracecz", "jenprace", "profesia", "volnamista"\]

Poznámka: Boolean parser je strict-only — nepodporuje implicitní AND. "python developer" je nutné psát jako "python AND developer". Viz validate\_boolean() pro kontrolu syntaxe.

Pipeline flow

1. \_scrape\_all() → pool \[Ad\] (všechny portály, kategorie, stránky; 0.5s delay mezi requesty)  
2. Pro každý query:  
   ├── Portal filter (ad.portal IN query.portals?)  
   ├── Boolean match (evaluate\_boolean AST — LRU cached, 1× parse per unique query)  
   ├── Exclude filter (has\_exclude\_terms)  
   ├── Location filter (substring)  
   └── Salary filter (number \>= min\_salary)  
3. Dedup (URL canonical → fuzzy in-memory → DB-level cross-portal)  
4. Detail fetch (parallel, bounded) — jen pro kandidáty vyžadující description  
5. Vrací dict\[str, list\[Ad\]\]

Porovnání s legacy scrapers

Metrika Legacy MCP-Jobs Zlepšení
Pipeline time ~210 s ~36 s 5.8× faster
Raw ads scraped ~500 1 073 2× více dat
Per-provider bazos ~80 s ~12 s 6.7× faster
Per-provider jobs ~40 s ~8 s 5.0× faster
Per-provider pracecz ~60 s ~16 s 3.8× faster
Unit tests 0 231 (9 contract)
Matching AND-only, first-match-wins Boolean AST + LRU cache plná logika
Diakritika ruční mapping NFKD normalizace automatická
Error handling except: continue (silent) structured logging + skip count žádné silent failures
Dedup URL only URL + fuzzy + DB cross-portal 3 vrstvy
Detail fetch sequential parallel (bounded)
Output CSV+MD per portal unified JSON/MD/HTML + PostgreSQL dual-write
Protokol žádný MCP (tools, resources, prompts) AI-native
Security none domain allowlist (SSRF) hardening
Subdomény bazos broken (www vs prace) automatická detekce

Výstup

Každý ETL běh generuje unified output/etl\_\{PROFILE\}\_\{ts\}.\{json,md,html\} s per-provider timingem:

\{  
  "pipeline\_elapsed\_s": 36.3,  
  "total\_raw\_scraped": 1073,  
  "total\_final\_matched": 28,  
  "precision\_pct": 2.6,  
  "provider\_detail": \{  
    "bazos": \{ "elapsed\_s": 12.0, "raw\_ads": 463, "errors": 0 \},  
    "jobs": \{ "elapsed\_s": 8.0, "raw\_ads": 210, "errors": 0 \},  
    "pracecz": \{ "elapsed\_s": 16.0, "raw\_ads": 400, "errors": 0 \}  
  \}  
\}

MCP interface

Tools

Tool Description
health\_check Server status and version
search\_jobs\_v2 Boolean search across CZ portals (pages guard: max 50)
search\_from\_config Full pipeline from YAML file path
search\_from\_yaml Full pipeline from inline YAML content
search\_status Status of background search job (async pipeline)
list\_portals Available portals and categories

Resources (L2) & Prompts (L3)

  • L2 Resourcesmcp-jobs://ads/list, mcp-jobs://ads/\{query\_id\}, mcp-jobs://ads/\{query\_id\}/report

  • L3 Promptssearch\_expert (převod přirozeného jazyka → boolean query + YAML snippet)

  • L4 Streaming — plánováno

Skills Catalog

119 IT skills v 27 kategoriích, kalibrovaných pro CZ trh (TIOBE 2026, ABSL Report, ManpowerGroup Q2).

Scoring formule v2

coverage_score = min(60.0, math.log1p(mentioned_weighted * 10.0) * 12.0)
count_score = (math.log2(1 + skill_count) / math.log2(8)) * 30.0
diversity_score = (unique_categories / skill_count * 10.0) if skill_count > 0 else 0.0
score = min(100.0, coverage_score + count_score + diversity_score)

Score distribuce (183 ads z PostgreSQL)

  0 (no skills):   93 ads (51%) — ne-IT queries
  1-39:             2 ads (1%)
  40-59:           19 ads (10%) — dual skill
  60-79:           16 ads (9%) — multi skill
  80+:             53 ads (29%) — rich IT stack

Kalibrované váhy (TOP 10)

Skill Váha Zdroj
Python 1.00 TIOBE #1
AI 0.80 LinkedIn +287%
K8s 0.60 LinkedIn +78%
LLM 0.60 Top trend
CNC 0.60 CZ industrial niche
AWS 0.50 Synergy 31%
Azure 0.50 Synergy 25%
Java 0.50 TIOBE #4
C# 0.50 TIOBE #5
SQL 0.80 TIOBE #8

Dokumentace

Deep reasoning a audit artefakty jsou v docs/, README zůstává interface/overview/proof:

  • docs/edukace\_pridani\_providera\_2026-08-19.md — metodologie přidávání providerů (case studies, dedup audit, F0-F6 checklist)

  • docs/sql\_ontologie\_mechanismy\_2026-08-15.md — SQL ontologie (DDL/DML/DQL, dependencies)

  • docs/postgresql\_zakladni\_prikazy\_2026-08-15.md — PostgreSQL reference (psql meta, dedup vzory)

  • docs/edukace\_faze1\_postgresql\_2026-08-15.md — retrospektiva Fáze 1 (PostgreSQL persistence)

  • docs/l2\_resources.md — MCP L2 Resources dokumentace

Známé limity

  • Rate limiting: 0.5s delay = 36s pipeline. Nutný pro ToS compliance.

  • Double scrape: SearchPipeline.run() vždy rescrapuje interně, nelze injectnout existující pool

  • Description matching: word boundaries VYPNUTY na description (záměrně — české skloňování)

  • Location filter: substring matching (ne geokód), pro "Praha" dostačující

  • Salary filter: heuristická extrakce čísel (různé formáty napříč portály)

  • Fuzzy dedup: heuristika — false positive možná (firma, dvě role, stejný title+location); kolize se logují

  • Runtime job state: in-memory — restart serveru ztrácí in-flight joby

  • Security: threat model není dokumentován — nutno doplnit před veřejnou publikací

  • Domain allowlist: SEC-001 SSRF protection — 6 povolených domén (bazos.cz, jobs.cz, prace.cz, jenprace.cz, profesia.cz, volnamista.cz)

Vývojový stav (2026-08-23)

Oblast Stav
Verze 0.5.0
Testy 231/231 PASS (222 unit + 9 contract), ruff 0
Portály 6/6 (jobs, pracecz, bazos, jenprace, profesia, volnamista)
Dedup 3-vrstvý (URL canonical → fuzzy in-memory → DB cross-portal)
PostgreSQL 183 ads, v2 scoring (coverage+log_count+diversity), backup v1
Skills 119 IT skills, 27 kategorií, v2 formule, per-skill CZ weights
Anti-bot Seznam.cz consent-page fix, end-of-list detekce
MCP L1-L3 (6 tools, resources, prompt), L4 plánováno
Dashboard v2: table-first, on_select, inline detail+status, sidebar KPI, h3 hyperlink
CI Test DB mcpjobs_test krok v ci.yml, pandas<3 pin
Backlog EROI dimenze (L1), employer tiers, location_score, 84 default skill vah

About

MCP & ETL for biggest czech web job servers - jobs.cz, prace.cz, bazos.cz, jenprace.cz, profesia.cz, volnamista.cz

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages