Skip to main content
Glama
Ispas-Catalin

Romanian Job Search MCP

Romanian Job Search MCP

Cache-first MCP server for Romanian job search. The first provider is Jooble, with a database ledger so Hermes can search locally most of the time and spend Jooble API requests only when asked.

Design

  • search_logged_jobs searches the local DB only. It never calls Jooble.

  • search_and_log_jooble_jobs can spend one Jooble API request, then upserts every returned job.

  • start_job_search_session lets Hermes pass a Jooble key from the agent-owned .env.

  • Raw API keys are not stored in the database. Request logs store only key_id and a fingerprint.

  • mark_job_interest stores interest/application state so the LLM does not need to remember.

  • get_job_detail_schema defines the stable structure Hermes should fill after researching a job page.

  • populate_job_details persists that LLM-filled structured detail record.

Related MCP server: trackly-cli

Key Ownership

Recommended deployment: keep Jooble keys in the Hermes/agent .env, not in the MCP container/appdata. Hermes should read its fallback keys and pass the selected key as api_key when it calls start_job_search_session.

The MCP keeps that raw key only in process memory for the session. The database stores request counts with a key id/fingerprint, not the secret. Named MCP-side keys via JOOBLE_API_KEYS still exist as an optional mode, mainly for standalone testing or an MCP-owned scheduled worker.

MCP Tools

  • start_job_search_session(api_key, request_budget?)

  • quota_status(session_id?, key_id?)

  • search_logged_jobs(keywords?, location?, marked_status?)

  • search_and_log_jooble_jobs(session_id, keywords, location?, radius?, salary?, page?, result_on_page?)

  • get_logged_job(job_id?, source_job_id?, include_raw?)

  • get_job_detail_schema()

  • populate_job_details(job_id, details, extracted_by?)

  • mark_job_interest(job_id, status, notes?)

  • list_marked_jobs(status?)

  • create_saved_search(...)

  • refresh_saved_search(session_id, saved_search_id)

Hermes Usage Pattern

  1. Start a session with a key from the Hermes .env:

    {"api_key": "${JOOBLE_RO_PRIMARY}", "request_budget": 3}

    If the primary key hits quota, Hermes can start a new session with its fallback key.

  2. Search locally first:

    {"keywords": "python backend", "location": "Bucuresti"}
  3. Spend Jooble quota only if local results are missing or stale:

    {
      "session_id": "returned-session-id",
      "keywords": "python backend",
      "location": "Bucuresti",
      "result_on_page": 50
    }
  4. Mark jobs instead of remembering them in the LLM context:

    {"job_id": 1, "status": "interested", "notes": "Remote-friendly Python API role"}
  5. For deeper details, Hermes should inspect the public job page itself, then save a structured extraction:

    {
      "job_id": 1,
      "details": {
        "job_title": "Python Developer",
        "company_name": "Example SRL",
        "company_website": null,
        "location": "Bucuresti",
        "work_mode": "hybrid",
        "employment_type": "full_time",
        "seniority": "mid",
        "contract_type": "CIM",
        "salary_text": null,
        "salary_min": null,
        "salary_max": null,
        "salary_currency": null,
        "description": "Concise role description extracted from the public page.",
        "responsibilities": ["Build APIs", "Maintain integrations"],
        "requirements": ["Python", "REST APIs"],
        "nice_to_have": [],
        "benefits": [],
        "technologies": ["Python", "FastAPI"],
        "languages": ["English"],
        "application_url": "https://ro.jooble.org/jdp/...",
        "source_url": "https://ro.jooble.org/jdp/...",
        "contact": null,
        "evidence": {
          "work_mode": "Short supporting note or snippet"
        },
        "missing_fields": ["salary_text"],
        "confidence": 82
      },
      "extracted_by": "hermes"
    }

    Use get_job_detail_schema when Hermes needs the exact current field contract. Unknown fields are rejected; unknown values should be null or [] and listed in missing_fields.

Local Development

py -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e ".[test]"
.\.venv\Scripts\python.exe -m pytest
.\.venv\Scripts\python.exe -m uvicorn ro_job_search_mcp.main:app --reload

The default Docker Compose MCP endpoint is http://localhost:8012/mcp, and health is http://localhost:8012/health.

Docker Compose / Unraid

  1. Copy .env.example to .env.

  2. Do not put Jooble keys in this .env when Hermes owns them.

  3. On Unraid, set APPDATA_PATH=/mnt/user/appdata/ro-job-search-mcp.

  4. Start:

    docker compose up -d --build

The MCP server listens on host port ${MCP_PORT:-8012} and container port 8000.

The worker service is behind the Docker Compose worker profile because it needs an MCP-side key. In the agent-owned key model, let Hermes call refresh_saved_search instead of running the worker.

Publishing Docker Images

GitHub Actions publishes the Docker image to GitHub Container Registry:

ghcr.io/ispas-catalin/ro-job-search-mcp

Publish by pushing a version tag:

git tag v0.1.0
git push origin v0.1.0

You can also run the Docker workflow manually from the GitHub Actions tab.

Jooble Quota Policy

Default limits are set for a 500-request key:

  • monthly hard limit: 500

  • monthly soft warning: 430

  • daily limit: 16

  • default cache TTL: 12h

Cached searches do not spend quota. Forced refreshes and stale cache refreshes do.

F
license - not found
-
quality - not tested
C
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.

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/Ispas-Catalin/ro-job-search-mcp'

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