Skip to main content
Glama
satovarb16
by satovarb16

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault

No arguments

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
analyze_jobA

Gather the general resume + scoring guide so Claude can score the match.

  1. Verifies a general resume exists (returns error envelope if not).

  2. Verifies work authorization has been declared at least once (returns error="no_work_authorization" if not — see design D7/module docstring for the precondition ordering decision).

  3. Returns the general resume, a scoring guide, an extracted echo of the caller's input, and a live work-authorization comparison.

The match score and APPLY/CONSIDER/SKIP recommendation are NOT computed by this tool — after calling it, score the candidate's resume against the job (already pasted into this conversation) and apply the scoring_guide's recommendation_rules in your reply.

The resume injected here is the GENERAL resume (design D6), not the most recently tailored one: scoring a job against a resume already tailored FOR that job would inflate the match score by scoring the resume against itself.

This tool NEVER raises for any documented failure mode — all such failures are encoded in the return envelope. An unexpected keyword argument (e.g. a JD text payload) is a caller programming error and raises normally, exactly as calling any Python function with an unknown keyword does.

Args: title: Job title, Claude-extracted from the pasted posting. company: Company name, Claude-extracted. country: Free-text country, Claude-extracted. Compared against the user's declared work authorization; a mismatch populates work_authorization with an advisory warning. url: The job posting URL, if any. Resolved to a job_id (exact match against the jobs table) so the general-resume selection can exclude a resume already tailored to this job. None when the user has no URL to give (e.g. a referral). custom_title: The user's handle for a URL-less job. Echoed back, AND — when url is absent — resolved to a job_id the same way url is, so Guard 1 (anti-self-scoring) stays armed for jobs saved without a url. If more than one saved job shares this custom_title, this tool refuses to guess and returns error="ambiguous_custom_title".

Returns: AnalyzeJobResult with extracted/resume/scoring_guide/ work_authorization populated on success, or error/message fields populated on failure ("no_resume" | "corrupt" | "ambiguous_custom_title" | "no_work_authorization").

save_job_analysisA

Persist an analyzed job record. Upserts by id then by url (D10).

Resolution order:

  1. id given -> that row is the target; unknown id -> error="not_found".

  2. No id, url given and matching an existing row -> that row is the target (upsert by URL, unchanged semantics from the prior JSON store, including "omitted optional argument preserves the previous value").

  3. Otherwise -> a brand new row. title/company are NEVER used to match an existing record (R3) — a user editing a title for clarity must not silently create a duplicate under an update path, and title text is never treated as an update key either way.

url and custom_title follow the same "explicit argument overwrites, omission (None) preserves the previous value" rule as score/ recommendation/notes/jd_text — this is what lets a URL be supplied later without disturbing the custom_title, and vice versa (SC-04). On a brand new row, at least one of url/custom_title must resolve to a non-None value, or the record has no way for a human to find it again (R1, SC-03).

This tool NEVER raises — all failures are encoded in the return envelope.

Args: title: Job title (Claude-extracted). Always required, always overwrites — no omit-preserve semantics. company: Company name (Claude-extracted). Same as title. country: Free-text country (Claude-extracted). Same as title. id: Existing job id to update. None to create or upsert-by-url. url: Job posting URL. Nullable/unique attribute, not the identity (D1). Omitted (None) preserves the existing value on an update. custom_title: User-supplied handle when url is absent. Same omit-preserve rule as url. jd_text: Full pasted job description, stored in the job_descriptions side table. Omitted (None) leaves any existing captured JD untouched. score: 0-100 match score (Claude-supplied). Omit-preserve. recommendation: APPLY/CONSIDER/SKIP (Claude-supplied). Omit-preserve. notes: Free-text notes. Omit-preserve.

Returns: SaveJobResult with success=True, id, url, custom_title, updated flag on success; success=False with error/message on failure.

get_jobA

Retrieve a single job record by id, url, or custom_title (D6, the REQUIRED read path for jd_text — without this tool, jd_text is write-only).

custom_title exists as a lookup key because it exists as a SAVE affordance: a job saved without a url is findable ONLY by the custom_title the user agreed to (analyze.py's _NO_URL_NOTICE promises exactly this). Without this parameter, that promise had no retrieval path at all — a caller could only dump every job via list_jobs and eyeball it.

custom_title is NOT unique (save_job_analysis deliberately never matches an existing record by it — R3/SC-08), so a lookup CAN match more than one job. Silently returning the first match would hide that ambiguity from the caller and could resolve to the wrong job. Instead: 0 matches -> not_found; exactly 1 -> that job; more than 1 -> error="ambiguous", naming every matching id so the caller can retry with a specific id.

has_description is ALWAYS present in the response, regardless of include_description — it is the affordance that makes the jd_text opt-in discoverable without dragging JD text into context by default. Also returns the linked resume version SUMMARIES (no content) — the headline query is "did I apply to X?" -> "yes, and with this resume." (SC-22). The full text still comes from get_resume_version.

This tool NEVER raises.

Args: id: Job id. Exactly one of id/url/custom_title must be given. url: Alternate lookup key (url is UNIQUE). custom_title: Alternate lookup key for a URL-less job. NOT unique — see above for the multi-match rule. include_description: When True and a description was captured, populates description with the full pasted JD text. Defaults to False so a routine status check does not drag JD text into context.

Returns: GetJobResult with success=True, job, description, has_description, resume_versions on success; success=False with error="not_found" | "invalid_input" | "corrupt" | "ambiguous" on failure.

list_jobsA

Return stored job records with optional filtering, sorting, and limiting.

Pipeline order: FILTER -> SORT -> LIMIT, expressed as WHERE/ORDER BY/ LIMIT (D9). Arguments are validated in Python BEFORE any SQL is built, so an invalid status still produces the "use one of: ..." message rather than a raw SQL error. This tool NEVER raises.

Args: since: ISO-8601 string cutoff (inclusive). status: One ApplicationStatus value, or a list to match any member. min_score: Minimum score threshold (inclusive). None scores excluded. company: Substring match against company, case-insensitive (lower() on both sides, ASCII-only — SQLite's lower() does not fold non-ASCII). This is SC-1, the release's headline query ("did I apply to Acme?") — D9. limit: Maximum number of records to return (after sort). sort_by: "analyzed_at" (default, newest first) or "score" (descending, None scores last).

Returns: ListJobsResult with success=True and filtered/sorted records on success; success=False with error_message on failure.

set_application_statusA

Set the application status of a stored job record, by id or url.

Transitions are deliberately UNVALIDATED — any of the 7 status values may transition to any other. This tool NEVER raises.

notes APPENDS one dated line to status_notes and never touches notes, the analysis field. Those were a single column until the two purposes collided in practice: recording "applied on the 17th" wiped the reasoning behind the score, so the highest-scoring jobs in the store were exactly the ones whose analysis was gone. Splitting them makes that structurally impossible rather than a rule to remember.

To edit the analysis itself, call save_job_analysis with an id — its omit-preserve semantics leave every field you do not pass alone.

Args: status: One of: not_applied, applied, interviewing, offer, rejected, withdrawn, ghosted. id: Job id (preferred — always resolvable, unlike url). url: Alternate lookup key when id is not known. notes: Optional follow-up note, appended to the timeline as "[YYYY-MM-DD] ". Omitted, the timeline is left untouched and only the status changes.

Returns: SetStatusResult with success=True, id, url, status, previous_status and the resulting status_notes on success; success=False with error="invalid_status" (record unchanged), error="not_found", or error/message on a store failure.

delete_jobA

Permanently delete a stored job record, by id or url.

For a record that should never have existed: a mistyped entry, a posting that turned out to be a duplicate, test data. This is NOT the same thing as the withdrawn status, which says "I pulled out of this process" — a real event worth keeping. Conflating the two poisons every later query, because a withdrawn job still counts as a job you looked at.

Lookup is by id or url ONLY — deliberately not custom_title, which get_job does accept. custom_title is not unique (save_job_analysis never matches on it), and deleting by an ambiguous key is precisely how the wrong record gets destroyed. Ambiguity is survivable on a read; here it is not.

What happens to everything pointing at the job:

  • job_descriptions row: deleted. The captured posting belongs to the job and has no meaning without it.

  • resume_versions: NOT deleted — job_id is set to NULL. The tree is append-only and stays that way: a tailored resume is a document you really sent, and it survives the posting it was aimed at. Only the pointer dies, which is why the append-only UPDATE trigger guards every column EXCEPT job_id.

There is no confirmation flag and no undo. The returned receipt names the job and every resume version that was unlinked, so the caller can report exactly what disappeared — with no delete_job there is no way back, the receipt is the safety mechanism.

This tool NEVER raises.

Args: id: Job id (preferred — always resolvable, unlike url). url: Alternate lookup key when id is not known.

Returns: DeleteJobResult with success=True and the receipt on success; success=False with error="invalid_input" (neither key given), "not_found", "corrupt", or "write_error".

save_resume_versionA

Append a new resume version to the store. Never mutates or deletes.

The store enforces a single-root tree: the first saved version's parent_id MUST be None; every subsequent version's parent_id MUST reference an existing version id. job_id, when given, MUST reference an existing job — call save_job_analysis first and pass the id it returns (the FK is real now, D1/D8). Content is stored raw and verbatim. This tool NEVER raises.

Args: content: Raw resume text, stored verbatim. label: Caller-supplied human-readable label. parent_id: id of the version this one derives from. None only on the very first save (establishes the base). job_id: Optional job id this version was tailored for.

Returns: SaveResumeVersionResult with success=True, id, label, parent_id, job_id on success; success=False with error/message on failure ("invalid_parent", "parent_not_found", "job_not_found", "invalid_input", "corrupt", "write_error").

get_resume_versionA

Retrieve a single resume version by id, or the most recent via "latest".

This tool NEVER raises.

Args: id: Exact version id, or the literal string "latest" for the most recently created version in the store.

Returns: GetResumeVersionResult with success=True and version on success; success=False with error="not_found" or error="corrupt".

list_resume_versionsA

Return stored resume versions, newest-first, with optional filtering.

Pipeline order: FILTER -> SORT -> LIMIT. This tool NEVER raises.

Args: job_id: When provided, only versions linked to this exact job id are returned. limit: Maximum number of records to return (after sort).

Returns: ListResumeVersionsResult with success=True and filtered/sorted SUMMARIES (no resume text); success=False with error_message.

set_work_authorizationA

Declare the FULL set of countries the user may legally work in.

REPLACES the previous declaration in its entirety (SC-26) — this is a statement about the whole set ("I can work in X and Y"), not an additive append. An empty list is a valid, distinct declaration (SC-27): "declared, zero countries" differs from "never declared" — analyze_job treats the two differently (no_work_authorization only for the latter).

Countries are canonicalized at write time (tools/_country.py); both the raw text and the canonical form are stored. Two raw spellings that canonicalize to the same country (e.g. "USA" and "United States" in one call) collapse to a single declared row — the first raw spelling encountered wins the echo.

This tool NEVER raises.

Args: countries: Free-text country names, as the user states them. An empty list explicitly declares zero authorized countries.

Entries that canonicalize to nothing ("", " ", "...") are not countries and are dropped. If that leaves nothing, the call is REJECTED rather than silently storing a declaration that means nothing — see the loop below.

Returns: SetWorkAuthorizationResult with success=True and the stored raw/ canonical forms on success; success=False with error="invalid_input" when no given name could be understood, "corrupt" on a broken database, or "write_error" when the write itself fails.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

A4.6/5.0

Scored across 10 tools

Disambiguation5/5

Each tool owns a distinct resource-action pair: analyze, save/get/list/delete jobs, status updates, resume version handling, and work authorization. save_job_analysis and set_application_status both target a job, but their descriptions explicitly separate analysis fields from status timeline mechanics, so an agent should not confuse them.

Naming Consistency5/5

All tool names follow a uniform snake_case verb_noun style (analyze_job, list_jobs, save_resume_version, set_work_authorization). The verbs are specific and the objects are consistently named, with no camelCase or vague action words.

Tool Count5/5

Ten tools is right in the ideal range and every tool earns its place: job CRUD plus analysis, status tracking, resume versioning, and work authorization. There is no apparent redundancy or bloat.

Completeness4/5

The core job-application lifecycle is well covered: analyze, save, retrieve, list, update status, and delete jobs, plus resume version storage/retrieval and work authorization declaration. The only notable gap is the lack of a direct get_work_authorization tool, though analyze_job returns the live comparison during analysis.

Maintenance

ActivityMaintained
ResponsivenessNo issues