evite-mcp
# evite-mcp
[](https://github.com/chrischall/evite-mcp/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/evite-mcp)
[](LICENSE)
A [Model Context Protocol](https://modelcontextprotocol.io) server for [Evite](https://www.evite.com) — read and act on your events as both **guest** (invitations received) and **host** (events you created): list events, view guest lists & RSVP tallies, RSVP, message guests, and create/edit events. Built on [`@chrischall/mcp-utils`](https://github.com/chrischall/mcp-utils).
> **Status: read + write tools live.** The five read tools work against Evite's internal API, authenticating from email/password (tier-1, `POST /ajax_login`), a raw cookie env var, or a signed-in browser tab (fetchproxy bootstrap). The thirteen write tools **ask you to confirm first** — nothing is sent until you approve (see [Confirmations](#confirmations)) — and their endpoints are **live-verified** (see [`docs/EVITE-API.md`](docs/EVITE-API.md)); a couple of request *bodies* are still assumed rather than captured ([#3](https://github.com/chrischall/evite-mcp/issues/3)).
## Tools
Six read tools (all read-only), thirteen confirmed write tools, `evite_upload_slots` (read-only: upload links for photos on the hosted connector), plus `evite_healthcheck`:
| Tool | Endpoint | Returns |
| --- | --- | --- |
| `evite_list_events` | `GET /services/events/v1/` | your events + a totals breakdown (`filterBy` = all/host/others, repeatable `status`, `offset`/`numResults` paging) |
| `evite_get_event` | `GET /services/event/v1/{id}` | single-event detail (event, settings, location) |
| `evite_list_guests` | `GET /services/event/v1/{id}/guests/` | the guest list + RSVP responses (delivery status, views, short links) |
| `evite_rsvp_summary` | (derived from guests) | just the RSVP summary (yes/no/maybe/noReply + head counts) |
| `evite_list_messages` | `GET /services/event/v1/{id}/posts/` | the event's Messages thread |
| `evite_list_templates` | scrapes `/invites/{category}/` | invitation template slugs (the `template_name` `evite_create_event` needs) + display names |
### Write tools (confirmed)
Every write tool **asks you to confirm before it sends anything**. A client that can show a confirmation prompt (Claude Code) shows one with the exact values that would be sent (unless `MCP_CONFIRM_ELICITATION=off`). Elsewhere (claude.ai, Claude Desktop) the first call performs no write (at most a read-only lookup — e.g. `evite_update_guest` reads the draft guest list to build an exact preview) and returns that preview plus a `confirmToken`; only a repeat call with the token performs the write. The token is single-use, expires, and is bound to the tool and the exact values — change anything and it is refused (`DRAFT_CHANGED`) with a fresh preview. See [Confirmations](#confirmations). Endpoints are verified; the CSRF token (`X-CSRFToken`, read fresh per request as it rotates) is attached centrally.
| Tool | Endpoint | Action |
| --- | --- | --- |
| `evite_rsvp` | `PUT /services/event/v1/{id}/guests/{guestId}` | RSVP for a guest (response + adult/kid head counts + optional note) |
| `evite_send_message` | `POST /tsunami/v1/services/event/{id}/guest/{gid}/messages` | send a private message to one guest (body assumed) |
| `evite_broadcast` | `POST /tsunami/v1/services/event/{id}/broadcast/` | broadcast a message to whole RSVP segments (`virtual_groups`) at once |
| `evite_upload_photo` | `POST …/photos/v1/{id}/upload/request/` → GCS → finish → shared-gallery | upload a photo to the event's shared photo album (4-step GCS signed upload) — from a local `path`, `inline` base64, or an upload-slot `blob_key` (see [Photo uploads](#photo-uploads)) |
| `evite_create_event` | `POST /services/event/v1/` (`{event:{…}}`) | create an event draft (needs `template_name`; the API 500s even on success) |
| `evite_update_event` | `PATCH /services/event/v1/{id}` (`{event:{…}}`) | edit an event (only the fields you pass change) |
| `evite_add_guest` | `POST /ajax/event/{id}/guestlist/draft/` | add guests to the draft (un-sent) list — `[{name,email}]` |
| `evite_update_guest` | `PATCH /ajax/event/{id}/guestlist/draft/` | edit a draft guest's name/email/phone |
| `evite_remove_guest` | `DELETE /ajax/event/{id}/guestlist/draft/{gid}` | remove a draft guest |
| `evite_send` | `POST /services/event/v1/{id}/send/` | **"Send now"** — emails the ready-to-send guests |
| `evite_cancel_event` | `POST …/actions/cancel/` | cancel an event / delete a draft (destructive; reversible) |
| `evite_reinstate_event` | `POST …/actions/reinstate/` | reinstate a cancelled event |
| `evite_duplicate_event` | `GET /plus/create/{id}/copy/` (→302) | copy an event into a fresh draft; returns the new event id |
The authoring flow is `evite_create_event` → `evite_add_guest` → `evite_send`. `evite_send`, `evite_send_message`, `evite_broadcast`, and `evite_cancel_event` have real-world effects (emails / cancellation notices), so their confirmation matters.
## Confirmations
| 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 writes 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, unless `MCP_CONFIRM_ELICITATION=off`. An unrecognised value is treated as `refuse`. |
| `MCP_CONFIRM_ELICITATION` | `on` | `off` never shows a confirmation prompt, so every client gets the `MCP_CONFIRM_MODE` behaviour. Set it for a client that says it can show prompts but never does (the write hangs — opencode 2.0.x). Any other value is treated as `on`, with a warning on stderr. |
| `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. |
## Photo uploads
`evite_upload_photo` takes the image as **exactly one of** three inputs (JPEG/PNG/GIF/WebP/HEIC, max 20 MB; the bytes must really be that image type, whatever the name says):
- **`path`** — local installs only. A file inside `EVITE_UPLOAD_DIR` (below).
- **`inline`** — `{ filename, content_base64 }`. Works anywhere; meant for a small photo, since every byte rides inside the tool call.
- **`blob_key`** — the hosted connector's way in. It cannot read files on your machine, so a `path` is refused there with directions. Instead, call `evite_upload_slots` with the filenames; each slot has a signed `uploadUrl` and a ready `curl` command that PUTs the file with the exact `Content-Type`. Then pass the slot's `blobKey` as `blob_key`. The upload is read on the preview and again on confirm, and its SHA-256 is bound into the confirmation, so a file replaced in between is refused (`DRAFT_CHANGED`). It is deleted only after Evite accepts the photo (the result's `blob` says whether that delete worked). If the upload fails it is kept, so a retry can reuse the same `blob_key`. `evite_upload_slots` refuses on a local install, which has no blob store.
| variable | default | |
|---|---|---|
| `EVITE_UPLOAD_DIR` | `~/Downloads/evite-mcp` | The directories `evite_upload_photo` may read from — one or more, separated by the platform path delimiter (`:` on macOS/Linux, `;` on Windows); `~` is allowed. A relative path is resolved against the first one. A path outside them (symlinks included), or a hidden file or directory inside them, is refused before the file is even looked at. Put photos to upload in `~/Downloads/evite-mcp`, or point this elsewhere. |
## Architecture
Fetchproxy-archetype MCP. Evite has no public API, so the server calls Evite's *internal* `/services/` web API using your session. `src/auth.ts` resolves that session in priority order:
1. **`EVITE_EMAIL` + `EVITE_PASSWORD`** (tier-1, preferred) — headless email/password form login: POST the creds to `https://www.evite.com/ajax_login`, then build the session from the response `Set-Cookie` jar (`x-evite-session`, `evtsession`, `csrftoken`, `x-evite-features`). No browser bridge, no hand-copied cookie. Both vars must be set, or the resolver falls through. *(Live.)*
2. **`EVITE_SESSION_COOKIE`** — a raw `cookie:` header copied from a signed-in `evite.com` tab (or set in CI). Used verbatim. *(Live.)*
3. **Fetchproxy bootstrap** (fallback) — lift the session cookies (`x-evite-session`, `evtsession`, `x-evite-features`, `csrftoken`) from a signed-in `evite.com` browser tab via `@fetchproxy/bootstrap`, which needs the [ContextMint Bridge](https://github.com/nullnet-app/contextmint-bridge/releases) browser extension (Chrome: load the chrome zip from the releases page unpacked. Safari isn't available yet — it will ship inside the ContextMint app, which has no public download — so use Chrome for now). ContextMint Bridge is the fetchproxy browser extension under its new name, from the same maintainer — fetchproxy's own [README](https://github.com/chrischall/fetchproxy#extension) points to it. Its source is public at [nullnet-app/contextmint-bridge](https://github.com/nullnet-app/contextmint-bridge): build it yourself, or check a release zip against the `.sha256` file published beside it (`shasum -a 256 -c contextmint-bridge-chrome-<version>.zip.sha256`). Bootstrap runs once; every API call then goes out via plain Node `fetch()` with the cookies attached. Opt out with `EVITE_DISABLE_FETCHPROXY=1`. *(Live.)*
One tier is intentionally **deferred** (the resolver is shaped to slot it in):
- **Fetchproxy as transport** (bot-wall retry through the browser bridge) — a fallback only needed if plain `fetch` trips a wall; not observed during discovery.
The thirteen write tools (rsvp, add/update/remove-guest, send, send-message, broadcast, upload-photo, create/update/cancel/reinstate/duplicate event) **ask you to confirm first** (a prompt, or a preview + single-use `confirmToken`; see [Confirmations](#confirmations)). The single private `client.write()` helper attaches the CSRF token via one centralized header (`CSRF_HEADER` = `X-CSRFToken`; the `csrftoken` cookie rotates mid-session, so it's read fresh per request). Endpoints span three bases — REST `/services/…`, the legacy `/ajax/event/{id}/…` guest list, and the `/tsunami/…` messaging service — all live-verified; a couple of request bodies remain assumed ([#3](https://github.com/chrischall/evite-mcp/issues/3)).
## Development
```bash
npm install # resolves @chrischall/mcp-utils from a local tarball (see issue #4)
npm run build
npm test
```
## Docs & roadmap
- Design spec: [`docs/superpowers/specs/2026-05-31-evite-mcp-design.md`](docs/superpowers/specs/2026-05-31-evite-mcp-design.md)
- Plan 1 (scaffold + spike): [`docs/superpowers/plans/2026-05-31-evite-mcp-scaffold-and-spike.md`](docs/superpowers/plans/2026-05-31-evite-mcp-scaffold-and-spike.md)
- Plan 2 (auth + read tools): [`docs/superpowers/plans/2026-05-31-evite-mcp-auth-and-reads.md`](docs/superpowers/plans/2026-05-31-evite-mcp-auth-and-reads.md)
- Internal API reference (verified from a live session): [`docs/EVITE-API.md`](docs/EVITE-API.md)
**Open work:** [#3](https://github.com/chrischall/evite-mcp/issues/3) — write endpoints + CSRF are now live-verified; only a couple of request *bodies* (send / send-message) remain assumed. [#4](https://github.com/chrischall/evite-mcp/issues/4) tracks publishing the shared lib. (Discovery [#1](https://github.com/chrischall/evite-mcp/issues/1), tier-1 email/password login [#2](https://github.com/chrischall/evite-mcp/issues/2), and the read tools are done.)
## License
MIT
TDQS
Scored across 21 tools
Most tools map cleanly to a distinct resource-action pair, with clear separation between event, guest, RSVP, message, and template operations. A few names could be momentarily confused, especially evite_send vs evite_send_message/evite_broadcast and evite_upload_photo vs evite_upload_slots, but the descriptions resolve the overlap.
All tools use a consistent evite_ prefix followed by snake_case verb/noun phrasing. The pattern is predictable throughout, with only minor noun-oriented names like evite_healthcheck.
At 21 tools, the surface is heavier than the ideal 3-15 range and feels somewhat dense. The Evite domain has several sub-areas, so most tools are justified, but the set is still large enough to risk cognitive overload.
Core event lifecycle, guest management, RSVP, messaging, templates, and photo upload are covered, including useful inverse and duplicate operations. Minor gaps exist around photo management, such as listing or deleting event photos, and there is no dedicated guest detail tool, but these are workaroundable.