Skip to main content
Glama
kimhjort

aria-mcp-affald-horsens

by kimhjort
README.md
# aria-mcp-affald-horsens

A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server for household waste collection schedules in **Horsens Kommune**. Built for [ARIA](https://github.com/kimhjort/aria) and shareable with the community.

Example output ARIA might give Kim:
> *i aften: rest/madaffald ud (tømmes mandag den 15. juni)*
> *næste papir/pap: tirsdag den 23. juni (om 10 dage)*

## Data Source

Data is fetched from **PerfectWaste** (`https://europe-west3-perfect-waste.cloudfunctions.net`), the backend powering Horsens Kommune's waste portal. The API is **unofficial and undocumented** — it was reverse-engineered from the portal and community Home Assistant integrations. There is no guarantee of stability; Horsens might migrate providers without notice.

The legacy `horsens.renoweb.dk` ASMX endpoint (`GetAffaldsplanMateriel_mitAffald`) is **MitID-gated** and cannot be used headlessly. Only address search on that endpoint is unauthenticated, but it is useless without schedule access. PerfectWaste is fully headless with no auth.

> **Best-effort only.** Always verify collection dates at [horsens.dk/affald](https://horsens.dk/affald).

## Install & Run

```bash
npx aria-mcp-affald-horsens
```

Or install globally:

```bash
npm install -g aria-mcp-affald-horsens
aria-mcp-affald-horsens
```

Requires Node.js 20 or later.

## Tools

### `find_address`

Search for a Horsens address and return PerfectWaste address candidates (id + display name).

**Parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `query` | string | Yes | Address string to search (e.g. `"Vestergade 5"`, `"Søndergade 1, 8700 Horsens"`) |

**Returns:**

```json
{
  "query": "Vestergade 5",
  "candidates": [
    { "addressID": "39429", "displayName": "Vestergade 5, 8732 Hovedgård" },
    { "addressID": "47864", "displayName": "Vestergade 5, 8700 Horsens" }
  ]
}
```

---

### `next_collection`

Return the next upcoming collection date for each waste fraction at an address.

**Parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `address` | string | Yes | Free-text address or raw PerfectWaste addressID (digits only) |

**Returns:**

```json
{
  "addressID": "47864",
  "addressDisplay": "Vestergade 5, 8700 Horsens",
  "asOf": "2026-06-13T08:00",
  "nextByFraction": [
    {
      "fractionName": "Rest - Mad",
      "date": "2026-06-15T00:00:00.000Z",
      "dateLocal": "2026-06-15",
      "dateLabel": "mandag den 15. juni 2026",
      "daysUntil": 2,
      "putOutTonight": false
    },
    {
      "fractionName": "Papir, pap og tekstilaffald",
      "date": "2026-06-23T00:00:00.000Z",
      "dateLocal": "2026-06-23",
      "dateLabel": "tirsdag den 23. juni 2026",
      "daysUntil": 10,
      "putOutTonight": false
    },
    {
      "fractionName": "Plast og drikkekarton - Glas og metal",
      "date": "2026-06-26T00:00:00.000Z",
      "dateLocal": "2026-06-26",
      "dateLabel": "fredag den 26. juni 2026",
      "daysUntil": 13,
      "putOutTonight": false
    }
  ],
  "notice": "Data source: PerfectWaste (unofficial API, Horsens Kommune). ..."
}
```

`putOutTonight` is `true` when the collection is tomorrow — use it for reminders like *"i aften: rest/madaffald ud"*.

---

### `collection_calendar`

Return all upcoming collection events for the next N weeks.

**Parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `address` | string | Yes | Free-text address or raw PerfectWaste addressID |
| `weeks` | number | No | Look-ahead in weeks (default 4, max 52) |

**Returns:** Same fields as `next_collection` events but as a flat chronological list covering the full window.

---

## Environment Variables

| Variable | Default | Description |
|---|---|---|
| `RENOWEB_HOST` | `https://europe-west3-perfect-waste.cloudfunctions.net` | Override the PerfectWaste base URL. Useful if another kommune uses the same backend at a different endpoint. |
| `RENOWEB_MUNICIPALITY` | `615` | Municipality code. `615` = Horsens. Other PerfectWaste kommuner may use different codes. |

The `RENOWEB_*` names are intentional — they keep the interface familiar and forward-compatible if a true RenoWeb headless path is discovered later.

## ARIA MCP Config

Add to your ARIA MCP config (`mcpServers` in ARIA credentials):

```json
{
  "command": "npx",
  "args": ["-y", "aria-mcp-affald-horsens"],
  "env": {}
}
```

ARIA can then answer questions like:
- *"hvornår tømmes papir næste gang?"*
- *"hvad skal ud i aften?"*
- *"vis affaldskalender for de næste 4 uger på Vestergade 5"*

## Fragility Notes

- **API is undocumented.** PerfectWaste has no published SLA for this endpoint. It has been stable since at least early 2026, but could break without notice.
- **Municipality codes.** The `615` code for Horsens was discovered empirically. Other kommuner using PerfectWaste will have different codes.
- **Address coverage.** Some commercial/institutional addresses (e.g. Rådhustorvet 4) return empty collection data — the portal apparently only covers residential collection routes.
- **Calendar depth.** PerfectWaste typically returns ~3 months of data. The `weeks` parameter is capped client-side.

## Development

```bash
git clone https://github.com/kimhjort/aria-mcp-affald-horsens
cd aria-mcp-affald-horsens
npm install
npm run build
npm test
```

## License

MIT — see [LICENSE](LICENSE).

TDQS

A4.4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clear, distinct purpose: address resolution, next collection, and calendar of upcoming collections. No overlap.

Naming Consistency5/5

All tool names use consistent snake_case verb_noun pattern: find_address, next_collection, collection_calendar.

Tool Count5/5

Three tools are well-scoped for the domain of waste collection lookup in a single municipality—sufficient without being excessive.

Completeness5/5

The set covers address disambiguation, immediate next collection, and future planning via calendar. No obvious gaps.

Maintenance

ActivityMaintained
ResponsivenessSyncing