Le serveur MCP
À quoi sert ce serveur. Laisser un assistant lire et agir sur un club (planning, réservations, tarifs, caisse…) au nom de la personne qui l’autorise, avec exactement ses droits.
apps/mcp est un second adaptateur, à côté de GraphQL : il appelle les mêmes commandes et requêtes de packages/domain, sans aucune règle métier propre. Une réservation refusée par MCP l’est pour la même raison qu’à l’accueil (SlotTaken, OutsideOpeningHours…).
L’identité : OAuth 2.1 avec Better Auth
Section intitulée « L’identité : OAuth 2.1 avec Better Auth »- Le serveur répond sur
/mcp(HTTP streamable, sans état). Le club vient de l’hôte de la requête, ou de?club=<slug>. - Sans jeton valide, il répond
401avec un en-têteWWW-Authenticatequi pointe vers/.well-known/oauth-protected-resource: le client découvre ainsi le serveur d’autorisation. - Le serveur d’autorisation est Better Auth, dans l’API, sur l’hôte du club. Les clients MCP s’enregistrent d’eux-mêmes (enregistrement dynamique, PKCE). Portées :
openid,profile,email,offline_access. - Le jeton est un JWT vérifié avec les clés de l’API : émetteur = l’API de l’hôte du club, audience = le
/mcpde ce même hôte. Un jeton d’un club ne sert pas sur un autre.
Pendant l’autorisation, le client ouvre la page de connexion du club, servie par l’API sur /oauth/connexion : lien par e-mail, code par SMS, ou mot de passe pour l’équipe (« Vous faites partie de l’équipe du club ? »).


Puis la page /oauth/consentement demande l’accord : « L’application … demande à agir en votre nom sur ce club », avec la liste de ce qu’elle pourra faire, et les boutons Autoriser et Refuser.


Les outils
Section intitulée « Les outils »Les droits sont ceux du rôle de la personne connectée, vérifiés dans le domaine : un joueur ne voit du planning que les créneaux occupés et ses propres réservations ; les outils marqués « staff » ou « owner » sont refusés aux autres.
Un refus revient en erreur d’outil, error: suivi du JSON de l’erreur du domaine (son nom en _tag, ses champs) : les mêmes codes que l’API GraphQL, présentés de la même façon. Une erreur d’infrastructure ou un bug ne donne qu’un message court (database error, internal error, ou le nom de l’erreur) ; le détail va dans les journaux du serveur.
- Planning et réservations :
planning,availability,booking,create_booking,confirm_booking,cancel_booking,cancel_series,move_booking,change_booking_players,check_in,undo_check_in,name_participant,create_client,mark_no_show,mark_participant_no_show,undo_participant_no_show,create_block,release_block,create_series. - Horaires et créneaux :
opening_hours,schedule_settings,schedule_change_preview,save_schedule,delete_schedule,set_site_default_schedule,assign_schedule,set_schedule_assignment_dates,unassign_schedule(horaires datés par site ou terrain, avec la liste des réservations laissées hors horaires avant d’enregistrer),add_closure,remove_closure,sport_configs,sport_step_preview,save_sport_step,save_sport_limits(fenêtre de réservation, matchs à venir par joueur, annulation gratuite propre au sport),my_booking_limits(les miennes, celles de ma formule si elle les relève). - Terrains :
resource_settings,save_resource(ajouter un terrain, le renommer, lui donner un partenaire : nom, lien, image envoyée depuis l’accueil ; son site et son sport ne changent plus). - Tarifs et périodes :
sports,tariffs,quote,quotes,create_tariff,update_tariff,delete_tariff,periods,period_week,create_period,update_period,delete_period,create_default_periods. - Formules et droits :
formules,create_formule,update_formule,sell_formule,formule_sales,entitlements,grant_entitlement,settle_share_with_entitlement,share_offersetchoose_share_offer(le prix de la part du titulaire : tarif ou une de ses remises),licences,set_licence,remove_licence. - Abonnements :
subscriptions,subscription,subscribable_formules,start_subscription,buy_subscription,cancel_subscription,undo_cancel_subscription,pause_subscription,resume_subscription,reactivate_subscription,collect_subscription,retry_subscription,change_subscription_method,start_card_update,set_subscription_settings. - Suppléments et cours :
addons,create_addon,update_addon,booking_items,add_booking_items,remove_booking_item,products,create_product,update_product,product_stock,restock_product,count_stock,coaching_series,create_coaching_series,coaching_series_for,enrol_in_coaching_series,record_coaching_attendance. - Notes de comptoir :
list_tabs,tab_of_booking,tab(ses lignes, ses réservations avec leurs parts et suppléments, payé et reste à régler),open_tab,close_tab(refusé tant qu’il reste à régler ; ses réservations s’en détachent),add_tab_line(le prix et la TVA du produit, le stock suivi bouge),cancel_tab_line(refusé une fois payée, le stock revient),attach_booking_to_tab,move_booking_to_tab,detach_booking_from_tab(les parts restent dues par leurs joueurs),pay_ticket(un encaissement sur plusieurs lignes : le montant se répartit selon ce qui est dû sur chacune ; le porte-monnaie débité est celui du payeur choisi ; la note se ferme quand plus rien n’est dû),ticket(ce que montre son reçu),create_sub_tab(une sous-note : des lignes ou une partie, prises sur ce qui reste à régler),split_tab(partager en N parts, « Part 1/N », au centime près),cancel_sub_tab(ses lignes reviennent dans ce qui reste) ;pay_ticketencaisse aussi une ou plusieurs sous-notes, et un montant plus petit laisse le reste à régler. - Événements :
club_events,club_event(les inscriptions : joueurs, licences, réponses, rang en liste d’attente),save_event,cancel_event,promote_event_entry(staff) ;register_event_entry,cancel_event_entry(staff, ou un joueur pour sa propre inscription jusqu’à la clôture, jusqu’au début si elle vient de la liste d’attente ; une inscription faite par l’accueil ne s’annule qu’à l’accueil) ;events_board,event_page(ce qu’un joueur voit : places, son inscription, les noms des inscrits selon le choix du club). - Clients et pilotage :
clients,client_bookings,client_file(la fiche client : solde, à régler, mouvements d’argent, réservations, formules, abonnements, notes, messages),add_client_note,remove_client_note,pilotage(le tableau de bord : activité, argent, membres, comparés à la période précédente),occupancy_by_day. - Argent :
booking_money,wallet,till,closings,close_day,verify_ledger,check_money(les règles de l’argent du club : commandes, porte-monnaie, parts réglées, réservations libérées, chaîne du journal),export_fec,collect_share,collect_booking,create_order_for_share,create_order_for_booking,record_payment,pay_with_wallet,create_top_up,set_top_up_tiers,stray_payments(l’argent reçu en trop, à rendre),provider_discrepancies(les écarts avec Stripe trouvés chaque nuit),acknowledge_discrepancy,record_discrepancy_charge,refund_payment,cancel_order. - Import (propriétaire) :
import_specs,import_start,import_map,import_rows,import_apply,import_report,import_jobs. - Club et équipe (propriétaire) :
who_am_i,invite_staff,staff,invitations,revoke_invitation,change_team_role,remove_from_team(le dernier responsable reste responsable),club_settings,set_block_presets,update_club_settings,club_payment_account(équipe : le compte Stripe du club et son statut),club_domains,add_club_domain,verify_club_domain,remove_club_domain. - Retours au pilote (administrateurs de la plateforme, dans le club de l’hôte) :
feedback_notes(les retours envoyés depuis l’accueil et l’application joueur, les plus récents d’abord, ouverts par défaut),feedback_note(un retour entier : chaque élément pointé, son chemin avec sondata-testid, le texte autour),mark_feedback_handled. - Messages :
client_notifications,resend_notification,set_notify_channel,notification_settings,update_notification_settings. - Côté joueur (les mêmes commandes que l’application joueur) :
my_bookings,book,open_match,join_open_match,buy_formule,search_members.open_matchlit une partie ouverte par l’identifiant de son lien ;join_open_matchla rejoint et règle la part (porte-monnaie ou droit tout de suite, carte avec uncheckoutUrl, la place gardée quelques minutes) ;addPartnerajoute l’organisateur à ses partenaires quandopen_matchrenvoiepartnerOffer.bookplace une pré-réservation puis règle la part du joueur (ou tout) par porte-monnaie, carte (il renvoie uncheckoutUrlà ouvrir) ou droit.search_membersne trouve, pour un joueur, que les profils publics. - Partenaires (joueur) :
my_network,send_partner_request,invite_partner(par e-mail ou téléphone, membre ou non),withdraw_partner_invitation,answer_partner_request,withdraw_partner_request,remove_partner_link,block_member,unblock_member,set_public_profile,set_joiners_become_partners(sur sa partie ouverte) ; le propriétaire autorise ou non les profils publics avecset_public_profiles_allowed.
Brancher un client en local
Section intitulée « Brancher un client en local »En local, chaque club vit sur <slug>.localhost en http. Or les clients MCP (Claude Code en tête) refusent d’envoyer un jeton à un point d’accès http qui n’est pas exactement localhost (« OAuth token requests MUST use TLS »). D’où --club : il sert un club sur l’hôte nu localhost, exempté de TLS.
- Lancez la pile avec un club sur l’hôte nu :
pnpm bo dev --club bo. L’API, le serveur MCP, l’accueil et l’appli joueur répondent alors aussi surhttp://localhost:<port>pour le clubbo. - Ajoutez le serveur à Claude Code :
claude mcp add --transport http bo http://localhost:<MCP_PORT>/mcp(pnpm bo auth login thibault@bo.testaffiche la commande avec le bon port ;pnpm bo doctorconfirme « dev club on localhost: bo »). - Dans Claude Code,
/mcp→bo→ Authenticate. La page de connexion du club s’ouvre surlocalhost; connectez-vous (mot de passe de devbooker-devpour l’équipe), puis Autoriser. - Appelez
who_am_i: il doit répondre Bø, avec votre rôle.
Quelques précisions :
- Seule la recherche du club change : l’hôte reste
localhost. Émetteurhttp://localhost:<API_PORT>/auth, audiencehttp://localhost:<MCP_PORT>/mcp, cookies surlocalhost. Une session ouverte surlocalhostne vaut pas surbo.localhost, et inversement. - Un seul club sur
localhost: l’en-têtex-bo-club-hostet les vrais hôtes de club gardent la priorité, mais?club=<slug>ne sert pas sur l’hôte nu (le client y obtiendrait un jeton pourlocalhost, refusé pour<slug>.localhost). Pour un autre club, relancez avec--club <slug>. - Après un changement de
--club, réauthentifiez le client : le jeton et la session restent ceux delocalhost, quel que soit le club. BO_DEV_CLUB(ce que pose--club) est refusé au démarrage siBO_BASE_DOMAINn’est paslocalhost: jamais en production.- Un client qui accepte
httppartout peut toujours utiliserhttp://bo.localhost:<MCP_PORT>/mcpsans--club. - La vraie réponse est TLS en local (mkcert + Caddy, un futur
bo dev --tls) : prévue, pas encore disponible.
Voir aussi
Section intitulée « Voir aussi »- L’API GraphQL, l’autre adaptateur sur les mêmes commandes.
- La référence de la CLI :
bo auth login,bo dev. - Les notions : réservation, membre et rôle.