job-radar-mcp
Click on "Deploy 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., "@job-radar-mcpScreen this job offer against my skills and show fit, gaps, and deal-breakers"
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.
job-radar-mcp
A local MCP server that lets your AI assistant screen job offers against a detailed profile of your skills, and keeps a running radar of the skills the market keeps asking for and you do not have yet. Everything lives in plain Markdown files on your own machine.
Why
Companies already use AI to filter candidates. It makes sense for candidates to use AI to filter offers too: not against a generic idea of "a good job", but against their own profile, their own goal and their own deal-breakers.
Reading an offer and asking "is this worth my time?" is easy to get wrong when you are tired, hopeful or in a hurry. This project gives your AI assistant the context to give you an honest answer, every time, and it remembers what each offer asked for that you are missing. After twenty offers you no longer guess what to learn next: the radar tells you.
Related MCP server: Job Application MCP
How it works
Two files sit at the heart of it, and they never overlap:
skills.md: what you already have. Each skill is[solid](you can defend it in an interview with evidence) or[partial](you have used it, you do not master it). Gaps never live here.gaps.md: what offers ask for and you do not have. Each term carries a counter of how many offers asked for it. The higher the count, the more urgent it is to learn.
When you analyse an offer, every missing skill goes to the radar: a new term is created with count 1, a known term goes up by one. When you actually acquire a skill (a course, a project, real experience), you graduate it: it leaves the radar and enters your profile with its level and the evidence. That is the only way from gap to profile.
flowchart LR
O["Job offer<br/>(you paste it)"] --> A["analyze_offer<br/>verdict + gaps"]
A -->|"missing skill"| R["gaps.md<br/>[count: N] Term"]
R -->|"asked again"| R
R -->|"you learn it<br/>confirm_skill"| P["skills.md<br/>[solid] / [partial]"]
P -->|"your profile today"| AEach analysis ends with one of four verdicts (good fit, reasonable
reach, too demanding, not worth the time), concrete evidence from your
CV, the cheapest gap to close, the terms you should be able to define in an
interview, and what to highlight in your CV for that specific offer. The
analysis is saved in offers/ and you can track each offer through its
lifecycle (analyzed, applied, interview, offer, accepted, or the side
exits rejected and discarded).
It can also render a one-page CV tailored to an offer (render_cv), choosing
only from content that already exists in your cv-data.json. It reorders and
trims, it never invents.
What it does NOT do
No scraping. It does not crawl job boards. You paste the offer text into your AI client.
No network, no database. The server reads and writes Markdown files in a folder you choose. That is all.
No AI of its own. The server does not think. Your AI client does the reasoning; this server is its memory and its hands.
Your data stays yours
The idea and the data are personal. Each user builds their own vault: their skills, their goal, their criteria, their offers. Nothing leaves your machine except what your AI client sends to its own model when you use it.
This repository only contains fictional data: the example-vault/ folder
belongs to Alex Morgan, a made-up junior developer from a made-up city. Copy it,
then replace everything with your own. Keep your real vault outside the repo
(the default location is ~/job-radar-vault), and .gitignore will block the
usual names (vault/, *-vault/, *.local.md) if you put one inside by
mistake.
Why MCP
MCP (Model Context Protocol) is an open standard for connecting AI assistants to tools and data. Building this as an MCP server means anyone can plug it into the AI client they already use (Claude Desktop, Claude Code, or any other MCP-compatible client) without depending on one specific app. Swap the client, keep your vault.
Quick start
1. Requirements
Node.js 22 or newer.
An MCP-compatible AI client, such as Claude Desktop or Claude Code.
Optional: Obsidian, to browse and edit your vault comfortably. The vault is a plain folder of Markdown, so any editor works.
2. Install
git clone https://github.com/JonniThorpe/job-radar-mcp.git
cd job-radar-mcp
npm install
npm run build3. Create your vault from the example
# macOS / Linux
cp -r example-vault ~/job-radar-vault# Windows (PowerShell)
Copy-Item -Recurse example-vault "$HOME\job-radar-vault"Then open the copy and make it yours. Start with profile/criteria.md (your
career goal goes under ## Goal) and profile/skills.md. Empty
profile/gaps.md down to its headings: the radar fills itself as you analyse
offers.
If the folder does not exist when the server starts, it tells you so on stderr, keeps running, and every tool answers with instructions instead of silently creating an empty vault.
4. Register the server in your client
It is a stdio server: the client launches it as a child process. Use the
absolute path to build/index.js and to your vault.
Claude Code
claude mcp add job-radar --scope user \
-e JOB_MCP_DATA_DIR=/absolute/path/to/job-radar-vault \
-- node /absolute/path/to/job-radar-mcp/build/index.jsClaude Desktop: add this block to claude_desktop_config.json (Settings >
Developer > Edit Config):
{
"mcpServers": {
"job-radar": {
"command": "node",
"args": ["/absolute/path/to/job-radar-mcp/build/index.js"],
"env": { "JOB_MCP_DATA_DIR": "/absolute/path/to/job-radar-vault" }
}
}
}On Windows, write paths with double backslashes (C:\\Users\\you\\...). If
Claude Desktop cannot find node, use the absolute path to the Node executable
as command: desktop apps do not always inherit your terminal's PATH.
Other clients: any MCP client that supports stdio servers takes the same
three things: the command (node), the argument (the path to
build/index.js) and the environment variable JOB_MCP_DATA_DIR.
5. First use
Ask your assistant to run the
pingtool. It answers with the data folder it is using.Start the
analyze_offerprompt. In Claude Code, type/and pick/mcp__job-radar__analyze_offer. In Claude Desktop, open the+menu and look for it under thejob-radarserver. Other clients list MCP prompts in their own way.Paste the offer text below it and send.
Read the verdict. The assistant then saves the analysis with
save_offerand records each missing skill withrecord_gap.Open
profile/gaps.mdand watch the counters grow over the next offers.
Every
npm run buildrequires restarting your client. An MCP server over stdio is launched once, when the client starts, and that process stays alive. There is no hot reload. In Claude Desktop, closing the window is not enough: quit it from the system tray or menu bar. Typical symptom: you ask for a new tool and the client insists it does not exist.
Reference
Tools (the model calls them)
Tool | What it does |
| Bumps a gap's count by one and adds the source. Creates it with count 1 if it is new. Never duplicates. |
| Lists gaps sorted by count, most urgent first. |
| Graduation: moves a skill from the radar to your profile. Only the evidence travels, not the sources. |
| Changes |
| Creates or updates an offer. On update it keeps the status and interview notes. |
| Moves an offer along its lifecycle and logs the change with a date. |
| Adds a dated note under "Interview notes". |
| Writes |
| Health check: says which data folder it uses, or what is wrong with it. |
Resources (you attach them)
URI | Content |
|
|
|
|
|
|
|
|
|
|
| The pipeline: status, company, title, score |
| One full offer: analysis, status log and notes |
Prompt (you start it)
Prompt | What it does |
| Returns the analysis template filled in with your skills, CV and criteria read from disk at that moment, including your goal from |
Vault layout
job-radar-vault/
profile/
skills.md - [solid|partial] Term (evidence) under '## Category'
gaps.md - [count: N] Term (Category). Sources: a; b
criteria.md ## Goal, deal-breakers and scoring axes, in free prose
cv.md your CV in Markdown: evidence for the analysis
cv-data.json your CV split into ids: what render_cv may use
interview-prep.md blank template for STAR stories (not read by the server)
offers/
YYYY-MM-company.md YAML frontmatter + analysis + status log + notes
output/
cv-<id>.html generated, never edit by handThe server never regenerates a file. It parses only the lines it recognises
(by pattern, such as - [count: N] ) and edits that exact line, leaving every
other byte untouched. That is why you can write your own notes, rules and prose
around the data lines, in Obsidian or any editor. The format is yours, not the
server's.
Obsidian (optional)
The vault works well as an Obsidian vault. Two community plugins pair nicely with it: Dataview (live tables of the radar and of offers by status, from the frontmatter) and Kanban (the offer pipeline as a board).
Configuration
Variable | Default |
|
|
Development
npm run build # tsc -> build/
npm test # tsc + node --test
npm run watch # rebuild on savesrc/
index.ts wiring only: resources + tools + prompts + stdio transport
config.ts data folder and derived paths
resources.ts the 7 resources
tools.ts the 9 tools, with their Zod schemas
prompts.ts analyze_offer
lib/
analysis-prompt.ts the analysis template and the goal reader
markdown.ts line-level primitives: headings, sections, EOL
skills.ts skills.md
radar.ts gaps.md and its counters
offers.ts frontmatter, statuses and notes
graduate.ts confirm_skill: the operation that spans both files
cv.ts render_cv: selection, height estimate, HTML
files.ts the ONLY module that touches the disk
templates/
cv.html CV design
example-vault/ fictional data, also used by the testssrc/lib/* are pure functions shaped like (content, args) -> { content, changed, message }, so the tests never touch your vault and run in a fraction
of a second. Comments in the source are in Spanish; everything user-facing is in
English.
Never use console.log in the server. stdout is the JSON-RPC channel and
writing to it breaks the protocol. Logs go to console.error (stderr).
You can talk to the server without any client by piping raw JSON-RPC:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"cli","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| JOB_MCP_DATA_DIR=./example-vault node build/index.jsRunning it against example-vault writes to it, so try things on a copy.
Contributing
Issues and pull requests are welcome. Ideas that are already on the table live
in ROADMAP.md; open an issue to discuss before building a large
change. Please keep npm test green, add tests for new behaviour in
src/lib, and never commit real personal data: test fixtures and
example-vault/ must stay fictional.
License
Available Tools
9 toolsadd_offer_noteAdd a note to an offerA
Adds a dated note to the 'Interview notes' section of an offer: who interviewed the user, what they asked, feedback received, or anything worth rereading before the next round. Notes accumulate, they are never overwritten.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Offer id. | |
| note | Yes | Note text. Multiple lines and Markdown are fine. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare idempotentHint=false and destructiveHint=false; the description adds a meaningful behavioral fact not derivable from them - notes accumulate and are never overwritten - which reassures the agent that repeated calls append rather than replace. It also implies the timestamp is applied server-side ('dated note'). It stops short of saying what is returned, which keeps it from a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose and target are front-loaded in the first clause, and the second sentence about accumulation is short and earns its place. The middle content list is slightly padded but still useful guidance.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter append tool with no output schema, the description covers what is created, where it goes, and the append-only behavior, which is most of what an agent needs. It omits any mention of permissions or the response shape, but neither is critical to invoking it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% with only two required parameters (id, note), so the schema already documents both; the description adds the fact that the note becomes 'dated' and lives in a specific section. That is minor added meaning beyond the schema, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (adds), resource (note), and target (the 'Interview notes' section of an offer) in one clause, plus the note's expected content. No sibling tool in the list adds notes, so the agent can identify this tool unambiguously.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives content guidance ('who interviewed the user, what they asked, feedback received') which implies when this tool is appropriate, but it never states an explicit when-to-use condition or names an alternative among siblings such as save_offer or record_gap. Usage must be inferred from the content examples.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
confirm_skillGraduate a skillA
Graduation: confirms the user now has a skill that was in the radar. Removes it from gaps.md and adds it to skills.md with its level and evidence. It is the ONLY path from gap to profile. Use it only when the user confirms they have acquired it (certification, project or real experience), never on your own initiative.
| Name | Required | Description | Default |
|---|---|---|---|
| term | Yes | Term exactly as it appears in the radar. | |
| level | Yes | solid = can defend it in an interview with evidence from a project. partial = has used it, does not master it. | |
| category | No | Only if the gap had no category in the radar. | |
| evidence | Yes | Why the user can defend it: certification, project or experience. Stored in the profile as a note. The offer that created the gap is NOT kept. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only cover idempotentHint=false and destructiveHint=false, so the description carries real weight: it discloses the mutation of two files, what gets written, and notably what gets dropped (the offer that created the gap is NOT kept). This is exactly the side-effect context a mutation tool needs beyond its annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the action and its effects, then the precondition. No filler; every clause carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema needed and full param documentation in the schema, the description supplies everything else an agent requires: precondition, side effects, and the exclusivity of this workflow path.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so term, level, category and evidence are already documented with format and enum meaning in the schema. The description only echoes 'level and evidence' and adds no syntax or format detail beyond that — baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('confirms the user now has a skill that was in the radar') and names the concrete side effects (removes from gaps.md, adds to skills.md). This clearly distinguishes it from siblings like record_gap and update_skill_level.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use ('only when the user confirms they have acquired it via certification, project or real experience'), explicit when-not ('never on your own initiative'), and states it is the ONLY path from gap to profile. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_gapsList gapsARead-only
Returns every gap in the radar sorted by count, highest first: the top entries are what most offers have asked for, and therefore the most urgent to learn.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already establishes this is a safe read, so the description only needs to add context. It does: the result is exhaustive ('every gap'), ordered by count descending, and the ordering carries meaning about urgency. It does not describe pagination behavior or the fields of a gap record.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence with the operation and its ordering rule front-loaded, followed by a short interpretive clause. There is no filler and nothing is buried.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-argument read-only listing tool this covers what an agent needs to select and call it. The only omission is the shape of a returned gap entry, which is a minor gap given there is no output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the schema is trivially fully covered and the baseline of 4 applies. Nothing about parameter meaning is left unclear because there is nothing to parametrize.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb and resource ('Returns every gap in the radar') and adds the ordering rule, so an agent immediately knows this is the enumeration counterpart to record_gap. It never names a sibling explicitly, so it stops short of the 5 criterion for sibling differentiation, but the intent is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The clause 'the top entries are what most offers have asked for, and therefore the most urgent to learn' implies the tool is for prioritization, but there is no explicit when-to-use, when-not, or named alternative. Usage is inferred rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pingPingARead-only
Checks that the server is alive and which data folder it is working on.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true is consistent with a health probe, and the description adds real value beyond the annotation by disclosing the second piece of information returned (the active data folder), which lets an agent use it as a configuration check, not just a liveness check.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One short sentence, front-loaded with the primary behavior and appending only the secondary return detail. No filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a no-param, no-output-schema tool the description is sufficient: it covers both the liveness result and the data-folder result, which is the entirety of what a caller needs. Minor omissions such as version or latency reporting are not required here.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are zero parameters, so the schema carries no burden and the baseline is 4; nothing in the description is needed to explain inputs.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (checks), the resource (server), and even an extra facet of the result (which data folder it is working on). It is trivially distinguishable from the domain siblings, though it doesn't need to differentiate itself from any of them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied — an agent would call this to verify connectivity or diagnose availability — but the description never says when to reach for it (startup, before a batch, after a failure) or that it is the only liveness probe available.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
record_gapRecord a gapA
Records that a job offer asks for a skill the user does NOT have. If the term is already in the radar its count goes up by one and the source is added; otherwise it is created with count 1. It never duplicates. Call it once per skill the offer asks for that is missing from the user's profile, including terms the user could not define in an interview. Never call it for something already in skills.md.
| Name | Required | Description | Default |
|---|---|---|---|
| term | Yes | The missing skill or concept, named the way an engineer would. E.g. 'Kubernetes'. | |
| source | Yes | Offer that asks for it, so it can be traced. E.g. 'Acme Backend Engineer, Remote'. | |
| category | Yes | Category it belongs to, used to place it under the right '##' heading in the profile once it graduates. E.g. 'Infrastructure'. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare idempotentHint=false and destructiveHint=false; the description adds the meaningful part by explaining the upsert semantics -- count increments and source is appended if the term exists, otherwise it is created with count 1 -- and that duplicates are never produced. It does not cover permissions, the response shape, or how the added source affects the existing entry, but the mutation consequences are well disclosed and consistent with the non-idempotent hint.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose and mutation behavior are front-loaded in the first three sentences, with usage rules following. Five sentences is on the long side for a three-parameter tool, and 'It never duplicates' partially overlaps with the later 'Never call it for something already in skills.md', but every sentence still carries actionable information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a mutation tool with no output schema and only partial annotation coverage (idempotency/destructiveness), the description supplies the core context an agent needs: the missing-skill scope, the create-vs-increment behavior, and the exclusion for skills already in the profile. Remaining gaps -- return value and any auth/permission requirement -- are minor for this operation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so term, source, and category are all already documented with examples in the schema. The description only implicitly reinforces what 'source' does (it is added to an existing entry), adding no format, naming, or placement detail beyond the schema. Baseline 3 applies when the schema carries the parameter burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a precise verb+resource+condition: it records that a job offer asks for a skill the user does NOT have. It also discloses the underlying data behavior (radar entry incremented vs. created with count 1), which distinguishes it clearly from siblings like list_gaps or confirm_skill.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit when-to-use ('call it once per skill the offer asks for that is missing from the user's profile, including terms the user could not define in an interview') and an explicit when-not ('Never call it for something already in skills.md'). However, it never names the alternative sibling tools an agent should route to for already-known skills (e.g. confirm_skill or update_skill_level), so the exclusion lacks a destination.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
render_cvRender a CV tailored to an offerADestructiveIdempotent
Writes output/cv-.html, a version of the CV tailored to one offer: it chooses which projects, which lines of each project, which bullets and which tags go in, and in what order. If not everything fits, trim lines inside each project before dropping a whole project. It CANNOT invent content: everything must already exist in profile/cv-data.json, and the whole call fails if you cite an id or tag that is not there. Only headline, summary and location accept free text, because they are framing. Omit any section to keep it complete as is. Do NOT call it on your own after an analysis: propose the tailoring and wait for the user to ask for it.
| Name | Required | Description | Default |
|---|---|---|---|
| note | No | Why these choices for this offer. Stored as a comment inside the HTML, invisible when printed. | |
| stack | No | Tech Stack groups to show, in order. Omit it to include them all unchanged. | |
| offerId | Yes | Id of the offer this CV is tailored to, format YYYY-MM-company. It names the output file. | |
| summary | No | Sidebar summary. Free text, but it may only reorder and emphasise what is already true in cv-data.json: do not add technologies or experience that are not there. About 300 characters; longer pushes the rest of the column off the page. | |
| headline | No | Headline under the name. Free text (accepts <br>): it is framing, not a factual claim. Never claim a job title the person has not held. Omit it to keep the one in cv-data.json. | |
| location | No | Location line in the contact block. Free text: where the person lives and where they can work. E.g. 'Northbridge · Open to remote'. Omit it to keep the one in cv-data.json. | |
| projects | No | Projects to show, in order: the bare id for the whole project, or {id, lines} to keep only the lines that matter for this offer. This is where tailoring pays off most: put first the ones that touch the offer and, if it does not fit, trim lines before dropping a whole project. Omit it to include them all. | |
| education | No | Education ids, in order. Omit it to include them all. | |
| experience | No | Jobs to show, in order. Omit it to include them all. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial behavior beyond the idempotentHint/destructiveHint annotations: content provenance (everything must already exist in profile/cv-data.json), an all-or-nothing failure mode when citing a missing id or tag, and the free-text boundary limited to headline/summary/location. It does not explicitly state that it overwrites an existing output/cv-<id>.html file, which is the main destructive consequence implied by destructiveHint=true.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the write action and output path, then layers constraints and the do-not-auto-call rule. Dense but every sentence carries a rule; slightly long-winded in the projects paragraph where the schema already repeats the trim-before-drop guidance.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, and the description covers the return artifact (file path, invisible stored comment), the validation failure mode, and the invocation workflow. An agent has everything needed to call it correctly and safely.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% (baseline 3), but the description adds real meaning: which fields accept free text versus must reference existing ids, the omit-to-keep-unchanged convention, and the rationale that projects is where tailoring pays off most. That goes beyond the schema's own property descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and artifact ('Writes output/cv-<id>.html, a version of the CV tailored to one offer') and immediately scopes it to selecting projects, lines, bullets and tags. No sibling (record_gap, save_offer, list_gaps, etc.) operates on CV rendering, so the distinction is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when NOT to call it ('Do NOT call it on your own after an analysis: propose the tailoring and wait for the user to ask for it') and gives a fallback ordering rule ('trim lines inside each project before dropping a whole project'). This is a clear workflow constraint an agent would otherwise violate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
save_offerSave an offer analysisAIdempotent
Creates or updates an offer file with its analysis. If the offer already exists ONLY the analysis is replaced: status and interview notes are kept.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Identifier and file name, format YYYY-MM-company. E.g. '2026-07-acme'. | |
| url | No | Link to the original offer. | |
| tags | No | Short labels. E.g. ['backend','remote']. | |
| score | No | Score following the scoring rules in criteria.md, if it defines any. | |
| title | Yes | Job title exactly as the offer states it. | |
| salary | No | Range or figure exactly as the offer publishes it. | |
| company | Yes | Company. | |
| markdown | Yes | The full analysis in Markdown, with the six points of the template. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare idempotentHint=true and destructiveHint=false, and the description meaningfully extends that by spelling out the upsert behavior and exactly what survives an overwrite (status and interview notes). It does not cover permissions, file location, or what happens to url/tags/score/salary on update.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences with the create-or-update behavior front-loaded and the preservation rule immediately after. No filler, and the most decision-relevant fact (only the analysis is replaced) is stated early.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema and 8 parameters, the description covers the central upsert risk well but leaves a real ambiguity: it says ONLY the analysis is replaced, yet the schema accepts url, tags, score, and salary, so an agent cannot tell whether those are updated or ignored on the update path. That gap matters for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all eight parameters are already documented in the schema with formats and examples. The description adds no parameter-level detail beyond calling the markdown payload 'the analysis', which is the baseline expectation when the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Creates or updates an offer file with its analysis') and immediately pins down the upsert semantics. It implicitly separates itself from set_offer_status and add_offer_note by stating that status and interview notes are preserved, though it never names those siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description tells you what the tool does on both the create and update paths, but gives no explicit when-to-use guidance, no prerequisites, and no routing to alternatives such as add_offer_note or set_offer_status for the fields it deliberately preserves. Usage is only implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
set_offer_statusChange an offer statusAIdempotent
Moves an offer along its lifecycle and logs the change with a date. Lifecycle: analyzed -> applied -> interview -> offer -> accepted. Side exits from any point: rejected (the company says no) and discarded (the user decides to stop).
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Offer id. E.g. '2026-07-acme'. | |
| status | Yes | New status, one of the seven in the lifecycle. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare idempotentHint=true and destructiveHint=false, so the safety profile is covered. The description adds genuinely new behavioral context beyond that: the operation writes a dated log entry and follows a fixed lifecycle with terminal side exits, which matters when reasoning about state changes. It stops short of stating what happens on an invalid transition or whether exits are reversible.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the core action before the lifecycle detail. Every clause carries information (action, log side effect, transition order, exit semantics) with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With only two well-documented parameters, no output schema, and annotations covering safety, the description supplies the one thing the schema cannot: the lifecycle semantics needed to pick a valid status. It leaves open edge behavior such as invalid transitions or reversibility of 'rejected'/'discarded', which is a minor gap for this tool's complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the enum is already enumerated, so the baseline is 3. The description goes further by giving the enum values meaning — the progression analyzed -> applied -> interview -> offer -> accepted, plus the two exit states and which of them is company-driven vs user-driven — context the bare enum list does not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource — moving an offer along its lifecycle and logging the change — and then enumerates the exact state machine, which distinguishes it from siblings like save_offer (creation) and add_offer_note (annotation). An agent can tell what this tool does without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied rather than stated: describing the lifecycle and the two side exits ('rejected', 'discarded') tells the agent which statuses are legitimate, but there is no explicit when-to-use or when-not-to-use guidance relative to save_offer or add_offer_note. The transition model is helpful context but not routing advice.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
update_skill_levelChange a skill levelAIdempotent
Changes the level of a term that is ALREADY in skills.md. For something that is not in the profile yet, use confirm_skill.
| Name | Required | Description | Default |
|---|---|---|---|
| term | Yes | Term exactly as it appears in skills.md. | |
| level | Yes | New level: solid or partial. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare idempotentHint=true and destructiveHint=false, so safety and repeatability are covered structurally. The description adds the important precondition that the term must already exist, which is behavioral context beyond the annotations, though it stops short of saying what happens if the term is absent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, front-loaded with the core action and the key precondition, with the routing alternative second. Every clause carries information; no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple two-parameter mutation with no output schema and annotations already covering idempotency and non-destructiveness, the description supplies the precondition and sibling routing an agent needs. Nothing essential is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters (term, level) are already fully documented, including the enum values. The description reinforces that the term must already exist but adds no syntax or format detail beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb (changes) and resource (level of a term in skills.md), and explicitly distinguishes itself from the sibling confirm_skill. An agent can tell immediately that this mutates an existing entry rather than creating one.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives an explicit when: the term is ALREADY in skills.md. It also gives an explicit when-not with the named alternative: 'For something that is not in the profile yet, use confirm_skill.' Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
9 tool updates
v0.1.0- First observed
add_offer_note - First observed
confirm_skill - First observed
list_gaps - First observed
ping - First observed
record_gap - First observed
render_cv - First observed
save_offer - First observed
set_offer_status - First observed
update_skill_level
TDQS
Scored across 9 tools
Each tool targets a distinct resource+action: skill-gap recording (record_gap), gap graduation (confirm_skill), level edits on existing skills (update_skill_level), offer persistence (save_offer), lifecycle (set_offer_status), notes (add_offer_note), and CV rendering (render_cv). The three skill-related tools are the closest, but their descriptions explicitly disambiguate (missing vs. acquired vs. already-profiled), so misselection is unlikely.
Almost every name follows a clean verb_noun pattern (record_gap, confirm_skill, update_skill_level, list_gaps, save_offer, set_offer_status, add_offer_note, render_cv) with no casing mixups. The lone exception is the health check 'ping', which breaks the pattern but is a conventional outlier, so the set stays highly predictable.
Nine tools is a tight, well-scoped set for a job-search assistant spanning three coherent areas (skills/gaps, offers, CV). Every tool earns its place with no redundant operations, falling squarely in the ideal 3-15 range.
The skill side covers create/graduate/update/list and offers cover save/status/notes, plus CV rendering, but there is no read/list operation for offers (no list_offers or get_offer) and no list_skills, leaving agents without a way to enumerate tracked offers or the full profile. These are notable read-side gaps even though the write workflows are solid.
Maintenance
Related MCP Connectors
Build an ATS-friendly resume and check it against a job description, fully offline.
Analyze job listings against your resume, track applications, and generate cover letters.
A job-search companion: tailor your CV to a role, score fit, fix ATS issues. Also via MCP.
Tailor a CV to a job posting: score the match, propose a reviewable rewrite, export PDF or Word.
Related MCP Servers
- AlicenseAqualityCmaintenanceClaude-powered job fit analyzer that analyzes job fit based on GitHub portfolio, resume, and preferences, tracks applications locally.2048 PyPI2MIT
- AlicenseNot gradedqualityBmaintenanceA local-first, open-source MCP server that analyzes jobs, matches your CV, tailors documents, and tracks applications — all on your machine with no data uploaded.AGPL 3.0
- AlicenseAqualityCmaintenanceA privacy-first MCP server for locally managing job, fellowship, and graduate-school applications. It offers tools for tracking application status, analyzing role fit, generating LaTeX CV/cover letters, interview prep, and discovering public jobs from ATS APIs.81MIT
- AlicenseNot gradedqualityCmaintenanceEnables drafting, validating, and reviewing tailored CVs and cover letters from private local career inputs, with reproducible PDF builds and application tracking support.MIT