{
  "markdown": "# simplepractice-mcp\n\nMCP server for the **SimplePractice Client Portal** — the side a practice's\n*clients* log into, not the clinician side. Appointments, billing, paperwork,\nand announcements, read over the portal's own JSON:API.\n\n> Developed and maintained by AI (Claude Code). Use at your own discretion.\n\n## What it reads\n\n| Tool | What it gives you |\n|---|---|\n| `simplepractice_get_account` | practice, current client, every client this login covers, cancellation policy, feature permissions |\n| `simplepractice_list_appointments` | scheduled or requested appointments, with clinician and location |\n| `simplepractice_list_billing_items` | invoices · statements · superbills · receipts · account history |\n| `simplepractice_get_billing_overview` | balance due and per-category counts |\n| `simplepractice_list_payment_methods` | saved cards — brand, last four, expiry |\n| `simplepractice_list_document_requests` | paperwork sent to you, with an outstanding-only filter |\n| `simplepractice_get_document_request` | one request in full, with its questions and answers |\n| `simplepractice_list_documents` | files the practice has shared |\n| `simplepractice_list_announcements` | practice announcements, with unread counts |\n| `simplepractice_session_status` · `_request_sign_in_link` · `_verify_sign_in_token` · `_verify_sign_in_pin` · `_sign_out` | sign-in |\n| `simplepractice_healthcheck` | Verify credentials and upstream reachability; reports failures as data, not exceptions |\n\nEverything is read-only. Cancelling, signing, and paying happen in the portal.\n\nThe reads that answer with a SimplePractice record rather than a projection —\nappointments, billing items, the billing overview, one document request,\nannouncements — take a `view`. It defaults to `compact`, which returns the slim\nprojection where this server has one and otherwise drops logo and avatar URLs a\nmodel cannot see; `view: \"full\"` returns the record untouched.\n`simplepractice_list_documents` deliberately takes none: what it returns is the\nfile reference, and a shared scan is a `.jpg`.\n\n## Setup\n\n```sh\nnpm install -g simplepractice-mcp\n```\n\nThere is nothing to configure. The practice comes from your sign-in link.\n\n| Variable | |\n|---|---|\n| `SIMPLEPRACTICE_PRACTICE` | optional — pins the server to one practice (slug or host) |\n| `SIMPLEPRACTICE_SESSION_FILE` | optional — session path (default `~/.simplepractice-mcp/session.json`, written `0600`) |\n\n## Signing in\n\nThe Client Portal has **no password**. SimplePractice emails a one-time link\n(or a 6-digit PIN); you trade it for a session cookie:\n\n1. Open the email your provider sent, copy the link.\n2. `simplepractice_verify_sign_in_token { link }` — pass the **whole** link.\n\nThe link is `https://<practice>.clientsecure.me/sign-in/token#<TOKEN>`, so one\npaste carries both halves of what the server needs: the token is the `#`\nfragment, and the host names the practice. Nothing is hardcoded, and the\nstored session remembers the practice for every later run —\n`simplepractice_session_status` reports which practice is in play and whether\nit came from a link, the environment variable, or the saved session.\n\nTo have a fresh link sent rather than using one you already have, name the\npractice once:\n\n```\nsimplepractice_request_sign_in_link { email, practice: \"achievebalancetherapy\", confirm: true }\n```\n\n`practice` can be omitted whenever the server already knows the practice —\nfrom an earlier sign-in, or from `SIMPLEPRACTICE_PRACTICE`.\n\nTwo sign-in links name no practice, and fall back to whichever one is already\nknown: the mobile-app variant SimplePractice sends\n(`https://clientsecure.me/client-portal-api/sign-in/token#<TOKEN>`, pointed at\nthe bare apex), and a bare token pasted without its link. A link on any host\noutside `*.clientsecure.me` is never adopted — the token is not sent there.\n\nLinks are single-use — replaying one answers\n`401 \"Authorization has already been used or expired\"` — and last 24 hours. The\nrequest endpoint is rate-limited per address **and** per IP, which is why\nsending is confirm-gated: a retry loop locks you out of the only way in. There\nis no refresh token; when the session lapses, you sign in again.\n\nThe whole chain is verified end to end against a live portal — request, the\nemailed link, the exchange returning `verified` plus a session cookie, and an\nauthenticated read with that new session.\n\nBecause that flow needs nothing but HTTP and your inbox, this server has no\nbrowser dependency and can run anywhere.\n\n## Without the server\n\n`skills/simplepractice-fpx` does the same reads with `curl`, either signing in\nby magic link or lifting the session cookie from a browser tab with\n[`fpx`](https://www.npmjs.com/package/@fetchproxy/cli).\n\n## Notes from building this\n\nThe portal is an Ember app that ships **public sourcemaps**, so its models,\nadapters and routes are readable directly — `docs/SIMPLEPRACTICE-API.md`\nrecords the endpoints and the traps, all confirmed against a live portal:\n\n- The SPA catch-all answers **HTTP 200 with `text/html`** for any path the API\n  does not define. `/cards` and `/client-billing-overviews` look like working,\n  empty endpoints and are not endpoints at all — both are `include`\n  relationships of `/clients/<id>`.\n- `hasDocumentPdf`, a card's `isDefault`, and the client's `permissions` blob\n  are all **strings**, not booleans or objects.\n- Billing pages by *cursor* (`page[before]` = a row's `cursorId`), appointments\n  page by *number*. The two are not interchangeable.\n\n## Development\n\n```sh\nnpm install\nnpm run build\nnpm test              # 214 tests\nnpm run test:coverage # 100% enforced\nnpm run typecheck     # vitest does not run tsc — this does\n```\n\n## License\n\nMIT\n",
  "bytes": 5704,
  "sha": "1222e904b77cf36670869339d6010123a5276bc4ba2a2858511ead3efbef1be2",
  "repo_slug": "chrischall/simplepractice-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_chrischall_simplepractice_mcp_85baabbc/readme"
}