{
  "$schema": "https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json",
  "version": "1.0",
  "protocolVersion": "2026-07-28",
  "supportedVersions": [
    "2026-07-28",
    "2025-11-25",
    "2025-06-18",
    "2025-03-26",
    "2024-11-05"
  ],
  "serverInfo": {
    "name": "dispola",
    "title": "Dispolà",
    "version": "1.2.0",
    "websiteUrl": "https://dispola.ca"
  },
  "description": "Places libres aujourd'hui et cette semaine chez les pros du Québec (coiffure, ongles, massothérapie, physio...). / Same-day appointment openings in Quebec.",
  "documentationUrl": "https://dispola.ca/llms.txt",
  "transport": {
    "type": "streamable-http",
    "endpoint": "/mcp"
  },
  "remotes": [
    {
      "type": "streamable-http",
      "url": "https://dispola.ca/mcp"
    }
  ],
  "sandbox": {
    "url": "https://dispola.ca/mcp/essai",
    "description": "Fausses places, aucun texto, code 000000. / Fake spots, no texts, code 000000."
  },
  "authentication": {
    "required": false
  },
  "capabilities": {
    "tools": {
      "listChanged": false
    },
    "resources": {
      "listChanged": false
    }
  },
  "instructions": "Dispolà : les places libres aujourd'hui, demain et cette semaine (annulations, trous d'horaire) chez les pros du Grand Montréal, de Québec, Sherbrooke et Gatineau : coiffure, ongles, cils, sourcils, esthétique, massothérapie, tatouage, physiothérapie, ostéopathie, kinésiologie, thérapie du sport, chiropratique, acupuncture.\n1. chercher_places : les places qui conviennent. Donne à la personne l'établissement, l'heure, le prix et le lien booking_url : c'est elle qui réserve.\n2. sur_demande (avec une catégorie, jamais pour aujourd'hui) : des commerces sans réservation en ligne, avec des moments estimés à confirmer. Ce ne sont pas des places. Deux façons de demander :\n   a. Sans quitter la conversation : envoyer_code avec le commerce, au moins 2 heures, le service, et le prénom et le cellulaire que la personne t'a donnés. Dispolà lui texte un code avec le texte exact de l'accord ; elle te le lit, et confirmer_code envoie la demande. Ne devine jamais un code, ne l'invente pas : c'est la personne qui le lit.\n   b. preparer_demande : un lien prérempli qu'elle ouvre, vérifie, et où elle entre elle-même son prénom et son cellulaire et coche le consentement. Ne soumets jamais le formulaire à sa place.\n   Dispolà appelle le commerce et texte l'heure confirmée à la personne, d'habitude dans la journée.\n3. Aucune place qui convient : lien_alerte_texto donne un formulaire prérempli. La personne y entre elle-même son cellulaire et consent aux textos ; ne soumets jamais le formulaire à sa place.\n4. categories_et_villes : ce qui existe, avec le nombre de places.\n5. proposer_limites : la personne signe d'avance, par code texto, ce que tu peux demander pour elle (catégorie, services, jours, heures, lieu, prix max, au plus 14 jours). confirmer_code te rend une clé mandat ; avec accepter_seul, envoyer_code (mandat) envoie seul une demande qui respecte toutes les limites, et la personne reçoit un texto. Dispolà vérifie chaque limite, ligne par ligne.\nChaque place a un champ pourquoi : les filtres qu'elle respecte, à expliquer à la personne.\nPour essayer sans rien envoyer : /mcp/essai (fausses places, code 000000).\nHeures à l'heure de Montréal. Prix en dollars canadiens, au prix du ou de la pro.\n\nEnglish: open appointment spots today, tomorrow and this week in Greater Montreal, Quebec City, Sherbrooke and Gatineau (hair, nails, lashes, brows, skin care, massage, tattoo, physio, osteo, kinesiology, athletic therapy, chiropractic, acupuncture). Search with chercher_places and give the person the booking_url: they book themselves. With a category it may also return sur_demande: businesses without online booking and estimated times, not open spots. To request one without leaving the chat, envoyer_code texts the person a code with the exact consent wording; they read it to you and confirmer_code sends the request. Or preparer_demande builds a prefilled link where they enter their own name and number and consent. Dispolà calls the business and texts the confirmed time. proposer_limites lets the person sign, by text code, limits within which you may request on their behalf. Sandbox: /mcp/essai. Nothing fits: lien_alerte_texto builds a prefilled text alert form; the person enters their own number and consents. Never submit it for them.",
  "tools": [
    {
      "name": "chercher_places",
      "title": "Chercher des places libres",
      "description": "Cherche les places libres d'une ville, par catégorie, jour, moment et quartier. Rend l'établissement, le quartier, le service, l'heure, la durée, le prix et le lien pour réserver. Avec une catégorie, rend aussi sur_demande : des commerces sans réservation en ligne, avec des moments estimés à confirmer. / Finds open appointment spots in a city, by category, day, time of day and neighbourhood, with the booking link. With a category, also returns sur_demande: businesses without online booking, with estimated times to be confirmed by phone.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "ville": {
            "type": "string",
            "enum": [
              "montreal",
              "quebec",
              "sherbrooke",
              "gatineau",
              "rive-nord",
              "rive-sud"
            ],
            "description": "La ville. / The city."
          },
          "categorie": {
            "type": "string",
            "enum": [
              "coiffure",
              "ongles",
              "cils",
              "sourcils",
              "esthetique",
              "massotherapie",
              "tatouage",
              "physiotherapie",
              "osteopathie",
              "kinesiologie",
              "therapie_sport",
              "chiropratique",
              "acupuncture"
            ],
            "description": "La catégorie (voir categories_et_villes). / The category."
          },
          "jour": {
            "type": "string",
            "pattern": "^(aujourdhui|demain|\\d{4}-\\d{2}-\\d{2})$",
            "description": "aujourdhui, demain ou AAAA-MM-JJ (heure de Montréal). / today, tomorrow or YYYY-MM-DD."
          },
          "moment": {
            "type": "string",
            "enum": [
              "am",
              "pm",
              "soir"
            ],
            "description": "am : avant midi ; pm : de midi à 17 h ; soir : après 17 h. / morning, afternoon, evening."
          },
          "quartier": {
            "type": "string",
            "minLength": 2,
            "maxLength": 60,
            "description": "Un quartier, par exemple Plateau ou Limoilou : trie du plus proche au plus loin. / A neighbourhood: sorts nearest first."
          },
          "lat": {
            "type": "number",
            "minimum": -90,
            "maximum": 90,
            "description": "Latitude, avec lng : trie du plus proche au plus loin. / Latitude, with lng: sorts nearest first."
          },
          "lng": {
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "description": "Longitude, avec lat. / Longitude, with lat."
          },
          "limite": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 10,
            "description": "Nombre de places, 10 par défaut. / Max spots, default 10."
          }
        },
        "required": [
          "ville"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "title": "Chercher des places libres",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      },
      "_meta": {
        "ui": {
          "resourceUri": "ui://dispola/places.html"
        },
        "openai/toolInvocation/invoking": "Je cherche des places…",
        "openai/toolInvocation/invoked": "Places trouvées"
      }
    },
    {
      "name": "categories_et_villes",
      "title": "Catégories et villes",
      "description": "Les villes, les catégories et leurs services, avec le nombre de places libres par ville et par catégorie d'ici 7 jours. / Cities, categories and services, with the number of open spots per city and category over the next 7 days.",
      "inputSchema": {
        "type": "object",
        "properties": {},
        "additionalProperties": false
      },
      "annotations": {
        "title": "Catégories et villes",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "lien_alerte_texto",
      "title": "Lien d'alerte texto",
      "description": "Construit le lien du formulaire prérempli d'alerte texto quand aucune place ne convient. La personne l'ouvre, entre elle-même son cellulaire et consent aux textos ; ne soumets jamais le formulaire. / Builds a prefilled text alert form link; the person enters their own number and consents. Never submit it.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "ville": {
            "type": "string",
            "enum": [
              "montreal",
              "quebec",
              "sherbrooke",
              "gatineau",
              "rive-nord",
              "rive-sud"
            ],
            "description": "La ville. / The city."
          },
          "service": {
            "type": "string",
            "enum": [
              "coiffure.coupe_femme",
              "coiffure.coupe_homme",
              "coiffure.coloration",
              "coiffure.mise_en_plis",
              "coiffure.barbe",
              "ongles.gel",
              "ongles.manucure",
              "ongles.remplissage",
              "ongles.pedicure",
              "cils.classique",
              "cils.hybride",
              "cils.volume",
              "cils.remplissage",
              "cils.rehaussement",
              "sourcils.restructuration",
              "sourcils.teinture",
              "sourcils.laminage",
              "sourcils.fil",
              "esthetique.soin_visage",
              "esthetique.cire",
              "esthetique.bresilienne",
              "massotherapie.suedois",
              "massotherapie.therapeutique",
              "massotherapie.tissus_profonds",
              "massotherapie.prenatal",
              "tatouage.flash",
              "tatouage.petit",
              "tatouage.retouche",
              "physiotherapie.evaluation",
              "physiotherapie.traitement",
              "osteopathie.traitement",
              "kinesiologie.seance",
              "therapie_sport.traitement",
              "chiropratique.traitement",
              "acupuncture.seance"
            ],
            "description": "Le code du service, par exemple ongles.gel (voir categories_et_villes). / Service code."
          },
          "quartier": {
            "type": "string",
            "minLength": 2,
            "maxLength": 60,
            "description": "Le quartier de la personne. / Their neighbourhood."
          },
          "jour": {
            "type": "string",
            "pattern": "^(aujourdhui|demain|\\d{4}-\\d{2}-\\d{2})$",
            "description": "aujourdhui, demain ou AAAA-MM-JJ (heure de Montréal). / today, tomorrow or YYYY-MM-DD."
          },
          "moment": {
            "type": "string",
            "enum": [
              "am",
              "pm",
              "soir"
            ],
            "description": "am : avant midi ; pm : de midi à 17 h ; soir : après 17 h. / morning, afternoon, evening."
          },
          "langue": {
            "type": "string",
            "enum": [
              "fr",
              "en"
            ],
            "default": "fr",
            "description": "Langue du formulaire. / Form language."
          }
        },
        "required": [
          "ville"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "title": "Lien d'alerte texto",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "preparer_demande",
      "title": "Préparer une demande sur demande",
      "description": "Bâtit le lien de la demande chez un commerce sans réservation en ligne (sur_demande de chercher_places), avec les heures, le service et le lieu de la personne déjà choisis. La personne l'ouvre, vérifie, entre elle-même son prénom et son cellulaire et consent ; Dispolà appelle le commerce et lui texte l'heure confirmée. Ne soumets jamais le formulaire. / Builds the prefilled request link for a business without online booking, with the person's times, service and location filled in. The person opens it, enters their own name and number and consents; Dispolà calls the business. Never submit it.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "commerce": {
            "type": "string",
            "pattern": "^[a-z0-9-]{1,120}$",
            "description": "Le code du commerce (champ commerce de sur_demande). / Business code (commerce field of sur_demande)."
          },
          "heures": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(,\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}){0,11}$",
            "description": "Les heures qui vont à la personne, prises dans moments de sur_demande, séparées par des virgules : au moins 2, en cas que la première soit prise. Ex. 2026-10-29T14:00,2026-10-29T15:00. / Times from sur_demande moments, comma-separated, at least 2."
          },
          "service": {
            "type": "string",
            "pattern": "^[a-z0-9-]{1,40}$",
            "description": "Le service précis (champ services de sur_demande, ex. coupe-courts). / Precise service code."
          },
          "lieu": {
            "type": "string",
            "minLength": 2,
            "maxLength": 60,
            "description": "Où est la personne : code postal ou quartier, pour un commerce de rechange proche d’elle. / Postal code or neighbourhood."
          },
          "lat": {
            "type": "number",
            "minimum": -90,
            "maximum": 90,
            "description": "Latitude de la personne, avec lng (arrondie à 100 m). / Latitude, with lng."
          },
          "lng": {
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "description": "Longitude, avec lat. / Longitude, with lat."
          },
          "langue": {
            "type": "string",
            "enum": [
              "fr",
              "en"
            ],
            "default": "fr",
            "description": "Langue du formulaire. / Form language."
          }
        },
        "required": [
          "commerce"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "title": "Préparer une demande sur demande",
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "envoyer_code",
      "title": "Envoyer le code texto de la demande",
      "description": "Prépare la demande chez un commerce sur demande et texte à la personne un code à 6 chiffres, avec le texte exact de son accord. Elle te le lit, puis confirmer_code envoie la demande. Le prénom et le cellulaire sont ceux qu'elle t'a donnés dans la conversation, pour elle-même. Avec mandat (la clé de confirmer_code après proposer_limites) et accepter_seul, une demande qui respecte toutes ses limites part sans code. / Prepares the request and texts the person a 6-digit code with the exact consent wording; they read it to you and confirmer_code sends it. With a mandate key and auto-accept, a request within all their signed limits is sent without a code.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "commerce": {
            "type": "string",
            "pattern": "^[a-z0-9-]{1,120}$",
            "description": "Le code du commerce (champ commerce de sur_demande). / Business code."
          },
          "heures": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(,\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}){0,11}$",
            "description": "Au moins 2 heures de moments, séparées par des virgules, qui vont toutes à la personne. / At least 2 times from moments, comma-separated."
          },
          "service": {
            "type": "string",
            "pattern": "^[a-z0-9-]{1,40}$",
            "description": "Le service précis (champ services de sur_demande). / Precise service code."
          },
          "prenom": {
            "type": "string",
            "minLength": 1,
            "maxLength": 30,
            "description": "Le prénom de la personne. / Their first name."
          },
          "cellulaire": {
            "type": "string",
            "minLength": 10,
            "maxLength": 20,
            "description": "Son cellulaire, Canada ou États-Unis. / Their mobile number."
          },
          "lieu": {
            "type": "string",
            "minLength": 2,
            "maxLength": 60,
            "description": "Où est la personne : code postal ou quartier. / Postal code or neighbourhood."
          },
          "lat": {
            "type": "number",
            "minimum": -90,
            "maximum": 90,
            "description": "Latitude, avec lng. / Latitude."
          },
          "lng": {
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "description": "Longitude, avec lat. / Longitude."
          },
          "mandat": {
            "type": "string",
            "minLength": 20,
            "maxLength": 120,
            "description": "La clé mandat rendue par confirmer_code après proposer_limites. / Mandate key."
          },
          "langue": {
            "type": "string",
            "enum": [
              "fr",
              "en"
            ],
            "default": "fr",
            "description": "Langue des textos. / Text language."
          }
        },
        "required": [
          "commerce",
          "heures",
          "service"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "title": "Envoyer le code texto de la demande",
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": false,
        "openWorldHint": true
      }
    },
    {
      "name": "confirmer_code",
      "title": "Confirmer avec le code texto",
      "description": "Le code à 6 chiffres que la personne t'a lu, reçu par texto après envoyer_code ou proposer_limites. Une demande part chez Dispolà, qui appelle le commerce ; des limites deviennent actives et tu reçois la clé mandat. Ne devine jamais un code. / The 6-digit code the person read to you. Sends the request, or activates their limits and returns the mandate key. Never guess a code.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "reference": {
            "type": "string",
            "pattern": "^[dm]\\d{1,12}-[A-Za-z0-9_-]{20,40}$",
            "description": "La référence rendue par envoyer_code ou proposer_limites. / The reference returned earlier."
          },
          "code": {
            "type": "string",
            "pattern": "^\\s*\\d{6}\\s*$",
            "description": "Les 6 chiffres lus par la personne. / The 6 digits."
          }
        },
        "required": [
          "reference",
          "code"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "title": "Confirmer avec le code texto",
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": false,
        "openWorldHint": true
      }
    },
    {
      "name": "proposer_limites",
      "title": "Proposer des limites à signer",
      "description": "Les limites dans lesquelles tu peux demander pour la personne (le mandat ouvert d'AP2) : catégorie, services, jours (au plus 14), heures, lieu et rayon, prix max, et si tu peux envoyer seul. Dispolà les lui texte mot pour mot avec un code ; elle te le lit, et confirmer_code te rend la clé mandat. Elle peut tout annuler en textant STOP AGENT. / Limits within which you may request on the person's behalf, signed by a text code.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "prenom": {
            "type": "string",
            "minLength": 1,
            "maxLength": 30,
            "description": "Le prénom de la personne. / Their first name."
          },
          "cellulaire": {
            "type": "string",
            "minLength": 10,
            "maxLength": 20,
            "description": "Son cellulaire. / Their mobile number."
          },
          "ville": {
            "type": "string",
            "enum": [
              "montreal",
              "quebec",
              "sherbrooke",
              "gatineau",
              "rive-nord",
              "rive-sud"
            ],
            "description": "La ville. / The city."
          },
          "categorie": {
            "type": "string",
            "enum": [
              "coiffure",
              "ongles",
              "cils",
              "sourcils",
              "esthetique",
              "massotherapie",
              "tatouage",
              "physiotherapie",
              "osteopathie",
              "kinesiologie",
              "therapie_sport",
              "chiropratique",
              "acupuncture"
            ],
            "description": "La catégorie. / The category."
          },
          "services": {
            "type": "string",
            "pattern": "^[a-z0-9-]{1,40}(,[a-z0-9-]{1,40}){0,9}$",
            "description": "Les services permis (codes de services de sur_demande), séparés par des virgules. Sans : toute la catégorie. / Allowed service codes."
          },
          "du": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Premier jour, AAAA-MM-JJ. Demain par défaut. / First day."
          },
          "au": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Dernier jour, au plus 14 jours d'ici. Les limites expirent ce soir-là. / Last day; limits expire that night."
          },
          "apres": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}$",
            "description": "Pas avant cette heure, HH:MM. / Not before."
          },
          "avant": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}$",
            "description": "Pas après cette heure, HH:MM. / Not after."
          },
          "lieu": {
            "type": "string",
            "minLength": 2,
            "maxLength": 60,
            "description": "Code postal ou quartier de la personne. / Postal code or neighbourhood."
          },
          "rayon_km": {
            "type": "number",
            "minimum": 0.5,
            "maximum": 25,
            "description": "Distance max du lieu, 3 km par défaut. / Max distance."
          },
          "prix_max": {
            "type": "integer",
            "minimum": 5,
            "maximum": 1000,
            "description": "Prix max en dollars. Inconnu d’avance chez un commerce sur demande : Léon le vérifie au téléphone. / Max price."
          },
          "accepter_seul": {
            "type": "string",
            "enum": [
              "oui",
              "non"
            ],
            "default": "non",
            "description": "oui : tu peux envoyer seul une demande qui respecte tout. non : un code à chaque demande. / Auto-accept."
          },
          "langue": {
            "type": "string",
            "enum": [
              "fr",
              "en"
            ],
            "default": "fr",
            "description": "Langue des textos. / Text language."
          }
        },
        "required": [
          "prenom",
          "cellulaire",
          "ville",
          "categorie"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "title": "Proposer des limites à signer",
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": false,
        "openWorldHint": true
      }
    }
  ],
  "resources": [
    {
      "uri": "ui://dispola/places.html",
      "name": "places",
      "title": "Places libres en cartes",
      "description": "Les places de chercher_places en cartes : établissement, quartier, heure, prix et bouton pour réserver ; les commerces sur demande avec leurs heures à cocher. / Open spots as cards with a booking button.",
      "mimeType": "text/html;profile=mcp-app"
    }
  ]
}