About insync-mcp
insync-mcp connects MCP clients to an InSync Healthcare (Qualifacts) patient portal for reading health records and transferring files. It can also send messages and care requests, with writes gated by a preview-and-confirm step.
Key features
- 01Read appointments, medications, and allergies
- 02Read messages, forms, surveys, and profile details
- 03Download portal documents and attachments
- 04Upload an insurance-card image
- 05Send messages and care requests with confirmation
Use cases
- 01Review appointments, medications, and portal messages
- 02Download documents or upload an insurance card
- 03Send a message, refill request, or appointment request
MCP support
Exposes portal reading, file transfer, messaging, and care-request tools to MCP clients. Writes require a preview and confirmation token; authentication can use portal credentials or a signed-in browser bridge.
Configuration
Local server config is the same for every client. Copy it into your client’s MCP config file.
npx -y @chrischall/insync-mcpINSYNC_PORTALYour practice's portal, scheme and host only, e.g. https://<practice>intouch.insynchcs.com.INSYNC_USERNAMEPortal username. Set with INSYNC_PASSWORD to sign in server-side. Leave both empty to relay through your signed-in browser tab.INSYNC_PASSWORDSecretPortal password. A rejected sign-in is never retried automatically, because the portal counts failures toward a lockout.INSYNC_TRANSPORTserver or fetchproxy. Default: server when both credentials are set, otherwise fetchproxy.INSYNC_WS_PORTOverride the fetchproxy WebSocket port (default 37149).INSYNC_OUTPUT_DIRWhere downloaded files are written locally (a path-delimiter list; ~ expands). Default ~/Downloads and ~/Documents. A per-call dest must resolve inside one of these.INSYNC_INLINE_DOWNLOADSLocal installs: true returns a downloaded file inline (up to 10 MiB) instead of writing it to disk; larger files are still saved. Ignored when hosted, which always delivers inline or by signed link. Default false.FETCHPROXY_WS_HOSTBind 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.FETCHPROXY_IDENTITY_DIRAbsolute path where the fetchproxy long-term identity keypair is stored (default ~/.fetchproxy/identity/).Local servers need a runtime on your machine (e.g. Node.js 18+). Config file locations differ by client; see your client’s docs.
Tools
Local servers expose tools only once running on your machine, and the maintainer has not provided a tool list.
Details
From the project READMEinsync-mcp
MCP server for an InSync Healthcare (Qualifacts) patient portal — the
behavioral-health portals at https://<practice>intouch.insynchcs.com (the
owner's is pfbhintouch). It reads appointments, medications, allergies, secure
messages, forms, surveys, documents, profile and account balance; downloads
portal files; uploads an insurance-card image; and sends secure messages,
refill requests, appointment requests and cancellation requests.
This project was developed and is maintained by AI. Use at your own discretion. It reads personal health information, and only ever from the signed-in user's own account.
Not read-only
This server is not read-only. Alongside the reads it:
-
downloads message attachments and account documents (a download changes no portal state and has an inverse — not persisting — so the download tools are still annotated
readOnly); -
uploads an insurance-card image to your own profile — a real write the practice sees, with no delete/undo through this server, so it is annotated
destructiveand confirm-gated (see below); -
insync_get_messageopening an unread message can mark it read, which the practice can see and which has no inverse. That tool is annotateddestructiveand gated behindallow_mark_read(default false): until you pass it, the tool returns the list-row metadata and a note, and never fetches the detail; -
sends things to the practice, each one confirm-gated and annotated
destructive, because none can be unsent or withdrawn through this server:Tool What it sends Look up first with insync_send_messagea new secure message to a practice group insync_list_message_recipientsinsync_reply_messagea reply to a message (refuses an unread one unless allow_mark_read)insync_list_messagesinsync_request_refilla refill request for current medications insync_get_refill_optionsinsync_request_appointmentan appointment request in an open slot (the practice confirms it) insync_find_appointment_slotsinsync_cancel_appointmenta cancellation request with a reason (the practice reviews it) insync_list_appointments
Not built: message attachments and patient signatures on messages, the "flexible window" appointment request, withdrawing a request, payments, and form submission.
Two ways to authenticate
| Mode | When | Credentials held |
|---|---|---|
| Bridge-less (default) | INSYNC_USERNAME + INSYNC_PASSWORD set | your portal password, in memory for the process |
| Browser bridge | no credentials configured | none |
Bridge-less signs in server-side with its own cookie jar — no browser, no
extension — which is what makes hosting possible. The password login was
live-verified on 2026-10-28 against the owner's practice: a single POST to
Login/userLogin, no MFA, no device challenge. A rejected sign-in is never
retried automatically, because the portal counts failed attempts toward an
account lockout. The cookie jar lives in memory for the life of the process;
nothing is written to disk.
Browser bridge relays requests through your own signed-in portal tab via the fetchproxy bridge and the ContextMint Bridge extension, reusing that session. It holds no credentials at all — its one virtue — so prefer it unless you specifically want the hosted, bridge-less path.
Selection is automatic: server when both credentials are set, otherwise
fetchproxy. INSYNC_TRANSPORT=server|fetchproxy forces one.
Storage configuration is held, never used directly
The login response carries some object-storage configuration. This server
never uses it to talk to object storage directly — all file transfer goes
through the portal's own session-authorized endpoints, so every read and write
stays under the portal's per-patient authorization. Any such configuration is
captured onto the session only, and is scrubbed by value from every tool
result, error and log. A download URL is refused unless it stays on
*.insynchcs.com.
Writes are unverified on the wire
Every lookup the write tools make — recipients, the reply form, the refill pick
lists, facilities, providers, visit types, slots, the cancellation window — was
verified live, read-only. The sends are derived from the portal's own
JavaScript and partial views. A new message (insync_send_message) and a
reply (insync_reply_message) were verified live on 2026-10-28 in
owner-supervised tests; the refill, appointment and cancellation sends have not
been exercised yet, because each one creates a request the practice acts on. A send that 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. See
docs/INSYNC-API.md.
Insurance-card upload
The insurance-card upload shape is derived from the portal's own JavaScript
and is UNVERIFIED on a live POST: no automated run performs a real upload,
because it mutates practice-visible state. An owner-supervised live test confirms
it later. The confirm gate, the file vetting and the re-read-after-write guidance
are all in place; only the on-the-wire success signal is pending confirmation.
See docs/INSYNC-API.md.
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 send/request
tools may read the portal while previewing (to resolve names and check the slot
or recipient); the upload preview reads nothing. The exact request body (for the
upload, the image bytes) 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. MCP_CONFIRM_MODE / MCP_CONFIRM_ELICITATION tune this (see the
env table).
Install
{
"mcpServers": {
"insync": {
"command": "npx",
"args": ["-y", "@chrischall/insync-mcp"],
"env": {
"INSYNC_PORTAL": "https://<practice>intouch.insynchcs.com",
"INSYNC_USERNAME": "your-portal-username",
"INSYNC_PASSWORD": "your-portal-password"
}
}
}
}
Leave INSYNC_USERNAME / INSYNC_PASSWORD out to use the browser bridge
instead; that path needs ContextMint Bridge, the fetchproxy browser
extension, installed from its
releases page, and
your portal open and signed in. Then call insync_healthcheck; the first call
prints a pair code to approve once in the extension popup.
Configuration
| Variable | Required | Default | Purpose |
|---|---|---|---|
INSYNC_PORTAL | yes | — | Your practice portal, scheme + host only (https://<practice>intouch.insynchcs.com), no path. The portal is per-practice, so a wrong host reads a different practice. |
INSYNC_USERNAME | bridge-less only | — | Portal username. Set with the password to sign in server-side. |
INSYNC_PASSWORD | bridge-less only | — | Portal password. Never retried on rejection (lockout risk). |
INSYNC_TRANSPORT | no | auto | server or fetchproxy. Default: server when both credentials are set, else fetchproxy. |
INSYNC_WS_PORT | no | 37149 | fetchproxy concentrator port. The whole fleet shares it; override only when hosting. |
INSYNC_OUTPUT_DIR | no | ~/Downloads, ~/Documents | Where downloaded files are written (a path-delimiter list; ~ expands). A per-call dest must resolve inside one of these. |
INSYNC_INLINE_DOWNLOADS | no | false | true returns downloaded bytes inline (base64) instead of writing to disk — for a hosted runner whose disk you cannot reach. |
MCP_CONFIRM_MODE | no | ask-user | Confirm-gate mode for every write. ask-user = preview + confirmToken round-trip; auto lets a capable host confirm in one step; an unrecognised value fails closed (refuse). |
MCP_CONFIRM_ELICITATION | no | on | on lets the host confirm a write interactively; off forces the confirmToken round-trip. |
A .env is read for local development; real environment values always win. See
.env.example.
The fpx skill
Bundled under skills/insync-fpx/ is a lightweight
alternative to running this server: the insync-fpx skill reads the same portal
from a shell through your signed-in browser tab with the
fpx CLI (capture the session once,
then curl the endpoints). Use it when you want portal data in a script or without
the MCP installed. The MCP is the richer surface; the skill is the quick path.
Context groups
Every tool except insync_healthcheck publishes a _meta.mint hint (group
health) for mcp-host's context groups: inert data to any other client. The
three canonical reads — insync_list_appointments (what's on),
insync_list_messages (what's new) and insync_list_forms (what's due) — also
take view: "brief", one line per item, title — <ISO date | no date> — <status>, inside the untrusted-data envelope. The other view values are
unchanged.
How it works
The portal is ASP.NET MVC. A signed-out session is never a 401: it is the login
page served 200 (<title>Portal :: Login), an {"isTimeout":true} body, or a
302 to /Login. This server detects that by content and surfaces an actionable
"not signed in" error naming the live transport. Every endpoint shape — reads,
file transfer and the upload — is documented, keys only (no values, which are
PHI), in docs/INSYNC-API.md.
License
MIT
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.