AgentStack
MCP verified MIT Self-run

Jobops

mcp-mohith-das-jobops · by HireBridge

A self-hosted Model Context Protocol server for the full job-search loop — portal scanning, JD evaluation, tailored resume + cover PDFs, outreach drafting, story bank, negotiation brief — all driven from your MCP-aware chat client (Claude Desktop, Cursor, any client that speaks streamable-HTTP MCP).

No reviews yet
0 installs
2 views
0.0% view→install

Install

$ agentstack add mcp-mohith-das-jobops

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Jobops? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

jobops

A self-hosted Model Context Protocol server for the full job-search loop — portal scanning, JD evaluation, tailored resume + cover PDFs, outreach drafting, story bank, negotiation brief — all driven from your MCP-aware chat client (Claude Desktop, Cursor, any client that speaks streamable-HTTP MCP).

The chat is the brain. This server executes the mechanical work and hands every artifact back as an http://localhost:7891/... link.

> Status: early. Works. APIs may still move pre-1.0.


Quickstart

# 1. Scaffold your working directory (cv.md, profile.yml, portals.yml, modes/*.md + SQLite DB)
npx @mohith_das/jobops init

# 2. Open cv.md, config/profile.yml, portals.yml and replace every  placeholder.
#    (Optional: tune modes/*.md — rubric, tailoring rules, outreach tone — your edits win.)

# 3. Rebuild the career_packet from your now-real cv.md
#    (or just re-run `init` — it auto-reseeds when it detects cv.md changed)
npx @mohith_das/jobops reseed

# 4. Confirm everything is wired
npx @mohith_das/jobops doctor

# 5. Boot the server (Chromium auto-installs on first run)
npx @mohith_das/jobops start
#  ▷ jobops listening on http://127.0.0.1:7891

# 6. Get a copy-paste config block for your MCP client
npx @mohith_das/jobops connect

# 7. Paste a job URL or pasted JD into your chat — the chat calls evaluate_job, draws on
#    your rubric + career_packet + tailoring rules, and walks the rest of the workflow.

That's the loop. Everything else (warm-intro finder, story bank, negotiation brief, batch rater, scheduler, …) is wired in but optional.

Editing the career packet — two safe directions

Your career_packet is the source of truth the chat uses to score JDs and draft materials. You can drive edits from either direction, and neither one silently destroys the other — both are explicit:

1. Chat-driven (the packet is your edit surface). Ask the chat to change a tagline, remove a project, tighten a bullet — it calls update_career_packet, which writes a new version and marks the packet user-edited. From then on a plain reseed will refuse to overwrite it (it warns and tells you to pass force or sync first). Section edits are ergonomic — "change my tagline" only re-sends Section 2, not the whole packet:

update_career_packet section="2" section_content='- **Builder PM** — "ships product with engineering teeth"'

For surgical, item-level changes there's edit_packet_item / remove_packet_item — address a section (projects, skills, taglines, or a number) and one item (by index or a matching substring) to change or drop a single bullet/project/skill/tagline without touching the rest. Every change still versions the packet, and edit_packet_item runs the visa-leakage scan on the new text. remove_packet_item echoes exactly what it removed. Since every change is a version, nothing is truly lostrestore_packet_version lists history and brings any prior version back (restore is itself reversible).

2. File-driven (cv.md / profile.yml are the source). Edit cv.md / config/profile.yml, then reseed to rebuild the packet from them:

npx @mohith_das/jobops reseed            # safe: refuses if the packet has chat edits not in cv.md
npx @mohith_das/jobops reseed --force    # rebuild from cv.md anyway (drops chat edits)

Bringing the two back in sync. When you've been editing in chat and want cv.md to catch up, run sync_packet_to_cv — it writes the packet back into cv.md + profile.yml so the source files reflect your chat edits (and a later reseed reproduces them instead of clobbering them). So the two directions are symmetric: reseed (cv.md → packet) and sync-back (packet → cv.md), both explicit, neither automatic.

reseed writes a NEW active version (previous demoted, history kept). doctor reports when the packet is chat-edited (expected — not a nag) vs when cv.md changed under a file-driven packet. Standing policy with no CV/profile field (naming conventions, rendering rules, custom guardrails) lives in modes/career_packet.md Section 9, which reseed always preserves.

> Operator's guide / project memory: [docs/PROJECT_MEMORY.md](docs/PROJECT_MEMORY.md) > is a single self-contained reference (architecture, every tool, env vars, setup, the > template system, sampling/elicitation/auth, hard rules, troubleshooting). Drop it into your > MCP client's project memory so you can ask "how do I X?" and get answers.


What it does

Two systems merged into one MCP server:

6-block A–F (+G legitimacy) report, archetype detection, ATS-friendly HTML→PDF resume

  • cover generation, story bank, negotiation playbook.
  • Pipeline side — port of a personal Postgres + n8n pipeline ("JSA"): Greenhouse /

Ashby / Lever / Workday pollers + closed-board Playwright scrapers, content-hash dedupe, batch LLM rater with strict-JSON parsing, warm-intro / founder DM drafter, visa signal from DOL OFLC H1B data.

Everything lives in a single Node process with a single SQLite file. No external Postgres, no n8n, no cloud anything. Bring your own LLM key (Gemini free tier by default, DeepSeek optional) if you want the API/batch paths; chat-mode tools work without one.

> Not affiliated with or endorsed by santifer's career-ops. This is an independent > project that ports + adapts the publicly-released MIT-licensed templates and rubric > shape into the MCP transport surface. See the [Attribution](#attribution) section.


Tools (58 — one MCP tools/list call away)

| Group | Tools | |---|---| | Evaluation | evaluate_job, batch_evaluate, get_top_jobs, evaluate_training, evaluate_project | | Materials | generate_materials, render_pdf (PDF / .tex / .docx), get_report | | Tracker | get_tracker, update_status, mark_ready_to_apply, delete_jobs (soft-delete → trash), restore_jobs, list_trashed, purge_jobs (permanent, backup-first) | | Sourcing | scan_portals (Greenhouse + Ashby + Lever + Workday + Amazon + Google + generic Playwright) | | Outreach | find_warm_intros, find_founders, draft_outreach, draft_followup, draft_reply, get_outreach_queue, update_outreach, get_followups_due | | Contacts | add_contacts (upsert from chat), export_contacts (CSV+JSON backup), import_contacts (merge, non-destructive), delete_contacts (soft-delete, recoverable) | | Interview / offer | extract_stories, get_story_bank, negotiation_brief | | Research | deep_research, enrich_company, daily_digest | | Profile + ops | get_career_packet, update_career_packet (whole-doc), edit_packet_item / remove_packet_item (one bullet/project/skill/tagline), restore_packet_version (history + restore), reseed_career_packet (safe by default), sync_packet_to_cv, update_profile (elicitation), cost_estimate, doctor (read-only health) | | Federation | compile_career_packet, get_career_packet_json (public-safe), sync_to_livingcv, generate_embeddings, get_embeddings, broadcast_signal, get_federation_status, update_jobops | | Apply (preview only — never submits) | apply_prefill | | Visa (optional, can be hidden) | visa_signal, import_h1b, import_linkedin | | Scheduler (opt-in cron, off by default) | scheduler_status, scheduler_enable, scheduler_disable |

Six MCP resources carry the editable behaviour — rubric, reportformat, tailoringrules, outreachtone, negotiationplaybook, career_packet — all loaded from modes/*.md and live-reloaded on edit. Tune scoring or tone without touching code.

> Tip: ask the chat to run doctor anytime — it's a read-only health report (same > checks as the npx @mohith_das/jobops doctor CLI command) covering packet ↔ cv.md sync state, > LLM provider/key, sampling + auth posture, active template, modes, visa scoring, and the > public base URL. Handy for "is my server wired right?" without leaving chat.


Trash & recovery (jobs)

Jobs follow the same non-destructive, recoverable, echo-before-delete philosophy as contacts and the career packet. Soft-delete is the default; hard delete is always explicit, confirmed, and backup-first.

  • delete_jobs moves 1..N jobs to the trash (by job_ids and/or by statuses — e.g.

trash everything in skip/discard/sourced). Trashed jobs drop out of the tracker, get_top_jobs, and batch rating but are retained and restorable. It echoes exactly which jobs (title + company) were trashed. This is recoverable — not a hard delete.

  • list_trashed shows what's currently in the trash (title, company, score, prior status,

when trashed) so you can review before restoring or purging.

  • restore_jobs brings trashed jobs back to their prior state.
  • purge_jobs is the only hard delete: it permanently removes trashed jobs (never live

ones). Pass job_ids, or purge_all: true (which requires confirm: true). A timestamped backup of the affected rows is written to the project root before deletion; it echoes exactly what was permanently removed.

In the tracker UI (/): the Status cell is an inline dropdown (edit in place), and each row has a compact Trash button (soft-delete; the row leaves and the summary cards update live). A dedicated [/trash](/trash) page lists trashed jobs where you can Restore an item, Delete permanently one (with a confirm dialog), or Empty trash (a clearly-warned, irreversible hard delete — backup written regardless). UI actions hit the same /api/* endpoints that share the exact core logic the chat tools use, so a job trashed in chat shows up on /trash and vice-versa.


Filtering, search & pagination (tracker)

At 1000+ jobs the tracker doesn't ship every row to the browser — it paginates server-side (SQL WHERE/LIMIT/OFFSET + a COUNT, never in-memory). The dashboard at / has:

  • Filters (combinable): multi-select status, min/max score, company (contains,

with a datalist of present companies), role and seniority, and a show trashed toggle.

  • Search: debounced, case-insensitive substring on title or company.
  • Pagination: page-size selector (25/50/100, default 50), first/prev/next/last + jump-to-page,

and a "N matching · showing X–Y" indicator.

  • Sort: click the Score / Company / Discovered headers (default score ↓).
  • Shareable URLs: every filter/sort/page is in the query string

(/?status=applied&min_score=70&q=engineer&sort=score&page=2) — bookmarkable and survives refresh.

  • The summary cards stay TRUE TOTALS — the whole active pipeline, independent of the current

filter/page. Inline status edits and trashing keep working within a filtered/paged view (the row updates/leaves and counts refresh without losing your place).

The same query powers the get_tracker MCP tool, so chat can ask for slices too — e.g. "show me applied jobs scored over 70 with 'engineer' in the title." get_tracker accepts statuses[], min_score/max_score, company, role_category, seniority, q, sort, dir, limit, offset, and show_trashed, and returns items + total_matching + full-pipeline counts_by_status.


Scoring without an LLM key — IF your client supports MCP sampling

The scoring tools (batch_evaluate, evaluate_job mode="api") can run on your connected client's model via MCP sampling — same rubric, same strict-JSON contract, no separate Gemini/DeepSeek keybut only if the connected client advertises the sampling capability in its initialize handshake.

> ⚠️ Most clients don't (yet) — including Claude Desktop, as of now. Claude Desktop > advertises only its UI extension, never sampling. The transport (stdio vs HTTP) is not > sufficient on its own — it's a per-client capability. So on Claude Desktop and similar > clients, batch/api scoring falls back to the BYO key (JOBOPS_LLM_PROVIDER + key), > which is expected and correct. Check current support at > modelcontextprotocol.io/clients.

  • It engages automatically if (and only if) a sampling-capable client connects — no

configuration needed. The gate checks both the client's advertised sampling capability and that the transport can carry the server→client request (stdio; the stateless HTTP transport can't, so it never even tries). When sampling isn't available, scoring uses the BYO key; if that isn't set either, evaluate_job mode="chat" (the default) still works — your chat scores it directly. Fallback is clean (no hang).

  • Bottom line for Claude Desktop users: set JOBOPS_LLM_PROVIDER + the matching key

for batch_evaluate / evaluate_job mode="api". (Plain mode="chat" needs no key.)

  • Cost. When sampling is used it runs on the client's model, so the cost is **borne by

the client**; cost_estimate records those calls flagged client-borne ($0 server cost).

  • Run the doctor tool to see the live state — it reports whether your current client

advertised sampling or whether you're on the BYO-key path.

  • Set JOBOPS_SAMPLING=false to force the BYO-key path even when sampling is available.

Frictionless profile setup (MCP elicitation)

update_profile uses MCP elicitation (form mode) so your client can collect identity fields + per-archetype taglines through a structured form — no hand-editing YAML. On accept it writes config/profile.yml and reseeds the career packet in one step.

Sensitive inputs (your LinkedIn export path, credentials) use URL-mode elicitation (2025-11-25): the server hands you a one-time local URL where you enter the value directly, so it never passes through the MCP client / chat transcript. import_linkedin uses this when you omit path and your client supports it.

Both are capability- and transport-gated (like sampling, elicitation is a server→client request that needs a stdio connection). Clients without elicitation support — and all HTTP clients — fall back to the file-based + argument paths (update_profile fields=…, edit config/profile.yml, pass import_linkedin path=…), which work exactly as before.


Designed to be made yours

The defaults assume nothing about your location, citizenship, role, or industry. Every behaviour-shaping piece is a markdown file you can rewrite. init copies these into /modes/ so they're yours to edit — the loader reads your project-root copy first and falls back to the bundled default, so you never touch the package install. A re-init never clobbers an edited copy (it warns and keeps your edits); doctor reports which files are user-overridden vs bundled.

| You can change… | By editing… | |---|---| | Scoring dimensions + weights | modes/rubric.md | | 6-block report shape | modes/report_format.md | | Resume/cover tailoring rules | modes/tailoring_rules.md | | Outreach tone + char caps | modes/outreach_tone.md | | Negotiation scripts + framework | modes/negotiation_playbook.md | | Your bullet/project bank | modes/career_packet.md (or via update_career_packet) | | Per-archetype taglines | config/profile.ymltaglines: (auto-fills career-packet Section 2 on reseed) | | Tracked companies + filters | portals.yml | | Identity + target roles | config/profile.yml |

Non-US users / non-sponsorship cases

Visa scoring is fully optional. Set:

export JOBOPS_VISA_SCORING=false

When off:

  • score_total = round(0.6 · resume_fit + 0.4 · taste_fit) (server-side authoritative)
  • The visa_signal, import_h1b, import_linkedin tools are hidden from tools/list
  • score_visa_fit is stripped from get_top_jobs items and the eval-report HTM

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.