SimplePractice Client Portal
simplepractice-mcp reads the Client Portal a practice gives its clients —
the patient side, not the clinician side. It is read-only: nothing here
cancels an appointment, signs a form, or pays a bill.
Signing in
The portal has no password. SimplePractice emails a one-time link (or a 6-digit PIN), and that is the only way in.
simplepractice_session_status— check first; a session persists between runs, so most of the time there is nothing to do. It also reports which practice is in play, and whether that came from a link, the environment, or the saved session.- If the user already has the email, skip straight to step 4 — asking for a second link when one is in their inbox spends a rate limit for nothing.
simplepractice_request_sign_in_linkwith the user's portal email. It asks for confirmation because it sends a real email and the endpoint is rate-limited per address and per IP — a retry loop locks the user out of the only auth path there is. A client that can show a prompt gets one (unless the server setsMCP_CONFIRM_ELICITATION=off); otherwise the first call sends nothing and returns a preview plus aconfirmToken: show the user the preview, and only after they approve call again with the same arguments and thatconfirmToken. Never send twice. If the server does not know the practice yet, passpractice(the slug, host, or portal URL) — otherwise it has no portal to ask.- The user opens the email and gives you the link. Pass it whole to
simplepractice_verify_sign_in_token— it takes the token out of the fragment and the practice out of the host, which is why the whole link is worth more than the token alone. Tokens are single-use and last 24 hours.
Nothing has to be configured: the practice comes from the link, and the stored
session remembers it. SIMPLEPRACTICE_PRACTICE only pins the server to one
practice. Two link shapes name no practice and need one already known — the
mobile variant on the bare clientsecure.me apex, and a bare token pasted
without its link.
There is no refresh token. When a session lapses the tools say to sign in again; that means another email.
simplepractice_sign_out only forgets the session stored on this machine. It
does not revoke it: SimplePractice is never told, so the session token stays
valid on SimplePractice's side until it expires there, and it does not sign the
Client Portal out anywhere else. Do not tell the user they are "signed out of
SimplePractice" — say the server forgot its session.
Reading
simplepractice_get_account— the practice, the current client, and the clients this login covers. Start here: one portal login can act for several people (a parent for two children), so confirm whose record you are about to report on before you report on it. It also returns the practice's real cancellation policy and the client's feature permissions.simplepractice_list_appointments(status?, page?, pageSize?, view?)—status: "scheduled"for confirmed and upcoming,"requested"for ones the practice has not confirmed yet.simplepractice_list_document_requests— paperwork.outstandingOnly: trueanswers "is anything waiting for me?", which is the usual question. It (andlist_documents/list_announcements) reads only the newestpageSizerows, max 50: whenhasMoreistruetheoutstanding/unreadcounts cover that page only, so do not say nothing is outstanding.simplepractice_get_billing_overview(view?)— balance due and per-category counts. Cheaper than listing the billing collections to find out they are empty.simplepractice_list_billing_items(kind?, before?, pageSize?, view?)— invoices, statements, superbills (the receipt to claim out-of-network insurance), receipts, or account history. Pages by cursor: pass the returnednextCursorback asbefore.simplepractice_list_payment_methods,simplepractice_list_documents,simplepractice_list_announcements.
Response shape (view)
Five of the reads take view: "compact" | "full", and compact is the
default: simplepractice_list_appointments,
simplepractice_list_billing_items, simplepractice_get_billing_overview,
simplepractice_get_document_request and
simplepractice_list_announcements.
That default is the point of the parameter. This rung used to be a
compact: false boolean — opt-in, so a caller had to know the slim shape
existed and ask for it. An efficiency that has to be requested is one that
usually is not, and the caller paying for it is the one least able to know.
Compact is not one thing here. One of the tools gets a real field projection; the other four get media stripping and no field projection at all, and the difference matters because expecting a named field set from the second group would be expecting something that was never going to be there.
simplepractice_list_appointmentsis projected, down to{id, startTime, endTime, service, clinician, location, videoRoomUrl, confirmationStatus, clientConfirmationStatus, isCancellable, fee}.clinicianis the first and last name JOINED into one string, andlocationcollapses the office record to"telehealth"or"name, city, state"— so if you are reaching forclinician.firstNameor theofficeobject, they are onfullonly.list_billing_items,get_billing_overview,get_document_requestandlist_announcementsare media-stripped only. No field projection is claimed, and that is deliberate rather than unfinished:billing-itemsis one polymorphic collection switched five ways (invoice,statement,superbill,receipt, account history), and a field list picked for an invoice would quietly drop half of a superbill. The same is true ofdocument-requests, where a consent form, a questionnaire and a Good Faith Estimate are different shapes under one endpoint. What compact takes is the practice logo and the clinician avatars; it touches nothing whose key names an amount, a date, or a document link.
One consequence worth knowing on announcements: the rung drops media keys,
never nulls. readAt: null is data — it is what "unread" means — so it
survives, and the unread count stays reconcilable against the rows beneath
it.
view: "full" returns SimplePractice's whole record. There is no raw
rung: full already IS the untouched upstream payload, so a third value
could only alias it. And view never reaches SimplePractice — it is
destructured off before the request is built, because client.list turns
whatever it is handed into a JSON:API query string and a stray view=compact
would arrive as a filter SimplePractice never defined.
The other ten tools take no view, and each has its own reason:
simplepractice_get_account,simplepractice_list_document_requestsandsimplepractice_list_payment_methodsare ALREADY hand-written projections — every field on them was picked by name with knowledge of the payload. There is no un-projected shape left underneath, so aviewthere would be a parameter that changes nothing, and running a blind rung over that output would let an un-grounded rule overrule a grounded one. (list_document_requestsalso takesincludeBody, a field the caller explicitly asked for; a blind rung could only take back something chosen on purpose.)simplepractice_list_documentsis the exception worth stating: its PRODUCT is the file references. A practice that shares a scan shares it as a.jpgor.png, and the blind rung drops any string whose path ends in an image extension — so compacting here would not shrink the answer, it would empty exactly the rows you came for.simplepractice_session_statusandsimplepractice_healthcheckanswer with status, not records.simplepractice_request_sign_in_link,simplepractice_verify_sign_in_pin,simplepractice_verify_sign_in_tokenandsimplepractice_sign_outare writes. A write's response is a receipt, with nothing to strip and everything to keep.
Reading the results honestly
- An empty billing list is a real answer. Plenty of practices invoice entirely outside the portal. "No invoices in the portal" is the true statement; "you owe nothing" is not one you can make from it.
- Do not tell someone they can cancel an appointment.
isCancellableand the practice'sclientMayCancelAppointments/cancellationNoticeHoursare what govern it, and cancelling has to happen in the portal anyway. - This is medical information. Report what was asked. Don't volunteer diagnoses, session notes, or a family member's records into a conversation that wasn't about them.
The shell alternative
simplepractice-fpx does the same reads with curl and no server, for
scripts or a machine without the MCP installed.
