Arbetsförmedlingen MCP
# Arbetsförmedlingen MCP
An MCP server for the open APIs of **Arbetsförmedlingen** (the Swedish Public Employment Service, [JobTech](https://jobtechdev.se)). It lets an AI assistant search Platsbanken job ads, read full ad texts, get labour-market statistics and look up taxonomy codes - using plain words, no API keys.
---
## 🚀 Features
- **Job search**: free text, locations, occupations, skills, employment type, remote work, traineeships, radius around coordinates.
- **Names instead of codes**: `"Göteborg"`, `"Systemutvecklare"`, `"Python"` are automatically mapped to taxonomy concept ids (you can also pass the ids directly).
- **Full job ad**: description, requirements, conditions, contacts, application link.
- **Statistics**: number of ads by region, municipality, occupation, skill.
- **Autocomplete and taxonomy**: suggestions like in Platsbanken and lookup of official names.
- **Compact responses**: only the needed fields, so the model context isn't filled with raw JSON.
- **No keys or registration**: uses the open JobSearch and Taxonomy APIs.
---
## 📂 Project structure
```text
.
├── README.md
├── package.json
├── src/
│ ├── index.js # Entry point (stdio transport)
│ ├── server.js # MCP tool definitions
│ ├── api.js # JobSearch and Taxonomy client, taxonomy cache
│ └── format.js # Ad and statistics compaction
└── test/
└── format.test.js
```
---
## 📦 Installation
```bash
cd arbetsformedlingen-mcp
npm install
```
Requires Node.js 20+.
---
## 🔌 Connecting
### Claude Code
```bash
claude mcp add arbetsformedlingen -- node /path/to/arbetsformedlingen-mcp/src/index.js
```
### Claude Desktop / Codex / any MCP client
```json
{
"mcpServers": {
"arbetsformedlingen": {
"command": "node",
"args": ["/path/to/arbetsformedlingen-mcp/src/index.js"]
}
}
}
```
### Debugging with MCP Inspector
```bash
npm run inspect
```
### Environment variables (optional)
| Variable | Default | Purpose |
|---|---|---|
| `JOBSEARCH_BASE_URL` | `https://jobsearch.api.jobtechdev.se` | JobSearch API address |
| `TAXONOMY_BASE_URL` | `https://taxonomy.api.jobtechdev.se/v1/taxonomy/main` | Taxonomy API address |
| `AF_TIMEOUT_MS` | `20000` | Request timeout |
---
## 🛠 Tools
All tools are read-only.
### `search_jobs` - job search
| Parameter | Description |
|---|---|
| `q` | Free text (job title, technology, employer) |
| `municipality`, `region` | Locations: names or concept ids |
| `occupation`, `occupation_field` | Occupations and occupational fields |
| `skill` | Skills |
| `employment_type`, `worktime_extent`, `workplace_model` | Employment type, extent (Heltid/Deltid), workplace model |
| `employer` | Employer name or organization number |
| `remote`, `trainee`, `larling`, `open_for_all` | Flags: remote work, traineeship, apprenticeship, "open to all" |
| `experience_not_required` | Only ads with no experience requirement |
| `driving_license_required` | Whether a driving licence is required |
| `published_after`, `published_before` | Publication period |
| `latitude`, `longitude`, `radius_km` | Radius search |
| `limit`, `offset` | Pagination (`limit` up to 2100 - pages of 100 are fetched sequentially; offset up to 2000) |
| `sort` | `relevance`, `pubdate-desc`, `pubdate-asc`, `applydate-desc`, `applydate-asc`, `updated` |
| `description_chars` | How many characters of the description to include in the card |
### `get_job` - job ad by ID
Full description, requirements (`must_have` / `nice_to_have`), salary, address, contacts and how to apply. Parameter: `id`.
### `job_stats` - statistics
Accepts the same filters as `search_jobs`, plus `group_by` (`region`, `municipality`, `occupation-name`, `occupation-group`, `occupation-field`, `country`, `employment-type`, `worktime-extent`, `skill`, `language`) and `top`.
### `suggest` - autocomplete
Suggestions by word prefix: `q`, `limit`.
### `search_taxonomy` - taxonomy lookup
Finds concept ids and official names: `type`, `query`, `limit`. Rarely needed - `search_jobs` maps names itself - but handy for ambiguous cases.
---
## ⚠️ Limitations
- Data comes from public APIs in real time; an internet connection is required.
- JobSearch itself caps a request at 100 ads and offset at 2000, so one search yields at most 2100 ads - refine filters (e.g. by date) for larger result sets.
- The `remote`, `trainee` and `larling` filters rely on analysis of ad texts and may be inaccurate.
- Ad texts are mostly in Swedish.
---
## 🧪 Tech stack
- Node.js ESM, no build step
- [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk) and `zod` for tool schemas
- [JobSearch API](https://jobsearch.api.jobtechdev.se) and [Taxonomy API](https://taxonomy.api.jobtechdev.se) from JobTech
- Tests: `npm test` (`node --test`)
TDQS
Scored across 5 tools
Each tool targets a distinct layer: search_jobs for ads, get_job for a single ad's detail, job_stats for aggregates, search_taxonomy for concept IDs, and suggest for autocomplete. The only soft overlap is search_taxonomy vs suggest, since both resolve names to taxonomy concepts, but the descriptions clearly frame one as a lookup/ID resolver and the other as start-of-word autocomplete.
All names are lowercase snake_case, which is consistent at the style level. The verb pattern is mostly verb_noun (search_jobs, search_taxonomy, get_job), with minor deviations: job_stats is noun-only and suggest is a bare verb without an object, though both remain unambiguous.
Five tools is tightly scoped to a job-search domain: search, detail, statistics, taxonomy lookup, and autocomplete. Nothing feels redundant and there is no obvious operation left out that would need its own tool.
The surface covers the full read path of a job board: discovery via search_jobs, drill-down via get_job, aggregation via job_stats, plus taxonomy resolution and autocomplete. Minor gaps exist—no bulk taxonomy enumeration or employer/application lookup—but agents can work around these using search_taxonomy and suggest.