{
  "schema_version": "2025-06-18",
  "spec": "https://github.com/webmachinelearning/webmcp",
  "name": "courtbooker-mcp",
  "title": "Courtbooker",
  "version": "0.1.0",
  "description": "Courtbooker exposes its MCP tools to in-browser AI agents via the imperative WebMCP API (navigator.modelContext, with window.ai / Gemini Nano fallbacks) and annotates key page actions with the declarative WebMCP attributes (toolname / tooldescription / toolaction). The same tools are also reachable remotely at the streamable-HTTP MCP endpoint below.",
  "webmcp": {
    "transport": "navigator.modelContext",
    "fallback_transports": [
      "document.modelContext",
      "window.ai.modelContext (Gemini Nano / built-in AI)",
      "window.ai",
      "window.agent"
    ],
    "registration": "automatic-on-page-load",
    "authentication": "in-browser session (Supabase). Tools run as the currently signed-in user. Only tools marked auth: 'none' below are callable without a session; every other tool (booking, cancelling, waitlist, member listings) requires an authenticated resident."
  },
  "declarative": {
    "attributes": [
      "toolname",
      "tooldescription",
      "toolaction"
    ],
    "pages": [
      {
        "url": "https://courtbooker.app/",
        "tools": [
          {
            "toolname": "search_communities",
            "tooldescription": "Search participating residential communities by name and open a community's public availability page.",
            "toolaction": "https://courtbooker.app/?q={query}"
          }
        ]
      },
      {
        "url": "https://courtbooker.app/{siteSlug}",
        "tools": [
          {
            "toolname": "view_court_availability",
            "tooldescription": "View live tennis and padel court availability for a community on a given day.",
            "toolaction": "https://courtbooker.app/{siteSlug}?date={YYYY-MM-DD}"
          },
          {
            "toolname": "reserve_court",
            "tooldescription": "Sign in as a resident to reserve a court (booking itself requires an authenticated session).",
            "toolaction": "https://courtbooker.app/auth"
          }
        ]
      },
      {
        "url": "https://courtbooker.app/support",
        "tools": [
          {
            "toolname": "contact_support",
            "tooldescription": "Send a message to the Courtbooker support team (name, email, topic, message).",
            "toolaction": "https://courtbooker.app/support"
          }
        ]
      }
    ]
  },
  "mcp": {
    "transport": "streamable-http",
    "url": "https://courtbooker.app/mcp",
    "discovery": "https://courtbooker.app/.well-known/mcp.json",
    "oauth_protected_resource": "https://courtbooker.app/.well-known/oauth-protected-resource"
  },
  "tools": [
    {
      "name": "list_public_sites",
      "title": "List public communities",
      "description": "List every publicly-visible residential community on Courtbooker. No authentication required — useful for agents to discover communities before a resident signs in. (Public — no authentication required.)",
      "auth": "none",
      "annotations": {
        "readOnlyHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "type": "object",
        "properties": {}
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "sites": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "slug": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "tagline": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "description": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "timezone": {
                  "type": "string",
                  "description": "IANA timezone, e.g. 'Europe/Amsterdam'."
                },
                "url": {
                  "type": "string",
                  "format": "uri",
                  "description": "Public URL of the community's page."
                }
              },
              "required": [
                "id",
                "slug",
                "name",
                "timezone",
                "url"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "sites"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "list_sites",
      "title": "List my communities",
      "description": "List residential communities the authenticated user belongs to,\nwith their site_id, name, slug, timezone, role, and residence.\nCall this first for any user-scoped question ('my bookings',\n'available slots today') to obtain the correct `site_id`.\nIf it returns more than one community, ask the user which one. (Requires an authenticated Courtbooker resident — remote MCP callers must send a valid OAuth bearer token; in-browser WebMCP calls run as the currently signed-in user.)",
      "auth": "required",
      "annotations": {
        "readOnlyHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "type": "object",
        "properties": {}
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "sites": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "site_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "site_name": {
                  "type": "string"
                },
                "site_slug": {
                  "type": "string"
                },
                "timezone": {
                  "type": "string"
                },
                "role": {
                  "type": "string",
                  "description": "Membership role: 'resident', 'admin', 'owner', etc."
                },
                "apartment_id": {
                  "anyOf": [
                    {
                      "$ref": "#/properties/sites/items/properties/site_id"
                    },
                    {
                      "type": "null"
                    }
                  ],
                  "description": "The caller's residence in this site."
                },
                "apartment_label": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "site_id"
              ],
              "additionalProperties": true
            },
            "description": "Communities the caller belongs to. Use `site_id` for other tools."
          }
        },
        "required": [
          "sites"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "list_courts",
      "title": "List courts",
      "description": "List active courts for a community, optionally filtered by sport\n(tennis or padel). Useful when the user asks 'which courts do we\nhave' or to resolve a court name to an id. For availability, use\n`list_available_slots` instead. (Requires an authenticated Courtbooker resident — remote MCP callers must send a valid OAuth bearer token; in-browser WebMCP calls run as the currently signed-in user.)",
      "auth": "required",
      "annotations": {
        "readOnlyHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "site_id": {
            "type": "string",
            "format": "uuid",
            "description": "Site UUID from list_sites."
          },
          "sport": {
            "type": "string",
            "enum": [
              "tennis",
              "padel"
            ],
            "description": "Filter by sport."
          }
        },
        "required": [
          "site_id"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "courts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "sport": {
                  "type": "string",
                  "enum": [
                    "tennis",
                    "padel"
                  ]
                },
                "status": {
                  "type": "string",
                  "description": "'active' when bookable."
                },
                "display_order": {
                  "anyOf": [
                    {
                      "type": "integer"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "required": [
                "id",
                "name",
                "sport",
                "status"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "courts"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "list_available_slots",
      "title": "List available slots",
      "description": "Return bookable slots for a community on a given local calendar date.\nUse this to answer questions like 'can I book tennis this afternoon' or\n'what padel slots are free tomorrow between 18:00 and 21:00'.\n\nPublic — callable without authentication. Unauthenticated calls see\nthe community's public availability view (default 08:00-22:00 hours,\n60-minute slots); signed-in residents get the exact opening hours,\nclosures and booking rules. Booking itself always requires auth.\n\nInputs:\n- site_id: from `list_sites` (signed in) or `list_public_sites`.\n- date: YYYY-MM-DD in the community's local timezone.\n- sport: 'tennis' or 'padel' to filter courts.\n- court_id: restrict to a single court.\n- from_time / to_time: optional local HH:MM window (24h). Slots must\n  START within [from_time, to_time). Use for 'between 4 and 8pm'.\n\nResponse: per court, `free_slots` with `start_at`/`end_at` in ISO-8601\nUTC plus `start_local`/`end_local` HH:MM in the site's timezone. Slots\nin the past or violating min-notice are omitted. Also returns each\ncourt's existing reservations for that day for context. (Public — no authentication required.)",
      "auth": "none",
      "annotations": {
        "readOnlyHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "site_id": {
            "type": "string",
            "format": "uuid",
            "description": "Site UUID from list_sites or list_public_sites."
          },
          "date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Local calendar date YYYY-MM-DD in the community's timezone."
          },
          "sport": {
            "type": "string",
            "enum": [
              "tennis",
              "padel"
            ]
          },
          "court_id": {
            "type": "string",
            "format": "uuid"
          },
          "from_time": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}$",
            "description": "Local HH:MM lower bound (inclusive) on slot start."
          },
          "to_time": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}$",
            "description": "Local HH:MM upper bound (exclusive) on slot start."
          }
        },
        "required": [
          "site_id",
          "date"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "site": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "timezone": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name",
              "timezone"
            ],
            "additionalProperties": false,
            "description": "Echo of the site the results belong to."
          },
          "date": {
            "type": "string",
            "description": "The queried local calendar date (YYYY-MM-DD)."
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone the times are interpreted in."
          },
          "slot_minutes": {
            "type": "integer",
            "description": "Community slot size in minutes. Pass this as `duration_minutes` to create_reservation."
          },
          "min_notice_minutes": {
            "type": "integer",
            "description": "Slots starting sooner than this are omitted."
          },
          "courts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "court_id": {
                  "$ref": "#/properties/site/properties/id"
                },
                "court_name": {
                  "type": "string"
                },
                "sport": {
                  "type": "string",
                  "enum": [
                    "tennis",
                    "padel"
                  ]
                },
                "free_slots": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "start_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Slot start in ISO-8601 UTC."
                      },
                      "end_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Slot end in ISO-8601 UTC."
                      },
                      "start_local": {
                        "type": "string",
                        "description": "Slot start as HH:MM in the site's timezone."
                      },
                      "end_local": {
                        "type": "string",
                        "description": "Slot end as HH:MM in the site's timezone."
                      }
                    },
                    "required": [
                      "start_at",
                      "end_at",
                      "start_local",
                      "end_local"
                    ],
                    "additionalProperties": false
                  },
                  "description": "Bookable slots that respect min-notice and time-window filter."
                },
                "reservations": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "court_id": {
                        "$ref": "#/properties/site/properties/id"
                      },
                      "start_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "end_at": {
                        "$ref": "#/properties/courts/items/properties/reservations/items/properties/start_at"
                      },
                      "status": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "court_id",
                      "start_at",
                      "end_at",
                      "status"
                    ],
                    "additionalProperties": false
                  },
                  "description": "Existing non-cancelled reservations on this court for the day."
                }
              },
              "required": [
                "court_id",
                "court_name",
                "sport",
                "free_slots",
                "reservations"
              ],
              "additionalProperties": false
            }
          },
          "note": {
            "type": "string",
            "description": "Present on unauthenticated calls: explains that the public default schedule was used."
          }
        },
        "required": [
          "site",
          "date",
          "timezone",
          "slot_minutes",
          "min_notice_minutes",
          "courts"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "create_reservation",
      "title": "Book a court",
      "description": "Book a court for the authenticated user. Use this after confirming\nan available slot with `list_available_slots` — pass one of the\nreturned `court_id` + `start_at` pairs and the site's `slot_minutes`\nas `duration_minutes` (usually 60).\n\nRequires an authenticated resident. `apartment_id` (the resident's\nresidence) is REQUIRED — read it from `list_sites`, which returns the\ncaller's `apartment_id` per community.\n\nRecommended flow:\n1. `list_sites` -> pick the community's site_id AND the caller's\n   apartment_id in that community.\n2. `list_available_slots` with sport/date/time-window.\n3. Show the user the candidate slot and confirm.\n4. Call `create_reservation` with that slot and the apartment_id.\n\nEnforced server-side by RLS and booking rules (min notice, per-day\nand per-week quotas, max concurrent, days-in-advance). Errors from\nthose rules are returned verbatim so you can relay them to the user.\n\nIf the insert fails because the slot is already booked, offer to call\n`join_waitlist` with the same court_id/start_at/duration_minutes so\nthe user is emailed if the slot frees up. (Requires an authenticated Courtbooker resident — remote MCP callers must send a valid OAuth bearer token; in-browser WebMCP calls run as the currently signed-in user.)",
      "auth": "required",
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": false,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "court_id": {
            "type": "string",
            "format": "uuid"
          },
          "start_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 start time (UTC or with offset)."
          },
          "duration_minutes": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "description": "Duration in minutes. Must match the community's slot size (usually 60)."
          },
          "apartment_id": {
            "type": "string",
            "format": "uuid",
            "description": "REQUIRED residence UUID (the resident id the booking is made under). Obtain it from `list_sites` — never fabricate it."
          }
        },
        "required": [
          "court_id",
          "start_at",
          "duration_minutes",
          "apartment_id"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "reservation": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "site_id": {
                "$ref": "#/properties/reservation/properties/id"
              },
              "court_id": {
                "$ref": "#/properties/reservation/properties/id"
              },
              "apartment_id": {
                "anyOf": [
                  {
                    "$ref": "#/properties/reservation/properties/id"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "start_at": {
                "type": "string",
                "format": "date-time"
              },
              "end_at": {
                "$ref": "#/properties/reservation/properties/start_at"
              },
              "status": {
                "type": "string",
                "description": "'confirmed' | 'cancelled' (community may add more states)."
              },
              "created_by": {
                "anyOf": [
                  {
                    "$ref": "#/properties/reservation/properties/id"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "timezone": {
                "type": "string",
                "description": "IANA timezone of the site."
              },
              "start_local": {
                "type": "string",
                "description": "Start as 'YYYY-MM-DD HH:MM' in the site's timezone. Show this to users."
              },
              "end_local": {
                "type": "string",
                "description": "End as 'YYYY-MM-DD HH:MM' in the site's timezone. Show this to users."
              }
            },
            "required": [
              "id",
              "site_id",
              "court_id",
              "apartment_id",
              "start_at",
              "end_at",
              "status"
            ],
            "additionalProperties": false,
            "description": "The row that was inserted. Its `id` is the `reservation_id` for cancel_reservation."
          }
        },
        "required": [
          "reservation"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "cancel_reservation",
      "title": "Cancel a reservation",
      "description": "Cancel a reservation the authenticated user owns (or has admin\nrights over). Sets status to 'cancelled'. Use `list_my_reservations`\nfirst to look up the `reservation_id` — never guess it.\n\nIdempotent: cancelling an already-cancelled reservation is a no-op.\nCommunity cancellation-deadline rules are enforced server-side and\nreturned as errors if violated. (Requires an authenticated Courtbooker resident — remote MCP callers must send a valid OAuth bearer token; in-browser WebMCP calls run as the currently signed-in user.)",
      "auth": "required",
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "reservation_id": {
            "type": "string",
            "format": "uuid"
          },
          "reason": {
            "type": "string"
          }
        },
        "required": [
          "reservation_id"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "reservation": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "site_id": {
                "$ref": "#/properties/reservation/properties/id"
              },
              "court_id": {
                "$ref": "#/properties/reservation/properties/id"
              },
              "apartment_id": {
                "anyOf": [
                  {
                    "$ref": "#/properties/reservation/properties/id"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "start_at": {
                "type": "string",
                "format": "date-time"
              },
              "end_at": {
                "$ref": "#/properties/reservation/properties/start_at"
              },
              "status": {
                "type": "string",
                "description": "'confirmed' | 'cancelled' (community may add more states)."
              },
              "created_by": {
                "anyOf": [
                  {
                    "$ref": "#/properties/reservation/properties/id"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "timezone": {
                "type": "string",
                "description": "IANA timezone of the site."
              },
              "start_local": {
                "type": "string",
                "description": "Start as 'YYYY-MM-DD HH:MM' in the site's timezone. Show this to users."
              },
              "end_local": {
                "type": "string",
                "description": "End as 'YYYY-MM-DD HH:MM' in the site's timezone. Show this to users."
              }
            },
            "required": [
              "id",
              "site_id",
              "court_id",
              "apartment_id",
              "start_at",
              "end_at",
              "status"
            ],
            "additionalProperties": false,
            "description": "The updated row with status='cancelled'."
          }
        },
        "required": [
          "reservation"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "list_my_reservations",
      "title": "List my reservations",
      "description": "List reservations belonging to the authenticated user. Use this to\nanswer 'when is my next booking', 'what do I have on Saturday', or to\nlook up a reservation_id before calling `cancel_reservation`.\n\nDefaults: upcoming, non-cancelled, oldest-first (next up first),\nlimit 50. Set `include_past` or `include_cancelled` to widen the\nresult. Filter by `site_id` when the user belongs to multiple\ncommunities.\n\nEach item includes the court name, sport, site name, and start/end\ntimes both in ISO-8601 UTC and formatted in the community's local\ntimezone (`start_local`, `end_local`). (Requires an authenticated Courtbooker resident — remote MCP callers must send a valid OAuth bearer token; in-browser WebMCP calls run as the currently signed-in user.)",
      "auth": "required",
      "annotations": {
        "readOnlyHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "site_id": {
            "type": "string",
            "format": "uuid"
          },
          "include_past": {
            "type": "boolean",
            "description": "Include reservations that already ended. Default false."
          },
          "include_cancelled": {
            "type": "boolean",
            "description": "Include cancelled reservations. Default false."
          },
          "limit": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 200
          }
        },
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "reservations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "site_id": {
                  "$ref": "#/properties/reservations/items/properties/id"
                },
                "site_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "court_id": {
                  "$ref": "#/properties/reservations/items/properties/id"
                },
                "court_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "sport": {
                  "anyOf": [
                    {
                      "type": "string",
                      "enum": [
                        "tennis",
                        "padel"
                      ]
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "apartment_id": {
                  "anyOf": [
                    {
                      "$ref": "#/properties/reservations/items/properties/id"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "status": {
                  "type": "string"
                },
                "start_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "end_at": {
                  "$ref": "#/properties/reservations/items/properties/start_at"
                },
                "timezone": {
                  "type": "string"
                },
                "start_local": {
                  "type": "string",
                  "description": "Start as 'YYYY-MM-DD HH:MM' in the site's timezone."
                },
                "end_local": {
                  "type": "string",
                  "description": "End as 'YYYY-MM-DD HH:MM' in the site's timezone."
                }
              },
              "required": [
                "id",
                "site_id",
                "site_name",
                "court_id",
                "court_name",
                "sport",
                "apartment_id",
                "status",
                "start_at",
                "end_at",
                "timezone",
                "start_local",
                "end_local"
              ],
              "additionalProperties": false
            },
            "description": "Ordered by start_at ascending — index 0 is the next upcoming booking."
          }
        },
        "required": [
          "reservations"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "join_waitlist",
      "title": "Join a slot's waitlist",
      "description": "Subscribe the authenticated user to the waitlist for a slot that is\nalready booked by someone else. Use this when `create_reservation`\nfails because the slot is taken (or when `list_available_slots` shows\nthe desired time under `reservations`) — offer the user to join the\nwaitlist for that exact court_id + start_at + duration.\n\nIf the blocking reservation is cancelled, everyone on the waitlist is\nemailed a claim link; the slot goes to whoever books first (first\ncome, first served). Joining does NOT reserve anything by itself.\n\nIdempotent: joining the same slot twice returns the existing entry.\nServer-side checks (slot actually booked, waitlist enabled for the\ncommunity, caller is a resident) are returned verbatim as errors. (Requires an authenticated Courtbooker resident — remote MCP callers must send a valid OAuth bearer token; in-browser WebMCP calls run as the currently signed-in user.)",
      "auth": "required",
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "court_id": {
            "type": "string",
            "format": "uuid"
          },
          "start_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 start time of the booked slot (UTC or with offset)."
          },
          "duration_minutes": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "description": "Duration in minutes. Must match the community's slot size (usually 60)."
          }
        },
        "required": [
          "court_id",
          "start_at",
          "duration_minutes"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "waitlist_entry_id": {
            "type": "string",
            "format": "uuid",
            "description": "The waitlist entry id. Pass this as `waitlist_id` to leave_waitlist."
          },
          "court_id": {
            "type": "string",
            "format": "uuid"
          },
          "start_at": {
            "type": "string",
            "format": "date-time"
          },
          "end_at": {
            "$ref": "#/properties/start_at"
          },
          "note": {
            "type": "string",
            "description": "Human-readable explanation of what happens next."
          }
        },
        "required": [
          "waitlist_entry_id",
          "court_id",
          "start_at",
          "end_at",
          "note"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "leave_waitlist",
      "title": "Leave a waitlist",
      "description": "Remove one of the authenticated user's waitlist entries so they stop\nreceiving notifications for that slot. Use `list_my_waitlist` first\nto look up the `waitlist_id` — never guess it.\n\nIdempotent: leaving an entry that was already removed returns\n`removed: false` instead of an error. (Requires an authenticated Courtbooker resident — remote MCP callers must send a valid OAuth bearer token; in-browser WebMCP calls run as the currently signed-in user.)",
      "auth": "required",
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "waitlist_id": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "waitlist_id"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "removed": {
            "type": "boolean",
            "description": "True when the entry was removed; false if it no longer existed."
          }
        },
        "required": [
          "removed"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "list_my_waitlist",
      "title": "List my waitlist entries",
      "description": "List the authenticated user's active waitlist entries (future slots\nonly). Use this to answer 'which slots am I waiting for?' or to look\nup a `waitlist_id` before calling `leave_waitlist`.\n\nEach entry includes the community, court, sport, slot times in\nISO-8601 UTC, and a status: 'waiting' until the slot frees up,\n'notified' once the claim email was sent. (Requires an authenticated Courtbooker resident — remote MCP callers must send a valid OAuth bearer token; in-browser WebMCP calls run as the currently signed-in user.)",
      "auth": "required",
      "annotations": {
        "readOnlyHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "inputSchema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "type": "object",
        "properties": {}
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "entries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "The `waitlist_id` for leave_waitlist."
                },
                "site_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "site_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "site_slug": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "court_id": {
                  "$ref": "#/properties/entries/items/properties/site_id"
                },
                "court_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "sport": {
                  "anyOf": [
                    {
                      "type": "string",
                      "enum": [
                        "tennis",
                        "padel"
                      ]
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "start_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "end_at": {
                  "$ref": "#/properties/entries/items/properties/start_at"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "waiting",
                    "notified"
                  ],
                  "description": "'waiting' until the slot frees up; 'notified' once the claim email was sent."
                },
                "created_at": {
                  "$ref": "#/properties/entries/items/properties/start_at"
                }
              },
              "required": [
                "id",
                "site_id",
                "site_name",
                "site_slug",
                "court_id",
                "court_name",
                "sport",
                "start_at",
                "end_at",
                "status",
                "created_at"
              ],
              "additionalProperties": false
            },
            "description": "Active future waitlist entries for the caller."
          }
        },
        "required": [
          "entries"
        ],
        "additionalProperties": false,
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    }
  ],
  "contact": "https://courtbooker.app/agents"
}