mcp-job-intel
Click on "Install 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., "@mcp-job-intelrank jobs for my profile and show top matches"
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.
Agentic Job Intelligence Pipeline (MCP + LLM)
An agent-driven pipeline that uses the Model Context Protocol to orchestrate external
tools for structured data retrieval, with an LLM scoring layer that ranks unstructured
job descriptions against a candidate profile. Context-window pressure is handled with a
staged metadata-first retrieval strategy (pipeline.py), and the same tools are also
exposed to a genuine tool-calling agent with its own planning loop (agent.py) and to a
REST + WebSocket API (api.py). See docs/ for the full write-up.
Run it
pip install -r requirements.txt
python pipeline.py --benchmark # token comparison, zero API calls
python pipeline.py --dry-run # real MCP subprocess handshake, no LLM
export OPENAI_API_KEY=sk-...
python pipeline.py --top 8 # fixed 3-stage pipeline
python agent.py --dry-run # agent tool discovery, no LLM calls
export OPENAI_API_KEY=sk-...
python agent.py --top 8 # tool-calling agent with a planning loop
python eval.py --prefilter-only # stage-1 recall, deterministic half, no key needed
pytest -q # in-process MCP server, no key needed
uvicorn api:app --reload # REST + WebSocket layer, http://localhost:8000
curl localhost:8000/health
curl -X POST localhost:8000/rank -H 'content-type: application/json' -d '{"use_llm": false}'Measured result
150-job corpus, shortlist of 8. prefilter() applies tag/title, seniority (drop senior
when candidate years < 4), and location (candidate's preferred city, alias-normalised, or
remote) gates, which cut the survivor count from 150 to 31:
Strategy | Prompt tokens | vs naive |
A — send all 150 full descriptions | 67,360 | — |
B — metadata-first, then fetch 8 | 13,802 | 4.9× cheaper |
C — prefilter → metadata → fetch 8 | 6,258 | 10.8× cheaper |
Reproduce with python pipeline.py --benchmark. Real numbers from this repo's
data/jobs.json today, not placeholders. Token counts use tiktoken's o200k_base
encoder (what gpt-4o / gpt-4o-mini actually use) — exact, not estimated. The old
chars ÷ 4 heuristic overestimated the naive-strategy cost by 17.4% on this corpus;
benchmark()'s heuristic_vs_real_tokens field reproduces that comparison.
Architecture
MCP SERVER (stdio subprocess) MCP CLIENT / pipeline.py
--------------------------------- ------------------------------------
tool list_jobs -> metadata <---- Stage 0 prefilter() [0 tokens]
tool get_job_details -> full text Stage 1 shortlist [~5k tokens]
tool get_candidate_profile Stage 2 score [~4k tokens]
tool corpus_stats
resource jobs://schema Meter tracks tokens per stage
prompt rank_jobsThe staged retrieval argument
Naive: hand every full description to the model and ask it to rank. Three problems.
Cost — 79k prompt tokens per run, and it grows linearly with the corpus.
Ceiling — past a few hundred postings it exceeds the context window outright. Not slow: impossible.
Quality — long-context recall degrades in the middle of a large prompt, so the ranking gets worse as you add more candidates.
Staged retrieval, cheapest filter first:
Stage | Mechanism | Cost | Why here |
0 | Deterministic tag/title/location filter in Python | free | Never let a model read what |
1 | LLM sees ~55 tokens of metadata per job, picks top 8 | ~5k | High-recall screen. Instructed to over-include, because stage 2 can reject. |
2 | Full descriptions for the 8 survivors only | ~4k | Full fidelity, paid for once, only where it changes the answer. |
The generalisable principle — and the thing to say out loud in an interview — is cascade by cost: order your filters cheapest-first, and set each stage's threshold for recall rather than precision, because a later stage can still reject but nothing can recover what an early stage dropped.
Agentic layer (agent.py)
pipeline.py is a fixed script: prefilter, then always shortlist, then always score.
agent.py hands the model the same MCP tools via OpenAI function calling and lets it plan
its own path — a genuine tool-calling agent, not a hardcoded sequence:
Structured final answer as a tool call. The agent doesn't "answer in JSON and hope" — finishing means calling a synthetic
submit_rankingstool whose parameter schema isschemas.RankingResult. Invalid arguments come back as a validation error the model can read and correct, for a bounded number of retries.Tool failures degrade, they don't crash. Any MCP tool exception becomes a normal
{"error": ...}tool result fed back to the model, so it can route around a bad call instead of taking the whole run down.A model that never converges still returns something. If it exhausts its step/retry budget without valid output, the agent falls back to the same deterministic
prefilter → shortlist → scorelogic aspipeline.py, and reportsfallback_used: true.Transient API errors get their own retry, via
tenacity, separate from the schema-retry loop above — a bad connection and a bad answer are different failure modes.
See docs/CODE_WALKTHROUGH.md for the step-by-step loop.
REST + WebSocket layer (api.py)
A FastAPI service wraps the MCP tools, pipeline.py, and agent.py so they're reachable
over HTTP instead of only as CLI scripts:
Endpoint | What it does |
| liveness check |
| metadata list / full detail, same no-description invariant as the MCP tool |
| corpus stats |
| run the ranking pipeline ( |
| same as |
One MCP stdio session is opened once at startup and shared behind a lock (api.MCPSession)
rather than spawning a subprocess per request — a deliberate simplification over a real
connection pool, documented as such in api.py's module docstring, not oversold as an
actual distributed system. Each request gets a correlation id (request_id), threaded
through logs and every streamed event, so a run can be traced across the async hops.
MCP notes worth knowing cold
Why it exists: N models × M integrations becomes N + M. One protocol, JSON-RPC 2.0 over stdio or Streamable HTTP.
Tools vs resources vs prompts: model-controlled / application-controlled / user-controlled. Getting this trio right is a common interview differentiator.
CallToolResultshape:content(blocks),structured_content(typed, wrapped as{"result": ...}for non-object returns),is_error. Seepipeline.call().Tool design is API design for a non-human caller.
list_jobsandget_job_detailsare split because that split is what enables staged retrieval. Docstrings are the tool description the model reads — vague docstring, wrong tool choice.Batch parameters over scalar ones:
get_job_details(job_ids: list[str])costs one round trip;get_job_detail(job_id: str)costs eight.
Known limits
Shortlist recall (does stage 1 keep the labelled-relevant jobs that survive prefilter?) needs a real
OPENAI_API_KEYto measure —python eval.py --top 8runs it; not run here for cost reasons.The corpus is synthetic. Real postings are messier — HTML, duplicates, stale listings.
No caching across runs, so repeated invocations pay stage 1 again.
Stage 1 recall — measured, not assumed
data/relevance_labels.json has 20 job IDs a human would call relevant to the candidate,
picked with a documented, reproducible rubric (see the file). eval.py checks two things
separately:
prefilter_recall— of the 20 labelled-relevant jobs, how many survive the deterministic prefilter? Free, no API key:python eval.py --prefilter-only→ 20/20, recall 1.0. The label rubric is a strict subset of prefilter's own gates, so this confirms prefilter isn't silently dropping the target role, rather than assuming it.shortlist_recall— of those, how many also survive the LLM shortlist at thetopyou actually run with?python eval.py --top 8— needsOPENAI_API_KEY, a real model call, so it isn't run in this repo; run it yourself when you have a key.
Your TODOs
prefilter()— add seniority and location gates. Re-run--benchmark, record the number.Done: 150 → 31 survivors, 10.8× reduction vs. naive.Swapapprox_tokensfor realtiktokencounting; note how far the ÷4 heuristic was off.Done: heuristic overestimated by 17.4%.Build a 20-job labelled relevance set and measure stage 1 recall.Done for the free half (prefilter_recall= 1.0); the paid half (shortlist_recall) is wired up ineval.py --top 8, run it with your own key.Wire the server into Claude Desktop's MCP config and call it by hand.Config snippet and restart instructions are indocs/OVERVIEW.md— actually registering it happens in your own Claude Desktop app, not something this repo can do for you.
Docs
docs/OVERVIEW.md— what this project is, what problem it solves and why, architecture, Claude Desktop wiring, local testability, known limitations.docs/CODE_WALKTHROUGH.md— every module, function by function.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
MCP server providing access to the Scorecard API to evaluate and optimize LLM systems.
GetJobzi MCP server for job search, application tracking, and career forecasting.
MCP server for AI job search — find jobs, track applications, get alerts. Claude, ChatGPT, Cursor.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/jaideepdnaik/mcp-job-intel'
If you have feedback or need assistance with the MCP directory API, please join our Discord server