Skip to main content
Glama
iwsen

Arbetsförmedlingen MCP

by iwsen
README.md
# 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

A4/5.0

Scored across 5 tools

Disambiguation4/5

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.

Naming Consistency4/5

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.

Tool Count5/5

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.

Completeness4/5

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues