A job search and tracking system for Taiwan job boards (104, CakeResume, Yourator) with skill-based scoring, salary parsing, and cover letter generation.
22 tools with READ_ONLY risk profile. Most have basic descriptions (50-150 chars) and parameter schemas, but lack depth. Tool names are action-oriented (search_, fetch_, parse_, score_, etc.) which is good. However, descriptions are often terse and lack context on WHEN to use each tool or what downstream tools expect. Parameters have types but many lack detailed constraints (enums, ranges, formats). Output schemas are not documented, LLMs cannot predict what fields to expect. Error handling is absent from descriptions. No tool annotations (readOnlyHint, idempotentHint). Composition is reasonable (tools chain via job dicts), but many utility functions (normalize_*, parse_*, format_*) feel like internal helpers exposed as tools rather than user-facing operations.
Remove duplicate jobs. Pass 1: deduplicate by URL (exact). Pass 2: deduplicate by normalized title, keeping the best source.
Fetch full descriptions for top jobs via detail API.
Fetch full job detail from 104 detail API.
Filter out jobs that have already been seen.
Format parsed salary as a readable monthly range string.
Generate a cover letter template for a job posting.
Generate a unique ID for a job based on URL (primary) or title+company+location.
Output schemas not documented. Tools like search_104, score_job, and deduplicate_jobs return complex objects (jobs list, match_score, match_reason) but LLMs cannot predict field names or types. This forces agents to guess downstream parameter names and risks broken tool chains.
Normalize and parse utility functions (normalize_104_job, normalize_cakeresume_job, parse_salary, format_monthly_range) feel like internal helpers, not user-facing tools. Descriptions lack context on WHEN an LLM should call them vs. letting higher-level tools handle normalization internally.
No error handling guidance in tool descriptions. Tools like fetch_job_detail, load_config, and load_skills_cache can fail (network, file not found, invalid JSON) but descriptions do not explain what to do on failure or how to recover.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | F | 45 | 2026-07-28+ | v2 |
Extract user info from config.
Check if a date string is within the last N hours.
Load configuration from JSON file.
Load previously seen job IDs from Notion tracker DB via skills_cache.
Load skills and projects from Notion cache file. Returns (skills, projects).
Normalize a 104 search result to the common job dict format.
Normalize a Cake job listing to common schema.
Parse a job posting date string into a datetime. Supports ISO 8601, date only, relative (English and Chinese), and various Taiwan formats.
Parse a Taiwan salary string into structured data. Returns dict with min, max, period, monthly_min, monthly_max or None if unparseable.
Return a scoring penalty if salary is below threshold.
Score a single job against skills. Returns (score, match_reason).
Score and sort jobs. Returns jobs with match_score and match_reason added.
Search Cake for each keyword, aggregate and deduplicate.
Call 104 search API for a single keyword. Returns (jobs, total_count).
Search Cake for jobs via _next/data endpoint. Returns (jobs, total_count).
Parameter constraints missing. search_104 accepts 'time_range' but description lists only examples ('day', '3days', 'week', 'month'), not an enum. parse_job_date accepts 'date_str' with no format specification. LLMs will hallucinate invalid values.
No tool annotations. All 22 tools are READ_ONLY but lack readOnlyHint annotation. This prevents MCP clients from optimizing caching, retry logic, or permission checks. Idempotent tools (parse_salary, parse_job_date, deduplicate_jobs) lack idempotentHint.