search_jobs
Find current job listings with freshness indicators to avoid outdated postings. Filter by location, remote work, keywords, and recency across multiple job boards.
Instructions
Search for real-time job listings with freshness badges on every result — so you never apply to a role that closed months ago. Sources: Remotive + RemoteOK + The Muse + HN 'Who is Hiring'. Supports location filtering, remote-only mode, keyword spotting (e.g. FIFO), and max age filtering. Returns timestamped freshcontext sorted freshest first.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Job search query e.g. 'typescript', 'mining engineer', 'FIFO operator', 'data analyst' | |
| location | No | Country, city, or 'remote' / 'worldwide' e.g. 'South Africa', 'Australia', 'remote' | |
| remote_only | No | Only return remote-friendly listings | |
| max_age_days | No | Hide listings older than N days (default 60, use 7 for very fresh only) | |
| keywords | No | Keywords to highlight in results e.g. ['FIFO', 'underground', 'contract'] | |
| max_length | No |
Implementation Reference
- src/server.ts:208-239 (handler)Registration of the search_jobs tool and its handler in src/server.ts.
server.registerTool( "search_jobs", { description: "Search for real-time job listings with freshness badges on every result — so you never apply to a role that closed months ago. Sources: Remotive + RemoteOK + The Muse + HN 'Who is Hiring'. Supports location filtering, remote-only mode, keyword spotting (e.g. FIFO), and max age filtering. Returns timestamped freshcontext sorted freshest first.", inputSchema: z.object({ query: z.string().describe("Job search query e.g. 'typescript', 'mining engineer', 'FIFO operator', 'data analyst'"), location: z.string().optional().describe("Country, city, or 'remote' / 'worldwide' e.g. 'South Africa', 'Australia', 'remote'"), remote_only: z.boolean().optional().default(false).describe("Only return remote-friendly listings"), max_age_days: z.number().optional().default(60).describe("Hide listings older than N days (default 60, use 7 for very fresh only)"), keywords: z.array(z.string()).optional().default([]).describe("Keywords to highlight in results e.g. ['FIFO', 'underground', 'contract']"), max_length: z.number().optional().default(8000), }), annotations: { readOnlyHint: true, openWorldHint: true }, }, async ({ query, location, remote_only, max_age_days, keywords, max_length }) => { try { const result = await jobsAdapter({ url: query, maxLength: max_length, location: location ?? "", remoteOnly: remote_only, maxAgeDays: max_age_days, keywords: keywords ?? [], }); const ctx = stampFreshness(result, { url: query, maxLength: max_length }, "jobs"); return { content: [{ type: "text", text: formatForLLM(ctx) }] }; } catch (err) { return { content: [{ type: "text", text: formatSecurityError(err) }] }; } } ); - src/adapters/jobs.ts:61-148 (handler)The jobsAdapter implementation that orchestrates fetching jobs from multiple sources.
export async function jobsAdapter(options: ExtractOptions): Promise<AdapterResult> { const query = options.url.trim(); const maxLength = options.maxLength ?? 8000; const location = options.location ?? ""; const remoteOnly = options.remoteOnly ?? false; const maxAgeDays = options.maxAgeDays ?? 60; const keywords = options.keywords ?? []; const [remotiveRes, remoteOkRes, arbeitnowRes, museRes, hnRes] = await Promise.allSettled([ fetchRemotive(query, location, maxAgeDays, keywords), fetchRemoteOK(query, location, maxAgeDays, keywords), fetchArbeitnow(query, location, maxAgeDays, keywords, remoteOnly), remoteOnly ? Promise.reject("skipped") : fetchMuse(query, location, maxAgeDays, keywords), fetchHNHiring(query, location, maxAgeDays, keywords), ]); const pool: Listing[] = []; const sourceStats: Record<string, number> = {}; const harvest = (res: PromiseSettledResult<{ listings: Listing[] }>, label: string) => { if (res.status === "fulfilled") { pool.push(...res.value.listings); sourceStats[label] = res.value.listings.length; } else { sourceStats[label] = 0; } }; harvest(remotiveRes as PromiseSettledResult<{ listings: Listing[] }>, "Remotive"); harvest(remoteOkRes as PromiseSettledResult<{ listings: Listing[] }>, "RemoteOK"); harvest(arbeitnowRes as PromiseSettledResult<{ listings: Listing[] }>, "Arbeitnow"); harvest(museRes as PromiseSettledResult<{ listings: Listing[] }>, "The Muse"); harvest(hnRes as PromiseSettledResult<{ listings: Listing[] }>, "HN Hiring"); if (!pool.length) { return { raw: [ `No job listings found for "${query}"${location ? ` in ${location}` : ""}.`, "", "Tips:", "• Try broader terms e.g. \"engineer\" instead of \"senior TypeScript engineer\"", "• Set location to \"remote\" for worldwide results", "• Increase max_age_days (default: 60)", "• Note: FIFO/mining/trades jobs are on specialist boards (myJobsNamibia, SEEK, mining-specific sites) — these sources are tech/remote focused", ].join("\n"), content_date: null, freshness_confidence: "low", }; } // Sort: freshest first pool.sort((a, b) => a.days - b.days); const freshCount = pool.filter(l => l.days <= 7).length; const goodCount = pool.filter(l => l.days > 7 && l.days <= 30).length; const staleCount = pool.filter(l => l.days > 30).length; const sourceSummary = Object.entries(sourceStats) .map(([src, n]) => `${src}:${n}`) .join(" "); const header = [ `# Job Search: "${query}"${location ? ` · ${location}` : ""}${remoteOnly ? " · remote only" : ""}`, `Retrieved: ${new Date().toISOString()}`, `Found: ${pool.length} listings — 🟢 ${freshCount} fresh 🟡 ${goodCount} recent 🔴 ${staleCount} older`, `Sources: ${sourceSummary}`, `⚠️ Sorted freshest first. Check badge before applying.`, keywords.length ? `🔍 Watching for: ${keywords.map(k => `⚡${k}`).join(", ")}` : null, "", ].filter(Boolean).join("\n"); const body = pool .map(l => l.text) .join("\n\n─────────────────────────────\n\n"); const raw = (header + "\n\n" + body).slice(0, maxLength); const freshestDays = pool[0]?.days ?? 9999; const newestDate = freshestDays < 9999 ? new Date(Date.now() - freshestDays * 86400000).toISOString().slice(0, 10) : null; return { raw, content_date: newestDate, freshness_confidence: freshestDays <= 7 ? "high" : freshestDays <= 30 ? "medium" : "low", }; }