Documentation menu

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-received events did we get this week, and how does that compare to last?"
  • "What did customer cus_9Xk2 do 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_stats and list_event_names reach 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 for get_user_timeline, 100 deliveries per page in get_event, and 200 entries per list in describe_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 429 carries a retry-after header 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.