@chrischall/eventbrite-mcp 0.2.0 → 0.3.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bundle.js +7320 -2286
- package/dist/tools/account.js +29 -18
- package/dist/tools/discovery.js +31 -23
- package/dist/tools/events.js +29 -18
- package/dist/tools/lookup.js +25 -15
- package/dist/version.js +1 -1
- package/dist/view.js +55 -0
- package/package.json +2 -2
- package/server.json +2 -2
package/dist/tools/account.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { viewArg, viewResponse } from '../view.js';
|
|
3
3
|
import { enc, qs, schemaContinuation } from './params.js';
|
|
4
4
|
/**
|
|
5
5
|
* Account-side tools on the documented API (`eventbriteapi.com/v3`, bearer
|
|
@@ -10,45 +10,52 @@ export function registerAccountTools(server, deps) {
|
|
|
10
10
|
const { client } = deps;
|
|
11
11
|
server.registerTool('eb_me', {
|
|
12
12
|
description: "Get the authenticated Eventbrite user's profile (id, name, primary email).",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
view: viewArg(),
|
|
15
|
+
},
|
|
13
16
|
annotations: { readOnlyHint: true },
|
|
14
|
-
}, async () => {
|
|
17
|
+
}, async ({ view }) => {
|
|
15
18
|
const data = await client.request('GET', '/users/me/');
|
|
16
|
-
return
|
|
19
|
+
return viewResponse(view, data);
|
|
17
20
|
});
|
|
18
21
|
server.registerTool('eb_my_orders', {
|
|
19
22
|
description: "List the authenticated user's ticket orders (attendee side), with the event expanded. time_filter narrows to upcoming or past events.",
|
|
20
23
|
annotations: { readOnlyHint: true },
|
|
21
24
|
inputSchema: {
|
|
25
|
+
view: viewArg(),
|
|
22
26
|
time_filter: z
|
|
23
27
|
.enum(['all', 'current_future', 'past'])
|
|
24
28
|
.optional()
|
|
25
29
|
.describe('Filter orders by event time (default all)'),
|
|
26
30
|
continuation: schemaContinuation,
|
|
27
31
|
},
|
|
28
|
-
}, async ({ time_filter, continuation }) => {
|
|
32
|
+
}, async ({ time_filter, continuation, view }) => {
|
|
29
33
|
const params = new URLSearchParams({ expand: 'event' });
|
|
30
34
|
if (time_filter)
|
|
31
35
|
params.set('time_filter', time_filter);
|
|
32
36
|
if (continuation)
|
|
33
37
|
params.set('continuation', continuation);
|
|
34
38
|
const data = await client.request('GET', `/users/me/orders/?${params}`);
|
|
35
|
-
return
|
|
39
|
+
return viewResponse(view, data);
|
|
36
40
|
});
|
|
37
41
|
server.registerTool('eb_my_organizations', {
|
|
38
42
|
description: 'List the organizations the authenticated user belongs to (organizer side). Use the returned org id with eb_org_events / eb_org_attendees / eb_org_orders.',
|
|
39
43
|
annotations: { readOnlyHint: true },
|
|
40
|
-
inputSchema: {
|
|
41
|
-
|
|
44
|
+
inputSchema: {
|
|
45
|
+
view: viewArg(), continuation: schemaContinuation
|
|
46
|
+
},
|
|
47
|
+
}, async ({ continuation, view }) => {
|
|
42
48
|
const params = new URLSearchParams();
|
|
43
49
|
if (continuation)
|
|
44
50
|
params.set('continuation', continuation);
|
|
45
51
|
const data = await client.request('GET', `/users/me/organizations/${qs(params)}`);
|
|
46
|
-
return
|
|
52
|
+
return viewResponse(view, data);
|
|
47
53
|
});
|
|
48
54
|
server.registerTool('eb_org_events', {
|
|
49
55
|
description: "List an organization's events (as organizer), optionally filtered by status.",
|
|
50
56
|
annotations: { readOnlyHint: true },
|
|
51
57
|
inputSchema: {
|
|
58
|
+
view: viewArg(),
|
|
52
59
|
org_id: z.string().describe('Organization id (from eb_my_organizations)'),
|
|
53
60
|
status: z
|
|
54
61
|
.enum(['all', 'live', 'draft', 'started', 'ended', 'completed', 'canceled'])
|
|
@@ -59,7 +66,7 @@ export function registerAccountTools(server, deps) {
|
|
|
59
66
|
.optional(),
|
|
60
67
|
continuation: schemaContinuation,
|
|
61
68
|
},
|
|
62
|
-
}, async ({ org_id, status, order_by, continuation }) => {
|
|
69
|
+
}, async ({ org_id, status, order_by, continuation, view }) => {
|
|
63
70
|
const params = new URLSearchParams();
|
|
64
71
|
if (status)
|
|
65
72
|
params.set('status', status);
|
|
@@ -68,12 +75,13 @@ export function registerAccountTools(server, deps) {
|
|
|
68
75
|
if (continuation)
|
|
69
76
|
params.set('continuation', continuation);
|
|
70
77
|
const data = await client.request('GET', `/organizations/${enc(org_id)}/events/${qs(params)}`);
|
|
71
|
-
return
|
|
78
|
+
return viewResponse(view, data);
|
|
72
79
|
});
|
|
73
80
|
server.registerTool('eb_org_attendees', {
|
|
74
81
|
description: "List attendees across an organization's events (organizer side).",
|
|
75
82
|
annotations: { readOnlyHint: true },
|
|
76
83
|
inputSchema: {
|
|
84
|
+
view: viewArg(),
|
|
77
85
|
org_id: z.string().describe('Organization id (from eb_my_organizations)'),
|
|
78
86
|
status: z
|
|
79
87
|
.enum(['attending', 'not_attending', 'unpaid'])
|
|
@@ -81,28 +89,29 @@ export function registerAccountTools(server, deps) {
|
|
|
81
89
|
.describe('Attendee status filter'),
|
|
82
90
|
continuation: schemaContinuation,
|
|
83
91
|
},
|
|
84
|
-
}, async ({ org_id, status, continuation }) => {
|
|
92
|
+
}, async ({ org_id, status, continuation, view }) => {
|
|
85
93
|
const params = new URLSearchParams();
|
|
86
94
|
if (status)
|
|
87
95
|
params.set('status', status);
|
|
88
96
|
if (continuation)
|
|
89
97
|
params.set('continuation', continuation);
|
|
90
98
|
const data = await client.request('GET', `/organizations/${enc(org_id)}/attendees/${qs(params)}`);
|
|
91
|
-
return
|
|
99
|
+
return viewResponse(view, data);
|
|
92
100
|
});
|
|
93
101
|
server.registerTool('eb_org_orders', {
|
|
94
102
|
description: "List orders across an organization's events (organizer side).",
|
|
95
103
|
annotations: { readOnlyHint: true },
|
|
96
104
|
inputSchema: {
|
|
105
|
+
view: viewArg(),
|
|
97
106
|
org_id: z.string().describe('Organization id (from eb_my_organizations)'),
|
|
98
107
|
continuation: schemaContinuation,
|
|
99
108
|
},
|
|
100
|
-
}, async ({ org_id, continuation }) => {
|
|
109
|
+
}, async ({ org_id, continuation, view }) => {
|
|
101
110
|
const params = new URLSearchParams();
|
|
102
111
|
if (continuation)
|
|
103
112
|
params.set('continuation', continuation);
|
|
104
113
|
const data = await client.request('GET', `/organizations/${enc(org_id)}/orders/${qs(params)}`);
|
|
105
|
-
return
|
|
114
|
+
return viewResponse(view, data);
|
|
106
115
|
});
|
|
107
116
|
// Simple org-scoped collections: same shape, same pagination, different noun.
|
|
108
117
|
const orgCollections = [
|
|
@@ -130,12 +139,13 @@ export function registerAccountTools(server, deps) {
|
|
|
130
139
|
inputSchema: {
|
|
131
140
|
org_id: z.string().describe('Organization id (from eb_my_organizations)'),
|
|
132
141
|
continuation: schemaContinuation,
|
|
142
|
+
view: viewArg(),
|
|
133
143
|
},
|
|
134
|
-
}, async ({ org_id, continuation }) => {
|
|
144
|
+
}, async ({ org_id, continuation, view }) => {
|
|
135
145
|
const params = new URLSearchParams();
|
|
136
146
|
if (continuation)
|
|
137
147
|
params.set('continuation', continuation);
|
|
138
|
-
return
|
|
148
|
+
return viewResponse(view, await client.request('GET', `/organizations/${enc(org_id)}/${noun}/${qs(params)}`));
|
|
139
149
|
});
|
|
140
150
|
}
|
|
141
151
|
server.registerTool('eb_org_report', {
|
|
@@ -152,8 +162,9 @@ export function registerAccountTools(server, deps) {
|
|
|
152
162
|
.optional()
|
|
153
163
|
.describe("Grouping dimension, e.g. 'event', 'day', 'ticket_class'"),
|
|
154
164
|
continuation: schemaContinuation,
|
|
165
|
+
view: viewArg(),
|
|
155
166
|
},
|
|
156
|
-
}, async ({ org_id, kind, start_date, end_date, event_status, group_by, continuation }) => {
|
|
167
|
+
}, async ({ org_id, kind, start_date, end_date, event_status, group_by, continuation, view }) => {
|
|
157
168
|
const params = new URLSearchParams();
|
|
158
169
|
if (start_date)
|
|
159
170
|
params.set('start_date', start_date);
|
|
@@ -165,6 +176,6 @@ export function registerAccountTools(server, deps) {
|
|
|
165
176
|
params.set('group_by', group_by);
|
|
166
177
|
if (continuation)
|
|
167
178
|
params.set('continuation', continuation);
|
|
168
|
-
return
|
|
179
|
+
return viewResponse(view, await client.request('GET', `/organizations/${enc(org_id)}/reports/${kind}/${qs(params)}`));
|
|
169
180
|
});
|
|
170
181
|
}
|
package/dist/tools/discovery.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveView } from '@chrischall/mcp-utils';
|
|
3
|
+
import { EB_VIEWS, viewArg, viewResponse } from '../view.js';
|
|
3
4
|
import { toCompactEvent } from '../discovery.js';
|
|
4
5
|
/**
|
|
5
6
|
* Public event discovery. Verified live 2026-07-30: the documented host serves
|
|
@@ -8,23 +9,31 @@ import { toCompactEvent } from '../discovery.js';
|
|
|
8
9
|
* ARE registered without a bridge. The fetchproxy bridge remains a
|
|
9
10
|
* fallback on the stdio path.
|
|
10
11
|
*/
|
|
12
|
+
/**
|
|
13
|
+
* `eb_search_events` is the ONE tool here whose compact rung is a real field
|
|
14
|
+
* projection (`toCompactEvent`) rather than the server-wide media strip, so it
|
|
15
|
+
* names what it keeps instead of borrowing the generic note.
|
|
16
|
+
*/
|
|
17
|
+
const SEARCH_NOTE = 'compact returns { id, name, start date/time, timezone, venue, city, online flag, free/sold-out flags, organizer, summary, url } per event; ' +
|
|
18
|
+
'"full" returns Eventbrite\'s whole search envelope, every field included.';
|
|
11
19
|
export async function registerDiscoveryTools(server, deps) {
|
|
12
20
|
const { discovery, transport } = deps;
|
|
13
21
|
server.registerTool('eb_resolve_place', {
|
|
14
22
|
description: "Resolve a location to Eventbrite's internal place id for eb_search_events. Accepts a plain location ('Charlotte, NC', 'Berlin, Germany') or a browse slug ('nc--charlotte'). A city on its own is rejected — include the state or country. Returns {placeId, name, slug, region, country} plus `shelves` — curated browse shelves (Popular, This Weekend, Online) harvested free from the same fetch.",
|
|
15
23
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
16
24
|
inputSchema: {
|
|
25
|
+
view: viewArg(),
|
|
17
26
|
location: z
|
|
18
27
|
.string()
|
|
19
28
|
.min(2)
|
|
20
29
|
.describe("Location, e.g. 'Charlotte, NC', 'Berlin, Germany', or the slug 'nc--charlotte'"),
|
|
21
30
|
},
|
|
22
|
-
}, async ({ location }) => {
|
|
31
|
+
}, async ({ location, view }) => {
|
|
23
32
|
const place = await discovery.resolveLocation(location);
|
|
24
|
-
return
|
|
33
|
+
return viewResponse(view, place);
|
|
25
34
|
});
|
|
26
35
|
server.registerTool('eb_search_events', {
|
|
27
|
-
description: 'Search public Eventbrite events (the consumer search absent from the documented API). Resolve the location to a place id first with eb_resolve_place. Filters: keyword, dates, category/subcategory/format ids (see eb_reference), free/paid, online-only.
|
|
36
|
+
description: 'Search public Eventbrite events (the consumer search absent from the documented API). Resolve the location to a place id first with eb_resolve_place. Filters: keyword, dates, category/subcategory/format ids (see eb_reference), free/paid, online-only. Answers with the slim per-event projection by default; pass view:"full" for Eventbrite\'s whole search envelope.',
|
|
28
37
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
29
38
|
inputSchema: {
|
|
30
39
|
q: z.string().optional().describe('Keyword query'),
|
|
@@ -49,10 +58,7 @@ export async function registerDiscoveryTools(server, deps) {
|
|
|
49
58
|
.array(z.enum(['places_borough', 'places_neighborhood']))
|
|
50
59
|
.optional()
|
|
51
60
|
.describe('Facet buckets to aggregate alongside results'),
|
|
52
|
-
|
|
53
|
-
.boolean()
|
|
54
|
-
.optional()
|
|
55
|
-
.describe('Return slim event summaries instead of full records (default false)'),
|
|
61
|
+
view: viewArg(SEARCH_NOTE),
|
|
56
62
|
},
|
|
57
63
|
}, async (args) => {
|
|
58
64
|
const data = await discovery.search({
|
|
@@ -70,34 +76,36 @@ export async function registerDiscoveryTools(server, deps) {
|
|
|
70
76
|
pageSize: args.page_size,
|
|
71
77
|
aggs: args.aggs,
|
|
72
78
|
});
|
|
73
|
-
if (args.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
results: results.map(toCompactEvent),
|
|
84
|
-
});
|
|
79
|
+
if (resolveView(args.view, EB_VIEWS) === 'full')
|
|
80
|
+
return viewResponse('full', data);
|
|
81
|
+
const results = data.events?.results;
|
|
82
|
+
// Drift fallback: if the envelope isn't the shape we know, hand back the
|
|
83
|
+
// raw response rather than an empty or half-filled projection — the same
|
|
84
|
+
// rule `projectOrRaw` applies, because a record with holes in it is
|
|
85
|
+
// indistinguishable from "there was nothing there".
|
|
86
|
+
if (!Array.isArray(results)) {
|
|
87
|
+
console.error('[eventbrite-mcp] destination search response missing events.results — returning raw response');
|
|
88
|
+
return viewResponse('full', data);
|
|
85
89
|
}
|
|
86
|
-
return
|
|
90
|
+
return viewResponse('full', {
|
|
91
|
+
pagination: data.events?.pagination,
|
|
92
|
+
results: results.map(toCompactEvent),
|
|
93
|
+
});
|
|
87
94
|
});
|
|
88
95
|
server.registerTool('eb_event_details', {
|
|
89
96
|
description: "Batch-fetch public event details by id. Uses your bearer token by default, falling back to the browser bridge when no token is configured. For ticket-class detail prefer eb_ticket_classes.",
|
|
90
97
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
91
98
|
inputSchema: {
|
|
99
|
+
view: viewArg(),
|
|
92
100
|
event_ids: z.array(z.string()).min(1).max(20).describe('Numeric event ids'),
|
|
93
101
|
expand: z
|
|
94
102
|
.string()
|
|
95
103
|
.optional()
|
|
96
104
|
.describe('Comma-separated expansions (default primary_venue,image,ticket_availability,event_sales_status,primary_organizer)'),
|
|
97
105
|
},
|
|
98
|
-
}, async ({ event_ids, expand }) => {
|
|
106
|
+
}, async ({ event_ids, expand, view }) => {
|
|
99
107
|
const data = await discovery.eventsByIds(event_ids, expand ? expand.split(',').map((s) => s.trim()) : undefined);
|
|
100
|
-
return
|
|
108
|
+
return viewResponse(view, data);
|
|
101
109
|
});
|
|
102
110
|
// eb_healthcheck diagnoses the BRIDGE. With no bridge
|
|
103
111
|
// there is nothing for it to report on, so it is not registered at all —
|
package/dist/tools/events.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { viewArg, viewResponse } from '../view.js';
|
|
3
3
|
import { enc, qs, schemaContinuation } from './params.js';
|
|
4
4
|
/** Reference lists served under /system/ rather than the API root. */
|
|
5
5
|
const SYSTEM_LISTS = new Set(['timezones', 'countries', 'regions']);
|
|
@@ -14,48 +14,54 @@ export function registerEventTools(server, deps) {
|
|
|
14
14
|
description: 'Get an Eventbrite event by id (works for any public event, not just yours). Event ids are the trailing digits in an event URL (…-tickets-<id>).',
|
|
15
15
|
annotations: { readOnlyHint: true },
|
|
16
16
|
inputSchema: {
|
|
17
|
+
view: viewArg(),
|
|
17
18
|
event_id: z.string().describe('Numeric event id'),
|
|
18
19
|
expand: z
|
|
19
20
|
.string()
|
|
20
21
|
.optional()
|
|
21
22
|
.describe('Comma-separated expansions (default venue,organizer,ticket_availability)'),
|
|
22
23
|
},
|
|
23
|
-
}, async ({ event_id, expand }) => {
|
|
24
|
+
}, async ({ event_id, expand, view }) => {
|
|
24
25
|
const exp = expand ?? 'venue,organizer,ticket_availability';
|
|
25
26
|
const data = await client.request('GET', `/events/${encodeURIComponent(event_id)}/?expand=${encodeURIComponent(exp)}`);
|
|
26
|
-
return
|
|
27
|
+
return viewResponse(view, data);
|
|
27
28
|
});
|
|
28
29
|
server.registerTool('eb_ticket_classes', {
|
|
29
30
|
description: "List an event's ticket classes (name, cost, free/paid, on-sale status).",
|
|
30
31
|
annotations: { readOnlyHint: true },
|
|
31
|
-
inputSchema: {
|
|
32
|
-
|
|
32
|
+
inputSchema: {
|
|
33
|
+
view: viewArg(), event_id: z.string().describe('Numeric event id')
|
|
34
|
+
},
|
|
35
|
+
}, async ({ event_id, view }) => {
|
|
33
36
|
const data = await client.request('GET', `/events/${encodeURIComponent(event_id)}/ticket_classes/`);
|
|
34
|
-
return
|
|
37
|
+
return viewResponse(view, data);
|
|
35
38
|
});
|
|
36
39
|
server.registerTool('eb_event_description', {
|
|
37
40
|
description: "Get an event's full HTML description.",
|
|
38
41
|
annotations: { readOnlyHint: true },
|
|
39
|
-
inputSchema: {
|
|
40
|
-
|
|
42
|
+
inputSchema: {
|
|
43
|
+
view: viewArg(), event_id: z.string().describe('Numeric event id')
|
|
44
|
+
},
|
|
45
|
+
}, async ({ event_id, view }) => {
|
|
41
46
|
const data = await client.request('GET', `/events/${encodeURIComponent(event_id)}/description/`);
|
|
42
|
-
return
|
|
47
|
+
return viewResponse(view, data);
|
|
43
48
|
});
|
|
44
49
|
server.registerTool('eb_reference', {
|
|
45
50
|
description: 'List Eventbrite reference data: categories (103=Music, 101=Business, 110=Food & Drink, …), subcategories, formats, timezones, countries or regions. Category/subcategory/format ids feed eb_search_events filters.',
|
|
46
51
|
annotations: { readOnlyHint: true },
|
|
47
52
|
inputSchema: {
|
|
53
|
+
view: viewArg(),
|
|
48
54
|
kind: z
|
|
49
55
|
.enum(['categories', 'subcategories', 'formats', 'timezones', 'countries', 'regions'])
|
|
50
56
|
.describe('Which list to fetch'),
|
|
51
57
|
},
|
|
52
|
-
}, async ({ kind }) => {
|
|
58
|
+
}, async ({ kind, view }) => {
|
|
53
59
|
// timezones/countries/regions hang off /system/; the bare /countries/ and
|
|
54
60
|
// /regions/ paths 404 (verified live 2026-07-30). The taxonomy lists
|
|
55
61
|
// (categories, subcategories, formats) sit at the root.
|
|
56
62
|
const path = SYSTEM_LISTS.has(kind) ? `/system/${kind}/` : `/${kind}/`;
|
|
57
63
|
const data = await client.request('GET', path);
|
|
58
|
-
return
|
|
64
|
+
return viewResponse(view, data);
|
|
59
65
|
});
|
|
60
66
|
server.registerTool('eb_event_attendees', {
|
|
61
67
|
description: "List a single event's attendees (organizer-side; requires access to that event). Use changed_since to poll incrementally instead of re-reading the whole list.",
|
|
@@ -68,8 +74,9 @@ export function registerEventTools(server, deps) {
|
|
|
68
74
|
.optional()
|
|
69
75
|
.describe('ISO 8601 UTC timestamp — only attendees changed since then'),
|
|
70
76
|
continuation: schemaContinuation,
|
|
77
|
+
view: viewArg(),
|
|
71
78
|
},
|
|
72
|
-
}, async ({ event_id, status, changed_since, continuation }) => {
|
|
79
|
+
}, async ({ event_id, status, changed_since, continuation, view }) => {
|
|
73
80
|
const params = new URLSearchParams();
|
|
74
81
|
if (status)
|
|
75
82
|
params.set('status', status);
|
|
@@ -77,7 +84,7 @@ export function registerEventTools(server, deps) {
|
|
|
77
84
|
params.set('changed_since', changed_since);
|
|
78
85
|
if (continuation)
|
|
79
86
|
params.set('continuation', continuation);
|
|
80
|
-
return
|
|
87
|
+
return viewResponse(view, await client.request('GET', `/events/${enc(event_id)}/attendees/${qs(params)}`));
|
|
81
88
|
});
|
|
82
89
|
server.registerTool('eb_event_attendee', {
|
|
83
90
|
description: "Get one attendee of an event by id (barcode, profile answers, check-in state).",
|
|
@@ -85,8 +92,9 @@ export function registerEventTools(server, deps) {
|
|
|
85
92
|
inputSchema: {
|
|
86
93
|
event_id: z.string().describe('Numeric event id'),
|
|
87
94
|
attendee_id: z.string().describe('Numeric attendee id'),
|
|
95
|
+
view: viewArg(),
|
|
88
96
|
},
|
|
89
|
-
}, async ({ event_id, attendee_id }) =>
|
|
97
|
+
}, async ({ event_id, attendee_id, view }) => viewResponse(view, await client.request('GET', `/events/${enc(event_id)}/attendees/${enc(attendee_id)}/`)));
|
|
90
98
|
server.registerTool('eb_event_orders', {
|
|
91
99
|
description: "List a single event's orders (organizer-side; requires access to that event).",
|
|
92
100
|
annotations: { readOnlyHint: true },
|
|
@@ -98,8 +106,9 @@ export function registerEventTools(server, deps) {
|
|
|
98
106
|
.optional()
|
|
99
107
|
.describe('ISO 8601 UTC timestamp — only orders changed since then'),
|
|
100
108
|
continuation: schemaContinuation,
|
|
109
|
+
view: viewArg(),
|
|
101
110
|
},
|
|
102
|
-
}, async ({ event_id, status, changed_since, continuation }) => {
|
|
111
|
+
}, async ({ event_id, status, changed_since, continuation, view }) => {
|
|
103
112
|
const params = new URLSearchParams();
|
|
104
113
|
if (status)
|
|
105
114
|
params.set('status', status);
|
|
@@ -107,7 +116,7 @@ export function registerEventTools(server, deps) {
|
|
|
107
116
|
params.set('changed_since', changed_since);
|
|
108
117
|
if (continuation)
|
|
109
118
|
params.set('continuation', continuation);
|
|
110
|
-
return
|
|
119
|
+
return viewResponse(view, await client.request('GET', `/events/${enc(event_id)}/orders/${qs(params)}`));
|
|
111
120
|
});
|
|
112
121
|
server.registerTool('eb_ticket_class', {
|
|
113
122
|
description: 'Get one ticket class of an event by id. Use eb_ticket_classes to list them first.',
|
|
@@ -115,8 +124,9 @@ export function registerEventTools(server, deps) {
|
|
|
115
124
|
inputSchema: {
|
|
116
125
|
event_id: z.string().describe('Numeric event id'),
|
|
117
126
|
ticket_class_id: z.string().describe('Numeric ticket class id'),
|
|
127
|
+
view: viewArg(),
|
|
118
128
|
},
|
|
119
|
-
}, async ({ event_id, ticket_class_id }) =>
|
|
129
|
+
}, async ({ event_id, ticket_class_id, view }) => viewResponse(view, await client.request('GET', `/events/${enc(event_id)}/ticket_classes/${enc(ticket_class_id)}/`)));
|
|
120
130
|
server.registerTool('eb_event_questions', {
|
|
121
131
|
description: "List the registration questions an event asks its buyers. Set canned=true for Eventbrite's standard question bank instead of the event's custom ones.",
|
|
122
132
|
annotations: { readOnlyHint: true },
|
|
@@ -126,6 +136,7 @@ export function registerEventTools(server, deps) {
|
|
|
126
136
|
.boolean()
|
|
127
137
|
.optional()
|
|
128
138
|
.describe("Fetch the standard question bank instead of the event's custom questions"),
|
|
139
|
+
view: viewArg(),
|
|
129
140
|
},
|
|
130
|
-
}, async ({ event_id, canned }) =>
|
|
141
|
+
}, async ({ event_id, canned, view }) => viewResponse(view, await client.request('GET', `/events/${enc(event_id)}/${canned ? 'canned_questions' : 'questions'}/`)));
|
|
131
142
|
}
|
package/dist/tools/lookup.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { viewArg, viewResponse } from '../view.js';
|
|
3
3
|
import { enc, qs, schemaContinuation, schemaEventStatus } from './params.js';
|
|
4
4
|
/**
|
|
5
5
|
* Single-object lookups on the documented API: resolve an id that appears
|
|
@@ -20,18 +20,21 @@ export function registerLookupTools(server, deps) {
|
|
|
20
20
|
.string()
|
|
21
21
|
.optional()
|
|
22
22
|
.describe("Comma-separated expansions, e.g. 'attendees,event'"),
|
|
23
|
+
view: viewArg(),
|
|
23
24
|
},
|
|
24
|
-
}, async ({ order_id, expand }) => {
|
|
25
|
+
}, async ({ order_id, expand, view }) => {
|
|
25
26
|
const params = new URLSearchParams();
|
|
26
27
|
if (expand)
|
|
27
28
|
params.set('expand', expand);
|
|
28
|
-
return
|
|
29
|
+
return viewResponse(view, await client.request('GET', `/orders/${enc(order_id)}/${qs(params)}`));
|
|
29
30
|
});
|
|
30
31
|
server.registerTool('eb_venue', {
|
|
31
32
|
description: 'Get a venue by id (name, address, geo). Venue ids appear on expanded events and in eb_org_venues.',
|
|
32
33
|
annotations: { readOnlyHint: true },
|
|
33
|
-
inputSchema: { venue_id: z.string().describe('Numeric venue id')
|
|
34
|
-
|
|
34
|
+
inputSchema: { venue_id: z.string().describe('Numeric venue id'),
|
|
35
|
+
view: viewArg(),
|
|
36
|
+
},
|
|
37
|
+
}, async ({ venue_id, view }) => viewResponse(view, await client.request('GET', `/venues/${enc(venue_id)}/`)));
|
|
35
38
|
server.registerTool('eb_venue_events', {
|
|
36
39
|
description: 'List the events held at a venue.',
|
|
37
40
|
annotations: { readOnlyHint: true },
|
|
@@ -39,20 +42,23 @@ export function registerLookupTools(server, deps) {
|
|
|
39
42
|
venue_id: z.string().describe('Numeric venue id'),
|
|
40
43
|
status: schemaEventStatus,
|
|
41
44
|
continuation: schemaContinuation,
|
|
45
|
+
view: viewArg(),
|
|
42
46
|
},
|
|
43
|
-
}, async ({ venue_id, status, continuation }) => {
|
|
47
|
+
}, async ({ venue_id, status, continuation, view }) => {
|
|
44
48
|
const params = new URLSearchParams();
|
|
45
49
|
if (status)
|
|
46
50
|
params.set('status', status);
|
|
47
51
|
if (continuation)
|
|
48
52
|
params.set('continuation', continuation);
|
|
49
|
-
return
|
|
53
|
+
return viewResponse(view, await client.request('GET', `/venues/${enc(venue_id)}/events/${qs(params)}`));
|
|
50
54
|
});
|
|
51
55
|
server.registerTool('eb_organizer', {
|
|
52
56
|
description: "Get an organizer's public profile by id (name, description, logo, social links).",
|
|
53
57
|
annotations: { readOnlyHint: true },
|
|
54
|
-
inputSchema: { organizer_id: z.string().describe('Numeric organizer id')
|
|
55
|
-
|
|
58
|
+
inputSchema: { organizer_id: z.string().describe('Numeric organizer id'),
|
|
59
|
+
view: viewArg(),
|
|
60
|
+
},
|
|
61
|
+
}, async ({ organizer_id, view }) => viewResponse(view, await client.request('GET', `/organizers/${enc(organizer_id)}/`)));
|
|
56
62
|
server.registerTool('eb_organizer_events', {
|
|
57
63
|
description: "List an organizer's events — the public way to see everything one organizer is running.",
|
|
58
64
|
annotations: { readOnlyHint: true },
|
|
@@ -61,8 +67,9 @@ export function registerLookupTools(server, deps) {
|
|
|
61
67
|
status: schemaEventStatus,
|
|
62
68
|
order_by: z.enum(['start_asc', 'start_desc', 'created_asc', 'created_desc']).optional(),
|
|
63
69
|
continuation: schemaContinuation,
|
|
70
|
+
view: viewArg(),
|
|
64
71
|
},
|
|
65
|
-
}, async ({ organizer_id, status, order_by, continuation }) => {
|
|
72
|
+
}, async ({ organizer_id, status, order_by, continuation, view }) => {
|
|
66
73
|
const params = new URLSearchParams();
|
|
67
74
|
if (status)
|
|
68
75
|
params.set('status', status);
|
|
@@ -70,7 +77,7 @@ export function registerLookupTools(server, deps) {
|
|
|
70
77
|
params.set('order_by', order_by);
|
|
71
78
|
if (continuation)
|
|
72
79
|
params.set('continuation', continuation);
|
|
73
|
-
return
|
|
80
|
+
return viewResponse(view, await client.request('GET', `/organizers/${enc(organizer_id)}/events/${qs(params)}`));
|
|
74
81
|
});
|
|
75
82
|
server.registerTool('eb_series_events', {
|
|
76
83
|
description: 'List the occurrences of a recurring event series. Search results and events carry a series_id when they belong to one.',
|
|
@@ -79,18 +86,21 @@ export function registerLookupTools(server, deps) {
|
|
|
79
86
|
series_id: z.string().describe('Numeric series id (from an event/search result)'),
|
|
80
87
|
status: schemaEventStatus,
|
|
81
88
|
continuation: schemaContinuation,
|
|
89
|
+
view: viewArg(),
|
|
82
90
|
},
|
|
83
|
-
}, async ({ series_id, status, continuation }) => {
|
|
91
|
+
}, async ({ series_id, status, continuation, view }) => {
|
|
84
92
|
const params = new URLSearchParams();
|
|
85
93
|
if (status)
|
|
86
94
|
params.set('status', status);
|
|
87
95
|
if (continuation)
|
|
88
96
|
params.set('continuation', continuation);
|
|
89
|
-
return
|
|
97
|
+
return viewResponse(view, await client.request('GET', `/series/${enc(series_id)}/events/${qs(params)}`));
|
|
90
98
|
});
|
|
91
99
|
server.registerTool('eb_user', {
|
|
92
100
|
description: "Get a public user profile by id. Use eb_me for the authenticated user (that call also returns private fields like emails).",
|
|
93
101
|
annotations: { readOnlyHint: true },
|
|
94
|
-
inputSchema: { user_id: z.string().describe('Numeric user id')
|
|
95
|
-
|
|
102
|
+
inputSchema: { user_id: z.string().describe('Numeric user id'),
|
|
103
|
+
view: viewArg(),
|
|
104
|
+
},
|
|
105
|
+
}, async ({ user_id, view }) => viewResponse(view, await client.request('GET', `/users/${enc(user_id)}/`)));
|
|
96
106
|
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.3.0'; // x-release-please-version
|
package/dist/view.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { minifiedResult, resolveView, stripMediaUrls, viewParam } from '@chrischall/mcp-utils';
|
|
2
|
+
/**
|
|
3
|
+
* The rungs this server honours (`@chrischall/mcp-utils`' `view` vocabulary;
|
|
4
|
+
* `chrischall/workflows` `docs/fleet-conventions.md`, "Response shape").
|
|
5
|
+
*
|
|
6
|
+
* **This server is in two tiers at once, and the note per tool says which.**
|
|
7
|
+
*
|
|
8
|
+
* Most read tools hand back Eventbrite's documented-API payload close to
|
|
9
|
+
* verbatim, and nothing here has a verified record of which of those fields
|
|
10
|
+
* matter. For those, compact does the one projection that needs no such
|
|
11
|
+
* knowledge: it strips image and avatar URLs. That is SUBTRACTIVE, so it
|
|
12
|
+
* cannot lose a field nobody knew about — the failure an invented field list
|
|
13
|
+
* would risk, where a record comes back with holes in it and reads like a
|
|
14
|
+
* verified answer.
|
|
15
|
+
*
|
|
16
|
+
* `eb_search_events` is the exception and always was: `toCompactEvent`
|
|
17
|
+
* (`src/discovery.ts`) is a real, hand-written field projection over the
|
|
18
|
+
* consumer search payload. The rollout that added this file described the
|
|
19
|
+
* whole server as un-grounded and did not notice it — the same mistake made in
|
|
20
|
+
* `viator-mcp` (#69, corrected in #72). A hand-written projection is grounded
|
|
21
|
+
* knowledge and is NOT then media-stripped: an un-grounded rule must never
|
|
22
|
+
* overrule a grounded one.
|
|
23
|
+
*
|
|
24
|
+
* So `viewArg` takes a per-tool note. A copy-pasted note is worse than a
|
|
25
|
+
* generic one — it tells a caller to expect a field that was never going to be
|
|
26
|
+
* there.
|
|
27
|
+
*/
|
|
28
|
+
export const EB_VIEWS = ['compact', 'full'];
|
|
29
|
+
/** The note for the media-strip tier: what compact does when nothing here knows the shape. */
|
|
30
|
+
const MEDIA_STRIP_NOTE = 'compact strips image/avatar URLs from the response; "full" returns Eventbrite\'s payload untouched. ' +
|
|
31
|
+
'No field projection on this tool: this server has no verified record of which Eventbrite fields matter here, ' +
|
|
32
|
+
'and inventing one would risk dropping a field a caller needs.';
|
|
33
|
+
/**
|
|
34
|
+
* The `view` parameter, taken by EVERY read tool in this server.
|
|
35
|
+
*
|
|
36
|
+
* Every one, deliberately — 12 of the 26 took it and 14 did not, which makes a
|
|
37
|
+
* caller memorise a map of which tools accept the vocabulary. An inconsistent
|
|
38
|
+
* surface is its own defect, and worse than a rung that occasionally has
|
|
39
|
+
* nothing to strip.
|
|
40
|
+
*/
|
|
41
|
+
export const viewArg = (note = MEDIA_STRIP_NOTE) => viewParam(EB_VIEWS, { note });
|
|
42
|
+
/**
|
|
43
|
+
* Answer in the requested rung.
|
|
44
|
+
*
|
|
45
|
+
* Only ever called from a READ tool. A write's response is a receipt — an id,
|
|
46
|
+
* a status — with nothing to strip and everything to keep.
|
|
47
|
+
*
|
|
48
|
+
* Callers that have ALREADY projected pass `'full'`: the value is theirs to
|
|
49
|
+
* hand back whole, and media-stripping a hand-written projection on the way
|
|
50
|
+
* out is exactly the un-grounded-overrules-grounded error.
|
|
51
|
+
*/
|
|
52
|
+
export function viewResponse(view, data) {
|
|
53
|
+
const rung = resolveView(view, EB_VIEWS);
|
|
54
|
+
return minifiedResult(rung === 'compact' ? stripMediaUrls(data) : data);
|
|
55
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrischall/eventbrite-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/eventbrite-mcp",
|
|
5
5
|
"description": "Eventbrite MCP server for Claude — developed and maintained by AI (Claude Code)",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@chrischall/mcp-utils": "^0.
|
|
48
|
+
"@chrischall/mcp-utils": "^0.23.2",
|
|
49
49
|
"@fetchproxy/server": "^2.2.0",
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
51
51
|
"dotenv": "^17.4.0",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/eventbrite-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.3.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/eventbrite-mcp",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.3.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|