Back to Browse

Therapyportal MCP Server

Developer ToolsLow Risk9.5Local
Free

TherapyNotes client portal: appointments, documents, billing, messages; confirm-gated sends, uploads

About

TherapyNotes client portal: appointments, documents, billing, messages; confirm-gated sends, uploads

Security Report

9.5
Low Risk9.5Low Risk

Valid MCP server (2 strong, 1 medium validity signals). 1 known CVE in dependencies (0 critical, 1 high severity) Package registry verified. Imported from the Official MCP Registry. Trust signals: trusted author (90/92 approved); 4 highly-trusted packages.

4 files analyzed · 2 issues found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

HTTP Network Access

Connects to external APIs or services over the internet.

env_vars

Check that this permission is expected for this type of plugin.

What You'll Need

Set these up before or after installing:

Your practice's portal code: the part after /p/ in its TherapyPortal URL (www.therapyportal.com/p/<code>/).Optional

Environment variable: THERAPYPORTAL_PRACTICE

The email you sign in to TherapyPortal with. Set with the password to sign in server-side; leave both empty to relay through your signed-in browser tab.Optional

Environment variable: THERAPYPORTAL_EMAIL

Your TherapyPortal password. A rejected sign-in is never retried automatically.Required

Environment variable: THERAPYPORTAL_PASSWORD

Only if the portal asks for a date of birth at sign-in: that date, as MM/DD/YYYY. Tried once; never retried, because the portal limits attempts.Required

Environment variable: THERAPYPORTAL_DOB

server or fetchproxy. Default: server when email and password are both set, otherwise fetchproxy.Optional

Environment variable: THERAPYPORTAL_TRANSPORT

fetchproxy concentrator port (fetchproxy transport only, default 37149). The whole fleet shares this one port; override only when hosting.Optional

Environment variable: THERAPYPORTAL_WS_PORT

Where downloaded files are written on the server transport (a path-delimiter list; ~ expands). Default ~/Downloads and ~/Documents. A per-call dest must resolve inside one of these.Optional

Environment variable: THERAPYPORTAL_OUTPUT_DIR

true returns downloaded bytes inline (base64) instead of writing to disk, for a hosted runner whose disk you cannot reach. Default false.Optional

Environment variable: THERAPYPORTAL_INLINE_DOWNLOADS

Folders an upload's file_path must be inside (a path-delimiter list; ~ expands). Default ~/Downloads, ~/Documents and ~/Desktop. Local installs only: a hosted server refuses file_path and takes uploads by upload slot or inline base64.Optional

Environment variable: THERAPYPORTAL_UPLOAD_DIR

Confirm-gate mode for every write (secure messages, document uploads). ask-user (default) returns a preview + confirmToken and needs a second call with that token; auto lets a capable host confirm in one step; an unrecognised value fails closed (refuse).Optional

Environment variable: MCP_CONFIRM_MODE

on (default) lets the host confirm a write interactively; off forces the confirmToken round-trip.Optional

Environment variable: MCP_CONFIRM_ELICITATION

Bind interface for the fetchproxy concentrator, a literal IP only (default 127.0.0.1). Keep on loopback; it exists for hosted deployments that run each MCP in its own network namespace.Optional

Environment variable: FETCHPROXY_WS_HOST

Absolute path where the fetchproxy long-term identity keypair is stored (default ~/.fetchproxy/identity/).Optional

Environment variable: FETCHPROXY_IDENTITY_DIR

How to Install

Add this to your MCP configuration file:

{
  "mcpServers": {
    "io-github-chrischall-therapyportal-mcp": {
      "env": {
        "MCP_CONFIRM_MODE": "your-mcp-confirm-mode-here",
        "THERAPYPORTAL_DOB": "your-therapyportal-dob-here",
        "FETCHPROXY_WS_HOST": "your-fetchproxy-ws-host-here",
        "THERAPYPORTAL_EMAIL": "your-therapyportal-email-here",
        "THERAPYPORTAL_WS_PORT": "your-therapyportal-ws-port-here",
        "THERAPYPORTAL_PASSWORD": "your-therapyportal-password-here",
        "THERAPYPORTAL_PRACTICE": "your-therapyportal-practice-here",
        "FETCHPROXY_IDENTITY_DIR": "your-fetchproxy-identity-dir-here",
        "MCP_CONFIRM_ELICITATION": "your-mcp-confirm-elicitation-here",
        "THERAPYPORTAL_TRANSPORT": "your-therapyportal-transport-here",
        "THERAPYPORTAL_OUTPUT_DIR": "your-therapyportal-output-dir-here",
        "THERAPYPORTAL_UPLOAD_DIR": "your-therapyportal-upload-dir-here",
        "THERAPYPORTAL_INLINE_DOWNLOADS": "your-therapyportal-inline-downloads-here"
      },
      "args": [
        "-y",
        "@chrischall/therapyportal-mcp"
      ],
      "command": "npx"
    }
  }
}

Documentation

View on GitHub

From the project's GitHub README.

therapyportal-mcp

MCP server for TherapyPortal, the client portal of TherapyNotes (https://www.therapyportal.com/p/<practice>/). It reads upcoming therapy appointments, paperwork the practice sent, the practice's standing forms, balances due, payment receipts and secure messages, and downloads completed documents and receipts as PDFs. It can also send secure messages and upload a file in answer to a document request, each behind a confirm gate.

This project was developed and is maintained by AI. Use at your own discretion. It reads mental-health records, and only ever from the signed-in user's own account.

Not read-only

Most tools are reads. Three are writes, and each one reaches the practice and cannot be undone through this server:

ToolWhat it sendsLook up first with
therapyportal_send_messagea new secure-message conversation, for one client, under a topictherapyportal_list_message_topics
therapyportal_reply_messagea reply in an existing conversation (leaves read state alone unless mark_thread_read)therapyportal_list_message_threads
therapyportal_upload_documenta file (local path, inline base64, or an upload slot when hosted) in answer to a pending document request, or an upload-required always-available formtherapyportal_list_documents, therapyportal_list_forms

All three are annotated destructive and confirm-gated (see below). The server signs no form, fills in no form and makes no payment. Anything that needs a signature (PatientSignatureRequired) is refused, with a pointer to the portal. A download is logged by the practice, the same way the portal logs the click.

On the privacy side, therapyportal_list_clients returns names and date of birth only. The portal's client record also carries the address, phones, email, an SSN field, billing notes and free-text comments, and none of those are ever passed through, on any view.

Tools

ToolWhat it returns
therapyportal_list_clientsthe clients linked to this login (for a parent, each child), with the patientId other tools take
therapyportal_list_appointmentsupcoming appointments: time, clinician, telehealth or location, which client; pending requests; practice phone. The portal shows no past visits
therapyportal_list_documentspaperwork requests (pending / completed / all) and their documents, with portalDocumentId and whether each needs an upload or a signature
therapyportal_list_formsthe practice's always-available forms (sign or upload as needed), with libraryDocumentId
therapyportal_get_document_countsper client, documents waiting to be completed or approved
therapyportal_list_balancesthe balance due per client, and whether autopay is on
therapyportal_list_receiptspayments, with the dates of service each covered, and paymentId
therapyportal_list_message_threadsone client's secure-message conversations (does not mark read)
therapyportal_get_thread_messagesthe messages in one conversation (does not mark read)
therapyportal_download_documenta completed form (rendered to PDF) or a file the practice uploaded
therapyportal_download_receipta payment receipt PDF
therapyportal_list_message_topicsthe topics a client can message (topicId, and whether each reaches clinical, billing or administrative staff), whether messaging is on, and whether the client has an assigned clinician
therapyportal_send_messagewrite. Start a new conversation (confirm-gated)
therapyportal_reply_messagewrite. Reply in a conversation (confirm-gated)
therapyportal_upload_documentwrite. Upload a PDF, JPG, JPEG, PNG, HEIC or DOCX (max 25 MB) for a document that asks for one (confirm-gated)
therapyportal_upload_slothosted only: a signed URL to PUT one file to, whose blobKey therapyportal_upload_document then takes. Sends nothing to the practice
therapyportal_healthcheckconnectivity, from whichever transport is live

Read tools take view. The default is compact (projected to the useful fields); full and raw return more. Text the practice wrote (document titles, instructions, messages) comes back inside an untrusted-content envelope.

Message tools are not verified against real messages. The account this was built against had none, so the thread and message reads return the portal's payload exactly as sent rather than a projection.

Writes

The three writes are derived from the portal's own JavaScript (SecureMessagingDataService, CreateMessageViewV2, ThreadViewV2, DocumentsDataService, the document view controllers and xhrfileuploader) and are UNVERIFIED on the wire: no automated run sends a message or uploads a file, because each one reaches the practice. An owner-supervised live test confirms them later. The lookups they make first (messaging settings, topics, the assigned-clinician check, the document and form lists) were verified live, read-only.

Before showing a preview, each write checks against live reads: the client is one of yours (therapyportal_list_clients), the practice has secure messaging turned on, the topic exists for that client, and, for an upload, that the document or form exists, is not completed, asks for an upload and needs no signature.

  • Messages. Plain text, 1 to 5000 characters, sent as the portal's editor sends it (one HTML paragraph per line). A practice may refuse new messages after several unanswered ones; a refusal says so. Archived conversations cannot be replied to.

  • Uploads. Give exactly one source:

    • file_path (local installs): the file must be inside THERAPYPORTAL_UPLOAD_DIR (default ~/Downloads, ~/Documents or ~/Desktop) and be a regular file (no symlinks). A hosted server refuses file_path with a "this server is remote" error: it cannot see the caller's files.
    • inline ({filename, content_base64, mime_type?}): the bytes as base64, strictly decoded (a truncated or mangled payload is refused, not sent). Works anywhere, but every byte rides in one request; keep it small.
    • blob_key (hosted): call therapyportal_upload_slot with the filename, PUT the file to the returned uploadUrl (its curl does exactly that), then pass the blobKey. The preview reads the uploaded copy and binds its SHA-256; the confirmed call re-reads it (a changed file is refused) and deletes it only once the portal accepted the upload. A failed or unconfirmed upload keeps it; a failed delete is reported, not thrown.

    Every source gets the same checks: an allowlisted extension, contents that match it, at most 25 MB, and the SHA-256 in the preview. Legacy .doc is refused (its format cannot be checked before upload); save it as PDF or .docx. The file is staged in 100 KB segments, validated by the portal, and only then submitted. Only the submit reaches the practice.

  • Sent once. A write is posted exactly once. It is never replayed after a re-sign-in, on either transport. A send the portal answers with a 5xx, or whose connection drops, is reported as status: "unknown" rather than as an error, so a model checks the matching list before trying again instead of sending twice.

Confirm gate

Every write is gated by @chrischall/mcp-utils' confirmWrite, through its defineWrite / registerWrite. The first call sends nothing and returns a preview + a confirmToken; only a second call carrying that token, after you approve the preview, sends. The exact request (for an upload, the file's name, type, size and SHA-256) and the shown preview are bound into the token, so an argument, file or portal state that changed since the preview is refused as DRAFT_CHANGED. Values that change on every send (the upload guid, the document date) are made at send time and are not bound. Practice-written text in a preview or a result (topic names, document titles, the returned conversation) is wrapped as untrusted content. MCP_CONFIRM_MODE / MCP_CONFIRM_ELICITATION tune the gate (see Configuration).

Transports

Server (default when credentials are set)Browser bridge
Howsigns in itself with your email and password; holds a cookie jar in memoryrelays every request through your signed-in Chrome tab via the ContextMint Bridge extension
NeedsTHERAPYPORTAL_EMAIL, THERAPYPORTAL_PASSWORD (and THERAPYPORTAL_DOB if the portal asks)the extension, and a signed-in portal tab
Hostable (claude.ai, phone)yesonly as a bridged registration
Downloadswritten to THERAPYPORTAL_OUTPUT_DIR, or inlinesaved by the browser to its download folder

The two can run side by side. A server-side sign-in does not end your browser's portal session (checked live), and the portal signs out an idle browser tab on its own after a while; that is the usual reason the bridge reports "not signed in".

A rejected password or date of birth is never retried. The date-of-birth check locks after a few wrong dates, so the server tries it once.

Configuration

Variable
THERAPYPORTAL_PRACTICErequired. The code after /p/ in your portal URL (a full URL also works)
THERAPYPORTAL_EMAILthe email you sign in with
THERAPYPORTAL_PASSWORDyour portal password
THERAPYPORTAL_DOBonly if the portal asks for a date of birth at sign-in: MM/DD/YYYY (M/D/YYYY and YYYY-MM-DD are accepted)
THERAPYPORTAL_TRANSPORTserver or fetchproxy. Default: server when email and password are set
THERAPYPORTAL_WS_PORTfetchproxy concentrator port, default 37149 (shared by the whole fleet)
THERAPYPORTAL_OUTPUT_DIRwhere downloads are written (path-delimiter list; ~ expands). Default ~/Downloads and ~/Documents; a per-call dest must be inside one
THERAPYPORTAL_INLINE_DOWNLOADStrue returns downloads inline (base64), for a hosted runner
THERAPYPORTAL_UPLOAD_DIRwhere an upload's file_path must be (path-delimiter list; ~ expands). Default ~/Downloads, ~/Documents and ~/Desktop. Ignored when hosted, where file_path is refused (use therapyportal_upload_slot + blob_key, or inline)
MCP_CONFIRM_MODEconfirm-gate mode for every write. ask-user (default) = preview + confirmToken round-trip; auto lets a capable host confirm in one step; an unrecognised value fails closed (refuse)
MCP_CONFIRM_ELICITATIONon (default) lets the host confirm a write interactively; off forces the confirmToken round-trip
FETCHPROXY_WS_HOST, FETCHPROXY_IDENTITY_DIRfetchproxy bind address and identity directory (advanced)

Copy .env.example to .env for a local install.

{
  "mcpServers": {
    "therapyportal": {
      "command": "npx",
      "args": ["-y", "@chrischall/therapyportal-mcp"],
      "env": { "THERAPYPORTAL_PRACTICE": "<code>" }
    }
  }
}

Without the server

skills/therapyportal-fpx does the same reads (not the writes) from a shell with the fpx CLI, through the signed-in tab. See its SKILL.md.

Development

npm install
npm run build
npm test                 # typecheck + vitest, all mocked
npm run test:coverage    # 100% threshold

The wire protocol (tokens, sign-in, download jobs, the write shapes) is in docs/THERAPYPORTAL-API.md.

Reviews

No reviews yet

Be the first to review this server!