careers-at-gov-mcp
by emansih
README.md
# careers-at-gov-mcp
A TypeScript [Model Context Protocol](https://modelcontextprotocol.io) server (stdio transport) for the Singapore public-service job board [Careers@Gov](https://jobs.careers.gov.sg/). It lets an MCP client search, filter and read public-service job postings.
## Tools
All tools are read-only and idempotent, and return both a readable text summary and `structuredContent` (with an `outputSchema`).
| Tool | What it does |
|---|---|
| `search_jobs` | Search and filter jobs. Input: `query?`, `employmentTypes?` (`Full-time`, `Traineeship`, `Internship`, `Contract`, `Others`), `experience?` (`0 - 1 year`, `1 - 3 years`, `4 - 6 years`, `7 - 9 years`, `> 10 years`), `organisations?`, `jobFunctions?`, `sort?` (`relevance`, `latest`, `closing date`), `page?` (20 jobs per page). Returns `total`, `page`, `totalPages`, `pageSize`, `jobs[]` and the equivalent `websiteUrl`. |
| `get_job` | Full details of one job, by `url` (full URL or `/jobs/...` path) or `source` + `id`: title, organisation, employment type, posted/closing info, description as plain text, apply link, organisation blurb. |
| `list_filter_options` | Valid filter values: employment types (with the raw job types each covers), experience levels, sort options, and the live organisations and job functions with current job counts. |
Organisation and job-function names are matched case-insensitively against the live lists; unknown values return an error with close matches.
### How it works
The site is not server-filtered: the home page embeds every current job (about 2,200) in its Next.js RSC payload (`self.__next_f`), and filtering, sorting and pagination run in the browser. This server fetches that page once, parses the embedded `jobs`, `agencies` and `departments` arrays, caches them in memory for 10 minutes (concurrent requests share one fetch), and applies the website's filter, sort and pagination rules locally. `get_job` fetches one detail page and reads its RSC `jobDetail` object (falling back to the `JobPosting` JSON-LD block).
Requests use a `Mozilla/5.0 (compatible; careers-at-gov-mcp/0.1; +https://github.com/emansih/)` user agent (the URL is the maintainer contact): the site's firewall returns 403 on detail pages for user agents that don't start with `Mozilla/5.0`.
### Search ranking caveat
The website sends the search query to Algolia from the browser, which this server does not call. Free-text search is implemented locally: the query is split into words, every word must prefix-match (e.g. `analys` matches `analyst`) the title, organisation, job function or one-line summary, and title matches rank highest (exact title, then phrase in title, then all words in title, then words spread across fields). Ranking approximates the website's and result counts will differ from it when a query is used. Filter-only searches (no query) match the website exactly. Salary ranges are not published in the listing data at the time of writing, so `salaryRange` is usually absent.
## Install
Published on npm as [`careers-at-gov-mcp`](https://www.npmjs.com/package/careers-at-gov-mcp). Requires Node.js 20+. No API key needed.
```bash
npx -y careers-at-gov-mcp # run on demand
npm install -g careers-at-gov-mcp # or install globally
```
## Build from source
```bash
pnpm install
pnpm build
```
## Run
```bash
pnpm start # run the built server (stdio)
pnpm dev # run from source with tsx (watch mode)
pnpm inspect # open the MCP Inspector against build/index.js
```
The server logs to stderr only; stdout carries the JSON-RPC stream. Requires Node 20 or newer (global `fetch`).
## Add to Claude Code
```bash
claude mcp add careers-at-gov -- npx -y careers-at-gov-mcp
```
On Windows (native, not WSL), wrap it with `cmd /c`: `claude mcp add careers-at-gov -- cmd /c npx -y careers-at-gov-mcp`.
## Add to Claude Desktop
In `claude_desktop_config.json`:
```json
{
"mcpServers": {
"careers-at-gov": {
"command": "npx",
"args": ["-y", "careers-at-gov-mcp"]
}
}
}
```
On Windows, use `"command": "cmd"` and `"args": ["/c", "npx", "-y", "careers-at-gov-mcp"]`.
## Layout
- `src/careers/` - fetching, RSC parsing, dataset cache, filters, local search, HTML-to-text.
- `src/tools/` - one file per tool, registered in `src/tools/index.ts`.
## Adding a tool
Create `src/tools/<name>.ts` using `defineTool(...)` (read-only, idempotent, open-world annotations by default) and add it to the list in `src/tools/index.ts`.
Be polite to the site: it is a government service, so keep request rates low (the in-memory cache means one listing fetch per 10 minutes).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues