@chrischall/eventbrite-mcp 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@chrischall/eventbrite-mcp",
3
+ "version": "0.0.0",
4
+ "mcpName": "io.github.chrischall/eventbrite-mcp",
5
+ "description": "Eventbrite MCP server for Claude — developed and maintained by AI (Claude Code)",
6
+ "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/chrischall/eventbrite-mcp.git"
10
+ },
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "mcp",
14
+ "model-context-protocol",
15
+ "claude",
16
+ "ai",
17
+ "eventbrite",
18
+ "events",
19
+ "tickets",
20
+ "event-discovery",
21
+ "orders"
22
+ ],
23
+ "type": "module",
24
+ "bin": {
25
+ "eventbrite-mcp": "dist/index.js"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ ".claude-plugin",
33
+ "skills/",
34
+ ".mcp.json",
35
+ "server.json"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc && npm run bundle",
39
+ "bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --banner:js='import { createRequire as __createRequire } from \"module\"; const require = __createRequire(import.meta.url);' --outfile=dist/bundle.js",
40
+ "dev": "node dist/index.js",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "test:coverage": "vitest run --coverage",
44
+ "worker:dev": "wrangler dev",
45
+ "worker:deploy": "wrangler deploy",
46
+ "worker:test": "vitest run --config vitest.workers.config.ts"
47
+ },
48
+ "dependencies": {
49
+ "@chrischall/mcp-utils": "^0.14.0",
50
+ "@fetchproxy/server": "^1.7.0",
51
+ "@modelcontextprotocol/sdk": "^1.29.0",
52
+ "dotenv": "^17.4.0",
53
+ "zod": "^4.4.2"
54
+ },
55
+ "devDependencies": {
56
+ "@chrischall/mcp-connector": "^1.1.1",
57
+ "@cloudflare/vitest-pool-workers": "^0.19.0",
58
+ "@cloudflare/workers-oauth-provider": "^0.8.1",
59
+ "@cloudflare/workers-types": "^5.20260708.1",
60
+ "@types/node": "^26.0.0",
61
+ "@vitest/coverage-v8": "^4.1.2",
62
+ "agents": "^0.19.0",
63
+ "esbuild": "^0.28.0",
64
+ "typescript": "^7.0.2",
65
+ "vitest": "^4.1.2",
66
+ "wrangler": "^4.110.0"
67
+ },
68
+ "allowScripts": {
69
+ "esbuild@0.28.1": true,
70
+ "workerd@1.20260722.1": true
71
+ }
72
+ }
package/server.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.chrischall/eventbrite-mcp",
4
+ "description": "Eventbrite for Claude — your tickets/orders, organizer data, and public event discovery",
5
+ "repository": {
6
+ "url": "https://github.com/chrischall/eventbrite-mcp",
7
+ "source": "github"
8
+ },
9
+ "version": "0.0.0",
10
+ "packages": [
11
+ {
12
+ "registryType": "npm",
13
+ "identifier": "@chrischall/eventbrite-mcp",
14
+ "version": "0.0.0",
15
+ "transport": {
16
+ "type": "stdio"
17
+ },
18
+ "environmentVariables": [
19
+ {
20
+ "name": "EVENTBRITE_TOKEN",
21
+ "description": "Personal OAuth token (eventbrite.com/platform/api-keys); needed for account tools",
22
+ "isRequired": false,
23
+ "format": "string",
24
+ "isSecret": true
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ }
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: eventbrite
3
+ description: "Query eventbrite.com from a shell — your tickets/orders, your organizations' events and attendees via the documented token API (curl), and public event discovery/search via the fpx browser bridge (the search API is WAF-walled and absent from the documented API). Use when you want Eventbrite data without an MCP server, in a script, or one-shot."
4
+ ---
5
+
6
+ # Eventbrite access (curl + fpx)
7
+
8
+ Two surfaces, two tools — match the tool to reachability:
9
+
10
+ 1. **Documented API `https://www.eventbriteapi.com/v3`** — reachable
11
+ server-side with a personal OAuth token. Use plain `curl`. Covers your
12
+ identity, your orders/tickets, organizations you own (events, attendees,
13
+ orders), and any event **by id**. It has **no public event search** —
14
+ Eventbrite removed it from the documented API in 2019.
15
+ 2. **Consumer site API `https://www.eventbrite.com/api/v3/destination/…`** —
16
+ WAF-blocked for any server-side client (returns an HTML "Whoops!" page).
17
+ Public event **search/discovery** lives only here, so those calls route
18
+ through the user's signed-in browser tab with `fpx`.
19
+
20
+ ## One-time setup
21
+
22
+ **Token (curl surface):** create a private token at
23
+ <https://www.eventbrite.com/platform/api-keys> (free for any Eventbrite
24
+ account) and export it as `EVENTBRITE_TOKEN`.
25
+
26
+ **Bridge (fpx surface):**
27
+
28
+ ```sh
29
+ npm install -g @fetchproxy/cli # provides `fpx`
30
+ fpx profile add eventbrite --domain eventbrite.com
31
+ fpx pair -p eventbrite # prints a pair code → approve in the Transporter popup
32
+ ```
33
+
34
+ Requires the **Transporter** extension with Chrome site access for
35
+ `eventbrite.com` and an open (signed-in for account data) eventbrite.com tab.
36
+ Pairing persists after the first approval.
37
+
38
+ ## Core calls
39
+
40
+ Token surface — every request is:
41
+
42
+ ```sh
43
+ curl -sS "https://www.eventbriteapi.com/v3/users/me/" \
44
+ -H "Authorization: Bearer $EVENTBRITE_TOKEN" | jq
45
+ ```
46
+
47
+ Bridge surface — stdout is data only, ready for `jq`:
48
+
49
+ ```sh
50
+ fpx get 'https://www.eventbrite.com/api/v3/destination/events/?event_ids=<id>&expand=event_sales_status,primary_venue' \
51
+ -p eventbrite | jq '.events[0]'
52
+ ```
53
+
54
+ Ready-to-run request bodies and `jq` recipes: `references/token-api.md`
55
+ (curl surface) and `references/discovery-api.md` (fpx surface, incl. event
56
+ search). Read the relevant reference before composing a call — the search
57
+ body shape and the resolve-location-first rule live there.
58
+
59
+ ## Rules
60
+
61
+ - **Resolve places before searching**: destination search takes place ids,
62
+ not free-text locations — autocomplete first (see `discovery-api.md`).
63
+ - **Pagination**: v3 responses carry a `pagination` envelope; loop with
64
+ `?continuation=<token>` while `has_more_items` is true.
65
+ - **fpx exit codes**: `2` bridge down (pair/approve in Transporter), `3` bot
66
+ wall (refresh a signed-in eventbrite.com tab), `4` upstream non-2xx.
67
+ - **Read-only**: these recipes only read data. The documented API supports
68
+ organizer writes (create/update events etc.) but they are out of scope
69
+ here — don't improvise write calls from this skill.
70
+ - Never log or commit the token or captured cookies.
@@ -0,0 +1,113 @@
1
+ # Eventbrite discovery API (`www.eventbrite.com/api/v3/destination/…`) — fpx recipes
2
+
3
+ Public event search/discovery. WAF-blocked server-side; every call here goes
4
+ through the fpx bridge (`-p eventbrite`). Request shapes below were captured
5
+ live from the site's own network traffic (2026-07-30, web_app discover
6
+ v10.14.65).
7
+
8
+ ## CSRF (search POST only)
9
+
10
+ GETs need nothing extra. The search POST requires the Django CSRF header,
11
+ whose value is the `csrftoken` cookie (32 chars, JS-readable). Read it
12
+ through the bridge — the profile must declare the cookie scope (see SKILL.md
13
+ setup):
14
+
15
+ ```sh
16
+ CSRF=$(fpx cookies csrftoken -p eventbrite | jq -r '.csrftoken')
17
+ ```
18
+
19
+ If the cookie is absent, load any eventbrite.com page in the browser first.
20
+
21
+ ## Resolve a place id first
22
+
23
+ Searches take internal place ids (e.g. Charlotte NC = `85981333`), never
24
+ place names. The site itself resolves locations via Google Places, but the
25
+ scriptable route is the SSR page store: fetch the browse page for the
26
+ location slug and pull `placeId` out of the embedded `__SERVER_DATA__`:
27
+
28
+ ```sh
29
+ # slug format: <state>--<city> (US) or <country>--<city>
30
+ fpx get 'https://www.eventbrite.com/d/co--denver/events/' -p eventbrite \
31
+ | grep -oE '"placeId":"[0-9]+"' | head -1
32
+ ```
33
+
34
+ The same SSR page embeds the full first page of results in
35
+ `search_data.events` — for a quick "what's on in <city>" that GET alone may
36
+ be all you need.
37
+
38
+ ## Event search
39
+
40
+ ```sh
41
+ CSRF=$(fpx cookies csrftoken -p eventbrite | jq -r '.csrftoken')
42
+ cat > /tmp/eb-search.json <<'EOF'
43
+ {
44
+ "browse_surface": "search",
45
+ "event_search": {
46
+ "q": "blues",
47
+ "places": ["85981333"],
48
+ "dates": ["current_future"],
49
+ "dedup": true,
50
+ "page": 1,
51
+ "page_size": 20
52
+ },
53
+ "expand.destination_event": [
54
+ "primary_venue", "image", "ticket_availability",
55
+ "event_sales_status", "primary_organizer"
56
+ ]
57
+ }
58
+ EOF
59
+ fpx post-json 'https://www.eventbrite.com/api/v3/destination/search/' @/tmp/eb-search.json \
60
+ -p eventbrite -H "X-CSRFToken: $CSRF" -H "X-Requested-With: XMLHttpRequest" \
61
+ | jq '.events.results[] | {id, name, start: .start_date, venue: .primary_venue.name, url}'
62
+ ```
63
+
64
+ Body knobs (all inside `event_search`, all optional except you want at least
65
+ `q` or `places`):
66
+
67
+ - `q` — keyword text.
68
+ - `places` — array of place-id strings (resolve first, above).
69
+ - `dates` — array; `"current_future"` plus optionally one of `"today"`,
70
+ `"tomorrow"`, `"this_weekend"`; or `date_range: {from, to}` (ISO dates).
71
+ - `tags` — category/format filters: `"EventbriteCategory/<id>"` (103 =
72
+ Music …), `"EventbriteSubCategory/<id>"`, `"EventbriteFormat/<id>"`. Ids
73
+ match the documented API's `/categories/` etc. (see token-api.md).
74
+ - `online_events_only` — bool.
75
+ - `price` — `"free"` or `"paid"`.
76
+ - `page`, `page_size` (site uses 20), `dedup: true`.
77
+
78
+ Response: `{events: {pagination: {object_count, page_count, continuation},
79
+ results: [...]}, ...}` — paginate by incrementing `page`.
80
+
81
+ ## Event detail by id (GET, no CSRF)
82
+
83
+ ```sh
84
+ fpx get 'https://www.eventbrite.com/api/v3/destination/events/?event_ids=<id>,<id>&expand=primary_venue,image,ticket_availability,event_sales_status,primary_organizer' \
85
+ -p eventbrite | jq '.events[]'
86
+ ```
87
+
88
+ Event ids are the trailing digits in event URLs (`…-tickets-<id>`). For
89
+ full ticket-class detail prefer the documented API (`token-api.md`) —
90
+ `/events/<id>/` works for any public event with just a token.
91
+
92
+ ## Your account data through the bridge (no token needed)
93
+
94
+ The www host proxies the documented v3 API with the browser session's auth,
95
+ so the whole `token-api.md` surface also works session-authed via fpx —
96
+ same paths, `www.eventbrite.com` host (live-verified: `/api/v3/users/me/`):
97
+
98
+ ```sh
99
+ fpx get 'https://www.eventbrite.com/api/v3/users/me/orders/?expand=event&time_filter=current_future' \
100
+ -p eventbrite | jq '.orders[] | {id, event: .event.name.text}'
101
+ ```
102
+
103
+ Prefer the token + curl for scripts (no browser dependency); use this when
104
+ no token is configured.
105
+
106
+ ## Gotchas
107
+
108
+ - A non-JSON 2xx body from any of these = the WAF interstitial leaked
109
+ through — refresh a signed-in eventbrite.com tab and retry.
110
+ - `log_requests` / `log_engagement` endpoints seen in captures are
111
+ analytics — don't call them.
112
+ - The `?stable_id=` query param on the site's own search calls is a client
113
+ analytics uuid — omit it.
@@ -0,0 +1,102 @@
1
+ # Eventbrite documented API (`eventbriteapi.com/v3`) — curl recipes
2
+
3
+ Auth on every call: `-H "Authorization: Bearer $EVENTBRITE_TOKEN"`.
4
+ Personal token: <https://www.eventbrite.com/platform/api-keys>.
5
+ All endpoints end with a trailing slash — omitting it 301s.
6
+
7
+ > Verification status (2026-07-30): `eventbriteapi.com` reachability and the
8
+ > 401 error envelope are live-verified. Endpoint shapes were live-verified
9
+ > through the session-authed www-host variant of the same v3 API
10
+ > (`www.eventbrite.com/api/v3/…`): `/users/me/`, `/users/me/orders/`
11
+ > envelope, `/users/me/organizations/` envelope, `/categories/`, and
12
+ > `/events/<id>/?expand=venue,ticket_availability` on a public event.
13
+ > `/ticket_classes/`, `/description/`, `/attendees/`, `/venues/`,
14
+ > `/subcategories/`, `/formats/` follow the official API reference but
15
+ > weren't exercised — eyeball the first response before building on a field.
16
+
17
+ Shorthand used below:
18
+
19
+ ```sh
20
+ eb() { curl -sS "https://www.eventbriteapi.com/v3$1" -H "Authorization: Bearer $EVENTBRITE_TOKEN"; }
21
+ ```
22
+
23
+ ## Identity
24
+
25
+ ```sh
26
+ eb /users/me/ | jq '{id, name, email: .emails[0].email}'
27
+ ```
28
+
29
+ ## Your tickets / orders (attendee side)
30
+
31
+ ```sh
32
+ # upcoming orders with the event expanded
33
+ eb '/users/me/orders/?expand=event&time_filter=current_future' \
34
+ | jq '.orders[] | {id, status, event: .event.name.text, start: .event.start.local}'
35
+
36
+ # one order with attendees (barcode-level detail)
37
+ eb '/orders/<order_id>/?expand=attendees' | jq
38
+ ```
39
+
40
+ `time_filter`: `all` | `current_future` | `past`.
41
+
42
+ ## Organizations you belong to (organizer side)
43
+
44
+ ```sh
45
+ eb /users/me/organizations/ | jq '.organizations[] | {id, name}'
46
+
47
+ # events for an org: status live|draft|started|ended|completed|canceled|all
48
+ eb '/organizations/<org_id>/events/?status=live&order_by=start_asc' \
49
+ | jq '.events[] | {id, name: .name.text, start: .start.local, url}'
50
+
51
+ # attendees / orders across the org
52
+ eb '/organizations/<org_id>/attendees/?status=attending' | jq '.attendees[] | {profile: .profile.name, email: .profile.email, event_id}'
53
+ eb '/organizations/<org_id>/orders/' | jq '.orders[] | {id, email, status}'
54
+ ```
55
+
56
+ ## Any event by id (works for public events, not just yours)
57
+
58
+ ```sh
59
+ eb '/events/<event_id>/?expand=venue,organizer,ticket_availability' \
60
+ | jq '{name: .name.text, start: .start.local, end: .end.local, venue: .venue.name, is_free, status, url}'
61
+
62
+ eb '/events/<event_id>/ticket_classes/' | jq '.ticket_classes[] | {name, free, cost: .cost.display, on_sale_status}'
63
+ eb '/events/<event_id>/description/' | jq -r .description # full HTML description
64
+ eb '/events/<event_id>/attendees/' | jq # your own events only
65
+ ```
66
+
67
+ Event ids are the long digits in any event URL (`…-tickets-<event_id>`).
68
+
69
+ ## Reference data
70
+
71
+ ```sh
72
+ eb /categories/ | jq '.categories[] | {id, name}'
73
+ eb /subcategories/ | jq '.subcategories[] | {id, name}'
74
+ eb /formats/ | jq '.formats[] | {id, name}'
75
+ eb '/venues/<venue_id>/' | jq '{name, address: .address.localized_address_display}'
76
+ ```
77
+
78
+ ## Pagination
79
+
80
+ Every list response carries:
81
+
82
+ ```json
83
+ {"pagination": {"object_count": 123, "page_size": 50, "has_more_items": true, "continuation": "..."}}
84
+ ```
85
+
86
+ Loop with `?continuation=<token>` (repeat the original params) while
87
+ `has_more_items` is true. `page_size` max is generally 100 for org-level
88
+ lists, 50 elsewhere.
89
+
90
+ ## Errors
91
+
92
+ Non-2xx bodies are JSON: `{"status_code": N, "error": "CODE",
93
+ "error_description": "..."}`. Live-verified examples: bad token →
94
+ `401 INVALID_AUTH`; missing CSRF (www host only) → `401 ACCESS_DENIED`.
95
+ `404 NOT_FOUND` for unknown ids; `429` when rate-limited (token buckets:
96
+ 2,000 calls/hour per token by default).
97
+
98
+ ## No search here
99
+
100
+ `GET /events/search/` was removed from this API (Dec 2019) — it now 404s.
101
+ Public event discovery only exists on the WAF-walled consumer surface; see
102
+ `discovery-api.md` (fpx bridge).