remind-mcp
# remind-mcp
MCP server for [Remind](https://www.remind.com) — read your classes, chats and messages, and
manage notification settings.
> This project was developed and is maintained by AI. Use at your own discretion.
Remind exposes a single GraphQL endpoint that is reachable server-side, so only the
*credential* needs a browser: the session is lifted once from a signed-in tab through the
fetchproxy bridge, and every request after that is a plain server-side fetch.
## Install
```bash
npx -y @chrischall/remind-mcp
```
```json
{
"mcpServers": {
"remind": { "command": "npx", "args": ["-y", "@chrischall/remind-mcp"] }
}
}
```
## Authentication
Two headers are captured from a signed-in `remind.com` tab: the full `Cookie` header and the
`x-csrf-token` value. Either let the bridge capture them (needs the **Transporter** Chrome
extension and a signed-in tab), or supply them yourself:
| Variable | Required | Description |
|---|---|---|
| `REMIND_COOKIE` | no | Captured `Cookie` request header. Skips the bridge when set with the next one. |
| `REMIND_CSRF_TOKEN` | no | Captured `x-csrf-token` value. |
| `REMIND_WS_PORT` | no | fetchproxy bridge concentrator port (default `37149`). |
| `REMIND_SESSION_FILE` | no | Where the captured session is cached. Defaults to `$MCP_DATA_DIR`/`$HOME` under `.remind-mcp`. |
The server boots without either, so a host's install-time `tools/list` probe succeeds; the
error surfaces on the first tool call instead.
**The browser is needed once.** A captured session is cached (mode `0600`) and reused across
restarts, and every call after the bootstrap is a plain server-side fetch. This matters because
the capture completes only while the signed-in tab is actually issuing a `/graphql` request — so
without the cache, a restart would sit waiting unless you happened to be using Remind.
The cache is bounded: analytics and consent cookies are dropped from the jar before it is used or
stored, the record is bound to the Remind account (`me.uuid`) it authenticated as when captured —
a restored session that answers as a different account, or as none, is discarded and re-captured —
and it is re-captured from the browser after at most 7 days. An env-supplied session is never
restored from the cache.
## Tools
| Tool | |
|---|---|
| `remind_me` | The signed-in account. |
| `remind_list_entities` | Classes and chats with unread counts — **start here**, it yields the uuids. |
| `remind_get_classes` | Full class detail by uuid. |
| `remind_list_chats` | Conversation streams and their permissions (`canSend`). |
| `remind_get_messages` | Messages in a chat stream. |
| `remind_get_notification_settings` | Preferences and delivery devices. |
| `remind_set_notification_devices` | Enable/disable delivery devices. **Asks you to confirm first.** |
| `remind_send_message` | Send to a chat or class. **Asks you to confirm first.** |
| `remind_graphql` | Arbitrary read-only GraphQL; introspection is enabled. Mutations refused. |
| `remind_healthcheck` | Verify the session still authenticates. |
## Confirmations
Write tools ask you to confirm before anything is sent. A client that can show a confirmation
prompt (Claude Code) shows one. Otherwise the first call makes **no network call** and returns a
preview of the exact payload plus a `confirmToken`; only a repeat call with that token (and the
same arguments) performs the write. A token works once, and changing any argument between the
two calls voids it.
| variable | default | |
|---|---|---|
| `MCP_CONFIRM_MODE` | `ask-user` | What a write does on a client that cannot show a confirmation prompt (claude.ai, Claude Desktop). `ask-user`: two steps — the first call does nothing and returns a preview plus a token, and the model must get your approval in chat before calling again with it. `auto`: the same two steps, but the model may use the token after reviewing the preview itself. `refuse`: writes are refused on such clients. A client that can show prompts (Claude Code) always gets the real prompt. An unrecognised value is treated as `refuse`. |
| `MCP_CONFIRM_TTL_SECONDS` | `600` | How long a token stays valid. |
| `MCP_CONFIRM_SECRET` | random per process | Signing key; set it only if tokens must survive a server restart. |
## Without the server
`skills/remind-fpx/` is a shell-only skill covering the same read surface with `fpx` + `curl` —
no MCP process required.
## Notes
- An expired session returns **HTTP 200** with `errors[0].message = "Unauthorized"`; the same
string also means "your account may not do that" (scheduled messages are owner/teacher-only).
- An unknown field reports as `Internal service error`, not a field error — introspect rather
than guess. `docs/REMIND-API.md` is the capture log.
## Development
```bash
npm install && npm run build && npm test
npm run test:coverage
```
## License
MIT
TDQS
Scored across 10 tools
Most tools target a distinct resource+action, and descriptions clarify roles (list_entities as the entry point, get_classes for detail, list_chats for streams). There is mild overlap between remind_list_entities and remind_list_chats (entities already includes chats), and remind_me vs remind_healthcheck both touch the session, but the descriptions resolve these boundaries.
All tools share a consistent remind_ prefix and predominantly follow verb_noun (get_classes, list_chats, send_message, set_notification_devices). Minor deviations like remind_me and remind_healthcheck/remind_graphql (noun-only) are readable and don't break the pattern.
Ten tools is well within the ideal range and each earns its place: identity, settings, entity/class/chat/message reads, a write, and a read-only GraphQL escape hatch. Nothing looks redundant or padded.
Core read and messaging workflows are covered (account, settings, entities, classes, chats, messages, send), and the read-only GraphQL tool provides a discovery fallback for unexposed fields. There are minor gaps around write operations like creating/editing classes or marking messages read, but agents can largely work around them.