{
  "markdown": "> 🇨🇭 **Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide)**\n\n# SECO Labor Market MCP Server\n\n![Version](https://img.shields.io/badge/version-0.4.0-blue)\n[![CI](https://github.com/malkreide/seco-labor-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/malkreide/seco-labor-mcp/actions/workflows/ci.yml)\n[![PyPI](https://img.shields.io/pypi/v/seco-labor-mcp)](https://pypi.org/project/seco-labor-mcp/)\n[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/)\n[![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-purple)](https://modelcontextprotocol.io/)\n[![No Auth Required](https://img.shields.io/badge/auth-none%20required-brightgreen)](https://github.com/malkreide/seco-labor-mcp)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\n🌐 **English** | **[Deutsch](README.de.md)**\n\nAn MCP (Model Context Protocol) server for Swiss labor market data from **SECO** (Staatssekretariat für Wirtschaft) and **AMSTAT** via opendata.swiss.\n\n<p align=\"center\">\n  <img src=\"assets/demo.png\" alt=\"Demo: Claude queries youth unemployment via seco-labor-mcp tool call\" width=\"720\">\n</p>\n\n---\n\n## Overview\n\nThis server connects AI models to Swiss labor market statistics — unemployment rates, job seekers, open positions, youth unemployment, and occupational breakdowns — all without requiring an API key.\n\n**Primary audiences:**\n- 🏫 **Schulamt / Education planning** — youth unemployment, vocational guidance data\n- 📊 **Research & analysis** — labor market trends, cantonal comparisons\n- 🤖 **AI agents** — automated labor market monitoring and reporting\n\n**Anchor query:**  \n*\"Welche Berufsgruppen haben im Kanton Zürich die höchste Jugendarbeitslosigkeit, und welche Lehrberufe unterliegen der Stellenmeldepflicht?\"*\n[→ More use cases by audience →](EXAMPLES.md)\n\n---\n\n## Data Sources (Phase 1 — No Auth Required)\n\n| Source | Description | Status |\n|--------|-------------|--------|\n| [opendata.swiss](https://opendata.swiss/de/dataset) | CKAN catalogue; the pinned BFS table `T3.3.0.1` carries the SECO annual series | ✅ Live |\n| [arbeit.swiss](https://www.arbeit.swiss) | Monthly press reports (PDF, structured URL pattern) | ✅ Live |\n| [amstat.ch](https://www.amstat.ch) | AMSTAT reference portal | ⚠️ JavaScript SPA, no public REST API |\n| [unfallstatistik.ch](https://www.unfallstatistik.ch) | Unfallstatistik UVG (SSUV/KSUV c/o Suva) — occupational accidents and diseases | ⚠️ PDF only, no API (see below) |\n\n---\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────┐\n│                  seco-labor-mcp                     │\n│                                                     │\n│  ┌─────────────┐    ┌──────────────────────────┐   │\n│  │  FastMCP    │    │      9 MCP Tools         │   │\n│  │  Server     │◄──►│  seco_search_datasets    │   │\n│  │  (stdio /   │    │  seco_get_dataset        │   │\n│  │   SSE)      │    │  seco_get_unemployment_* │   │\n│  └─────────────┘    │  seco_get_youth_*        │   │\n│         │           │  seco_get_job_seekers    │   │\n│         ▼           │  seco_get_open_positions │   │\n│  ┌─────────────┐    │  seco_get_monthly_url    │   │\n│  │  httpx      │    │  seco_list_cantons       │   │\n│  │  async      │    └──────────────────────────┘   │\n│  └──────┬──────┘                                   │\n└─────────┼───────────────────────────────────────────┘\n          │\n          ▼\n  ┌───────────────────────────────────┐\n  │  opendata.swiss CKAN API          │\n  │  https://opendata.swiss/api/3/    │\n  │  action/package_search            │\n  │  action/package_show              │\n  └───────────┬───────────────────────┘\n              │\n              ▼\n  ┌───────────────────────────────────┐\n  │  SECO Data Resources              │\n  │  CSV / XLSX / PDF Downloads       │\n  │  (monthly labor market data)      │\n  └───────────────────────────────────┘\n```\n\n---\n\n## Where the figures come from — and what is missing\n\n**SECO is no longer a publisher on opendata.swiss.** Verified 2026-08-14:\n`organization_show` returns 404, and none of the 176 entries in\n`organization_list` is SECO. Until then the server filtered every search on\nthat organisation and therefore returned **nothing** — a name lookup that\nmisses looks exactly like an empty search.\n\nThe registered unemployed and job seekers are still SECO's figures: the **BFS\npublishes them** in table `T3.3.0.1` and names SECO in the footer. The server\nreads that table through a **pinned dataset id** (`sources.py`), checked\nagainst the live source by a live test.\n\n| Series | 2000 | 2025 |\n|---|---|---|\n| Registered job seekers (SECO) | 124.6 | 214.1 |\n| Registered unemployed (SECO) | 72.0 | 133.7 |\n| ILO unemployed (BFS) | 126.5 | 248.5 |\n\n*thousands, annual average*\n\nThe three series do **not** measure the same thing: in 2000 the ILO figure is\n1.76× the registered one. The server reports them separately and labelled, and\nnever converts one into the other.\n\n### The cantonal layer: four cantons, four schemas\n\nThere is no national monthly series — but **four cantons publish their own\nRAV figures**, each in its own portal with its own column names. For those,\n`seco_get_unemployment_overview(canton=…)` returns real values:\n\n| Canton | Granularity | from | Level | Note |\n|---|---|---|---|---|\n| **TG** | monthly | 2016-01 | canton | only series **by age class** → youth unemployment as a count |\n| **FR** | monthly | 2004-01 | canton **and Switzerland** | carries the national monthly figure as a comparison row |\n| **ZG** | monthly | 1993-01 | canton | youth unemployment only as a **rate**, not a count |\n| **ZH** | **annual** | 1991 | **municipality** | no monthly values; districts and regions sit in the same column as municipalities and are separated out |\n\n**The other 22 cantons get a named refusal** — no figure from another canton\nand no national aggregate. Partial coverage that feels complete is worse than\nnone.\n\nThe four series are **not comparable with each other** and do not add up to a\nSwiss figure: different time axes, different geographic levels, and in ZG's\ncase a rate rather than a count.\n\n**Still not available:** unemployment by occupational group, open positions as\na national series, and youth unemployment for Switzerland or for 24 of the 26\ncantons. The affected tools say so and return **no** substitute figure. These\nvalues exist interactively on [amstat.ch](https://www.amstat.ch/v2/amstat_de.html),\nwhich offers no interface a server could call.\n\n---\n\n## Tools\n\n| Tool | Description | Key Use Case |\n|------|-------------|--------------|\n| `seco_search_datasets` | Search labour-market datasets on opendata.swiss (publisher shown per hit) | Discovery |\n| `seco_get_dataset` | Full metadata + download links for a dataset | Data access |\n| `seco_get_unemployment_overview` | Registered unemployed: national annual, cantonal for TG/FR/ZG/ZH | Labor market overview |\n| `seco_get_youth_unemployment` | Youth unemployment (15–24) — **TG** (count) and **ZG** (rate) only | 🎓 Berufswahlberatung |\n| `seco_get_job_seekers` | Registered job seekers, national, annual series from 2000 | Training demand |\n| `seco_get_open_positions` | Open positions — **no national series available** | Sector analysis |\n| `seco_get_unemployment_by_occupation` | Breakdown by Berufshauptgruppe — **no machine-readable source** | 🎓 Vocational guidance |\n| `seco_get_monthly_report_url` | Generate/verify PDF report URL | Source access |\n| `seco_list_cantons` | All 26 canton codes and names | Utility |\n| `seco_get_uvg_overview` | UVG key figures on occupational accidents and diseases | Risk overview |\n| `seco_get_uvg_by_branch` | Results per NOGA 2008 economic branch | 🎓 Vocational guidance |\n| `seco_get_uvg_trends` | Ten-year accident time series per branch | Trend analysis |\n\n12 of a maximum of 15 tools.\n\n---\n\n## Unfallstatistik UVG (SSUV)\n\nThe three `seco_get_uvg_*` tools cover the risk side of the same labour market\nthe unemployment tools describe: how many occupational accidents and diseases\noccur per branch, and how that develops over ten years.\n\n**The publisher is not SECO.** The Unfallstatistik UVG is issued by the\nKoordinationsgruppe KSUV and the Sammelstelle SSUV c/o Suva, Lucerne. The\n`seco_` prefix addresses this server, not the source; every response names the\nactual publisher in its `source` field.\n\n### Architecture decision: C (dump-first)\n\nVerified live on 2026-08-05, full write-up in\n[`PROBE_REPORT_UVG.md`](PROBE_REPORT_UVG.md).\n\nThe source has **no API**. A link scan across every data page returned 165 PDFs\nand zero files with `.csv`, `.xlsx` or `.json`. opendata.swiss does not list the\nsource at all (`count=0` for six of seven search terms), and the BFS dam-api\nsilently ignores its filter parameters. What remains is machine-readable in\npractice but not by design:\n\n| Access | Format | Refresh |\n|---|---|---|\n| `schluesselzahlen_d.htm` | HTML table, 5 years, Switzerland-wide | annually |\n| `Ts{YY}.pdf` | annual edition, tables 1.2 and 2.4 by NOGA | annually, June |\n| `WirtKl_{BUV\\|NBUV}_{NN}.pdf` | ten-year series per NOGA division | annually, January |\n\nPDFs are cached for 24 h and fetched with 2s/4s/8s backoff.\n\n### What every response tells you\n\n- `source_freshness.data_year` — the **data** year, not the edition year. The\n  2026 edition reports 2024; that two-year lag is stated, not buried.\n- `totals_check` — parsed rows are summed and compared against the total\n  printed in the same publication. A broken layout shows up here instead of\n  becoming a plausible wrong number.\n- `significant` — the source marks statistically significant year-on-year\n  changes with an asterisk. That flag is preserved per data point, so a change\n  is only reported as significant where the source says so.\n\n---\n\n## Installation\n\n### Claude Desktop (stdio)\n\nAdd to `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"seco-labor\": {\n      \"command\": \"uvx\",\n      \"args\": [\"seco-labor-mcp\"]\n    }\n  }\n}\n```\n\n### Cloud / SSE\n\n```bash\npip install seco-labor-mcp\nMCP_TRANSPORT=sse PORT=8000 seco-labor-mcp\n```\n\nThe SSE server binds to **`127.0.0.1` (loopback) by default** to prevent\nNeighborJack on shared networks. For container deployments where you actually\nneed to accept traffic from outside the container, set `HOST=0.0.0.0`\nexplicitly — ideally in your Dockerfile / orchestrator config, and only behind\nan upstream proxy or firewall:\n\n```bash\nHOST=0.0.0.0 MCP_TRANSPORT=sse PORT=8000 seco-labor-mcp   # container only\n```\n\n### Development\n\n```bash\ngit clone https://github.com/malkreide/seco-labor-mcp.git\ncd seco-labor-mcp\npip install -e \".[dev]\"\npytest tests/ -m \"not live\" -v\n```\n\n---\n\n## Usage Examples\n\n### Search for youth unemployment data\n```\nTool: seco_search_datasets\nInput: { \"query\": \"Jugendarbeitslosigkeit Alter\", \"limit\": 5 }\n```\n\n### Get cantonal unemployment for Zürich\n```\nTool: seco_get_unemployment_overview\nInput: { \"canton\": \"ZH\", \"response_format\": \"markdown\" }\n```\n\n### Get monthly report URL\n```\nTool: seco_get_monthly_report_url\nInput: { \"year\": 2026, \"month\": 2, \"language\": \"de\" }\n```\n\n---\n\n## Key Concepts\n\n### Arbeitslose vs. Stellensuchende\n\n> **Eselsbrücke**: Arbeitslose ⊂ Stellensuchende — Arbeitslose sind eine Teilmenge.\n\n| Term | Definition | Dec 2025 |\n|------|-----------|----------|\n| Arbeitslose | RAV-registered, immediately available | ~149'000 (3.2%) |\n| Stellensuchende | All RAV-registered (incl. training programs) | ~233'900 |\n\n### Youth Unemployment Seasonality\n\n- **July/August**: Sharp increase (school leavers without placements)\n- **September/October**: Decline (apprenticeship starts)\n- The residual that remains after the autumn decline signals structural need for bridge programs (Brückenangebote)\n\n### Stellenmeldepflicht (since 2020)\n\nOccupations with ≥5% unemployment rate must be reported to the RAV before posting publicly. The list changes annually. This is directly relevant for vocational counseling — these professions have highest availability for Swiss job seekers.\n\n---\n\n## Portfolio Synergies\n\n| Server | Synergy |\n|--------|---------|\n| `swiss-statistics-mcp` | BFS population/employment data for deeper context |\n| `zurich-opendata-mcp` | City of Zurich-level education and social data |\n| `swiss-snb-mcp` | Economic context (GDP, wages) for labor market interpretation |\n| `fedlex-mcp` | ALV (Arbeitslosenversicherung) legislative framework |\n\n---\n\n## Known Limitations\n\n- `amstat.arbeit.swiss` has no public REST API (JavaScript SPA) → workaround via CKAN\n- Occupational/sectoral detail requires CSV download from SECO resources\n- Monthly press report URL patterns may vary for older reports\n- Cantonal sub-municipal data not available at this level\n- UVG figures come from PDF parsing — the layout was stable across the 2025 and\n  2026 editions, but a redesign can break it. The `totals_check` in every\n  response is what makes such a break visible rather than silent.\n- UVG data lags roughly two years (the 2026 edition reports 2024)\n- UVG branch detail follows NOGA 2008 and groups some divisions (`41 – 42`,\n  `77, 79 – 82`); there is no cantonal breakdown at this level\n- Detailed UVG data beyond the publications sits behind the SSUV closed user\n  group and is out of scope for this no-auth server\n\n**Phase 2 roadmap:**\n- Automatic CSV caching with 24h TTL\n- Direct XLSX parsing for cantonal breakdowns\n- Integration with `zh-education-mcp` for Schulamt-specific correlations\n\n---\n\n## Data License\n\nTwo different licences apply — the code of this server is MIT either way, but the\ndata is not covered by it.\n\n**SECO / AMSTAT data** published on opendata.swiss is under **Creative Commons\nCCZero** (public domain).\nSource: Staatssekretariat für Wirtschaft (SECO) — [seco.admin.ch](https://www.seco.admin.ch)\n\n**Unfallstatistik UVG data** is **not** openly licensed. The publication states:\n\n> «Abdruck – ausser für kommerzielle Nutzung – mit Quellenangabe gestattet.»\n> (Reproduction permitted, except for commercial use, with attribution.)\n\nThat is a non-commercial restriction with an attribution requirement. It belongs\nto KSUV/SSUV and cannot be lifted by this repository's MIT licence: the MIT terms\ncover the code, not the figures the code retrieves. **If you use this server\ncommercially, the UVG tools are not covered** — clarify directly with the\nSammelstelle (`unfallstatistik@suva.ch`). Every UVG response repeats this\nrestriction in its `source` field, because a README is not passed to the model.\n\n---\n\n## Safety & Limits\n\n| Aspect | Details |\n|--------|---------|\n| **Access** | Read-only (`readOnlyHint: true`) — the server cannot modify or delete any data |\n| **Personal data** | No personal data — all sources are aggregated, anonymous public statistics |\n| **Rate limits** | No enforced external limits; server caps queries at 20 results by default; 30 s HTTP timeout |\n| **Authentication** | No API keys required — opendata.swiss and arbeit.swiss are publicly accessible |\n| **Licenses** | SECO data under [Creative Commons CCZero](https://creativecommons.org/publicdomain/zero/1.0/) (public domain) |\n| **Terms of Service** | Subject to ToS of: [opendata.swiss](https://opendata.swiss/de/terms-of-use), [SECO](https://www.seco.admin.ch), [arbeit.swiss](https://www.arbeit.swiss) |\n| **GDPR / DSG** | Fully compliant — no personal data transmitted or stored; all data is official public statistics |\n\n---\n\n## MCP Protocol Version\n\nThe protocol version is negotiated at the `initialize` handshake by the SDK,\nnot chosen by this server. The revision it is built and audited against is\n**`2025-11-25`**, which is `LATEST_PROTOCOL_VERSION` in the pinned `mcp`\nrelease that fastmcp brings in.\n\n`tests/test_protocol_version.py` holds three things against each other: this\nline, that SDK constant, and the revision a real handshake against the server\nobject actually returns. An SDK bump that changes the revision therefore fails\nCI instead of drifting silently.\n\nThe sister servers in this portfolio pin a *pair* of revisions — a handshake\nceiling and a modern one — because `mcp` 2.x serves two protocol eras over the\nsame server. fastmcp 3.x pins `mcp` 1.x, where `mcp.types.version` does not\nexist and one revision is the whole story. `test_das_sdk_kennt_hier_nur_eine_aera`\nis tied to the SDK rather than to this paragraph and fails the day an upgrade\nbrings the two-era constants in.\n\n---\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines.\n\n---\n\n## Security\n\nSee [SECURITY.md](SECURITY.md) for the security posture and how to report a\nvulnerability.\n\n---\n\n## License\n\nReleased under the [MIT License](LICENSE) — Copyright © 2026 Hayal Oezkan.\n\n---\n\n## Author\n\n**Hayal Oezkan** · [github.com/malkreide](https://github.com/malkreide)\n\n<!-- mcp-name: io.github.malkreide/seco-labor-mcp -->\n\n<!-- BEGIN GENERATED: install -->\n## Installation\n\nRun via [`uv`](https://docs.astral.sh/uv/)'s `uvx` — no clone or manual install needed. Add to your MCP client config (`mcpServers` for Claude Desktop, Cursor and Windsurf; use a top-level `servers` key for VS Code in `.vscode/mcp.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"seco-labor-mcp\": {\n      \"command\": \"uvx\",\n      \"args\": [\n        \"seco-labor-mcp\"\n      ]\n    }\n  }\n}\n```\n<!-- END GENERATED: install -->\n",
  "bytes": 17288,
  "sha": "eac7f02971f5c5ee24869924af00a37dc72010fd17d5926218c26e637abfb1c7",
  "repo_slug": "malkreide/seco-labor-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_malkreide_seco_labor_mcp_b67017e1/readme"
}