teamup-mcp
MCP server for the Teamup Calendar API, built with the official @modelcontextprotocol/sdk.
Tools
| Tool | Description |
|---|---|
get_events |
Retrieve events in a date range — with optional title filters (title_contains, title_starts_with, title_regex) |
create_event |
Create a new event |
update_event |
Update an existing event |
delete_event |
Delete an event |
search_events |
Full-text search across title, notes, location and who fields |
get_subcalendars |
List all sub-calendars |
find_available_slots |
Find free court slots for a given day — returns all free windows per court that fit the requested game type |
Prerequisites
- Node.js 18+
- A Teamup API key — generate one at teamup.com under Account → API Keys
- Your calendar key — visible in the calendar URL:
https://teamup.com/<calendar-key>
Installation
git clone https://github.com/your-user/teamup-mcp
cd teamup-mcp
npm install
npm run build
Running
TEAMUP_API_KEY=your_api_key node dist/index.js
During development you can skip the build step with tsx:
TEAMUP_API_KEY=your_api_key npm run dev
Claude Code integration
MCP servers are configured in ~/.claude/.mcp.json (global, all projects) or .mcp.json in your project root (project-only). Add the teamup entry inside the mcpServers object:
{
"mcpServers": {
"teamup": {
"command": "node",
"args": ["/absolute/path/to/teamup-mcp/dist/index.js"],
"env": {
"TEAMUP_API_KEY": "your_api_key_here",
"TEAMUP_CALENDAR_KEY": "your_calendar_key_here"
}
}
}
}
Setting TEAMUP_CALENDAR_KEY as an env var means you never need to pass calendar_key in individual tool calls. Restart Claude Code after saving the file.
Usage examples
List sub-calendars
get_subcalendars(calendar_key="ks123abc456def")
Get events for a week
get_events(
calendar_key="ks123abc456def",
start_date="2024-06-01",
end_date="2024-06-07"
)
Create an event
create_event(
calendar_key="ks123abc456def",
title="Team standup",
start_dt="2024-06-03T09:00:00+02:00",
end_dt="2024-06-03T09:30:00+02:00",
subcalendar_id=12345678
)
Update an event
version_idcomes from theversionfield returned byget_eventsorcreate_event. It is required by Teamup to prevent lost-update conflicts.
update_event(
calendar_key="ks123abc456def",
event_id="987654321",
version_id="abc123...",
title="Team standup (moved)",
start_dt="2024-06-03T10:00:00+02:00",
end_dt="2024-06-03T10:30:00+02:00"
)
Delete an event
delete_event(
calendar_key="ks123abc456def",
event_id="987654321",
version_id="abc123..."
)
Filter events by title
get_events(
start_date="2026-01-01",
end_date="2026-12-31",
title_starts_with="U10"
)
Available title filters (applied client-side after API fetch):
| Parameter | Example | Matches |
|---|---|---|
title_contains |
"U10" |
Any title containing "U10" |
title_starts_with |
"U10" |
Titles that begin with "U10" |
title_regex |
"^U1[02]" |
Titles matching the regex (case-insensitive) |
Search events (full-text)
search_events(
query="Burgthann",
start_date="2026-01-01",
end_date="2026-12-31"
)
Searches across title, notes, location and who fields via the Teamup /search API endpoint.
Find available court slots
find_available_slots(
date="2026-07-05",
game_type="singles",
earliest_time="09:00",
latest_time="21:00"
)
| Parameter | Type | Description |
|---|---|---|
date |
YYYY-MM-DD |
Day to search |
game_type |
singles | doubles |
Determines required duration: singles = 90 min, doubles = 120 min |
earliest_time |
HH:MM |
Earliest acceptable start time |
latest_time |
HH:MM |
Latest end of window (default: 22:00) |
Returns all free time windows per court (Belegung > Platz 1–4) that are long enough for the requested game type:
[
{
"subcalendar_id": 8137510,
"subcalendar_name": "Belegung > Platz 1",
"free_windows": [
{ "from": "09:00", "to": "11:30" },
{ "from": "14:00", "to": "21:00" }
]
}
]
Testing — Funktionstest mit echten Kalender-Daten
Die folgenden Prompts decken alle Tools ab und basieren auf dem realen Kalender ks2zsf595tyfr4313t. Da TEAMUP_CALENDAR_KEY als Umgebungsvariable gesetzt ist, wird calendar_key in keinem Aufruf benötigt.
✅ get_subcalendars — alle 8 Sub-Kalender auflisten
Prompt:
Zeige mir alle Sub-Kalender im Teamup-Kalender.
Erwartetes Ergebnis: 8 Einträge:| ID | Name ||---|---|| 7774417 | Medenspielplan || 8137510 | Belegung > Platz 1 || 8137714 | Belegung > Platz 2 || 8137813 | Belegung > Platz 3 || 8137814 | Belegung > Platz 4 || 8285738 | ASV-Halle Herren || 12586059 | ASV-Halle Damen || 13375969 | Belegung > Information |
✅ get_events — Einträge in einem Datumsbereich
Prompt:
Hole alle Einträge vom 01.07.2026 bis 31.07.2026.
Erwartetes Ergebnis: Mehrere Einträge, darunter:
U10 : TC Greding II(05.07.2026, Platz 1 + Platz 2)U10 : TeG Altmühlgrund II(12.07.2026, Platz 1 + Platz 2)
✅ get_events mit title_starts_with — Titelfilter
Prompt:
Zeige alle Einträge in 2026, deren Titel mit "U10" beginnt.
Erwartetes Ergebnis: Genau 9 Einträge (einige doppelt wegen Mehrfach-Platz-Belegung):
| Datum | Titel | Kalender |
|---|---|---|
| 03.05.2026 | U10 : 15:00 Uhr TSV Pyrbaum | Medenspielplan |
| 17.05.2026 | U10 : TV Hilpoltstein II | Belegung > Platz 3 |
| 17.05.2026 | U10 : TV Hilpoltstein II | Belegung > Platz 4 |
| 14.06.2026 | U10 : 14:00 Uhr TSV Freystadt | Medenspielplan |
| 28.06.2026 | U10 : 15:00 Uhr TSV Burgthann | Medenspielplan |
| 05.07.2026 | U10 : TC Greding II | Belegung > Platz 1 |
| 05.07.2026 | U10 : TC Greding II | Belegung > Platz 2 |
| 12.07.2026 | U10 : TeG Altmühlgrund II | Belegung > Platz 1 |
| 12.07.2026 | U10 : TeG Altmühlgrund II | Belegung > Platz 2 |
Hinweis: Die doppelten Einträge sind kein Fehler — das Heimspiel belegt jeweils zwei Plätze gleichzeitig.
✅ search_events — Volltextsuche
Prompt:
Suche nach "Burgthann" in 2026.
Erwartetes Ergebnis: 1 Eintrag:
U10 : 15:00 Uhr TSV Burgthannam 28.06.2026 (Medenspielplan)
✅ find_available_slots — freie Plätze finden
Prompt:
Wann ist am 05.07.2026 ein Platz frei für ein Einzel? Ab 09:00 Uhr.
Erwartetes Ergebnis: Platz 3 und Platz 4 ganztägig frei (Platz 1 + 2 sind durch das U10-Spiel belegt):
Belegung > Platz 3:
09:00 – 22:00
Belegung > Platz 4:
09:00 – 22:00
✅ create_event — neuen Eintrag anlegen
Prompt:
Lege ein Einzel-Training auf Platz 2 am 20.08.2026 von 10:00 bis 11:30 Uhr an. Titel: "Training Lars".
Erwartetes Ergebnis: Neues Event mit id und version-Feld in der Antwort — diese werden für Update und Delete benötigt.
✅ update_event — Eintrag bearbeiten
Prompt:
Verschiebe das Training vom 20.08.2026 auf 11:00–12:30 Uhr. (event_id und version_id aus dem vorherigen Schritt verwenden)
Erwartetes Ergebnis: Geändertes Event mit neuer version-ID zurück.
✅ delete_event — Eintrag löschen
Prompt:
Lösche das Training vom 20.08.2026 wieder. (event_id und version_id aus dem Update-Schritt verwenden)
Erwartetes Ergebnis: Bestätigung Event … deleted successfully.
Project structure
src/
index.ts # MCP server entrypoint & tool handlers
teamup-client.ts # Typed Teamup REST API client
dist/ # Compiled output (after npm run build)