mcp-job-intel

mcp-job-intel

This MCP server enables job intelligence retrieval and ranking, providing tools to list jobs, fetch details, access corpus statistics, and score candidates against job descriptions using staged retrieval to optimize LLM context usage.

Category
访问服务器

README

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_jobs

The staged retrieval argument

Naive: hand every full description to the model and ask it to rank. Three problems.

  1. Cost — 79k prompt tokens per run, and it grows linearly with the corpus.
  2. Ceiling — past a few hundred postings it exceeds the context window outright. Not slow: impossible.
  3. 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 if could discard. 150 → 91.
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_rankings tool whose parameter schema is schemas.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 → score logic as pipeline.py, and reports fallback_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
GET /health liveness check
GET /jobs, GET /jobs/{id} metadata list / full detail, same no-description invariant as the MCP tool
GET /stats corpus stats
POST /rank run the ranking pipeline (agentic planning loop by default, or the fixed staged pipeline); use_llm: false runs the free deterministic half only
WS /ws/rank same as POST /rank, but streams one event per agent step as it happens, instead of a single response at the end

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.
  • CallToolResult shape: content (blocks), structured_content (typed, wrapped as {"result": ...} for non-object returns), is_error. See pipeline.call().
  • Tool design is API design for a non-human caller. list_jobs and get_job_details are 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_KEY to measure — python eval.py --top 8 runs 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-only20/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 the top you actually run with? python eval.py --top 8 — needs OPENAI_API_KEY, a real model call, so it isn't run in this repo; run it yourself when you have a key.

Your TODOs

  1. prefilter() — add seniority and location gates. Re-run --benchmark, record the number. Done: 150 → 31 survivors, 10.8× reduction vs. naive.
  2. Swap approx_tokens for real tiktoken counting; note how far the ÷4 heuristic was off. Done: heuristic overestimated by 17.4%.
  3. 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 in eval.py --top 8, run it with your own key.
  4. Wire the server into Claude Desktop's MCP config and call it by hand. Config snippet and restart instructions are in docs/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.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选