An MCP server for the Confetti API. It exposes all 63 Confetti API operations as MCP tools, so assistants like Claude Code can manage events, tickets, contacts, pages, and payments directly.
Beta — tool definitions may change.
confetti-mcp is a stateless HTTP server speaking the MCP streamable-http
transport. You supply your own Confetti API key on each connection; the server
stores no credentials and holds no per-user state.
Because there is no session, GET /mcp and DELETE /mcp — the
streamable-http verbs for resuming or closing one — return 405: there is
nothing to resume and nothing to delete. Use POST /mcp for everything.
Tools are generated at startup from the confetti client's model registry, so
the tool surface tracks the API rather than being hand-maintained.
There's no hosted instance — self-hosting (see below) is the supported way to
run it today. https://your-host in the examples below stands for wherever
you deploy it.
docker run -p 8080:8080 deviesdevelopment/confetti-mcpPublished from the first tagged release onward. Until then, build it locally with the commands below.
Or build the image from the Dockerfile in this repo:
docker build -t confetti-mcp .
docker run -p 8080:8080 confetti-mcp| Variable | Default | Purpose |
|---|---|---|
PORT |
8080 |
Listen port |
CONFETTI_API_HOST |
api.confetti.events |
Upstream API host |
CONFETTI_API_PROTOCOL |
https |
Upstream protocol |
LOG_LEVEL |
info |
Accepted but not yet wired to log output; reserved |
GET / is an unauthenticated health endpoint, suitable for a load balancer or
container probe. It reports the server name and version and touches nothing
upstream:
curl https://your-host/
# {"status":"ok","server":"confetti-mcp","version":"0.2.0","usage":"POST /mcp with an \"Authorization: Bearer <confetti-api-key>\" header.","filtering":"All 63 tools are exposed by default. Narrow the connection with ?ops= and ?resources= ..."}The image's own HEALTHCHECK uses this endpoint, so plain Docker and compose
restart a wedged container without any extra configuration.
The server writes one structured JSON line to stderr per failed tool call and
per rejected request — request id, tool name, error class, upstream status class,
and duration. It deliberately never logs the API key, the tool arguments, the
request URL, or the error message, because all four can carry caller data and the
URL carriers put the key in the path or query string. A regression test asserts
the key never reaches any output channel through any carrier
(test/server/no-key-in-logs.ts).
Successful calls are not logged at all.
Managed platforms ignore the Dockerfile HEALTHCHECK and probe a path you
configure instead — point them at /. On Azure App Service for Containers that
is the separate Health check feature (Settings → Health check → path /),
which is off by default: without it a container that is listening but wedged is
never restarted, and Always On only keeps it warm. The equivalents elsewhere:
| Platform | Setting |
|---|---|
| Azure App Service | Health check path / |
| AWS ECS / ALB | Target group health check path / |
| Kubernetes | livenessProbe.httpGet.path: / |
| Docker / compose | Nothing to configure — the image's HEALTHCHECK is used |
The server never reads an API key from its own environment — keys always come from the caller.
claude mcp add --transport http confetti https://your-host/mcp \
--header "Authorization: Bearer $CONFETTI_API_KEY" \
--scope userCheck it connected with /mcp inside a session. claude mcp add saves the
config without validating credentials, so a bad key shows up as failed at
connect time rather than at add time.
Commit a .mcp.json that carries no secret:
{
"mcpServers": {
"confetti": {
"type": "http",
"url": "${CONFETTI_MCP_URL:-https://your-host}/mcp",
"headers": { "Authorization": "Bearer ${CONFETTI_API_KEY}" }
}
}
}Both ${VAR} and ${VAR:-default} expand in url and headers. Each developer
exports CONFETTI_API_KEY in their own shell.
An entry with a
urlbut notypeis a configuration error — Claude Code reads it as a stdio server and skips it. Always include"type": "http".
{
"mcpServers": {
"confetti": {
"type": "http",
"url": "https://your-host/mcp",
"headersHelper": "echo '{\"Authorization\":\"Bearer '\"$(op read op://Vault/confetti/api-key)\"'\"}'"
}
}
}Claude Code runs the helper at connect time. On a 401 or 403 it re-runs the
helper and retries once, so a rotated key heals without restarting the session.
The full tool list — all 63 tools — serialises to roughly 73 KB of JSON, about 20,000 tokens of context spent before you've asked anything. Narrow it per connection with query parameters on the connect URL:
| URL | Tools |
|---|---|
/mcp |
all 63 |
/mcp?ops=read |
29 — reads only |
/mcp?ops=get,post,put |
53 — no deletes |
/mcp?resources=events,tickets |
8 |
/mcp?resources=events&ops=read |
2 |
ops accepts domain verbs (read, create, update, delete) and HTTP verb
aliases (get, post, put, patch, delete). read covers both find and
find_all.
The filter is enforced, not advisory: a tool excluded by ?ops= / ?resources=
is refused if a client calls it directly by name, not merely hidden from
tools/list. A connection opened with ?ops=read cannot invoke a delete tool
even by naming it exactly.
Which setup you need depends on the surface:
Code tab — reads ~/.claude.json, .mcp.json, and
claude_desktop_config.json, so it supports headers exactly like the CLI. Use
any of the configurations above; nothing extra is needed.
Chat surface (and claude.ai web) — these use the custom connector UI, whose only fields are the server URL and, under Advanced settings, an OAuth client id and secret. There is no way to send a header, so put the key in the URL:
https://your-host/mcp?apiKey=YOUR_CONFETTI_API_KEY
Add it under Settings → Connectors → Add custom connector. Filters still work:
https://your-host/mcp?apiKey=YOUR_CONFETTI_API_KEY&ops=read
Prefer a header wherever the client allows one. A key in a URL is visible in browser history, proxy logs, and anything that records request lines — the URL forms exist because these two surfaces cannot do better, not because the tradeoff is even.
In precedence order:
Authorization: Bearer <key>— recommended.X-Api-Key: <key>— alias.?apiKey=<key>— URL fallback, for clients that cannot set headers.POST /mcp/k/<key>— the other URL fallback, for clients or proxies that handle a path segment better than a query parameter.
A malformed Authorization header is rejected rather than falling through to a
weaker carrier. An empty value in carriers 2–4 falls through to the next.
npm install
npm run lint
npm test
npm run build
npm run dev