mcpbeat Sign in

Zooza MCP Server

answering

Zooza MCP Server is answering right now. Last checked 1 min ago. It exposes 35 tools. Last commit 16 Sep 2026.

MCP server for Zooza — class scheduling, attendance, and booking for activity businesses.

Uptime history 47 days of history
47 days agonow
100.0%
Uptime 24h
92 of 92 checks
35
Tools
read from the server
228 ms
Response time
average over 24h
6
Stars
last commit 16 Sep 2026

What changed 51

Every tool that appeared, vanished or quietly changed what it asks for. Recorded since 26 August 2026. No other catalogue keeps this.

8 Sep 3 tool descriptions were rewritten classes_find_classes, sessions_find_events, whoami
8 Sep a tool appeared trainers_add_helpers
8 Sep a tool changed version
27 Aug 4 tool descriptions were rewritten classes_find_classes, sessions_find_events, sessions_update and 1 more
27 Aug 3 tools changed the parameters they ask for classes_find_classes, sessions_find_events, sessions_update
27 Aug a tool changed version
26 Aug 14 tools appeared bookings_add_lead, classes_add_course, classes_find_resource and 11 more
26 Aug 10 tools disappeared classes_commit_update, classes_find_billing_periods, classes_find_places and 7 more
26 Aug 8 tools changed the parameters they ask for bookings_find, classes_commit_class, classes_find_classes and 5 more
26 Aug 5 tool descriptions were rewritten bookings_find, classes_find_classes, classes_find_courses and 2 more
and 1 more, back to 26 August 2026

What the code does

We read the source, 20 h ago · tools taken from the live server · rules 3dff92dd89df

Capabilities

What this server is able to do. For an MCP server this is often the job itself — a terminal server runs commands because that is what it is for. Listed so you know what you are plugging in, not as an accusation.

A line of code looks encrypted src/icon.ts:11
  "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAAABmJLR0QA/wD/AP+gvaeTAAAPaklEQVR4nO3de3BU5RnH8fd5T64SJNzE2EQ04bIJBBGpVQwgdSMXk3TGzvQ2HXuZOrYDSqUjo7ResNgOdmynt2k77UzbcaYFQwsqWojNeG1taU2lWETUhCAmXDaBBEJCsu/79o+kJFBCNsme9zl73t/nL1TI8xDz3ZzN2XOWjDECwFWSewEATggAnIYAwGkIAJyGAMBpCACch…
Dumps the whole environment src/config.ts:34
  for (const [key, value] of Object.entries(process.env)) {
Long encoded blob in source src/icon.ts:11
  "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAAABmJLR0QA/wD/AP+gvaeTAAAPaklEQVR4nO3de3BU5RnH8fd5T64SJNzE2EQ04bIJBBGpVQwgdSMXk3TGzvQ2HXuZOrYDSqUjo7ResNgOdmynt2k77UzbcaYFQwsqWojNeG1taU2lWETUhCAmXDaBBEJCsu/79o+kJFBCNsme9zl73t/nL1TI8xDz3ZzN2XOWjDECwFWSewEATggAnIYAwGkIAJyGAMBpCACch…

Is this your server and something here is wrong? Tell us — corrections are free and do not require a plan.

This code can reach further than it looks

We found places where it runs commands, builds paths or queries from values it is given. None of that is a flaw by itself — it becomes one when the code changes, and code changes quietly between releases. We re-read it on every one.

Three servers free · no card

Connect this server

Endpoint below is the one we actually reach during checks — not the one copied from a README. Last verified 1 min ago.

run in your terminal
claude mcp add mcp-server --transport http https://mcp.zooza.app/mcp
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "mcp-server": {
      "url": "https://mcp.zooza.app/mcp"
    }
  }
}
~/.codex/config.toml
[mcp_servers.mcp-server]
url = "https://mcp.zooza.app/mcp"
.cursor/mcp.json
{
  "mcpServers": {
    "mcp-server": {
      "url": "https://mcp.zooza.app/mcp"
    }
  }
}
.vscode/mcp.json
{
  "mcpServers": {
    "mcp-server": {
      "url": "https://mcp.zooza.app/mcp"
    }
  }
}

Available tools 35

Read directly from the server with tools/list, grouped by what they act on. If a tool disappears, we record the date.

classes
classes_add_course
Create a new programme (course) — the top-level container in Zooza that holds pricing, payment settings, and booking-form configuration. Classes and sessions are added inside it afterwards; a programme cannot accept bookings until it has at least one class. IMPORTANT routing rule: only create a programme for a genuinely NEW product or offering. If the user is re-running an existing programme — new term, new time slot, new venue, new instructor — do NOT create a programme; create a class inside the existing programme instead (classes_preview_schedule → classes_commit_class; the class inherits all programme settings). This tool asks only the essentials; Zooza defaults everything else, and settings can be changed later with classes_update_course_settings. The new programme is created public with online booking enabled. Summarise name, kind, and price to the user and get their OK before calling.
classes_commit_class
Writes a class to api-v1 in one shot: creates the schedule, attaches any selected payment templates (bundled inline), and posts the assembled events array. Call this only after the user has confirmed the class shell (from `classes_preview_schedule`) and the full event list (accumulated from one or more `classes_preview_events` calls). For lead-collection classes, pass `events: []`. Returns the created schedule's id and url plus the list of created event ids. If api-v1 silently skips any events (a known quirk), the tool surfaces the mismatch as an error so the caller knows the partial state. `schedule.name` is OPTIONAL — omit unless the user explicitly asked for a custom class name. api-v1 auto-renders `{course_name} {class_name} {session_dates}` end-user-facing when name is absent.
classes_find_classes
Search this company's CLASSES — the scheduled groups inside a programme (a "class" / "group" / "skupina"; internally a *schedule*) — by name (substring) and resolve them to a `schedule_id`. Reach for this whenever the user names a specific group rather than a whole programme ("the Nejaké class", "the Monday 5pm group", "her Wednesday ballet class"), or whenever a downstream tool needs a `schedule_id` — most importantly `comms_send_message` targeting everyone in one class (`audience.schedule_id`). This is the missing middle rung between `classes_find_courses` (finds the PROGRAMME → `course_id`) and `sessions_find_events` (finds individual dated SESSIONS → `event_id`): a class is one recurring group within a programme, made of many sessions. Optionally narrow by `course_id` (classes inside one programme), `trainer_id`, `place_id`, `day` of week, `registration_type`, `in_trial: true` (only classes currently offering a TRIAL), `active_only: true` (exclude classes whose schedule has ENDED), or `lead_only: true` (only lead-collection pipelines). To answer "the latest classes that actually have sessions" in ONE call, combine `with_sessions: true` (only classes whose schedule has ≥1 session) with `sort: "created_desc"` and a `page_size` — no need to scan `sessions_find_events`. `sort` also takes created_asc / date_asc / date_desc / name_asc / registrations_desc. Returns a slim list — `{schedule_id, name, course_id, start, end, time, trainer_id, trainer_name, place_id, place_name, capacity, registrations_count, sessions_count, status, in_trial, registration_url, schedule_type}` — enough to disambiguate when several classes share a name, never enough to mutate. `sessions_count` is the class's number of sessions (a stored/materialised count — fine for overview and "how many", may lag a very recent edit; chain `sessions_find_events` for an exact live count). `schedule_type` tells a real class (`fixed_period`) from a lead pipeline (`lead_collection`) — use `lead_only: true` to find the pipeline `bookings_add_lead` needs. `registration_url` is the public link a prospect clicks to book that specific class (empty when the class isn't publicly bookable or the company has no registration widget). Combine filters in ONE call — e.g. `{place_id, in_trial: true, active_only: true}` returns the bookable trial classes at a venue in a single query; do not split them across separate calls. `course_id` is returned but not the course name (resolve it with `classes_find_courses` if you need it). `additional_trainers` lists the class's ADDITIONAL lecturers — people who work it alongside the main instructor in `trainer_id`/`trainer_name` — as `{trainer_id, trainer_name, role}`, `[]` when there are none. At class level this is the eligibility roster: it says who may work the class, NOT which sessions each one actually works — for that call `sessions_find_events` with the `schedule_id` and read each session's own `additional_trainers`. Roles render as `secondary` = "Secondary instructor", `assistant` = "Assistant", `helper` = "Assistant instructor", `trainer` = "Instructor". To change the roster use `trainers_add_helpers`. By default returns active + paused (inactive) classes; pass `include_archived: true` to search archived classes instead. Does NOT create or change classes (that is `classes_preview_schedule` → `classes_commit_class`) and does NOT list a class's sessions (use `sessions_find_events` with the `schedule_id`).
classes_find_courses
Search the company's courses by name (substring match) and optionally by registration_type. Returns a slim list of matches — `{id, name, registration_type, target_audience, price, schedules_count, ...}` — enough to disambiguate, not enough to act. Use this whenever the user names a course in natural language; never demand a raw course_id. Archived courses are excluded by default (pass `include_archived: true` to opt in). Pagination defaults to page 0, page_size 25 (max 200); `truncated: true` is returned when more matches exist than the current page reveals. `registration_type` business meanings (when filtering, AND when surfacing results to the user — always translate to these terms, never show the raw enum value): - `single` — drop-in / per-session: customer books one event at a time. - `full2` — full-course enrollment: customer signs up for the entire course/schedule in one go. - `open` — open-ended / membership: no fixed enrollment window; customer joins and stays.
classes_find_resource
Resolve a NAME the operator said into an id, for four kinds of company-level records. Pick `kind`: - `place` — venues. Returns `{id, name, city, street, rooms: [{id, name, capacity}]}`. Rooms are inlined because picking a venue is usually followed by picking a room. Filters: name, city. - `billing_period` — term blocks (e.g. "Autumn 2026"). Returns `{id, name, active, period_start, period_end}`; either date may be null, an open-ended period is valid. Filter: name. Match on the DATES, not just the name — a period covering the term you want may be named anything. - `trainer` — team members assignable to classes. Returns `{id, full_name, email, active, virtual}`. Filters: name, place_id, course_id. **Virtual trainers** are always included regardless of place/course filters — they are system-wide placeholders with `virtual: true`, a synthetic id (>= 9000000000000) and no email. Pick one when the operator says "we'll decide later", "no trainer yet", "TBD", "unassigned", "guest", "external speaker". Three ship by default: 'To be decided', 'Trainer unassigned', 'Guest trainer'. - `trainer_rate_type` — named pay rates (e.g. "Hourly", "Per class"). Returns `{id, name, minutes, type}`. This is the ONLY way to turn a rate the operator names into the `trainer_rate_type_id` that classes_update and sessions_update need — NEVER guess that id. `include_inactive` (place: n/a) defaults false. Sending a filter that does not apply to the chosen kind returns the list of filters that do. For PROGRAMMES use classes_find_courses, for CLASSES classes_find_classes, for people enrolled use bookings_find — those are separate, richer tools.
classes_list_schedule_patterns
Returns all valid field values for building class schedules and payment plans in Zooza. No Zooza API call — hardcoded from Events_Preview.php and Payment_Schedule.php. Call this BEFORE classes_preview_schedule or classes_commit_class to avoid validation errors. Critical: weekdays must be 3-letter lowercase (mon/tue/wed...), NOT 'monday' or '1'. Critical: until_date and count are mutually exclusive — sending both causes an API error. Examples: domain="event_generation" → cadences, weekdays, time format; domain="payment_schedule" → schedule types and billing frequencies; empty call → both sections.
classes_preview_events
Expands one or more recurrence patterns and/or ad-hoc dates into the concrete list of class sessions, honouring holiday-skip flags. Stateless — performs no writes. Call this once per pattern the user describes during class creation. Accumulate the returned sessions across multiple calls (Claude side) until the user says they're done, then pass the full list to `classes_commit_class`. Each block must carry EXACTLY ONE of `count` (stop after N sessions) or `until_date` (stop on a fixed date) — count mode is preferred when the user says "X sessions". A top-level `to_date` acts as a fallback `until_date` for any block that omits both. `place_id` is required so api-v1 can apply the correct subdivision-scoped school-holiday calendar. When you SHOW the expanded sessions to the user, render them as a weekly GRID (days across the top, time down the left — the Zooza app calendar layout): one representative week with the run range + session count in a caption, NOT a flat date list — unless the user explicitly asks to see every date.
classes_preview_schedule
Resolves a new class's *schedule shell* — the course, venue, trainer, capacity, prices, billing period, and default payment templates — and returns the result alongside any warnings. Performs no writes. Use this first in a class-creation flow to confirm the basic class settings with the user before collecting session dates via `classes_preview_events` and committing via `classes_commit_class`. Defaults are copied from the parent course where the caller hasn't specified them (capacity from `target_audience`, prices from the course's pricing fields). Always surface the `warnings[]` array to the user — entries about `online_registration` and `billing_period_id` are real decisions to confirm, not noise. For lead-collection classes (`schedule_type: lead_collection`), the events step is skipped entirely after this preview. `name` is OPTIONAL — do NOT pass it unless the user explicitly asked for a custom class name. End-user-facing display is auto-rendered by api-v1 as `{course_name} {class_name} {session_dates}`, so leaving it blank gives users the most informative label by default. Only set `name` when the user says something like 'call it "Morning Yoga Group A"'.
classes_update
Edit one or more existing classes (a "class"/"timetable" is the recurring group within a programme) — name, price, registration fee, capacity, make-up/replacement extra capacity ("počet miest navyše pre náhradné hodiny" → extra_capacity/extra_capacity_usage, NOT registrations_cap, which caps the NUMBER OF REGISTRATIONS), registration-count cap, billing period, online-registration, status — and/or instructor, venue, or session duration. TWO CALLS. First WITHOUT `token`: returns a preview of exactly what changes and how many sessions are affected, plus a single-use token. Show it to the operator and get explicit approval. Then call again with `token` + `confirmed: true` to apply — send nothing else, the change is frozen in the plan. To alter anything, run the first call again. Changing instructor/venue/duration forces a `session_scope` choice about existing sessions: "upcoming" (this class + its future sessions — usually what people mean), "all" (every session incl. past), or "class_only" (re-advertise the class but leave existing sessions on their old value — the #1 cause of "I changed it but the sessions still show the old value", so only pick it deliberately). This scope is the OPERATOR's call, not yours: when they haven't stated one, PRESENT the three options and their consequences and let them choose — do NOT silently pick a scope, and do NOT stall. If the operator asks what a cascade edit will do before applying, explain this same taxonomy. Handles one class or many at once. To edit specific individual sessions (move one date, change one session's room), use sessions_update instead. To cancel sessions, use the cancellation tools.
classes_update_course_settings
Change the settings of an existing programme (course) — pricing, online booking, make-up sessions, trial, auto-enrolment, attendance, feedback, basic info, or archiving. Works one section at a time, like the settings tiles in the Zooza app. TWO CALLS. First call WITHOUT `token`: returns a diff of current → proposed values, warnings, and a single-use token. Show that diff to the user and get their approval. Second call with `token` + `confirmed: true`: applies it. Send nothing else on the second call — the token already carries the change. The token expires in 15 minutes; if it is expired or used, run the first call again. Resolve the programme first with `classes_find_courses` (needs `course_id`). This tool edits the PROGRAMME level — rules inherited by all its classes. To change one class/group (capacity, venue, instructor, time), use the classes tools instead. Some sections only apply to "booking for full programme duration" programmes: trial, make-up sessions, auto-enrolment.
sessions
sessions_add_summary
Write a post-session summary on one event. Two independent fields: - `public_summary` — visible to attendees / parents via their in-app Zooza feed. Use when the user says "write a summary for the parents," "send a recap," "note for the families," etc. After write, every attendee's Person_Feed gets a `SUMMARY_PUBLIC` entry — parents see it in their client portal. - `internal_summary` — admin / team only. Use when the user says "add a note for the team," "private note," "reminder for next week," etc. Not visible to parents. At least one of the two must be provided. Both can be written in one call — the tool fans out the two PUTs api-v1 requires (the upstream endpoint dispatches on which field is in the body, so they cannot be combined). The tool checks the caller's role (**owner / assistant only** — trainers (`member`) cannot write summaries) and the event's `summary_public_locked` flag before writing; refuses cleanly when blocked. Returns the post-write state so you can confirm to the user what's now visible to whom. **Pairs naturally with `sessions_mark_attendance`.** After marking attendance for a session, offer to write a summary (always optional in V1; no api-v1 rule makes it mandatory). Don't volunteer a summary for an event that already has one (`summary.public_set=true` in the sessions_get_attendance / sessions_mark_attendance result) unless the user explicitly asks to update it.
sessions_find_events
List **events** (scheduled sessions of classes) in the caller's company. Use this whenever you need to resolve an `event_id` from natural language ("my next class," "Monday's ballet," "all swim sessions this week," "Sarah's classes tomorrow") before chaining into another tool like `sessions_get_attendance` or `sessions_mark_attendance`. With no filters at all, returns the company's **upcoming** scheduled sessions (from today onward, earliest first) — not just the caller's — so a bare call stays near-term instead of dumping years of history. **Any** filter you add returns the FULL matching set, including PAST sessions: pass a `schedule_id` to get a class's entire history (past + future), or use `from`/`to` for an explicit window. There is no `past` flag — past sessions are just a range with `from` set early (or omitted alongside another scope). Filters cover date window, course, schedule, trainer, place, room, segment, billing period, status, and event-type (over-capacity, substituted, cancelled, etc.). Each returned row includes denormalised names (trainer, place, event-number), the event's date and duration, `capacity`, `free_spots` (remaining places = capacity − going, or null for open/unlimited events — use this to answer "which sessions still have space"), and an `attendance_counts` object (`going`, `attended`, `noshow`, `canceled`, `canceled_late`, `waitlist`). Read-only — does not modify events. **Additional lecturers.** Two separate fields, and they mean different things. `additional_trainers` = who is actually working THAT session alongside the main instructor. `class_additional_trainers` = the parent class's roster of people ELIGIBLE to work it, who are not necessarily on that session. Answer "who is helping on Wednesday?" from `additional_trainers`, never from the roster. Both are always arrays (`[]` = nobody), and neither includes the main instructor, who stays in `trainer_id`/`trainer_name`. Each entry is `{trainer_id, trainer_name, role}`; `role` is the raw enum — show it to operators as `secondary` = "Secondary instructor", `assistant` = "Assistant", `helper` = "Assistant instructor", `trainer` = "Instructor". `trainer_name` can be null if the lookup failed — that is not proof the trainer is gone. To CHANGE any of this, use `trainers_add_helpers`. **Critical: "my sessions" / "what am I teaching" / "my classes today".** When the user is asking for THEIR OWN sessions (any first-person framing), you MUST pass `trainer_id` matching `whoami.identity.user_id`. Without it, this tool returns every trainer's events in the company — which is almost never what the user meant when they said "my." The only exception: when the caller's role is `member` or `external_member`, the server silently auto-scopes to their assignments anyway; `meta.scoped_to` in the response flags when this has happened. Filter notes: - `trainer_id` matches across FIVE trainer relationships including pre-substitution and schedule-level extras. Treat it as "events trainer X is connected to," not strictly "events trainer X currently teaches." - `status` uses raw db terms: `scheduled` (default — only state attendance can be tracked on), `unplanned` (includes cancelled events), `finished`, or `any`. - `segment_id=[0]` is a sentinel matching events with NO segment assignment. - Counters in `attendance_counts` may be sub-second-stale; for real-time counts on one event, chain into `sessions_get_attendance`. DISPLAYING A CLASS'S TIMETABLE: when the user wants to SEE a class's sessions (e.g. viewing or COPYING a class), render them as a weekly GRID — days across the top (Mon–Sun), time down the left, like the Zooza app calendar — collapsed to the weekday+time pattern with the run range + session count in a one-line caption; list individual dates only if the user explicitly asks. (Display only — ignore when you are merely resolving an event_id to chain into another tool.)
sessions_get_attendance
Read who's enrolled in **one event** (a single session of a class) and their current attendance, so you can show the list and then mark it. Pass an `event_id`; the tool returns each enrolled attendee, their current attendance value (if already marked), and per-row context the LLM needs to mark attendance correctly: `allowed_statuses[]` (the statuses the **current caller** is permitted to set for THIS attendee), `is_trial` / `is_last_trial_session` flags, warnings about cross-company or cascade-sensitive (full2) cases, and — for open-type registrations only — `entrance_voucher` info (how many unused vouchers the attendee has, and whether one is already spent on this event). Use this **before** `sessions_mark_attendance` whenever the user has not already dictated the full list of attendees and marks — typically: "open attendance for X," "who's enrolled in tomorrow's class," "show me Monday's attendance." If the event's course has attendance tracking disabled, the tool returns an `attendance_tracking_disabled` error rather than an empty list. This tool is read-only — it never writes attendance, notes, or summaries. **Talking to the user — vocabulary.** Zooza's customers are activity brands — dance, swim, language, sport, STEAM schools. Call this **"attendance," "the attendance list," "the class list," or "who's coming."** Don't expose the tool name or use sports/HR jargon ("roster") — it reads as foreign to these businesses. When the user asks to "see attendance" / "open the register" / "who's in Monday's class," just call this tool and render the list directly. **Attendee vs client (critical for children's-class programmes).** Each row carries TWO people: - `attendee` — who actually shows up to the session. Often a child (Zooza data-model name: `customer`). May have `user_id: 0` when they aren't a registered account holder, which is normal for children. `attendee.date_of_birth` is available. - `client` — the account holder / payer (Zooza data-model name: `buyer`). Usually the parent. Has a real `user_id`. Contact info (`email`, `phone`) lives on the client when the attendee is a child; copy from client when speaking to / messaging the family. - `display_name` — a pre-formatted one-line label. When attendee == client (adult attending themselves), just the one name. When they differ, `attendee_name (client_name)` — e.g. `"Jozko Jozko (Martin Rapavy)"`. Use this when listing attendees; the LLM doesn't need to compose it from scratch. Response shape notes: - `allowed_statuses[]` already factors in the caller's role, `company.trainer_attendance_management`, and the row's cross-company state. Do not propose a status not in this array — refuse locally and explain instead of calling `sessions_mark_attendance` to discover the constraint. - `is_last_trial_session` is currently `null` in V1 (derivation requires either a new api-v1 field or extra per-row lookups; deferred). Treat `is_trial=true` as the trigger for caution — a future enrichment will tighten this. - `entrance_voucher` is non-null only when `course.registration_type="open"`. Check it before setting `sessions_mark_attendance`'s `use_voucher=true` on a `going` write. - `summary` block at the top level surfaces whether this event already has a public / internal session summary (`public_set` / `internal_set`), whether the public one is locked, and whether the caller's role is permitted to write summaries (`writable_by_caller`). After the user has marked attendance, the LLM can use this to offer `sessions_add_summary` as a follow-up when appropriate.
sessions_mark_attendance
Record per-attendee attendance for **one event** (a single session of a class — e.g. "Monday Ballet on 2026-06-03 at 09:00"). Pass an `event_id` and a list of attendees, each with their own attendance value (`attended`, `noshow`, `canceled`, `going`, `ignore`). Each value is set on **that one attendee for that one event**, never on the event as a whole. The tool writes each attendee individually and returns a per-row outcome. Use this **after** you already know the event and the attendees you want to mark — typically because the user dictated them or because you previously called `sessions_get_attendance`. If you don't yet know which event or who's enrolled, call `sessions_find_events` or `sessions_get_attendance` first. This tool does **not** cancel or reschedule the event itself or handle trialist follow-ups — those are separate tools. **Follow-up chaining.** The response includes a top-level `summary` block with `public_set` / `internal_set` / `writable_by_caller` flags. After a successful mark, if `summary.public_set=false` AND `summary.writable_by_caller=true`, proactively offer the user the option to write a parent-visible recap via `sessions_add_summary`. If `writable_by_caller=false`, don't offer (the caller's role can't write summaries). If `public_set=true`, don't volunteer an update unless asked. **Trial follow-ups.** A per-row `pending_action: "trial_followup"` (with `todo_id`) means that attendee just completed their trial by being marked `attended` — a follow-up (parent feedback + continuing-class recommendation) is now pending. Tell the user it's waiting and offer to handle it; the attendance skill resolves it against the todo. This tool only surfaces the hint — it does not orchestrate the follow-up. If the field is absent, there's nothing pending. Attendance value semantics: - `attended` — attendee was present. - `noshow` — attendee did not show up and did not warn. - `canceled` — attendee cancelled (admin-recorded). Triggers server-side make-up credit creation automatically when the programme allows it; do not call any other tool to issue credits. - `going` — pre-event RSVP / "planning to attend." Restricted for member/receptionist roles under `trainer_attendance_management="limited"`. - `ignore` — hide this event from the attendee's history (Zooza-specific; rare). `use_voucher` is a tentative V1 design: only meaningful when `attendance="going"` AND `course.registration_type="open"`. Check the attendee's `entrance_voucher.unused_entrance_vouchers > 0` (from `sessions_get_attendance`) before setting it to true; the server silently downgrades to cash debt when no voucher is available.
sessions_update
Edit specific individual sessions (events) of a class, OR add new sessions to a class. Two modes, one tool. EDIT-MODE — pass `event_ids` + `changes`: reschedule a session's date/time, or change a hand-picked session's instructor, venue/room, block, or duration. Works on one session or a chosen set. ADD-MODE — pass `schedule_id` + `sessions`: CREATE one or more new sessions on an existing class (e.g. "add one more session at the end", "add a make-up class on 2026-05-04"). Each new session needs a `date`; its time, duration, trainer, venue and room default from the class. To append after the last session, first resolve the class's latest session with sessions_find_events, then pass the next date. New sessions are created billable so a priced class keeps charging. The two modes are mutually exclusive — send event_ids/changes OR schedule_id/sessions, never both. TWO CALLS either way. First WITHOUT `token`: returns a preview (per-session before→after for edits, or the list of sessions to be created for adds) plus a single-use token. Show it to the operator and get explicit approval (and, if `notify` is set, confirm that clients will be emailed). Then call again with `token` + `confirmed: true` to apply — send nothing else, the plan is frozen. Use EDIT-MODE when the user points at particular sessions ("move next Tuesday's class to Wednesday 5pm", "give Friday's session to Jana"). To change an attribute across ALL or all upcoming sessions of a class in one go, use classes_update with session_scope instead. To cancel sessions, use the cancellation tools — this tool does not cancel.
comms
comms_find_replies
Read inbound replies a customer has sent back to Zooza emails, and optionally mark a reply handled. Use it to see whether a lead responded and what they said — filter by the lead's registration id, sender email, state (unread / todo / resolved), or date. To act on a reply, pass `mark_reply_id` + `mark_state` to flag it `todo` (needs a human) or `resolved` (handled), or `read`. Replies only appear here if the original email went out through Zooza tied to that registration. This does NOT send anything — use comms_send_message to reply. The idempotency pattern: read `unread` replies, act, then mark `resolved` so the same reply isn't handled twice.
comms_list_merge_vars
Returns all valid merge variables for Zooza message templates (email, SMS, WhatsApp). Format: *|VARIABLE_NAME|* (MailChimp-compatible). No Zooza API call — hardcoded from Merge_Vars::merge_vars() in api-v1. Use this BEFORE writing any message template to get correct variable names. Claude must not invent variable names — only variables listed here are valid. Examples: category="financial" → payment and balance vars; medium="sms" → SMS-safe vars with HTML warnings; empty call → full catalogue grouped by category.
comms_list_templates
Lists the automated email templates Zooza sends to this company's clients — registration confirmations, trial follow-ups, cancellation notices, session reminders, loyalty/discount emails, and custom templates. For each template returns its trigger `type`, subject line, and whether the company uses the stock Zooza default or has customized it. Use this to see which automated emails exist, check what has been customized, or look up a template's `type` before previewing or sending it (comms_send_message accepts that `type`). This tool only lists email templates — for the merge variables (*|FIRST_NAME|* etc.) usable inside template bodies, use comms_list_merge_vars instead. Read-only; sends nothing. Bodies are full HTML and large — only set include_body when the user asks to see template content.
comms_send_message
Email clients of this company. Describe the audience (a course/programme, a class schedule, a specific booking, one client, a saved segment, an ad-hoc cohort, or course-level labels) and the content (an existing template `type` from comms_list_templates, or a custom subject + body which may use *|MERGE_VAR|* tags from comms_list_merge_vars). TWO CALLS. First WITHOUT `token`: sends NOTHING. Returns the estimated recipient count, a sample of recipients, the content as it will be sent, warnings (unknown merge tags, zero recipients), and a single-use token. Show that plan to the operator and get explicit confirmation. Then call again with `token` + `confirmed: true` to actually send. Calling the first form again with adjusted filters is free and repeatable — refine the audience that way rather than guessing. LARGE SENDS need a SECOND confirmation. If the recipient count exceeds the approval threshold, the sending call returns `requires_second_confirmation: true` with the count and job id and sends NOTHING yet. Show the operator the exact recipient count and ask again (e.g. "Send to all 105 clients?"). Only after they explicitly agree, call once more with the SAME token, `confirmed: true`, and `confirm_large_send: true`. If they decline, send nothing. Resolve names to ids first: classes_find_courses for a course/programme → course_id, classes_find_classes for a class/group by name → schedule_id, sessions_find_events for a single session → event_id; never guess ids. When the operator names an ad-hoc cohort rather than the whole company — "everyone who hasn't paid", the unpaid roster, the waitlist, this week's sign-ups — resolve it with bookings_find and pass the resulting registration_id LIST as audience.registration_id. Reserve audience.whole_company for genuinely company-wide sends; do NOT use it as a shortcut for a named subset, or you email far more people than the operator asked for.
bookings
bookings_add_lead
Create a LEAD — a lightweight registration on a lead-collection schedule — for a prospective customer, from their name and email. Use this to capture an inbound enquiry as a trackable Zooza record you can later label, message, and check for conversion. It does NOT enrol the person in a real class, take payment, or email the customer (the server authenticates with an App-type key, which sends no customer communication). It works ONLY against schedules whose type is `lead_collection`; for a genuine class booking, or anything that should charge or notify the customer, do NOT use this tool. Not idempotent — calling twice creates two leads, so the caller must guard against re-processing the same enquiry. Returns the new registration id (the `order_id` that `comms_find_replies` and `labels_mark` consume downstream).
bookings_find
Find this company's bookings — a client's enrolment in a class (registration; "prihláška"/"Buchung") — and resolve them to a `registration_id`, or a client to a `user_id`. Use for "is X enrolled?", "who's in this class?", "who hasn't paid?" (set `payment_status:["unpaid","partially_paid"]`), and "find client X". Filter by `search` (loose: name/email/phone) or `name`, by `course_id`/`schedule_id` (resolve via classes_find_courses / classes_find_classes), `user_id`, `registration_id` (one exact booking by its id), `status`, `payment_status`, or booking date with `created_from`/`created_to` (the "new registrations this week" lever). `distinct:true` returns one row per client (→ `user_id`) for person lookups. Chain a result's `registration_id` or `user_id` straight into comms_send_message (`audience.registration_id` / `audience.user_id`). Class/programme NAMES aren't returned — resolve the ids via classes_find_* if you need them. Defaults to active enrolments; guest, waitlist, canceled and deleted are excluded unless you pass `status`. Read-only — does not create or change bookings.
setup
setup_add_payment_template
Create a company-level payment plan template ("splátková šablóna") — the object that defines HOW a programme's price is collected: in how many instalments, how often, with what discount and rounding. A programme set to instalment collection produces NO instalment schedule until a template is attached, so this is the step that makes instalment billing actually happen. CRITICAL — the template does NOT carry the price. The amount always comes from the programme/class; the template only says how to split it. So "€200 in 4 × €50" is: programme price 200 (set via classes_add_course or classes_update_course_settings) PLUS this template with frequency: 'absolute', value: 4. The €50 is derived. Never put 50 in `value`. What `value` means depends on `frequency`: - `absolute` → the TOTAL NUMBER of instalments (4 = four payments). This is the usual choice for "split into N". - `after_events` → number of sessions per instalment (charge every N sessions). - `monthly` / `quarterly` / `half_yearly` / `yearly` → `value` is NOT used for dates; set `value_date` to the day of month to bill on (0 = anchor to the start date). - With `schedule_type: 'pay_as_you_go'` → `value` is a UNIT MULTIPLIER, not money: the client is charged value × the programme's unit_price. Keep it a small count. `schedule_type` must match the programme's price type: 'in_advance', 'single_payment' and 'by_attendance' work with a normal course fee; 'pay_as_you_go' is for membership pricing. Pass `course_id` to attach the template to a programme immediately — Zooza validates the combination and rejects a mismatch with the reason. Without `course_id` the template is created but attached to nothing (still fine — attach it later or in the app). Requires the edit_company permission.
setup_update_course_templates
Choose which payment plan templates a programme offers clients — attach new ones, and DETACH ones that should not be there. Detaching is the point: Zooza attaches templates by itself when a programme's price type or payment collection changes, and on a company with many templates that can silently put dozens of plans on a programme. This is how you clean that up. TWO CALLS. First WITHOUT `token`: writes nothing and returns what is attached now, what would be attached, and the exact attach/detach list. Show it to the operator. Then call again with `token` + `confirmed: true` to apply. `template_ids` is the COMPLETE list the programme should end up with — anything attached but missing from it is detached. Pass an empty array to detach everything. Detaching removes the plan from the programme and from its classes; bookings already ON that plan keep their existing payment schedule, so no client is re-billed, but new bookings can no longer pick it. Use setup_add_payment_template to CREATE a template. Use classes_find_courses to resolve the programme.
todos
todos_add
Create a to-do item for a Zooza operator — a task a human needs to action. Give it a `message` and the `to_user_id` of the person it's assigned to. Optionally link it to a record (`entity_type` + `entity_id`, e.g. a registration) so the operator can open the thing it's about, and set a `due_date`. Use this to escalate — e.g. a lead asked a question that needs a human reply. It creates an OPEN todo in Zooza's normal to-do list; it does not email anyone. There is no `inbound_reply` entity type — link a reply escalation to its registration instead.
todos_mark
Change the status of a to-do item: `done` (completed), `cancelled` (won't do), or `open` (reopen). Only OPEN todos can be marked `done` or `cancelled`; a `done` or `cancelled` todo can only be reopened to `open`. Marking `done` stamps completion time automatically.
explain
explain_data_model
Returns a structured description of Zooza's domain entities — hierarchy, roles, valid field values (enums), parent/child relationships, and disambiguation rules. No Zooza API call is made — purely hardcoded domain knowledge. Call this before any class-creation, booking, or attendance tool to avoid entity confusion. Examples: entity="programme" → registration_type enums; entity="booking" → status values; entity="credit" → make-up / replacement entitlements (the "unused/expired make-ups" and "credits" concept); empty call → full hierarchy with all entities.
labels
labels_mark
Attach or detach a label (a named tag) on a Zooza course, schedule, or registration. Set `present: true` to attach (the label is created automatically if it doesn't exist yet — attach is idempotent), `present: false` to detach. Use it to tag records for grouping or pipeline state — e.g. mark a lead registration `converted`, or flag one `todo`. Works ONLY on courses, schedules, and registrations. NOTE: labels on a SCHEDULE can be customer-visible on the public booking widget (output flags this as `public_facing`); labels on courses and registrations are internal. Does not send anything.
negotiate
negotiate_terminology
Free tool — no Zooza API call, no company_id required. Two modes: "start" → returns the 8-question interview template for Claude to conduct conversationally. "build" → validates answers against the Zooza glossary, returns a TerminologyProfile JSON plus a /remember instruction so Claude saves the profile to memory. Run once per user. The saved profile is auto-loaded in every future Zooza session — no re-configuration needed. Call get_skill('negotiate-terminology') before starting the interview.
payments
payments_add_plan
Put a booking on a payment plan — the instalment calendar the client actually pays against. A plan attached to a programme or class is NOT inherited by bookings; each booking has to have it applied, and until then the client owes nothing and sees no payment schedule. TWO CALLS. First WITHOUT `token`: writes nothing and returns the TOTAL plus the instalment dates and how many sessions each one covers. Zooza's preview does not expose per-instalment amounts before the plan exists — divide the total by the instalment count when telling the user, and say it is the expected split. Show that to the operator, then call again with `token` + `confirmed: true` to apply. `total_price` is the WHOLE amount for this booking, not a per-session price. Say "EUR 200 for the term split into 4" and pass total_price: 200 — Zooza does the division. Omit it to let Zooza price the booking from the class instead. (This is the opposite of `unit_price` on classes_add_course, which IS per session.) You do not need a plan id: the tool lists the plans available on the booking's own class and picks the only one automatically. WARNING — if the booking already has a plan, applying another REPLACES it and rebuilds the ledger; the preview says so.
reports
reports_get_data
Return the REAL, pre-aggregated numbers for ONE business question about an activity brand — and the basis for SHOWING it. This is how you show an operator a report / dashboard / chart of their business numbers (occupancy, unpaid, churn, attendance, trials, retention, revenue, "how are we doing", per programme / venue / instructor): call this, then COMPOSE a focused report as an ARTIFACT in the conversation that renders in the side panel — do NOT hand the user a link or open a browser page. Views: occupancy, unpaid, churn, attendance, trials, retention, clients_by_location, replacements, summary. Use view="replacements" for ANY question about make-up / replacement credits — "unused make-ups", "expiring make-ups", "credits", "náhrady / náhradné hodiny", "are we overloaded on make-ups", make-up demand vs available slots per programme (this IS the credits report; Zooza HAS make-up credits even though they are not in the business_dashboard views). The result has `headline` (computed key figures), `rows` (chart/table-ready, named, capped), `note` (a data-aware caption), `currency`, and `period`. RULES: every number you show the user MUST come from this result verbatim — never invent, estimate, or recompute figures, and never draw a chart without calling this first. Render charts with inline SVG/CSS — no external CDN or chart library (the artifact sandbox blocks them). If a view returns no rows, say so plainly. Follow get_skill("report-compose").
skill
get_skill
Returns the full markdown playbook for one of the registered skills. Call this BEFORE starting a flow named in the server's instructions — the playbook contains the interview steps, mapping rules, and confirmation pattern for that scenario. Available skills: business-model-validator, class-management, communication, feedback-nudge, negotiate-terminology, report-compose, report-discovery, report-page-new, schedule-optimization.
submit
submit_feedback
Submit user feedback about the Zooza MCP integration to the engineering team. Two paths: - 'path: "github"' — returns a prefilled issue-creation URL on the **public** `zooza-dev/zooza-mcp-server` repo. The user opens it in their browser and files the issue themselves (no MCP-side auth). The body MUST be fully anonymized (no user_id, company_id, company name, user email/name, course/class/event names, customer/client identifiers). The server runs a safety-net regex and will reject the call if obvious identifiers (long numbers, emails) remain. - 'path: "internal"' — files an issue on the user's behalf in the **private** `zooza-dev/zooza-mcp` repo, recording their authenticated user_id and company_id so engineering can follow up. Use this for users who don't have GitHub or prefer the private channel. ALWAYS show the user the exact 'title' and 'body' and get explicit affirmative confirmation before calling — once invoked with 'path: "internal"', the issue is filed and cannot be undone from this tool. The 'feedback-nudge' skill (load via `get_skill name=feedback-nudge`) describes when to proactively offer this tool and how to anonymize properly.
terminology
get_terminology
Search the Zooza domain glossary. Returns canonical term names, definitions, cross-language synonyms, disambiguation rules, and AI guidance notes. No Zooza API call is made — purely local lookup against the compiled glossary. Use this to resolve ambiguous user input before calling operational tools. Examples: query="hodina" → Session; query="kurz", language="sk" → Programme; category="product-hierarchy" → all hierarchy terms; empty call → full index.
trainers
trainers_add_helpers
Register **additional lecturers** — a second instructor, assistant, or helper — on one or more classes, and control which of their sessions each one actually works. This is NOT how you set or change a class's main instructor (that is `classes_update`, or `sessions_update` for one-off substitutions); additional lecturers are extra people who work *alongside* the main instructor. By default a lecturer you add here works **every** session of the class — that is the normal arrangement. Restrict one to certain days by giving that assignment `weekdays` (1=Monday … 7=Sunday), or to hand-picked sessions with `event_ids`. This handles the whole "Martin works Mondays, Peter works Tuesdays, both on Wednesdays" pattern across a programme's classes in one action. Select classes with `schedule_ids`, or the way operators say it — `course_id` plus `billing_period_id` ("the Junior classes in Winter 2026"). Resolve `trainer_id` first with `classes_find_resource kind:"trainer"`, and the programme and billing period with `classes_find_courses` and `classes_find_resource kind:"billing_period"`. `role` is one of `secondary` ("Secondary instructor", the default), `assistant` ("Assistant"), `helper` ("Assistant instructor") or `trainer` ("Instructor"), and is per CLASS — Zooza cannot give someone one role on Mondays and another on Wednesdays. This writes across every class and session you select, so it is a two-step tool: call it once with no token to get a plan naming every class and session count, show that to the operator, then call it again with the returned token and `confirmed: true`. To SEE who is currently assigned, use `sessions_find_events` (per session) or `classes_find_classes` (the class roster).
whoami
whoami
Returns the connected user's identity, the companies they can operate on, regional context, and the session's token state. Call ONCE at the start of every conversation. How to interpret the response: - 'status: "ok"' — authenticated, at least one company available. Pick a 'company_id' from 'companies' for follow-up calls (ask the user if more than one exists). - 'status: "no_companies"' — authenticated but no companies linked. Surface 'status_message' verbatim. No other tool will work. - 'status: "invalid_user"' — account rejected by api-v1. Surface 'status_message' verbatim. - 'status: "api_error"' — api-v1 unreachable. Surface 'status_message' and suggest retry. Identity fields (use these to scope follow-up calls to the calling user): - 'identity.user_id' — the caller's Zooza user id. Pass this as 'trainer_id' to filter sessions_find_events / sessions_get_attendance / etc. to the user's OWN data whenever the user says "my sessions," "my classes today," "what am I teaching tomorrow," etc. Without it, sessions_find_events returns ALL company events, not just the caller's. - 'identity.email', 'identity.name' — for display only. Regional context fields (use these to adapt behaviour): - 'server_region' — which Zooza installation this token routes to, taken from the JWT 'region' claim: "eu" (SK/CZ/DE/RO/HU/IT/PL), "uk", "us", "asia". This is the API-instance region, NOT the company's market region. Null in dev-fallback (no JWT). - 'company.region' — the company's MARKET region code (e.g. "sk", "cz", "de", "en"). Different axis from 'server_region': one EU instance ('server_region: "eu"') serves Slovak, Czech, German, … companies. - 'company.locale' — BCP-47 locale for date/number/currency formatting (e.g. "sk-SK", "cs-CZ", "en-GB"). - 'company.language' — the company's primary language code. - 'company.currency' — the company's currency (e.g. "EUR", "CZK", "GBP"). Use 'company.region' and 'company.language' to resolve terminology: a Slovak company saying "kurz" means Programme; a Czech company saying "lekce" means Session. When region context is available, skip asking the user to clarify language. Branding (per company in 'companies[].branding'): - 'branding.logo' (boolean) and 'branding.primary_color' (#hex or null) report what brand assets were cached for this company. Use 'primary_color' to brand a report artifact you compose for the client; the actual logo image is held server-side and never passes through you. Null branding = fetch failed or nothing configured; default to Zooza styling. Feedback context (used by the 'feedback-nudge' skill): - 'last_feedback_at' — ISO timestamp of the user's last MCP feedback submission, or null if never. Drives the skill's 7-day cool-off on proactive feedback nudges. - 'feedback_count' — total submissions to date (0 if never). Tool-list freshness (Zooza has no marketplace auto-update, so proactively spot an out-of-date connector). There are TWO independent staleness signals — check BOTH: 1. NEW or REMOVED tools — compare 'tool_surface.names' (every tool the live server exposes) against the tools you actually have available in THIS session. Any name in that list you do NOT have = the client is missing newly-added tools. 2. UPDATED tools (same names, changed behaviour) — compare 'server_version' in this response against the [SURFACE_BUILD: …] marker at the very end of THIS tool description. If they differ, the tool DEFINITIONS you currently hold are from an OLDER build even when every tool name still matches — an existing tool's description, inputs, or behaviour changed after the client last connected. (This is why a deploy that only tweaks existing tools is still detectable.) If EITHER signal fires, tell the user once — briefly, not naggingly — that their Zooza tools are out of date, and how to refresh: in Claude Desktop or Claude Code toggle the Zooza connector off then on (or reconnect / reinstall it); a browser client may just need a reload. Do NOT prompt when neither signal fires. Never surface 'sub' or 'scopes' to the user — diagnostic only. [SURFACE_BUILD: 0.13.0]

Tools removed

Tools this server used to expose. Anything built against them stopped working on the day they went.

classes_commit_update
removed 26 Aug 2026
classes_find_billing_periods
removed 26 Aug 2026
classes_find_places
removed 26 Aug 2026
classes_prepare_update
removed 26 Aug 2026
comms_commit_message
removed 26 Aug 2026
comms_prepare_message
removed 26 Aug 2026
sessions_commit_update
removed 26 Aug 2026
sessions_prepare_update
removed 26 Aug 2026
trainers_find
removed 26 Aug 2026
trainers_find_rate_types
removed 26 Aug 2026

Endpoints

URLTransportStateLatencyChecked
https://mcp.zooza.app/mcp streamable-http answering 237 ms 1 min ago

Alternatives to Zooza MCP Server

same job, measured the same way
MCP Server Cal
by aiwerk

Cal.com scheduling MCP server: manage bookings, event types, and availability.

55 installs/wk local only
Precision Concretecoating
by precision-concretecoating

Precision Concrete Coating MCP for services, estimates, scheduling, and business information.

33 tools answering
Cron
by ryudi84

MCP server for cron expression parsing, validation, and scheduling.

local only
Servicialo MCP Server
by servicialo

Open protocol for booking and scheduling professional services via AI agents

local only
Nusii MCP Server
by nusii

Nusii MCP server for drafting, pricing, and tracking business proposals.

answering
Oura Ring MCP Server
by jordanburke

Oura Ring MCP server for the Oura API v2: sleep, readiness, activity, heart rate, and more.

83 installs/wk local only
Enigma MCP Server
by enigma

An MCP server that provides access to trusted data about business identity and activity

answering
CLIO Slurm
by iowarp

MCP server for Slurm workload management and HPC job scheduling

local only

Zooza MCP Server — questions

Answers built from our own checks of this server.

What can Zooza MCP Server do?
It exposes 35 tools, read directly from the server on our last check. Among them: bookings_add_lead, bookings_find, classes_add_course, classes_commit_class, classes_find_classes, classes_find_courses and 29 more. The full list with descriptions is on this page — we take it from the server itself via tools/list, not from a README. How MCP servers expose tools in the first place →
What is Zooza MCP Server mostly used for?
Its tools cluster around classes, sessions and comms. That is what this server is built to work with — the grouping comes from the actual tool names, not from a category we assigned.
Is Zooza MCP Server working right now?
We send a real MCP handshake every 15 minutes. Over the last 24 hours 92 of 92 checks got a reply (100.0%), average response time 228 ms. The bar chart above shows every period we have measured.
Did Zooza MCP Server ever remove tools?
Yes. classes_commit_update, classes_find_billing_periods, classes_find_places, classes_prepare_update and 6 more are no longer exposed — we recorded the date each one disappeared. A tool vanishing usually means a breaking change for anything that depended on it.
How do I connect Zooza MCP Server?
Copy the ready config from this page — we generate it for Claude Code, Claude Desktop, Codex, Cursor and VS Code, each with the file path that client actually reads. It is a remote server, so there is nothing to install — the client connects to the address.
Does Zooza MCP Server need an API key?
No. Zooza MCP Server completed a full MCP handshake with us as an anonymous client and listed its tools without asking for anything. All 35 of them are readable on this page. This is what we observed, not what the docs claim.
How fast is Zooza MCP Server?
It answers our handshake in 228 ms on average, which is faster than 61% of all working MCP servers we measure. The comparison comes from our own checks across the whole registry, every 15 minutes.
Is Zooza MCP Server open source?
Yes — it is published under the MIT licence, written in TypeScript and 6 stars on GitHub. The source link is on this page, so you can read exactly what it does with your data before you connect it.