simplepractice-mcp 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,18 +21,27 @@ The portal has **no password**. SimplePractice emails a one-time link (or a
21
21
  6-digit PIN), and that is the only way in.
22
22
 
23
23
  1. `simplepractice_session_status` — check first; a session persists between
24
- runs, so most of the time there is nothing to do.
25
- 2. `simplepractice_request_sign_in_link` with the user's portal email. It is
24
+ runs, so most of the time there is nothing to do. It also reports which
25
+ practice is in play, and whether that came from a link, the environment, or
26
+ the saved session.
27
+ 2. If the user already has the email, skip straight to step 4 — asking for a
28
+ second link when one is in their inbox spends a rate limit for nothing.
29
+ 3. `simplepractice_request_sign_in_link` with the user's portal email. It is
26
30
  confirm-gated because it sends a real email and the endpoint is rate-limited
27
31
  **per address and per IP** — a retry loop locks the user out of the only
28
- auth path there is. Ask before sending, and never send twice.
29
- 3. The user opens the email and gives you the link. Pass it whole to
32
+ auth path there is. Ask before sending, and never send twice. If the server
33
+ does not know the practice yet, pass `practice` (the slug, host, or portal
34
+ URL) — otherwise it has no portal to ask.
35
+ 4. The user opens the email and gives you the link. Pass it **whole** to
30
36
  `simplepractice_verify_sign_in_token` — it takes the token out of the
31
- fragment itself. Tokens are single-use and last 24 hours.
37
+ fragment *and* the practice out of the host, which is why the whole link is
38
+ worth more than the token alone. Tokens are single-use and last 24 hours.
32
39
 
33
- `SIMPLEPRACTICE_PRACTICE` must name the practice's portal — the slug or the
34
- full `<practice>.clientsecure.me` host, from the link the provider emailed.
35
- If it is unset, every tool says so on its first call.
40
+ Nothing has to be configured: the practice comes from the link, and the stored
41
+ session remembers it. `SIMPLEPRACTICE_PRACTICE` only pins the server to one
42
+ practice. Two link shapes name no practice and need one already known — the
43
+ mobile variant on the bare `clientsecure.me` apex, and a bare token pasted
44
+ without its link.
36
45
 
37
46
  There is no refresh token. When a session lapses the tools say to sign in
38
47
  again; that means another email.
@@ -82,13 +82,21 @@ hammering it locks you out of the only auth path there is. Wait it out.
82
82
  **b. Take the token out of the emailed link.** The link looks like
83
83
 
84
84
  ```
85
- https://<practice>.clientsecure.me/sign-in/token/verify#<TOKEN>
85
+ https://<practice>.clientsecure.me/sign-in/token#<TOKEN>
86
86
  ```
87
87
 
88
- The token is the **URL fragment**, after the `#`. Because it is a fragment it
89
- is never sent to the server by a browser navigation — the app reads it in JS
90
- and posts it. So you must copy it yourself; following the link with `curl`
91
- does nothing.
88
+ SimplePractice also mails a mobile-app variant on the bare apex,
89
+ `https://clientsecure.me/client-portal-api/sign-in/token#<TOKEN>`. Either
90
+ works — the path is irrelevant, only the fragment matters.
91
+
92
+ The token is the **URL fragment**, after the `#` (about 300 characters).
93
+ Because it is a fragment it is never sent to the server by a browser
94
+ navigation — the app reads it in JS and posts it. So you must copy it
95
+ yourself; following the link with `curl` does nothing.
96
+
97
+ If you are pulling the link out of a raw message rather than clicking it, note
98
+ the mail is **quoted-printable**: the URL is wrapped across lines with trailing
99
+ `=`, and a naive regex will hand you a silently truncated token. Decode first.
92
100
 
93
101
  **c. Trade it for a session cookie.**
94
102
 
@@ -60,10 +60,20 @@ sp -X POST "$SP_API/sign-in-tokens" \
60
60
 
61
61
  ### 1.2 Exchange the token — `POST /sessions/token`
62
62
 
63
- The emailed link is `https://<practice>.clientsecure.me/sign-in/token/verify#<TOKEN>`.
64
- The token is the **fragment**. A browser never sends a fragment to the server;
65
- the Ember app reads `location.hash` and posts it. Fetching the link with `curl`
66
- accomplishes nothing — copy the part after `#`.
63
+ The emailed link is
64
+ **`https://<practice>.clientsecure.me/sign-in/token#<TOKEN>`** — `/sign-in/token`,
65
+ *not* the `sign-in/token/verify` the app's route tree implies. A second variant,
66
+ sent for the mobile app, points at the bare apex under the API namespace:
67
+ `https://clientsecure.me/client-portal-api/sign-in/token#<TOKEN>`. Either works —
68
+ take the fragment, ignore the path.
69
+
70
+ The token is the **fragment** (303–317 characters observed). A browser never
71
+ sends a fragment to the server; the app reads `location.hash` and posts it.
72
+ Fetching the link with `curl` accomplishes nothing — copy the part after `#`.
73
+
74
+ Both emails are quoted-printable, so the URL is **wrapped across lines with
75
+ trailing `=`**. Pulling it out of a raw message with a naive regex silently
76
+ truncates the token — decode the quoted-printable first.
67
77
 
68
78
  ```sh
69
79
  sp -X POST "$SP_API/sessions/token" \
@@ -80,13 +90,10 @@ Success sets the `simplepractice-session` cookie (Rails/Devise) and returns
80
90
  | `expired` | older than 24h — request a new link |
81
91
  | `merged` | the account was merged into another; sign in from the new portal |
82
92
 
83
- Tokens are single-use; replaying one gives `401`/`422`.
84
-
85
- > Not exercised live during this build: the exchange call itself. §1.1 was
86
- > confirmed against a real account (`202`), and the shape above comes from
87
- > `routes/sign-in/token/verify.js` + `services/sign-in.js`, but reading the
88
- > emailed token was out of scope for the agent that wrote this. Treat
89
- > `meta.status` handling as source-derived until you've run it once.
93
+ Tokens are single-use — confirmed by replay, which answers
94
+ `401 {"title":"Authorization has already been used or expired"}`. That is a 401
95
+ on a sign-in endpoint, where you have no session yet; it means *get a new
96
+ link*, not *your session expired*.
90
97
 
91
98
  ### 1.3 PIN variant — `POST /sessions/pin`
92
99