MCP Server Reference
What is MCP?
The Model Context Protocol (MCP) is an open standard that lets AI assistants call external tools during a conversation. Instead of copy-pasting data into a chat window, your AI client (Claude, Cursor, Gemini, etc.) automatically calls the right tool, fetches live data from Spekta, and uses it to answer your question — all within the conversation.
Think of MCP as a typed RPC layer between LLMs and data sources. Each "tool" is a function with a JSON Schema describing its inputs and outputs. The AI decides which tool to call, fills in the parameters from context, and shows you the result in plain English.
Spekta exposes 6 MCP tools covering events, ticket sales, revenue, comparisons, attendee stats, and provider connections.
Transport options
| Client | Transport | URL |
|---|---|---|
| Claude Desktop | SSE (Server-Sent Events) | https://api.spekta.io/mcp/sse |
| ChatGPT Custom Actions | REST (HTTP POST) | https://api.spekta.io/tools/<tool_name> |
| Cursor, Gemini, others | SSE | https://api.spekta.io/mcp/sse |
| Local / self-hosted | stdio or SSE | See Self-hosting Guide |
Authentication
API key setup
Every request (SSE or REST) must include a Spekta API key.
Generate a key
- Log in to Spekta.
- Go to Settings → API Keys.
- Click Generate key, give it a name, and copy the value immediately — it is shown only once.
Keys look like: tix_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Passing the key
For MCP SSE clients, set the X-Spekta-API-Key header in your MCP config:
{
"mcpServers": {
"spekta": {
"command": "npx",
"args": ["mcp-remote", "https://api.spekta.io/mcp/sse"],
"env": {
"X-Spekta-API-Key": "tix_live_xxxxxxxx"
}
}
}
}
For direct REST calls:
curl -X POST https://api.spekta.io/tools/list_events \
-H "X-Spekta-API-Key: tix_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"provider": "all", "page": 1, "page_size": 20}'
Coming in Phase 3: OAuth 2.0 flow for team-level access without sharing individual API keys.
Key scopes
Keys currently have full read access to all data for the owning tenant. Write access (connecting providers, triggering syncs) is reserved for dashboard UI operations and is not exposed via MCP.
Revoking a key
Go to Settings → API Keys and click Revoke next to the key. Revoked keys return 401 immediately.
Tool Reference
1. list_events
List events visible to the authenticated tenant, with optional filters.
Input schema
{
"type": "object",
"properties": {
"provider": {
"type": "string",
"default": "all",
"description": "Filter by provider ID (e.g. 'eventbrite', 'weezevent') or 'all'"
},
"date_from": {
"type": "string",
"format": "date",
"description": "Start date filter, YYYY-MM-DD"
},
"date_to": {
"type": "string",
"format": "date",
"description": "End date filter, YYYY-MM-DD"
},
"status": {
"type": "string",
"default": "all",
"enum": ["upcoming", "live", "past", "cancelled", "all"]
},
"search": {
"type": "string",
"description": "Full-text search against event name and description"
},
"page": {
"type": "integer",
"minimum": 1,
"default": 1
},
"page_size": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
}
}
}
Output schema
{
"type": "object",
"properties": {
"events": {
"type": "array",
"items": {
"type": "object",
"properties": {
"event_id": { "type": "string", "format": "uuid" },
"name": { "type": "string" },
"provider": { "type": "string" },
"start_date": { "type": "string", "format": "date-time" },
"end_date": { "type": "string", "format": "date-time" },
"venue": { "type": "string" },
"status": { "type": "string" },
"capacity": { "type": "integer" }
}
}
},
"total": { "type": "integer" },
"page": { "type": "integer" },
"page_size": { "type": "integer" }
}
}
Example
curl -X POST https://api.spekta.io/tools/list_events \
-H "X-Spekta-API-Key: tix_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"provider": "eventbrite",
"date_from": "2025-06-01",
"date_to": "2025-12-31",
"status": "upcoming",
"page_size": 10
}'
{
"events": [
{
"event_id": "a1b2c3d4-...",
"name": "Summer Beats Festival",
"provider": "eventbrite",
"start_date": "2025-08-15T18:00:00Z",
"venue": "Parc de la Villette, Paris",
"status": "upcoming",
"capacity": 5000
}
],
"total": 42,
"page": 1,
"page_size": 10
}
2. get_ticket_sales
Return time-series ticket sales data for an event.
Input schema
{
"type": "object",
"required": ["event_id"],
"properties": {
"event_id": {
"type": "string",
"format": "uuid",
"description": "Spekta event UUID (from list_events)"
},
"date_from": { "type": "string", "format": "date" },
"date_to": { "type": "string", "format": "date" },
"granularity": {
"type": "string",
"enum": ["hour", "day", "week", "month"],
"default": "day"
},
"provider": {
"type": "string",
"default": "all"
},
"breakdown_by": {
"type": "string",
"enum": ["ticket_type", "provider", "none"],
"default": "none"
}
}
}
Output schema
{
"type": "object",
"properties": {
"event_id": { "type": "string" },
"granularity": { "type": "string" },
"series": {
"type": "array",
"items": {
"type": "object",
"properties": {
"period": { "type": "string", "description": "ISO 8601 period start" },
"tickets_sold": { "type": "integer" },
"gross_revenue_cents": { "type": "integer" },
"currency": { "type": "string" }
}
}
}
}
}
Example
curl -X POST https://api.spekta.io/tools/get_ticket_sales \
-H "X-Spekta-API-Key: tix_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"event_id": "a1b2c3d4-...",
"granularity": "week",
"breakdown_by": "ticket_type"
}'
3. get_revenue
Return revenue figures, optionally grouped by event, provider, ticket type, or month.
Input schema
{
"type": "object",
"properties": {
"event_ids": {
"type": "array",
"items": { "type": "string", "format": "uuid" },
"description": "Limit to specific events; omit for all events"
},
"date_from": { "type": "string", "format": "date" },
"date_to": { "type": "string", "format": "date" },
"provider": { "type": "string", "default": "all" },
"ticket_type": {
"type": "string",
"description": "Filter by ticket type name (case-insensitive substring match)"
},
"group_by": {
"type": "string",
"enum": ["event", "provider", "ticket_type", "month", "none"],
"default": "none"
},
"currency": {
"type": "string",
"default": "EUR",
"description": "Target currency for conversion (ISO 4217)"
}
}
}
Output schema
{
"type": "object",
"properties": {
"currency": { "type": "string" },
"total_gross": { "type": "number" },
"total_net": { "type": "number" },
"total_fees": { "type": "number" },
"total_refunds": { "type": "number" },
"tickets_sold": { "type": "integer" },
"rows": {
"type": "array",
"items": {
"type": "object",
"properties": {
"group_key": { "type": "string" },
"gross": { "type": "number" },
"net": { "type": "number" },
"fees": { "type": "number" },
"refunds": { "type": "number" },
"tickets_sold": { "type": "integer" }
}
}
}
}
}
Example
curl -X POST https://api.spekta.io/tools/get_revenue \
-H "X-Spekta-API-Key: tix_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"date_from": "2025-01-01",
"date_to": "2025-12-31",
"group_by": "provider",
"currency": "EUR"
}'
4. compare_events
Side-by-side comparison of two or more events across a metric.
Input schema
{
"type": "object",
"required": ["event_ids", "metric"],
"properties": {
"event_ids": {
"type": "array",
"items": { "type": "string", "format": "uuid" },
"minItems": 2,
"maxItems": 10
},
"metric": {
"type": "string",
"enum": [
"tickets_sold",
"revenue_gross",
"revenue_net",
"attendance_rate",
"early_bird_sales",
"avg_ticket_price"
]
},
"date_window_days_before_event": {
"type": "integer",
"minimum": 1,
"description": "Limit sales data to N days before each event's start date (useful for comparing sales velocity)"
},
"normalize": {
"type": "boolean",
"default": false,
"description": "Express metric as % of capacity rather than absolute value"
}
}
}
Output schema
{
"type": "object",
"properties": {
"metric": { "type": "string" },
"normalized": { "type": "boolean" },
"events": {
"type": "array",
"items": {
"type": "object",
"properties": {
"event_id": { "type": "string" },
"event_name": { "type": "string" },
"value": { "type": "number" },
"rank": { "type": "integer" }
}
}
}
}
}
Example
curl -X POST https://api.spekta.io/tools/compare_events \
-H "X-Spekta-API-Key: tix_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"event_ids": ["uuid-1", "uuid-2", "uuid-3"],
"metric": "revenue_gross",
"normalize": true
}'
5. get_attendee_stats
Aggregated attendee statistics for an event. No PII is returned — all breakdowns are aggregate counts.
Input schema
{
"type": "object",
"required": ["event_id"],
"properties": {
"event_id": {
"type": "string",
"format": "uuid"
},
"breakdown_by": {
"type": "string",
"enum": ["ticket_type", "purchase_channel", "country", "none"],
"default": "none"
}
}
}
Output schema
{
"type": "object",
"properties": {
"event_id": { "type": "string" },
"total_attendees": { "type": "integer" },
"checked_in": { "type": "integer" },
"check_in_rate": { "type": "number", "description": "0.0 – 1.0" },
"breakdown": {
"type": "array",
"items": {
"type": "object",
"properties": {
"group_key": { "type": "string" },
"count": { "type": "integer" },
"pct": { "type": "number" }
}
}
}
}
}
Example
curl -X POST https://api.spekta.io/tools/get_attendee_stats \
-H "X-Spekta-API-Key: tix_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"event_id": "a1b2c3d4-...",
"breakdown_by": "ticket_type"
}'
6. get_provider_connections
List connected providers and their health status for the authenticated tenant.
Input schema
{
"type": "object",
"properties": {}
}
(No parameters required.)
Output schema
{
"type": "object",
"properties": {
"connections": {
"type": "array",
"items": {
"type": "object",
"properties": {
"provider": { "type": "string" },
"status": {
"type": "string",
"enum": ["connected", "syncing", "error"]
},
"last_synced_at": { "type": "string", "format": "date-time" },
"events_count": { "type": "integer" }
}
}
}
}
}
Example
curl -X POST https://api.spekta.io/tools/get_provider_connections \
-H "X-Spekta-API-Key: tix_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{}'
{
"connections": [
{
"provider": "eventbrite",
"status": "connected",
"last_synced_at": "2025-08-01T10:30:00Z",
"events_count": 42
},
{
"provider": "weezevent",
"status": "connected",
"last_synced_at": "2025-08-01T10:31:00Z",
"events_count": 18
}
]
}
Error codes
All errors follow a consistent JSON envelope:
{
"error": {
"code": "UNAUTHORIZED",
"message": "API key is invalid or has been revoked.",
"request_id": "req_xxxxxxxx"
}
}
| HTTP status | Error code | Cause |
|---|---|---|
| 400 | VALIDATION_ERROR | Input failed JSON Schema validation. Check details field for per-field errors. |
| 401 | UNAUTHORIZED | Missing or invalid X-Spekta-API-Key. |
| 403 | FORBIDDEN | Key is valid but lacks permission for this operation. |
| 404 | NOT_FOUND | The requested event ID does not exist or belongs to a different tenant. |
| 422 | UNPROCESSABLE | Input was valid JSON but semantically invalid (e.g. date_from after date_to). |
| 429 | RATE_LIMITED | Too many requests. See Rate limits. |
| 500 | INTERNAL_ERROR | Unexpected server error. The request_id can be shared with support. |
| 503 | SERVICE_UNAVAILABLE | Database or dependency health check failed. |
Handling errors in TypeScript
const res = await fetch('https://api.spekta.io/tools/list_events', {
method: 'POST',
headers: {
'X-Spekta-API-Key': process.env.SPEKTA_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({ provider: 'all' }),
});
if (!res.ok) {
const err = await res.json();
console.error(`[${err.error.code}] ${err.error.message}`);
// retry only on 429 or 503
}
Rate limits
Rate limits are applied per API key.
| Plan | Requests / minute | Requests / day |
|---|---|---|
| Free | 20 | 1 000 |
| Pro | 100 | 10 000 |
| Business | 500 | 100 000 |
When you exceed the limit, the server returns HTTP 429 with a Retry-After header (seconds until the limit resets):
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1722510000
The in-memory rate limiter resets every 60 seconds. If you are running bulk analytics, use page_size=100 and add a short pause between requests to stay within limits.
MCP clients: Claude Desktop and similar MCP hosts make calls on your behalf. If you hit rate limits inside Claude, wait a moment and retry the conversation — the AI will automatically recall the tool.