Production-grade MCP server demonstrating LLM-based job posting scoring with provider abstraction, strict input validation, and minimal trust surface.
Career Scout MCP demonstrates solid tool design with clear naming, comprehensive descriptions, and well-structured schemas. All four tools follow verb_noun conventions (rescore_, tag_, query_, regenerate_). Descriptions are detailed (100-200 chars typical) and explain WHAT, WHEN, and consequences. Input schemas are complete with types and descriptions. Output models are pydantic-based with documented fields. However, error handling lacks recovery guidance (e.g., 'ValueError if posting not found' but no suggestion for next steps), and tool annotations (readOnlyHint, destructiveHint) are absent despite clear read/write semantics. Parameter descriptions could be more prescriptive about constraints (e.g., date_after format, reason length).
List postings matching the filter criteria, with their latest score. Returns only postings that have at least one score (the JOIN excludes never-scored ones). Empty result is valid output.
Render a digest HTML of the current top-20 postings. Output path is settings.digest_output_path (operator-controlled, not user-input — no path traversal vector). Writes a single self-contained HTML file with minimal inline styling.
Re-score one posting against the current rubric via the configured LLM. Loads the posting, loads the current rubric, scores it via LiteLLM, persists the new score row, and returns the structured result. Raises ValueError if posting not found; ScoringError on LLM/parse failure.
Record that a stored score does not match operator judgment. Writes to tagged_mismatches.db. The tune_rubric prompt aggregates these for rubric refinement workflows. Reason must be non-empty.
Error handling lacks recovery guidance. 'ValueError if posting not found' tells the agent nothing, should suggest 'Try query_postings() to list available postings' or similar.
Tool annotations missing. rescore_posting and tag_mismatched_score are destructive (WRITE risk), regenerate_digest is destructive (file I/O), query_postings is read-only. Annotations (destructiveHint, readOnlyHint) would help agents reason about side effects.
Parameter constraints not fully specified. date_after lacks format hint (ISO 8601?), reason in tag_mismatched_score lacks length/content constraints, limit in query_postings lacks bounds (1-100?).
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 65 | <=2025-11-25 | v2 |
regenerate_digest description is vague about output format. 'Render a digest HTML', what does the HTML contain? How many postings? What fields? Agents need to know what to expect.
No idempotency guarantees documented. rescore_posting and tag_mismatched_score modify state, are they safe to retry? If an agent retries after a network timeout, will it create duplicate scores or mismatches?