{
  "markdown": "# salonrunner-mcp\n\nAn [MCP](https://modelcontextprotocol.io) server that lets an AI assistant **find, book, and\ncancel salon appointments** through your personal SalonRunner / Rosy Salon Software client\naccount — the same booking site many salons use.\n\nIt's **self-hosted**: you deploy your own instance. Run it locally as a tool (Claude Desktop,\nCursor, Copilot CLI) where credentials stay on your machine, or as a remote connector for\n**claude.ai** where you log in with your SalonRunner account on the connector's login screen.\n\n> ⚠️ Unofficial, uses undocumented endpoints, personal use only. Read [DISCLAIMER.md](./DISCLAIMER.md).\n\n## Tools\n\n| Tool | What it does |\n|------|--------------|\n| `list_services` | List bookable services (name, id, price) |\n| `list_providers` | List stylists; optionally only those who do a given service |\n| `find_availability` | Open slots for a service over a date range (optionally one provider) |\n| `list_my_appointments` | Your upcoming appointments |\n| `book_appointment` | Book a slot returned by `find_availability` |\n| `cancel_appointment` | Cancel by appointment id |\n\n## How it works\n\n```\nlist/find/book/cancel\n        │\n   this server ──login──► app.salonrunner.com  (session cookie)\n        │      ──authv2─► customer JWT (30 min, auto-refreshed)\n        │      ──reads──► app.rosysalonsoftware.com/api/v2  (Bearer JWT)\n        └──────writes───► /customer/appointments/{book,cancel}.json  (cookie)\n```\n\n`customerId` is discovered from your account after login; `corporateId` is read from the JWT.\nAvailability is computed from the provider's per-service duration and the salon's slot grid\n(`SALONRUNNER_SLOT_MINUTES`, default 15).\n\n## Configuration\n\nThere are two ways to run it, and they get their salon credentials differently:\n\n- **Local (stdio):** credentials come from the environment (`.env`).\n- **Remote (HTTP, claude.ai):** credentials are entered on the connector's **login screen** and\n  encrypted into the token — the server needs **no** salon credentials in its environment.\n\n| Variable | Used by | Notes |\n|----------|---------|-------|\n| `SALONRUNNER_SALON_ID` | stdio (+ optional HTTP) | The `id` in your booking URL `…/customer/login.htm?id=XXXXX`. In HTTP mode it's optional — if set, it pre-fills the salon id on the connector's login screen (handy for single-salon deployments and re-auth) |\n| `SALONRUNNER_USERNAME` / `SALONRUNNER_PASSWORD` | stdio | Your client login |\n| `SALONRUNNER_CUSTOMER_ID` | both | Auto-discovered; set only if discovery fails |\n| `SALONRUNNER_SLOT_MINUTES` | both | Salon booking granularity (default 15) |\n| `SALONRUNNER_READ_ONLY` | both | `true` disables book/cancel while you try it out |\n| `SESSION_SIGNING_KEY` | HTTP | Signs tokens + encrypts the credentials inside them; survives restarts/scale-to-zero (>=16 chars) |\n| `PUBLIC_URL` | HTTP | This server's public URL, e.g. `https://your-app.fly.dev` |\n\nIn HTTP mode the salon id + username + password are collected on the login screen (validated by a\nreal SalonRunner login) and encrypted into the OAuth token, so **one deployment can serve multiple\nsalons** and there are no salon secrets on the server.\n\n## Option A — Local (Claude Desktop / Cursor / Copilot CLI)\n\nPublished on npm as [`salonrunner-mcp`](https://www.npmjs.com/package/salonrunner-mcp). Install globally:\n\n```bash\nnpm install -g salonrunner-mcp\n```\n\nThen point your client at the `salonrunner-mcp` command (no build, no absolute paths). Claude Desktop (`claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"salonrunner\": {\n      \"command\": \"salonrunner-mcp\",\n      \"env\": {\n        \"SALONRUNNER_SALON_ID\": \"21248\",\n        \"SALONRUNNER_USERNAME\": \"you@example.com\",\n        \"SALONRUNNER_PASSWORD\": \"your-password\"\n      }\n    }\n  }\n}\n```\n\nPrefer no global install? Use `\"command\": \"npx\"` with `\"args\": [\"-y\", \"salonrunner-mcp\"]` and the same `env`.\n\n<details>\n<summary>Run from a clone instead</summary>\n\n```bash\nnpm install && npm run build\n```\n\nThen use `\"command\": \"node\"` with `\"args\": [\"/absolute/path/to/salonrunner-mcp/dist/stdio.js\"]` and the same `env`.\n</details>\n\nNo hosting, no OAuth — credentials stay on your machine. Recommended if you don't need claude.ai.\n\n## Option B — Remote (claude.ai custom connector)\n\nclaude.ai can only use **remote** MCP servers, so you deploy your own instance.\n\n### Deploy to Fly.io\n\n```bash\nfly launch --no-deploy          # pick a unique app name; creates the app\nfly secrets set \\\n  SESSION_SIGNING_KEY=$(node -e \"console.log(require('crypto').randomBytes(32).toString('base64url'))\") \\\n  PUBLIC_URL=https://YOUR-APP.fly.dev\nfly deploy\nfly scale count 1               # in-memory MCP sessions: keep a single instance\n```\n\nNo salon credentials are configured here — users supply them on the login screen. The server\n**refuses to start** without `SESSION_SIGNING_KEY`. Credentials are validated by a real\nSalonRunner login and then encrypted into the (signed) token, which claude.ai stores, so the app\n**scales to zero** between uses and you **authorize only once** — cold starts (~3s) are\ntransparent and never re-prompt.\n\n### Connect in claude.ai\n\n1. **Settings → Connectors → Add custom connector**.\n2. URL: `https://YOUR-APP.fly.dev/mcp`\n3. Claude opens the connector's login screen → enter your **salon id + username + password**.\n4. The six tools appear in chat.\n\n### Run the remote server locally (testing)\n\n```bash\nSESSION_SIGNING_KEY=local-dev-please-change PUBLIC_URL=http://localhost:8787 npm run start:http\n```\n\n## Security model\n\nTwo independent auth layers:\n\n1. **claude.ai ↔ this server** — OAuth 2.1 (PKCE + dynamic client registration). The login\n   screen authenticates the user with a **real SalonRunner login**; the credentials are then\n   **AES-GCM encrypted and embedded inside the HMAC-signed token** (keyed by `SESSION_SIGNING_KEY`).\n   No server-side session store, so authorization survives restarts and scale-to-zero.\n2. **this server ↔ SalonRunner** — login → session cookie → short-lived JWT, auto-refreshed,\n   using the credentials decrypted from the caller's token.\n\nThe server holds **no salon credentials at rest** — they live (encrypted) inside each user's\ntoken and are only decrypted in memory per request. One deployment can serve multiple salons.\nA leaked token can't be revoked individually; rotate `SESSION_SIGNING_KEY` to invalidate **all**\ntokens (everyone re-enters credentials once). Keep `SESSION_SIGNING_KEY` secret and serve only\nover HTTPS.\n\n## Notes & limitations\n\n- Built on **undocumented** customer endpoints; they can change without notice. Base URLs are\n  configurable so you can adapt quickly.\n- Real bookings/cancellations incur the salon's **cancellation-policy fees**. Tools surface the\n  service/provider/time before acting; consider running with `SALONRUNNER_READ_ONLY=true` first.\n- `SALONRUNNER_SLOT_MINUTES` must match your salon's scheduling grid (default 15) for accurate\n  availability.\n- The officially documented, partner-only **Rosy Salon Software API** (`api.salonrunner.com`)\n  is a separate product requiring a salon-issued ApiKey; this project does not use it.\n\n## License\n\nMIT — see [LICENSE](./LICENSE). No warranty.\n",
  "bytes": 7170,
  "sha": "f8152026e0a3705362f2fde043b00d33f93bd8e14357f8424aca5be1e0938a29",
  "repo_slug": "topness-msft/salonrunner-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_topness_msft_salonrunner_mcp_082b8936/readme"
}