MCP server
Connect Claude, Cursor or ChatGPT to your EventSend events — endpoint, authentication, client setup, and the tool reference.
EventSend has a read-only Model Context Protocol server. Connect an AI assistant to it and you can ask, in plain language, what happened in your product: the assistant calls a small set of tools that read your organization's events, delivery outcomes, daily analytics and quota, and answers from what they return.
Questions it is built for:
- "Did anything fail in checkout today?"
- "How many
payment-receivedevents did we get this week, and how does that compare to last?" - "What did customer
cus_9Xk2do before they cancelled?" - "Are deliveries to Slack failing?"
- "How much of our monthly allowance is used, and when do we run out at this rate?"
Nothing it does can change your account. There is no tool that creates, edits or deletes anything, and no tool ever returns a token, a signing secret or a destination's configuration.
POST https://eventsend.io/mcp
Authorization: Bearer mcp_YOUR_KEY
Content-Type: application/json
Endpoint
The server speaks the MCP Streamable HTTP transport at https://eventsend.io/mcp, and it
answers every request with a plain JSON response rather than an event stream. It is stateless: no
session identifier is issued, and each request stands on its own. The request body is capped at
64 KB, which is far more than any tool call needs.
That is all a client needs to know. Any client that supports remote MCP servers over HTTP works; the ones people ask about most are listed under Client setup.
Authentication
There are two kinds of credential, and which one you use depends on where the assistant runs.
Hosted connectors sign in
claude.ai, ChatGPT and the connectors panel in Claude Desktop connect through a sign-in flow: you
paste https://eventsend.io/mcp as the connector URL, the client sends you to EventSend, you log in
if you are not already, choose which of your organizations the connection may read, and approve.
Nothing is copied or pasted, and the connection sees exactly what you see in each organization you
picked — it is tied to your membership, so if you leave an organization the connection loses it
too.
Any member of an organization can connect it this way. The connections that include an organization are listed on its MCP settings tab, where an owner or admin can remove the organization from any of them.
Local clients use an MCP key
Clients that run on your machine — Claude Code, Cursor, and anything that reads a config file —
send an MCP key in an Authorization: Bearer header. Keys start with mcp_, belong to one
organization, and are created on that organization's MCP settings tab (Settings → MCP) by an
owner or admin. Each key has a name so you can tell them apart, and the tab shows when each one
was last used.
A key can be rotated (a new value is issued and the old one stops working on the next request) or revoked. Both take effect immediately. Treat a key like any other credential: keep it in your client's configuration, not in a repository, and revoke it if it leaks.
Organizations
A signed-in connection can cover several organizations, with one marked as the default. Ask about
the default by name-dropping nothing at all; to ask about another, say so ("in Acme, did anything
fail today?") and the assistant passes the organization along with the question. If it is ever
unsure which organizations it can reach, it has a list_organizations tool that tells it, and it
is told to use that tool whenever you name an organization.
An MCP key is a one-organization credential. Naming a different organization with a key produces an error rather than an answer, so if you want a local client to see two organizations, create a key in each and configure both.
Client setup
The endpoint is the same everywhere; only the credential differs.
Claude Code
claude mcp add --transport http eventsend https://eventsend.io/mcp \
--header "Authorization: Bearer mcp_YOUR_KEY"
Cursor — add the server to ~/.cursor/mcp.json (or .cursor/mcp.json in a project):
{
"mcpServers": {
"eventsend": {
"url": "https://eventsend.io/mcp",
"headers": {
"Authorization": "Bearer mcp_YOUR_KEY"
}
}
}
}
Claude Desktop — either add a connector from the app's settings with the URL
https://eventsend.io/mcp and sign in when prompted, or add the same JSON block as Cursor's to
its MCP configuration file to use a key instead.
claude.ai — Settings → Connectors → Add custom connector, name it, paste
https://eventsend.io/mcp, and sign in to EventSend when the browser opens. Pick the organizations
to connect and approve.
ChatGPT — in the connectors settings, add an MCP server with the URL https://eventsend.io/mcp
and choose the sign-in option. Approve the organizations you want it to read.
Once connected, every client shows the tools below and the assistant decides which to call; you do not have to name them.
Tools
Nine tools, all read-only. Every tool except list_organizations accepts an optional org — the
organization's slug — and reads the default organization when it is omitted.
| Tool | Answers | Reads |
|---|---|---|
list_organizations |
Which organizations can this connection ask about, and which is the default? | Only the credential itself: slug, name, your role, time zone and retention window per organization. |
search_events |
What came in? Filter by text search, event name, level, status, user_id, route or source, and a time window; page with a cursor. |
Raw events within your plan's retention window, with up to ten recent deliveries per event. |
get_event |
Everything about one event, by its evt_… id. |
The event, its route and source, and its deliveries paged by cursor. |
get_user_timeline |
What did this customer do? Up to five identifiers at once, ordered by when things happened. | Raw events carrying those user_id values, plus a summary (first and last seen, counts by level and by event name) for the first page. |
list_event_names |
Which event names are most common over a range of days? | Daily aggregates, kept for twelve months. |
event_stats |
How many of this event per day, per level, and how does it compare to the previous period? | Daily aggregates. |
delivery_health |
Are deliveries succeeding per destination, and what failed recently? | Per-destination daily aggregates, plus the most recent failed deliveries. |
get_usage |
How much of the allowance is used, what is the projection, which plan is this? | The current usage period, the plan, and the retention window. |
describe_setup |
What routes, sources, topics and destinations exist, and what are their ids? | Names and ids only — never a token, secret or destination configuration. |
Some examples of how questions map to tools:
| You ask | The assistant calls |
|---|---|
| "Did anything fail in checkout today?" | search_events with search: "checkout", level: "error", since: "24h" |
| "How many sales this week versus last?" | event_stats with event_name: "payment-received" and days: 7 |
"What did cus_9Xk2 do before churning?" |
get_user_timeline with user_ids: ["cus_9Xk2"] |
| "Are deliveries to Slack failing?" | delivery_health, then search_events for the failing events |
| "Which events are noisiest?" | list_event_names with days: 7 |
| "Are we going to hit the cap?" | get_usage |
The identity contract
Per-customer questions only work if every event about a customer carries the same identifier.
That is what the optional user_id field on the events API is for: an
opaque, stable identifier you choose — an account id, or your Stripe customer id — sent as the same
value from every system that reports on that customer. It is matched exactly, so cus_9Xk2 and
CUS_9XK2 are two different customers, and it is not an EventSend user.
Stripe sources follow the contract for you: every event that has a customer attached sets user_id
to the Stripe customer id, and never to the email. If your own application sends its events with the Stripe customer id too, one
timeline covers signup, payment, failure and churn regardless of which system reported each step.
Email addresses belong in payload, not in user_id. They change, they are personal data, and
they make a poor join key. get_user_timeline accepts up to five identifiers in one call, so an
assistant that learns an email from one event's payload and a customer id from another can still
merge the two.
The other optional field, occurred_at, is when the thing actually
happened. Timelines are ordered by it, so a webhook that arrives late, or a backfill of history,
lands in the right place. When you omit it, the moment EventSend received the event is used, which
is right for almost everything sent live.
Time
Tools that read raw events (search_events, get_user_timeline, the recent failures in
delivery_health) take instants. since and until accept three forms: an ISO-8601
timestamp (2026-09-16T10:15:00Z), a calendar date (2026-09-16, read as midnight in your
organization's time zone), or a relative window such as 30m, 24h, 7d or 2w. Nothing more
than 366 days back is accepted, and the retention window bounds what actually comes back.
Tools that read aggregates (list_event_names, event_stats, the per-destination part of
delivery_health) take calendar dates only: from and to, or days for the last N days
including today. Aggregates are daily buckets in your organization's time zone, so a rolling
"last 24 hours" cannot be answered exactly from them — ask search_events for that. When a range
ends today, the response carries partial_period: true because today's bucket is still filling.
Aggregates count events by the time they were recorded, not by occurred_at; the response says
so in a time_basis field.
Every response states the organization's time zone and the server's current time, so the assistant can turn "this morning" into the right window.
Limits
- Read-only. There is nothing to undo.
- Raw events are bounded by your plan's retention window: 7 days on Free, 30 on Starter, 60 on
Growth, 90 on Scale. Once an event is pruned no tool can see it. Daily aggregates are kept for
twelve months on every plan, so
event_statsandlist_event_namesreach back further than the raw events do. - A search walks at most 50,000 events per page. A filter that matches nothing in a large window returns an empty page with a cursor and says the window was not exhausted, and the assistant can keep going.
- Page sizes are 100 events for
search_events, 200 forget_user_timeline, 100 deliveries per page inget_event, and 200 entries per list indescribe_setup. - Responses are capped at 256 KB. A page that would exceed it is cut short and its cursor moved
back to the last event actually returned, so nothing is skipped. An event whose payload alone is
too large comes back with the payload omitted and a note to fetch it with
get_event. - 5 requests per second per credential, with a burst of 50. That is well above what an assistant
needs; a
429carries aretry-afterheader in seconds. - No secrets, ever. Route tokens, source signing secrets, MCP keys and destination configurations are not part of any response.
Revoking access
Everything that can read your organization is listed in one of two places, and can be revoked from there:
- Settings → MCP on the organization (owner or admin): every MCP key, with rotate and revoke, and every signed-in connection that includes this organization, with Remove access. Removing access takes the organization out of that connection only; the member's other organizations on it are untouched.
- Connected apps in your personal settings (any member): every connection you approved, the organizations each covers, and a Disconnect button that ends the whole connection. To change which organizations a connection covers, connect again from the client and pick differently.
Revocation takes effect on the next request. There is no grace period and nothing to wait for.