teamhood-mcp
Official# teamhood-mcp
Read-only MCP-server voor de Teamhood Open API. Draait lokaal via stdio, zodat je API-key je machine niet verlaat.
## Waarom read-only, en hoe hard
`src/client.ts` is de enige module die het netwerk raakt. Er is geen codepad naar PUT, PATCH of DELETE.
POST kan wel, maar uitsluitend voor twee paden in `POST_QUERY_ALLOWLIST`:
- `/timelogs`
- `/boards/{boardId}/item-activities`
Teamhood modelleert die twee als zoekopdracht in plaats van als GET, omdat het filter te groot is voor een querystring. Ondanks het werkwoord zijn het leesoperaties: ze geven data terug en veranderen niets. Zonder die uitzondering kan deze server geen uren lezen, want een `GET /timelogs` bestaat niet.
De allowlist is een hardgecodeerde lijst reguliere expressies en wordt gecontroleerd voordat er een socket opengaat. Elk ander pad, inclusief `POST /items`, wordt geweigerd met een expliciete fout.
Belangrijker nog: **de Teamhood Open API heeft geen endpoint om een tijdregistratie aan te maken.** `POST /api/v1/timelogs` heet "List Timelogs" en verwacht een filter met `startDate`, `endDate` en optioneel `userIds`. Uren wegschrijven kan dus sowieso niet via deze API, door welke client dan ook. Een urenvoorstel blijft iets wat je met de hand overneemt.
## Installeren
```bash
cd teamhood-mcp
npm install
npm run build
```
## Configureren
Haal je API-URL en API-key op in Teamhood via **Company Account → Integrations**.
```bash
cp .env.example .env
# vul TEAMHOOD_API_URL en TEAMHOOD_API_KEY in
```
De server laadt dat `.env`-bestand zelf in, gezocht naast deze package en niet in je working directory. Een MCP-client start de server namelijk vanuit een willekeurige map. Geef je de waarden liever mee via een `env`-blok in je MCP-config, dan mag `.env` gewoon ontbreken: wat al in de omgeving staat, wint.
Twee soorten sleutels:
- **Account-key** reikt over het hele bedrijfsaccount. Dat is wat je nodig hebt voor een volledig overzicht.
- **Workspace-key** ziet alleen die ene workspace. Teamhood raadt die aan om de schade te beperken als een sleutel ooit lekt.
Zet de key nooit in de repository en deel hem niet in een chat.
## Koppelen aan Claude
Voeg dit toe aan je MCP-configuratie:
```json
{
"mcpServers": {
"teamhood": {
"command": "node",
"args": ["/Users/gaetanbols/Developer/Claude/Timetracking/teamhood-mcp/dist/index.js"],
"env": {
"TEAMHOOD_API_URL": "https://api-JOUWTENANT.teamhood.com",
"TEAMHOOD_API_KEY": "je-key-hier",
"TEAMHOOD_TIMEZONE": "Europe/Brussels"
}
}
}
}
```
Voor Claude Code kan het ook in één regel:
```bash
claude mcp add teamhood \
--env TEAMHOOD_API_URL=https://api-JOUWTENANT.teamhood.com \
--env TEAMHOOD_API_KEY=je-key-hier \
-- node /Users/gaetanbols/Developer/Claude/Timetracking/teamhood-mcp/dist/index.js
```
## Eerst testen zonder Claude
```bash
npm run inspect
```
Dat opent de MCP Inspector, waar je elk tool los kan aanroepen en de ruwe respons ziet.
## Tools
| Tool | Waarvoor |
|---|---|
| `teamhood_list_workspaces` | Alle workspaces met hun ID. Begin hier. |
| `teamhood_list_users` | Gebruikers, met laatste activiteitsdatum waar beschikbaar. |
| `teamhood_list_boards` | Boards binnen een workspace. |
| `teamhood_get_board_structure` | Rijen en statussen van een board. |
| `teamhood_search_items` | Tickets zoeken, om te zien of er al één bestaat voor een stuk werk. |
| `teamhood_get_item` | Eén item volledig, inclusief geaggregeerde estimation en tracked time. |
| `teamhood_get_item_activity` | Activiteitenlog van een **board** over een periode. Teamhood biedt dit per board aan, niet per item. |
| `teamhood_get_time_logs` | Geregistreerde uren over een periode, per dag gegroepeerd. Zonder `workspaceId` loopt het tool alle workspaces af. |
| `teamhood_describe_api` | Haalt de Swagger van de host-root op en toont alle paden met hun methodes. |
| `teamhood_raw_get` | Vrije GET, als noodklep wanneer een endpoint anders heet. |
## Over tijdzones
De Teamhood API levert timestamps in UTC. Een registratie die je om 00:00 Belgische tijd dateert, is 22:00 UTC de dag ervoor. In een ruwe export ziet die er dus uit alsof hij op de vorige dag hoort.
`teamhood_get_time_logs` geeft daarom bij elk tijdstip zowel de ruwe UTC-waarde als `localDate` en `localTime`, berekend in `TEAMHOOD_TIMEZONE`. Groeperen gebeurt op `localDate`, dus dagtotalen kloppen met wat je in de interface ziet.
## Als een tool een 404 geeft
De Open API verschilt licht per accountversie. De server probeert bij elk tool meerdere bekende spellingen van een pad en onthoudt welke werkte. Werkt geen enkele:
1. Roep `teamhood_describe_api` aan voor de lijst met GET-paden in jouw omgeving.
2. Test het juiste pad met `teamhood_raw_get`.
3. Voeg dat pad toe aan de kandidatenlijst in `src/index.ts`.
## Uren wegschrijven kan niet
Een eerdere versie van dit bestand schetste hoe je later een `teamhood_log_time` zou bouwen. Dat blijkt niet te kunnen: in de Swagger van Teamhood v1 staat onder Timelogs alleen `POST /api/v1/timelogs`, en dat is de leesquery. Er is geen endpoint dat een tijdregistratie aanmaakt, wijzigt of verwijdert.
Wat de API wel kan schrijven: items, boards, rijen, workspaces, attachments en relaties. Uren horen daar niet bij. Boeken blijft dus handwerk in de interface.
Zet daarbij het tijdstip op iets als 12:00 lokale tijd. Een registratie op 00:00 wordt 22:00 UTC de dag ervoor en verschuift in exports naar de verkeerde dag.
TDQS
Scored across 10 tools
Each tool targets a distinct resource (workspaces, users, items, boards, time logs, API spec) with a clear action. No two tools overlap in purpose; even get_item_activity and get_time_logs are clearly separated by content.
All tools follow a consistent teamhood_ + verb_noun pattern in snake_case. Examples like list_workspaces, get_item, and get_board_structure are uniform, with raw_get and describe_api also fitting the verb_noun convention.
10 tools is a well-scoped size for a read-only Teamhood integration. Each tool covers a necessary read operation (workspaces, users, items, boards, time logs) and the two API discovery tools provide a safety net without bloating the set.
The tool surface covers all major read operations for the domain: listing workspaces/boards/users, searching and fetching items, activity, board structure, and time logs. Minor gaps like item comments or project-level queries exist, but raw_get and describe_api mitigate these by allowing direct API access.