MCP tools
27 tools over stdio, mapping onto the CLI verbs one for one. 22 of them are free; the 5 that spend a model call say so in the description a client reads, so an agent knows the cost before it calls.
The server is a thin adapter and nothing more: every tool calls the
same runners and the same Storage methods the CLI calls, and holds
no logic of its own (ADR 0008). That is why the two surfaces cannot
disagree about what set_status does.
Start it with karani mcp. Connecting a client has
the configuration for Claude Code, Claude Desktop, Cursor and
anything else that speaks stdio.
Finding work
The two tiers. ingest is free and qualify is not, which is the whole shape of the pipeline (ADR 0001).
ingest
Fetch postings from all sources, pre-filter, and store them.
Network-heavy: hits every configured ATS board and feed. Returns run statistics including per-source outcomes and drop reasons.
Takes no parameters.
sweep
Mark jobs not seen for days (default from settings) as closed.
| Parameter | Type | Default |
|---|---|---|
days | integer | unset |
discover
Probe companies discovered on feed sources for ATS presence (Greenhouse/Lever/Ashby/Workable) and promote hits for future ingest runs. Network-bound; requires Postgres (no-op on the in-memory fallback).
| Parameter | Type | Default |
|---|---|---|
limit | integer | 10 |
qualify
Billed. Spends a model call.
LLM-qualify pending pre-filtered rows against the resume.
Billed per row. agent_mode=True runs the tool-using agent loop
(OpenRouter only) — much more expensive; use on top candidates only.
| Parameter | Type | Default |
|---|---|---|
agent_mode | boolean | false |
concurrency | integer | 3 |
limit | integer | 20 |
model | string | unset |
provider | string | unset |
resume_path | string | unset |
Reviewing
What came in, and what to do about it today. next_actions is the one to reach for first in a conversation — it answers “what should I do on the job hunt today?” without the client having to assemble it from three other calls.
next_actions
Prioritized worklist for the daily loop, bucketed by required action:
- review: qualified/maybe roles awaiting a verdict (fit + freshness ranked)
- to_draft: verdict was
applybut no draft exists yet - to_submit: drafts sitting in
drafting/ready - follow_up: applied >= follow_up_days ago with no response
Call this first each session; it is the pipeline’s answer to “what should happen next”.
| Parameter | Type | Default |
|---|---|---|
follow_up_days | integer | 7 |
review_limit | integer | 10 |
shortlist
Top qualified/maybe roles ranked by fit score, as structured rows.
Excludes roles already reacted to (except later).
| Parameter | Type | Default |
|---|---|---|
limit | integer | 20 |
digest
Render the top qualified roles as text, md, or html.
| Parameter | Type | Default |
|---|---|---|
format | string | md |
limit | integer | 20 |
get_job
Full detail for one job: qualification, gaps, state, and optionally the job description text.
| Parameter | Type | Default |
|---|---|---|
job_id | integer | required |
include_description | boolean | false |
pipeline_stats
DB counts plus the application state-machine funnel.
Takes no parameters.
Building and sending
The pack, then the console that lays it out, then the write-back that records what you actually sent. karani never submits (ADR 0012), so there is deliberately no tool here that would.
draft
Billed. Spends a model call.
Build the full application pack for a job: cover letter + bullets
- answers, an AI-voice humanizer pass, a complete tailored resume,
and artifact uploads to object storage with presigned tweak-and-
submit links. Moves the job to
drafting. Billed (up to three LLM calls; KARANI_HUMANIZE / KARANI_TAILOR_RESUME toggle the extras).
| Parameter | Type | Default |
|---|---|---|
job_id | integer | required |
model | string | unset |
output_path | string | unset |
provider | string | unset |
resume_path | string | unset |
submission_console
Build the submission console: one self-contained HTML file listing every pack waiting to be sent, with that job’s REAL application questions (read from the Greenhouse/Lever/Ashby public APIs), the answers, and presigned document links. Free - no LLM call. karani never submits: open the file, send each application yourself, then call mark_submitted.
| Parameter | Type | Default |
|---|---|---|
limit | integer | 50 |
output_path | string | unset |
refresh_questions | boolean | false |
mark_submitted
Record applications the USER sent (karani never sends them):
status applied, the timestamp, and a snapshot of the artifact
version that went out. This is what arms follow-ups and gives the
funnel its labels. via: console | portal | email | referral.
| Parameter | Type | Default |
|---|---|---|
job_ids | integer[] | required |
notes | string | “ |
via | string | console |
warm_path | boolean | unset |
Tracking
The state machine. Moving an application to screen or interview through set_status fires an interview prep pack by itself (ADR 0024) — the tool call that looks like bookkeeping is the one that spends two model calls.
set_status
Set the application state: new, drafting, ready, applied, screen,
interview, offer, rejected, declined, or ghosted. When marking
applied, pass warm_path=true/false (referral or direct contact vs
cold portal) — it feeds the warm-vs-cold split in funnel_stats.
| Parameter | Type | Default |
|---|---|---|
job_id | integer | required |
status | string | required |
warm_path | boolean | unset |
add_stage
Append an interview/application stage to the job’s stage log, e.g. recruiter_screen, hiring_manager, technical, onsite. After the stage, bank the questions actually asked via record_question — they compound into future prep packs.
| Parameter | Type | Default |
|---|---|---|
job_id | integer | required |
stage | string | required |
notes | string | “ |
record_question
Bank a question actually asked in an interview for this job. Company-scoped: every future prep pack for this company recalls the bank, so preps get sharper with each interview.
| Parameter | Type | Default |
|---|---|---|
job_id | integer | required |
question | string | required |
stage | string | “ |
record_outcome
Record the final outcome: offer, rejection, ghosted, declined, or withdrew. Hard signal for future taste calibration.
| Parameter | Type | Default |
|---|---|---|
job_id | integer | required |
outcome | string | required |
record_verdict
Record your reaction to a suggestion: apply, shortlist, later, skip, or applied. Feeds the taste-calibration few-shot loop on future runs.
| Parameter | Type | Default |
|---|---|---|
job_id | integer | required |
verdict | string | required |
Preparing
For the conversation after the application.
prep
Billed. Spends a model call.
Interview prep pack for a job in the pipeline: a refreshed
company dossier, likely questions derived from the qualifier’s gap
analysis, STAR stories mined from the candidate’s own records (each
traced back to the record it came from), derived comp research for
the band, questions to ask grounded in company data, and warm-path
openers. Also fires automatically when an application moves to
screen or interview. Writes drafts/prep_<id>_<company>.md and
uploads ATS-safe documents. Billed (two LLM calls).
| Parameter | Type | Default |
|---|---|---|
job_id | integer | required |
model | string | unset |
provider | string | unset |
resume_path | string | unset |
stage | string | interview |
draft_followup
Billed. Spends a model call.
Draft a follow-up note for an application gone quiet, hooked on a specific fact from the company dossier. Karani drafts; the user sends. Writes drafts/followup_<id>_<company>.md. Billed (one LLM call).
| Parameter | Type | Default |
|---|---|---|
job_id | integer | required |
model | string | unset |
provider | string | unset |
company_intel
Cached public-signal dossier for a company: background, engineering presence (GitHub), and warm-path candidates. TTL-refreshed (14 days); reused by prep, follow-up, and agent-mode qualification.
| Parameter | Type | Default |
|---|---|---|
company | string | required |
force_refresh | boolean | false |
warm_paths
Warm-path candidates at a company — engineers with a public presence (currently: public GitHub org members). Referrals and direct contact convert far better than cold portal applications; karani surfaces candidates, the user decides whom to contact.
| Parameter | Type | Default |
|---|---|---|
company | string | required |
Measuring
Conversion, segmented and interval-reported (ADR 0022).
funnel_stats
Conversion funnel over all applications: response, interview, and offer rates — overall and split by fit band, source, and qualify/draft prompt version. The measurement layer for positioning experiments.
Takes no parameters.
Memory
What karani retains between conversations. remember is the API for “I learned something about this company somewhere else”: every later qualification recalls it.
remember
Store a distilled fact in karani’s memory layer, e.g. a preference
(“prefers infra roles over pure ML”), a company fact (“GitLab responds
within a week”), or a strategy note (“data-platform angle beats
causal-ML angle for infra roles”). kind: preference | company |
outcome | strategy | question. Recalled automatically during
qualification and on demand via recall.
| Parameter | Type | Default |
|---|---|---|
content | string | required |
company | string | unset |
kind | string | preference |
recall
Query karani’s memory layer. Returns the most relevant stored facts for a free-text query, optionally filtered by kind or scoped to a company (company scoping still includes global memories).
| Parameter | Type | Default |
|---|---|---|
query | string | required |
company | string | unset |
kind | string | unset |
limit | integer | 5 |
Pushing it somewhere
The other surfaces, driven from here.
notify_slack
Push the digest or the next-actions worklist to the configured Slack channel (SLACK_CHANNEL + SLACK_BOT_TOKEN). kind: digest | actions.
| Parameter | Type | Default |
|---|---|---|
kind | string | digest |
limit | integer | 10 |
notion_sync
Mirror every tracked application (any job with a verdict or an
application status) onto the Notion job-hunt board. Idempotent: pages
are created once and patched thereafter. Requires NOTION_TOKEN and
NOTION_DATABASE_ID (create the board with python -m ingestion.cli notion init <parent_page_id>).
Takes no parameters.
autopilot
Billed. Spends a model call.
Run one autopilot pass: draft application packs for the top-fit qualified roles not yet reviewed and deliver each to Slack as a review card with Approve / Skip / I-applied buttons. Billed (one draft per candidate, capped by max_drafts, default 3; fit floor default 85). Karani never submits — the human does.
| Parameter | Type | Default |
|---|---|---|
max_drafts | integer | unset |
min_fit | integer | unset |