luma-events-mcp 0.1.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/LICENSE +21 -0
- package/README.md +257 -0
- package/dist/index.js +54 -0
- package/dist/luma-client.js +68 -0
- package/dist/tools/add-guests.js +48 -0
- package/dist/tools/create-coupon.js +41 -0
- package/dist/tools/create-event.js +74 -0
- package/dist/tools/create-ticket-type.js +45 -0
- package/dist/tools/get-calendar.js +17 -0
- package/dist/tools/get-event.js +20 -0
- package/dist/tools/get-guest.js +27 -0
- package/dist/tools/list-coupons.js +34 -0
- package/dist/tools/list-events.js +45 -0
- package/dist/tools/list-guests.js +55 -0
- package/dist/tools/list-ticket-types.js +20 -0
- package/dist/tools/send-invites.js +33 -0
- package/dist/tools/update-event.js +68 -0
- package/dist/tools/update-guest-status.js +37 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dominik Grusemann
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# luma-events-mcp
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
A Luma Calendar MCP server exposing event, guest, and ticket operations as tools for AI assistants.
|
|
6
|
+
|
|
7
|
+
[](https://github.com/bettervibe-org/luma-events-mcp/actions/workflows/release.yml)
|
|
8
|
+
[](https://www.npmjs.com/package/luma-events-mcp)
|
|
9
|
+
[](https://choosealicense.com/licenses/mit/)
|
|
10
|
+
[](https://modelcontextprotocol.io)
|
|
11
|
+
[](https://github.com/semantic-release/semantic-release)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- Get calendar info associated with your API key
|
|
18
|
+
- List, create, and update events
|
|
19
|
+
- List guests, get guest details, add guests, update guest status, send invites
|
|
20
|
+
- List and create ticket types
|
|
21
|
+
- List and create coupons
|
|
22
|
+
- Dual pagination mode: auto-paginate all results or manual cursor-based paging
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"luma": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["luma-events-mcp"],
|
|
32
|
+
"env": {
|
|
33
|
+
"LUMA_API_KEY": "<your Luma API key>"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Get your API key from your Luma dashboard (requires Luma Plus).
|
|
41
|
+
|
|
42
|
+
## Development
|
|
43
|
+
|
|
44
|
+
### Quick Start
|
|
45
|
+
|
|
46
|
+
Run the MCP server in development mode with auto-reload:
|
|
47
|
+
```bash
|
|
48
|
+
npm run dev
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This will run the TypeScript code directly with watch mode and automatically load environment variables from `.env`.
|
|
52
|
+
|
|
53
|
+
### Manual Build
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm run build
|
|
57
|
+
node dist/index.js
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Available Tools
|
|
61
|
+
|
|
62
|
+
<!-- TOOLS:START - generated by scripts/gen-tool-docs.ts -->
|
|
63
|
+
### get-calendar
|
|
64
|
+
|
|
65
|
+
Get the calendar associated with the current API key
|
|
66
|
+
|
|
67
|
+
Parameters: none
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
- Calendar object with api_id, name, and geo fields
|
|
71
|
+
|
|
72
|
+
### list-events
|
|
73
|
+
|
|
74
|
+
List events for the calendar. Optionally filter by date range. Without pagination params, returns all events; with them, returns one page.
|
|
75
|
+
|
|
76
|
+
Parameters:
|
|
77
|
+
- `after`: string (optional) — Only events starting after this ISO 8601 datetime
|
|
78
|
+
- `before`: string (optional) — Only events starting before this ISO 8601 datetime
|
|
79
|
+
- `pagination_limit`: number (optional) — Page size (max 50)
|
|
80
|
+
- `pagination_cursor`: string (optional) — Cursor from a previous page
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
- Array of event objects, or paginated result with next_cursor
|
|
84
|
+
|
|
85
|
+
### get-event
|
|
86
|
+
|
|
87
|
+
Get admin details of a specific event by its ID
|
|
88
|
+
|
|
89
|
+
Parameters:
|
|
90
|
+
- `event_id`: string — Event ID (e.g. evt-...)
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
- Full event object including name, dates, location, description, and ticket info
|
|
94
|
+
|
|
95
|
+
### list-guests
|
|
96
|
+
|
|
97
|
+
List guests for an event. Without pagination params, returns all guests; with them, returns one page.
|
|
98
|
+
|
|
99
|
+
Parameters:
|
|
100
|
+
- `event_id`: string — Event ID (e.g. evt-...)
|
|
101
|
+
- `approval_status`: enum (`approved` | `session` | `pending_approval` | `invited` | `declined` | `waitlist`) (optional) — Filter by approval status
|
|
102
|
+
- `sort_column`: enum (`name` | `email` | `created_at` | `registered_at` | `checked_in_at`) (optional) — Sort column
|
|
103
|
+
- `pagination_limit`: number (optional) — Page size
|
|
104
|
+
- `pagination_cursor`: string (optional) — Cursor from a previous page
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
- Array of guest objects, or paginated result with next_cursor
|
|
108
|
+
|
|
109
|
+
### get-guest
|
|
110
|
+
|
|
111
|
+
Get detailed info for a single event guest. The id field accepts a guest ID (gst-...), guest key (g-...), ticket key, or email address.
|
|
112
|
+
|
|
113
|
+
Parameters:
|
|
114
|
+
- `event_id`: string — Event ID (e.g. evt-...)
|
|
115
|
+
- `id`: string — Guest identifier: guest ID (gst-...), guest key (g-...), or email
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
- Full guest object with approval status, ticket info, event_ticket_orders, and profile
|
|
119
|
+
|
|
120
|
+
### list-ticket-types
|
|
121
|
+
|
|
122
|
+
List all ticket types for an event
|
|
123
|
+
|
|
124
|
+
Parameters:
|
|
125
|
+
- `event_id`: string — Event ID (e.g. evt-...)
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
- Array of ticket type objects with name, price, and availability info
|
|
129
|
+
|
|
130
|
+
### create-event
|
|
131
|
+
|
|
132
|
+
Create a new event on the calendar
|
|
133
|
+
|
|
134
|
+
Parameters:
|
|
135
|
+
- `name`: string — Event title
|
|
136
|
+
- `start_at`: string — Start datetime (ISO 8601)
|
|
137
|
+
- `timezone`: string — IANA timezone (e.g. America/New_York)
|
|
138
|
+
- `end_at`: string (optional) — End datetime (ISO 8601)
|
|
139
|
+
- `description_md`: string (optional) — Event description in markdown
|
|
140
|
+
- `meeting_url`: string (optional) — Virtual event URL
|
|
141
|
+
- `geo_address_json`: value (optional) — Address object with city, place, etc.
|
|
142
|
+
- `geo_latitude`: number (optional) — Latitude
|
|
143
|
+
- `geo_longitude`: number (optional) — Longitude
|
|
144
|
+
- `cover_url`: string (optional) — Cover image URL (must be Luma CDN)
|
|
145
|
+
- `visibility`: enum (`public` | `members-only` | `private`) (optional) — Event visibility
|
|
146
|
+
- `max_capacity`: number (optional) — Maximum attendee capacity
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
- The created event object
|
|
150
|
+
|
|
151
|
+
### update-event
|
|
152
|
+
|
|
153
|
+
Update an existing event. Only provided fields are changed.
|
|
154
|
+
|
|
155
|
+
Parameters:
|
|
156
|
+
- `event_id`: string — Event ID (e.g. evt-...)
|
|
157
|
+
- `suppress_notifications`: boolean (optional) — Prevent guest notifications for name/time/location changes
|
|
158
|
+
- `name`: string (optional) — Event title
|
|
159
|
+
- `start_at`: string (optional) — Start datetime (ISO 8601)
|
|
160
|
+
- `end_at`: string (optional) — End datetime (ISO 8601)
|
|
161
|
+
- `timezone`: string (optional) — IANA timezone
|
|
162
|
+
- `description_md`: string (optional) — Event description in markdown
|
|
163
|
+
- `meeting_url`: string (optional) — Virtual event URL
|
|
164
|
+
- `cover_url`: string (optional) — Cover image URL
|
|
165
|
+
- `visibility`: enum (`public` | `members-only` | `private`) (optional) — Event visibility
|
|
166
|
+
- `max_capacity`: number (optional) — Maximum attendee capacity
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
- The updated event object
|
|
170
|
+
|
|
171
|
+
### add-guests
|
|
172
|
+
|
|
173
|
+
Add guests to an event. By default guests are approved and receive one default ticket.
|
|
174
|
+
|
|
175
|
+
Parameters:
|
|
176
|
+
- `event_id`: string — Event ID (e.g. evt-...)
|
|
177
|
+
- `guests`: array of object — Array of guest objects with email
|
|
178
|
+
- `approval_status`: enum (`approved` | `pending_approval` | `waitlist`) (optional) — Approval status for added guests (default: approved)
|
|
179
|
+
- `send_email`: boolean (optional) — Send notification email (default: true)
|
|
180
|
+
- `ticket`: value (optional) — Ticket type to assign to all guests
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
- Confirmation of added guests
|
|
184
|
+
|
|
185
|
+
### update-guest-status
|
|
186
|
+
|
|
187
|
+
Update a guest's approval status (approve or decline)
|
|
188
|
+
|
|
189
|
+
Parameters:
|
|
190
|
+
- `event_id`: string — Event ID (e.g. evt-...)
|
|
191
|
+
- `guest`: value — Guest identifier object (e.g. { email: '...' } or { id: 'gst-...' })
|
|
192
|
+
- `status`: enum (`approved` | `declined`) — New status for the guest
|
|
193
|
+
- `should_refund`: boolean (optional) — Refund the guest if declining a paid ticket
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
- Confirmation of status update
|
|
197
|
+
|
|
198
|
+
### send-invites
|
|
199
|
+
|
|
200
|
+
Send invites to guests for an event. Sends email and SMS if phone is linked.
|
|
201
|
+
|
|
202
|
+
Parameters:
|
|
203
|
+
- `event_id`: string — Event ID (e.g. evt-...)
|
|
204
|
+
- `guests`: array of object — Array of guest objects with email and optional message
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
- Confirmation of sent invites
|
|
208
|
+
|
|
209
|
+
### create-ticket-type
|
|
210
|
+
|
|
211
|
+
Create a new ticket type for an event
|
|
212
|
+
|
|
213
|
+
Parameters:
|
|
214
|
+
- `event_id`: string — Event ID (e.g. evt-...)
|
|
215
|
+
- `name`: string — Ticket type name
|
|
216
|
+
- `type`: enum (`free` | `paid`) — Ticket pricing type
|
|
217
|
+
- `require_approval`: boolean (optional) — Require host approval
|
|
218
|
+
- `is_hidden`: boolean (optional) — Hide from public listing
|
|
219
|
+
- `description`: string (optional) — Ticket type description
|
|
220
|
+
- `max_capacity`: number (optional) — Maximum tickets available
|
|
221
|
+
- `cents`: number (optional) — Price in cents (for paid tickets)
|
|
222
|
+
- `currency`: string (optional) — Currency code (e.g. USD, EUR)
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
- The created ticket type object
|
|
226
|
+
|
|
227
|
+
### list-coupons
|
|
228
|
+
|
|
229
|
+
List all coupons for an event. Without pagination params, returns all coupons; with them, returns one page.
|
|
230
|
+
|
|
231
|
+
Parameters:
|
|
232
|
+
- `event_id`: string — Event ID (e.g. evt-...)
|
|
233
|
+
- `pagination_limit`: number (optional) — Page size
|
|
234
|
+
- `pagination_cursor`: string (optional) — Cursor from a previous page
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
- Array of coupon objects, or paginated result with next_cursor
|
|
238
|
+
|
|
239
|
+
### create-coupon
|
|
240
|
+
|
|
241
|
+
Create a coupon for an event. Coupon terms cannot be edited after creation.
|
|
242
|
+
|
|
243
|
+
Parameters:
|
|
244
|
+
- `event_id`: string — Event ID (e.g. evt-...)
|
|
245
|
+
- `code`: string — Coupon code (1-20 chars, case-insensitive)
|
|
246
|
+
- `discount`: value — Discount object (e.g. { type: 'percent', percent_off: 20 })
|
|
247
|
+
- `remaining_count`: number (optional) — Number of uses (0-1000000; use 1000000 for unlimited)
|
|
248
|
+
- `valid_start_at`: string (optional) — Validity start (ISO 8601)
|
|
249
|
+
- `valid_end_at`: string (optional) — Validity end (ISO 8601)
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
- The created coupon object
|
|
253
|
+
<!-- TOOLS:END -->
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { LumaClient } from "./luma-client.js";
|
|
5
|
+
import { registerAddGuests } from "./tools/add-guests.js";
|
|
6
|
+
import { registerCreateCoupon } from "./tools/create-coupon.js";
|
|
7
|
+
import { registerCreateEvent } from "./tools/create-event.js";
|
|
8
|
+
import { registerCreateTicketType } from "./tools/create-ticket-type.js";
|
|
9
|
+
import { registerGetCalendar } from "./tools/get-calendar.js";
|
|
10
|
+
import { registerGetEvent } from "./tools/get-event.js";
|
|
11
|
+
import { registerGetGuest } from "./tools/get-guest.js";
|
|
12
|
+
import { registerListCoupons } from "./tools/list-coupons.js";
|
|
13
|
+
import { registerListEvents } from "./tools/list-events.js";
|
|
14
|
+
import { registerListGuests } from "./tools/list-guests.js";
|
|
15
|
+
import { registerListTicketTypes } from "./tools/list-ticket-types.js";
|
|
16
|
+
import { registerSendInvites } from "./tools/send-invites.js";
|
|
17
|
+
import { registerUpdateEvent } from "./tools/update-event.js";
|
|
18
|
+
import { registerUpdateGuestStatus } from "./tools/update-guest-status.js";
|
|
19
|
+
const server = new McpServer({
|
|
20
|
+
name: "luma-events-mcp",
|
|
21
|
+
version: "0.1.0",
|
|
22
|
+
});
|
|
23
|
+
async function main() {
|
|
24
|
+
const apiKey = process.env.LUMA_API_KEY;
|
|
25
|
+
if (!apiKey) {
|
|
26
|
+
console.error("Missing LUMA_API_KEY environment variable");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const client = new LumaClient(apiKey);
|
|
30
|
+
try {
|
|
31
|
+
await client.testConnection();
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error("Failed to connect to Luma API:", error);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
registerGetCalendar(client, server);
|
|
38
|
+
registerListEvents(client, server);
|
|
39
|
+
registerGetEvent(client, server);
|
|
40
|
+
registerListGuests(client, server);
|
|
41
|
+
registerGetGuest(client, server);
|
|
42
|
+
registerListTicketTypes(client, server);
|
|
43
|
+
registerCreateEvent(client, server);
|
|
44
|
+
registerUpdateEvent(client, server);
|
|
45
|
+
registerAddGuests(client, server);
|
|
46
|
+
registerUpdateGuestStatus(client, server);
|
|
47
|
+
registerSendInvites(client, server);
|
|
48
|
+
registerCreateTicketType(client, server);
|
|
49
|
+
registerListCoupons(client, server);
|
|
50
|
+
registerCreateCoupon(client, server);
|
|
51
|
+
const transport = new StdioServerTransport();
|
|
52
|
+
await server.connect(transport);
|
|
53
|
+
}
|
|
54
|
+
main();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const DEFAULT_BASE_URL = "https://public-api.luma.com";
|
|
2
|
+
export class LumaApiError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
body;
|
|
5
|
+
constructor(status, body) {
|
|
6
|
+
super(`Luma API ${status}: ${body}`);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.body = body;
|
|
9
|
+
this.name = "LumaApiError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class LumaClient {
|
|
13
|
+
apiKey;
|
|
14
|
+
baseUrl;
|
|
15
|
+
constructor(apiKey, baseUrl) {
|
|
16
|
+
this.apiKey = apiKey;
|
|
17
|
+
this.baseUrl = baseUrl ?? process.env.LUMA_BASE_URL ?? DEFAULT_BASE_URL;
|
|
18
|
+
}
|
|
19
|
+
async request(method, path, body) {
|
|
20
|
+
const url = `${this.baseUrl}${path}`;
|
|
21
|
+
const headers = {
|
|
22
|
+
"x-luma-api-key": this.apiKey,
|
|
23
|
+
"content-type": "application/json",
|
|
24
|
+
};
|
|
25
|
+
const res = await fetch(url, {
|
|
26
|
+
method,
|
|
27
|
+
headers,
|
|
28
|
+
...(body !== undefined && { body: JSON.stringify(body) }),
|
|
29
|
+
});
|
|
30
|
+
const text = await res.text();
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
throw new LumaApiError(res.status, text);
|
|
33
|
+
}
|
|
34
|
+
return text ? JSON.parse(text) : undefined;
|
|
35
|
+
}
|
|
36
|
+
async paginate(path, params) {
|
|
37
|
+
const all = [];
|
|
38
|
+
let cursor;
|
|
39
|
+
do {
|
|
40
|
+
const searchParams = new URLSearchParams(params);
|
|
41
|
+
if (cursor)
|
|
42
|
+
searchParams.set("pagination_cursor", cursor);
|
|
43
|
+
const query = searchParams.toString();
|
|
44
|
+
const fullPath = query ? `${path}?${query}` : path;
|
|
45
|
+
const page = await this.request("GET", fullPath);
|
|
46
|
+
all.push(...page.entries);
|
|
47
|
+
cursor = page.has_more ? page.next_cursor : undefined;
|
|
48
|
+
} while (cursor);
|
|
49
|
+
return all;
|
|
50
|
+
}
|
|
51
|
+
async paginateSingle(path, params, paginationLimit, paginationCursor) {
|
|
52
|
+
const searchParams = new URLSearchParams(params);
|
|
53
|
+
if (paginationLimit)
|
|
54
|
+
searchParams.set("pagination_limit", String(paginationLimit));
|
|
55
|
+
if (paginationCursor)
|
|
56
|
+
searchParams.set("pagination_cursor", paginationCursor);
|
|
57
|
+
const query = searchParams.toString();
|
|
58
|
+
const fullPath = query ? `${path}?${query}` : path;
|
|
59
|
+
const page = await this.request("GET", fullPath);
|
|
60
|
+
return {
|
|
61
|
+
entries: page.entries,
|
|
62
|
+
next_cursor: page.has_more ? page.next_cursor : undefined,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async testConnection() {
|
|
66
|
+
await this.request("GET", "/v1/calendar/get");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const guestSchema = z.object({
|
|
3
|
+
email: z.string().describe("Guest email address"),
|
|
4
|
+
name: z.string().optional().describe("Guest display name"),
|
|
5
|
+
phone_number: z.string().optional().describe("Phone number"),
|
|
6
|
+
});
|
|
7
|
+
export const addGuestsDefinition = {
|
|
8
|
+
name: "add-guests",
|
|
9
|
+
description: "Add guests to an event. By default guests are approved and receive one default ticket.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
event_id: z.string().describe("Event ID (e.g. evt-...)"),
|
|
12
|
+
guests: z.array(guestSchema).describe("Array of guest objects with email"),
|
|
13
|
+
approval_status: z
|
|
14
|
+
.enum(["approved", "pending_approval", "waitlist"])
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Approval status for added guests (default: approved)"),
|
|
17
|
+
send_email: z
|
|
18
|
+
.boolean()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Send notification email (default: true)"),
|
|
21
|
+
ticket: z
|
|
22
|
+
.record(z.string(), z.unknown())
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Ticket type to assign to all guests"),
|
|
25
|
+
},
|
|
26
|
+
returns: "Confirmation of added guests",
|
|
27
|
+
};
|
|
28
|
+
export function registerAddGuests(client, server) {
|
|
29
|
+
server.registerTool(addGuestsDefinition.name, {
|
|
30
|
+
description: addGuestsDefinition.description,
|
|
31
|
+
inputSchema: addGuestsDefinition.inputSchema,
|
|
32
|
+
}, async (args) => {
|
|
33
|
+
const body = {
|
|
34
|
+
event_id: args.event_id,
|
|
35
|
+
guests: args.guests,
|
|
36
|
+
};
|
|
37
|
+
if (args.approval_status !== undefined)
|
|
38
|
+
body.approval_status = args.approval_status;
|
|
39
|
+
if (args.send_email !== undefined)
|
|
40
|
+
body.send_email = args.send_email;
|
|
41
|
+
if (args.ticket !== undefined)
|
|
42
|
+
body.ticket = args.ticket;
|
|
43
|
+
const data = await client.request("POST", "/v1/event/add-guests", body);
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const createCouponDefinition = {
|
|
3
|
+
name: "create-coupon",
|
|
4
|
+
description: "Create a coupon for an event. Coupon terms cannot be edited after creation.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
event_id: z.string().describe("Event ID (e.g. evt-...)"),
|
|
7
|
+
code: z.string().describe("Coupon code (1-20 chars, case-insensitive)"),
|
|
8
|
+
discount: z
|
|
9
|
+
.record(z.string(), z.unknown())
|
|
10
|
+
.describe("Discount object (e.g. { type: 'percent', percent_off: 20 })"),
|
|
11
|
+
remaining_count: z
|
|
12
|
+
.number()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Number of uses (0-1000000; use 1000000 for unlimited)"),
|
|
15
|
+
valid_start_at: z.string().optional().describe("Validity start (ISO 8601)"),
|
|
16
|
+
valid_end_at: z.string().optional().describe("Validity end (ISO 8601)"),
|
|
17
|
+
},
|
|
18
|
+
returns: "The created coupon object",
|
|
19
|
+
};
|
|
20
|
+
export function registerCreateCoupon(client, server) {
|
|
21
|
+
server.registerTool(createCouponDefinition.name, {
|
|
22
|
+
description: createCouponDefinition.description,
|
|
23
|
+
inputSchema: createCouponDefinition.inputSchema,
|
|
24
|
+
}, async (args) => {
|
|
25
|
+
const body = {
|
|
26
|
+
event_id: args.event_id,
|
|
27
|
+
code: args.code,
|
|
28
|
+
discount: args.discount,
|
|
29
|
+
};
|
|
30
|
+
if (args.remaining_count !== undefined)
|
|
31
|
+
body.remaining_count = args.remaining_count;
|
|
32
|
+
if (args.valid_start_at !== undefined)
|
|
33
|
+
body.valid_start_at = args.valid_start_at;
|
|
34
|
+
if (args.valid_end_at !== undefined)
|
|
35
|
+
body.valid_end_at = args.valid_end_at;
|
|
36
|
+
const data = await client.request("POST", "/v1/event/create-coupon", body);
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const createEventDefinition = {
|
|
3
|
+
name: "create-event",
|
|
4
|
+
description: "Create a new event on the calendar",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
name: z.string().describe("Event title"),
|
|
7
|
+
start_at: z
|
|
8
|
+
.string()
|
|
9
|
+
.datetime({ offset: true })
|
|
10
|
+
.describe("Start datetime (ISO 8601)"),
|
|
11
|
+
timezone: z.string().describe("IANA timezone (e.g. America/New_York)"),
|
|
12
|
+
end_at: z
|
|
13
|
+
.string()
|
|
14
|
+
.datetime({ offset: true })
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("End datetime (ISO 8601)"),
|
|
17
|
+
description_md: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Event description in markdown"),
|
|
21
|
+
meeting_url: z.string().optional().describe("Virtual event URL"),
|
|
22
|
+
geo_address_json: z
|
|
23
|
+
.record(z.string(), z.unknown())
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Address object with city, place, etc."),
|
|
26
|
+
geo_latitude: z.number().optional().describe("Latitude"),
|
|
27
|
+
geo_longitude: z.number().optional().describe("Longitude"),
|
|
28
|
+
cover_url: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Cover image URL (must be Luma CDN)"),
|
|
32
|
+
visibility: z
|
|
33
|
+
.enum(["public", "members-only", "private"])
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Event visibility"),
|
|
36
|
+
max_capacity: z.number().optional().describe("Maximum attendee capacity"),
|
|
37
|
+
},
|
|
38
|
+
returns: "The created event object",
|
|
39
|
+
};
|
|
40
|
+
export function registerCreateEvent(client, server) {
|
|
41
|
+
server.registerTool(createEventDefinition.name, {
|
|
42
|
+
description: createEventDefinition.description,
|
|
43
|
+
inputSchema: createEventDefinition.inputSchema,
|
|
44
|
+
}, async (args) => {
|
|
45
|
+
const body = {
|
|
46
|
+
name: args.name,
|
|
47
|
+
start_at: args.start_at,
|
|
48
|
+
timezone: args.timezone,
|
|
49
|
+
};
|
|
50
|
+
if (args.end_at !== undefined)
|
|
51
|
+
body.end_at = args.end_at;
|
|
52
|
+
if (args.description_md !== undefined)
|
|
53
|
+
body.description_md = args.description_md;
|
|
54
|
+
if (args.meeting_url !== undefined)
|
|
55
|
+
body.meeting_url = args.meeting_url;
|
|
56
|
+
if (args.geo_address_json !== undefined)
|
|
57
|
+
body.geo_address_json = args.geo_address_json;
|
|
58
|
+
if (args.geo_latitude !== undefined)
|
|
59
|
+
body.coordinate = {
|
|
60
|
+
latitude: args.geo_latitude,
|
|
61
|
+
longitude: args.geo_longitude,
|
|
62
|
+
};
|
|
63
|
+
if (args.cover_url !== undefined)
|
|
64
|
+
body.cover_url = args.cover_url;
|
|
65
|
+
if (args.visibility !== undefined)
|
|
66
|
+
body.visibility = args.visibility;
|
|
67
|
+
if (args.max_capacity !== undefined)
|
|
68
|
+
body.max_capacity = args.max_capacity;
|
|
69
|
+
const data = await client.request("POST", "/v1/event/create", body);
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const createTicketTypeDefinition = {
|
|
3
|
+
name: "create-ticket-type",
|
|
4
|
+
description: "Create a new ticket type for an event",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
event_id: z.string().describe("Event ID (e.g. evt-...)"),
|
|
7
|
+
name: z.string().describe("Ticket type name"),
|
|
8
|
+
type: z.enum(["free", "paid"]).describe("Ticket pricing type"),
|
|
9
|
+
require_approval: z.boolean().optional().describe("Require host approval"),
|
|
10
|
+
is_hidden: z.boolean().optional().describe("Hide from public listing"),
|
|
11
|
+
description: z.string().optional().describe("Ticket type description"),
|
|
12
|
+
max_capacity: z.number().optional().describe("Maximum tickets available"),
|
|
13
|
+
cents: z.number().optional().describe("Price in cents (for paid tickets)"),
|
|
14
|
+
currency: z.string().optional().describe("Currency code (e.g. USD, EUR)"),
|
|
15
|
+
},
|
|
16
|
+
returns: "The created ticket type object",
|
|
17
|
+
};
|
|
18
|
+
export function registerCreateTicketType(client, server) {
|
|
19
|
+
server.registerTool(createTicketTypeDefinition.name, {
|
|
20
|
+
description: createTicketTypeDefinition.description,
|
|
21
|
+
inputSchema: createTicketTypeDefinition.inputSchema,
|
|
22
|
+
}, async (args) => {
|
|
23
|
+
const body = {
|
|
24
|
+
event_id: args.event_id,
|
|
25
|
+
name: args.name,
|
|
26
|
+
type: args.type,
|
|
27
|
+
};
|
|
28
|
+
if (args.require_approval !== undefined)
|
|
29
|
+
body.require_approval = args.require_approval;
|
|
30
|
+
if (args.is_hidden !== undefined)
|
|
31
|
+
body.is_hidden = args.is_hidden;
|
|
32
|
+
if (args.description !== undefined)
|
|
33
|
+
body.description = args.description;
|
|
34
|
+
if (args.max_capacity !== undefined)
|
|
35
|
+
body.max_capacity = args.max_capacity;
|
|
36
|
+
if (args.cents !== undefined)
|
|
37
|
+
body.cents = args.cents;
|
|
38
|
+
if (args.currency !== undefined)
|
|
39
|
+
body.currency = args.currency;
|
|
40
|
+
const data = await client.request("POST", "/v1/event/ticket-types/create", body);
|
|
41
|
+
return {
|
|
42
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const getCalendarDefinition = {
|
|
2
|
+
name: "get-calendar",
|
|
3
|
+
description: "Get the calendar associated with the current API key",
|
|
4
|
+
inputSchema: {},
|
|
5
|
+
returns: "Calendar object with api_id, name, and geo fields",
|
|
6
|
+
};
|
|
7
|
+
export function registerGetCalendar(client, server) {
|
|
8
|
+
server.registerTool(getCalendarDefinition.name, {
|
|
9
|
+
description: getCalendarDefinition.description,
|
|
10
|
+
inputSchema: getCalendarDefinition.inputSchema,
|
|
11
|
+
}, async () => {
|
|
12
|
+
const data = await client.request("GET", "/v1/calendar/get");
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const getEventDefinition = {
|
|
3
|
+
name: "get-event",
|
|
4
|
+
description: "Get admin details of a specific event by its ID",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
event_id: z.string().describe("Event ID (e.g. evt-...)"),
|
|
7
|
+
},
|
|
8
|
+
returns: "Full event object including name, dates, location, description, and ticket info",
|
|
9
|
+
};
|
|
10
|
+
export function registerGetEvent(client, server) {
|
|
11
|
+
server.registerTool(getEventDefinition.name, {
|
|
12
|
+
description: getEventDefinition.description,
|
|
13
|
+
inputSchema: getEventDefinition.inputSchema,
|
|
14
|
+
}, async (args) => {
|
|
15
|
+
const data = await client.request("GET", `/v1/event/get?id=${encodeURIComponent(args.event_id)}`);
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const getGuestDefinition = {
|
|
3
|
+
name: "get-guest",
|
|
4
|
+
description: "Get detailed info for a single event guest. The id field accepts a guest ID (gst-...), guest key (g-...), ticket key, or email address.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
event_id: z.string().describe("Event ID (e.g. evt-...)"),
|
|
7
|
+
id: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe("Guest identifier: guest ID (gst-...), guest key (g-...), or email"),
|
|
10
|
+
},
|
|
11
|
+
returns: "Full guest object with approval status, ticket info, event_ticket_orders, and profile",
|
|
12
|
+
};
|
|
13
|
+
export function registerGetGuest(client, server) {
|
|
14
|
+
server.registerTool(getGuestDefinition.name, {
|
|
15
|
+
description: getGuestDefinition.description,
|
|
16
|
+
inputSchema: getGuestDefinition.inputSchema,
|
|
17
|
+
}, async (args) => {
|
|
18
|
+
const params = new URLSearchParams({
|
|
19
|
+
event_id: args.event_id,
|
|
20
|
+
id: args.id,
|
|
21
|
+
});
|
|
22
|
+
const data = await client.request("GET", `/v1/event/get-guest?${params.toString()}`);
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const listCouponsDefinition = {
|
|
3
|
+
name: "list-coupons",
|
|
4
|
+
description: "List all coupons for an event. Without pagination params, returns all coupons; with them, returns one page.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
event_id: z.string().describe("Event ID (e.g. evt-...)"),
|
|
7
|
+
pagination_limit: z.number().optional().describe("Page size"),
|
|
8
|
+
pagination_cursor: z
|
|
9
|
+
.string()
|
|
10
|
+
.optional()
|
|
11
|
+
.describe("Cursor from a previous page"),
|
|
12
|
+
},
|
|
13
|
+
returns: "Array of coupon objects, or paginated result with next_cursor",
|
|
14
|
+
};
|
|
15
|
+
export function registerListCoupons(client, server) {
|
|
16
|
+
server.registerTool(listCouponsDefinition.name, {
|
|
17
|
+
description: listCouponsDefinition.description,
|
|
18
|
+
inputSchema: listCouponsDefinition.inputSchema,
|
|
19
|
+
}, async (args) => {
|
|
20
|
+
const params = { event_id: args.event_id };
|
|
21
|
+
const usePagination = args.pagination_limit !== undefined ||
|
|
22
|
+
args.pagination_cursor !== undefined;
|
|
23
|
+
if (usePagination) {
|
|
24
|
+
const result = await client.paginateSingle("/v1/event/coupons", params, args.pagination_limit, args.pagination_cursor);
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const entries = await client.paginate("/v1/event/coupons", params);
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: "text", text: JSON.stringify(entries) }],
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const listEventsDefinition = {
|
|
3
|
+
name: "list-events",
|
|
4
|
+
description: "List events for the calendar. Optionally filter by date range. Without pagination params, returns all events; with them, returns one page.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
after: z
|
|
7
|
+
.string()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Only events starting after this ISO 8601 datetime"),
|
|
10
|
+
before: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Only events starting before this ISO 8601 datetime"),
|
|
14
|
+
pagination_limit: z.number().optional().describe("Page size (max 50)"),
|
|
15
|
+
pagination_cursor: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Cursor from a previous page"),
|
|
19
|
+
},
|
|
20
|
+
returns: "Array of event objects, or paginated result with next_cursor",
|
|
21
|
+
};
|
|
22
|
+
export function registerListEvents(client, server) {
|
|
23
|
+
server.registerTool(listEventsDefinition.name, {
|
|
24
|
+
description: listEventsDefinition.description,
|
|
25
|
+
inputSchema: listEventsDefinition.inputSchema,
|
|
26
|
+
}, async (args) => {
|
|
27
|
+
const params = {};
|
|
28
|
+
if (args.after)
|
|
29
|
+
params.after = args.after;
|
|
30
|
+
if (args.before)
|
|
31
|
+
params.before = args.before;
|
|
32
|
+
const usePagination = args.pagination_limit !== undefined ||
|
|
33
|
+
args.pagination_cursor !== undefined;
|
|
34
|
+
if (usePagination) {
|
|
35
|
+
const result = await client.paginateSingle("/v1/calendar/list-events", params, args.pagination_limit, args.pagination_cursor);
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const entries = await client.paginate("/v1/calendar/list-events", params);
|
|
41
|
+
return {
|
|
42
|
+
content: [{ type: "text", text: JSON.stringify(entries) }],
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const listGuestsDefinition = {
|
|
3
|
+
name: "list-guests",
|
|
4
|
+
description: "List guests for an event. Without pagination params, returns all guests; with them, returns one page.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
event_id: z.string().describe("Event ID (e.g. evt-...)"),
|
|
7
|
+
approval_status: z
|
|
8
|
+
.enum([
|
|
9
|
+
"approved",
|
|
10
|
+
"session",
|
|
11
|
+
"pending_approval",
|
|
12
|
+
"invited",
|
|
13
|
+
"declined",
|
|
14
|
+
"waitlist",
|
|
15
|
+
])
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Filter by approval status"),
|
|
18
|
+
sort_column: z
|
|
19
|
+
.enum(["name", "email", "created_at", "registered_at", "checked_in_at"])
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Sort column"),
|
|
22
|
+
pagination_limit: z.number().optional().describe("Page size"),
|
|
23
|
+
pagination_cursor: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Cursor from a previous page"),
|
|
27
|
+
},
|
|
28
|
+
returns: "Array of guest objects, or paginated result with next_cursor",
|
|
29
|
+
};
|
|
30
|
+
export function registerListGuests(client, server) {
|
|
31
|
+
server.registerTool(listGuestsDefinition.name, {
|
|
32
|
+
description: listGuestsDefinition.description,
|
|
33
|
+
inputSchema: listGuestsDefinition.inputSchema,
|
|
34
|
+
}, async (args) => {
|
|
35
|
+
const params = {
|
|
36
|
+
event_id: args.event_id,
|
|
37
|
+
};
|
|
38
|
+
if (args.approval_status)
|
|
39
|
+
params.approval_status = args.approval_status;
|
|
40
|
+
if (args.sort_column)
|
|
41
|
+
params.sort_column = args.sort_column;
|
|
42
|
+
const usePagination = args.pagination_limit !== undefined ||
|
|
43
|
+
args.pagination_cursor !== undefined;
|
|
44
|
+
if (usePagination) {
|
|
45
|
+
const result = await client.paginateSingle("/v1/event/get-guests", params, args.pagination_limit, args.pagination_cursor);
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const entries = await client.paginate("/v1/event/get-guests", params);
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text", text: JSON.stringify(entries) }],
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const listTicketTypesDefinition = {
|
|
3
|
+
name: "list-ticket-types",
|
|
4
|
+
description: "List all ticket types for an event",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
event_id: z.string().describe("Event ID (e.g. evt-...)"),
|
|
7
|
+
},
|
|
8
|
+
returns: "Array of ticket type objects with name, price, and availability info",
|
|
9
|
+
};
|
|
10
|
+
export function registerListTicketTypes(client, server) {
|
|
11
|
+
server.registerTool(listTicketTypesDefinition.name, {
|
|
12
|
+
description: listTicketTypesDefinition.description,
|
|
13
|
+
inputSchema: listTicketTypesDefinition.inputSchema,
|
|
14
|
+
}, async (args) => {
|
|
15
|
+
const data = await client.request("GET", `/v1/event/ticket-types/list?event_id=${encodeURIComponent(args.event_id)}`);
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const inviteGuestSchema = z.object({
|
|
3
|
+
email: z.string().describe("Guest email address"),
|
|
4
|
+
message: z
|
|
5
|
+
.string()
|
|
6
|
+
.optional()
|
|
7
|
+
.describe("Personalized invite message (max 200 chars)"),
|
|
8
|
+
});
|
|
9
|
+
export const sendInvitesDefinition = {
|
|
10
|
+
name: "send-invites",
|
|
11
|
+
description: "Send invites to guests for an event. Sends email and SMS if phone is linked.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
event_id: z.string().describe("Event ID (e.g. evt-...)"),
|
|
14
|
+
guests: z
|
|
15
|
+
.array(inviteGuestSchema)
|
|
16
|
+
.describe("Array of guest objects with email and optional message"),
|
|
17
|
+
},
|
|
18
|
+
returns: "Confirmation of sent invites",
|
|
19
|
+
};
|
|
20
|
+
export function registerSendInvites(client, server) {
|
|
21
|
+
server.registerTool(sendInvitesDefinition.name, {
|
|
22
|
+
description: sendInvitesDefinition.description,
|
|
23
|
+
inputSchema: sendInvitesDefinition.inputSchema,
|
|
24
|
+
}, async (args) => {
|
|
25
|
+
const data = await client.request("POST", "/v1/event/send-invites", {
|
|
26
|
+
event_id: args.event_id,
|
|
27
|
+
guests: args.guests,
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const updateEventDefinition = {
|
|
3
|
+
name: "update-event",
|
|
4
|
+
description: "Update an existing event. Only provided fields are changed.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
event_id: z.string().describe("Event ID (e.g. evt-...)"),
|
|
7
|
+
suppress_notifications: z
|
|
8
|
+
.boolean()
|
|
9
|
+
.optional()
|
|
10
|
+
.describe("Prevent guest notifications for name/time/location changes"),
|
|
11
|
+
name: z.string().optional().describe("Event title"),
|
|
12
|
+
start_at: z
|
|
13
|
+
.string()
|
|
14
|
+
.datetime({ offset: true })
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Start datetime (ISO 8601)"),
|
|
17
|
+
end_at: z
|
|
18
|
+
.string()
|
|
19
|
+
.datetime({ offset: true })
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("End datetime (ISO 8601)"),
|
|
22
|
+
timezone: z.string().optional().describe("IANA timezone"),
|
|
23
|
+
description_md: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Event description in markdown"),
|
|
27
|
+
meeting_url: z.string().optional().describe("Virtual event URL"),
|
|
28
|
+
cover_url: z.string().optional().describe("Cover image URL"),
|
|
29
|
+
visibility: z
|
|
30
|
+
.enum(["public", "members-only", "private"])
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Event visibility"),
|
|
33
|
+
max_capacity: z.number().optional().describe("Maximum attendee capacity"),
|
|
34
|
+
},
|
|
35
|
+
returns: "The updated event object",
|
|
36
|
+
};
|
|
37
|
+
export function registerUpdateEvent(client, server) {
|
|
38
|
+
server.registerTool(updateEventDefinition.name, {
|
|
39
|
+
description: updateEventDefinition.description,
|
|
40
|
+
inputSchema: updateEventDefinition.inputSchema,
|
|
41
|
+
}, async (args) => {
|
|
42
|
+
const body = { event_id: args.event_id };
|
|
43
|
+
if (args.suppress_notifications !== undefined)
|
|
44
|
+
body.suppress_notifications = args.suppress_notifications;
|
|
45
|
+
if (args.name !== undefined)
|
|
46
|
+
body.name = args.name;
|
|
47
|
+
if (args.start_at !== undefined)
|
|
48
|
+
body.start_at = args.start_at;
|
|
49
|
+
if (args.end_at !== undefined)
|
|
50
|
+
body.end_at = args.end_at;
|
|
51
|
+
if (args.timezone !== undefined)
|
|
52
|
+
body.timezone = args.timezone;
|
|
53
|
+
if (args.description_md !== undefined)
|
|
54
|
+
body.description_md = args.description_md;
|
|
55
|
+
if (args.meeting_url !== undefined)
|
|
56
|
+
body.meeting_url = args.meeting_url;
|
|
57
|
+
if (args.cover_url !== undefined)
|
|
58
|
+
body.cover_url = args.cover_url;
|
|
59
|
+
if (args.visibility !== undefined)
|
|
60
|
+
body.visibility = args.visibility;
|
|
61
|
+
if (args.max_capacity !== undefined)
|
|
62
|
+
body.max_capacity = args.max_capacity;
|
|
63
|
+
const data = await client.request("POST", "/v1/event/update", body);
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const updateGuestStatusDefinition = {
|
|
3
|
+
name: "update-guest-status",
|
|
4
|
+
description: "Update a guest's approval status (approve or decline)",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
event_id: z.string().describe("Event ID (e.g. evt-...)"),
|
|
7
|
+
guest: z
|
|
8
|
+
.record(z.string(), z.unknown())
|
|
9
|
+
.describe("Guest identifier object (e.g. { email: '...' } or { id: 'gst-...' })"),
|
|
10
|
+
status: z
|
|
11
|
+
.enum(["approved", "declined"])
|
|
12
|
+
.describe("New status for the guest"),
|
|
13
|
+
should_refund: z
|
|
14
|
+
.boolean()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Refund the guest if declining a paid ticket"),
|
|
17
|
+
},
|
|
18
|
+
returns: "Confirmation of status update",
|
|
19
|
+
};
|
|
20
|
+
export function registerUpdateGuestStatus(client, server) {
|
|
21
|
+
server.registerTool(updateGuestStatusDefinition.name, {
|
|
22
|
+
description: updateGuestStatusDefinition.description,
|
|
23
|
+
inputSchema: updateGuestStatusDefinition.inputSchema,
|
|
24
|
+
}, async (args) => {
|
|
25
|
+
const body = {
|
|
26
|
+
event_id: args.event_id,
|
|
27
|
+
guest: args.guest,
|
|
28
|
+
status: args.status,
|
|
29
|
+
};
|
|
30
|
+
if (args.should_refund !== undefined)
|
|
31
|
+
body.should_refund = args.should_refund;
|
|
32
|
+
const data = await client.request("POST", "/v1/event/update-guest-status", body);
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "luma-events-mcp",
|
|
3
|
+
"mcpName": "io.github.bettervibe-org/luma-events-mcp",
|
|
4
|
+
"description": "A Luma Calendar MCP server exposing event, guest, and ticket operations as tools for AI assistants.",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"luma-events-mcp": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc && shx chmod +x dist/*.js",
|
|
16
|
+
"dev": "tsx --watch --env-file=.env src/index.ts",
|
|
17
|
+
"prepare": "lefthook install",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"test:coverage": "vitest run --coverage",
|
|
21
|
+
"check": "biome check .",
|
|
22
|
+
"check:fix": "biome check --write .",
|
|
23
|
+
"check:ci": "biome ci .",
|
|
24
|
+
"docs": "tsx scripts/gen-tool-docs.ts",
|
|
25
|
+
"docs:check": "tsx scripts/gen-tool-docs.ts --check",
|
|
26
|
+
"validate": "npm run check && npm test && npm run knip && npm run docs:check && npm run build",
|
|
27
|
+
"smoke": "npm run build && tsx --env-file=.env scripts/smoke.ts",
|
|
28
|
+
"watch": "tsc --watch",
|
|
29
|
+
"semantic-release": "semantic-release",
|
|
30
|
+
"knip": "knip"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/bettervibe-org/luma-events-mcp.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/bettervibe-org/luma-events-mcp/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/bettervibe-org/luma-events-mcp#readme",
|
|
40
|
+
"author": "Dominik Grusemann",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0",
|
|
44
|
+
"npm": ">=9.0.0"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
48
|
+
"zod": "^4.4.3"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@biomejs/biome": "^2.4.15",
|
|
52
|
+
"@commitlint/cli": "^21.0.1",
|
|
53
|
+
"@commitlint/config-conventional": "^21.0.1",
|
|
54
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
55
|
+
"@semantic-release/git": "^10.0.1",
|
|
56
|
+
"@semantic-release/github": "^12.0.8",
|
|
57
|
+
"@semantic-release/npm": "^13.1.5",
|
|
58
|
+
"@types/node": "^25.9.1",
|
|
59
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
60
|
+
"knip": "^6.14.2",
|
|
61
|
+
"lefthook": "^2.1.8",
|
|
62
|
+
"semantic-release": "^25.0.3",
|
|
63
|
+
"shx": "^0.4.0",
|
|
64
|
+
"tsx": "^4.22.3",
|
|
65
|
+
"typescript": "^6.0.3",
|
|
66
|
+
"vitest": "^4.1.7"
|
|
67
|
+
}
|
|
68
|
+
}
|