Skip to main content
Glama
careers-at-gov-mcp

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).