@meetbot/mcp 1.2.7 → 1.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.
@@ -16,48 +16,25 @@ export class MeetbotMCPStreamable {
16
16
  constructor() {
17
17
  this.server = new McpServer({
18
18
  name: 'meetbot-mcp',
19
- version: '1.2.3',
19
+ version: '1.3.0',
20
20
  description: 'Meet.bot MCP Server for scheduling and booking',
21
21
  });
22
22
  this.setupToolHandlers();
23
+ this.setupPromptHandlers();
23
24
  }
25
+ /** Tool annotations for MCP/Smithery quality: audience, priority, lastModified per spec */
26
+ static TOOL_ANNOTATIONS = {
27
+ audience: ['user', 'assistant'],
28
+ priority: 0.8,
29
+ lastModified: '2025-03-05T00:00:00Z',
30
+ };
24
31
  setupToolHandlers() {
25
- // Configure Meet.bot authentication tool
26
- this.server.registerTool('configure_meetbot', {
27
- title: 'Configure Meet.bot Authentication',
28
- description: 'Configure Meet.bot API authentication (required before using other tools)',
29
- inputSchema: {},
30
- }, async (_, extra) => {
31
- console.log('🔧 Configure Meetbot called for session:', extra.sessionId);
32
- if (!extra.sessionId) {
33
- throw new Error('Session ID is required');
34
- }
35
- // Check if client is already configured
36
- const existingClient = this.clients.get(extra.sessionId);
37
- if (existingClient) {
38
- return {
39
- content: [
40
- {
41
- type: 'text',
42
- text: '✅ Meet.bot client is already configured and ready to use.',
43
- },
44
- ],
45
- };
46
- }
47
- return {
48
- content: [
49
- {
50
- type: 'text',
51
- text: '⚠️ Meet.bot client not configured. Please ensure the Authorization header is provided during connection.',
52
- },
53
- ],
54
- };
55
- });
56
32
  // Get scheduling pages tool
57
33
  this.server.registerTool('get_scheduling_pages', {
58
34
  title: 'Get Scheduling Pages',
59
35
  description: 'Get all scheduling pages for the authenticated user',
60
36
  inputSchema: {},
37
+ annotations: MeetbotMCPStreamable.TOOL_ANNOTATIONS,
61
38
  }, async (_, extra) => {
62
39
  if (!extra.sessionId) {
63
40
  throw new Error('Session ID is required');
@@ -67,7 +44,7 @@ export class MeetbotMCPStreamable {
67
44
  }
68
45
  const client = this.clients.get(extra.sessionId);
69
46
  if (!client) {
70
- throw new Error('Meet.bot client not configured. Please use configure_meetbot first.');
47
+ throw new Error('Meet.bot client not configured. Provide an Authorization: Bearer <token> header when connecting to the MCP server.');
71
48
  }
72
49
  const pages = await client.getPages();
73
50
  return {
@@ -86,8 +63,9 @@ export class MeetbotMCPStreamable {
86
63
  title: 'Get Page Information',
87
64
  description: 'Get information about a specific scheduling page',
88
65
  inputSchema: {
89
- page: z.string().describe('The URL of the scheduling page'),
66
+ page: z.string().describe('The URL of the scheduling page (e.g. https://meet.bot/your-page)'),
90
67
  },
68
+ annotations: MeetbotMCPStreamable.TOOL_ANNOTATIONS,
91
69
  }, async ({ page }, extra) => {
92
70
  if (!extra.sessionId) {
93
71
  throw new Error('Session ID is required');
@@ -97,7 +75,7 @@ export class MeetbotMCPStreamable {
97
75
  }
98
76
  const client = this.clients.get(extra.sessionId);
99
77
  if (!client) {
100
- throw new Error('Meet.bot client not configured. Please use configure_meetbot first.');
78
+ throw new Error('Meet.bot client not configured. Provide an Authorization: Bearer <token> header when connecting to the MCP server.');
101
79
  }
102
80
  if (!page) {
103
81
  throw new Error('Page URL is required');
@@ -117,20 +95,21 @@ export class MeetbotMCPStreamable {
117
95
  title: 'Get Available Slots',
118
96
  description: 'Get available booking slots for a scheduling page',
119
97
  inputSchema: {
120
- page: z.string().describe('The URL of the scheduling page'),
121
- count: z.number().optional().describe('Maximum number of slots to return'),
122
- start: z.string().optional().describe('Start date in YYYY-MM-DD format'),
123
- end: z.string().optional().describe('End date in YYYY-MM-DD format'),
124
- timezone: z.string().optional().describe('Timezone in IANA format (e.g., America/New_York)'),
125
- booking_link: z.boolean().optional().describe('Include shareable booking links'),
98
+ page: z.string().describe('The URL of the scheduling page (e.g. https://meet.bot/your-page)'),
99
+ count: z.number().optional().describe('Maximum number of slots to return (defaults to server limit)'),
100
+ start: z.string().optional().describe('Start date for the range in YYYY-MM-DD format'),
101
+ end: z.string().optional().describe('End date for the range in YYYY-MM-DD format'),
102
+ timezone: z.string().optional().describe('IANA timezone for slot times (e.g. America/New_York, Europe/London)'),
103
+ booking_link: z.boolean().optional().describe('If true, include shareable booking links in the response'),
126
104
  },
105
+ annotations: MeetbotMCPStreamable.TOOL_ANNOTATIONS,
127
106
  }, async ({ page, ...args }, extra) => {
128
107
  if (!extra.sessionId) {
129
108
  throw new Error('Session ID is required');
130
109
  }
131
110
  const client = this.clients.get(extra.sessionId);
132
111
  if (!client) {
133
- throw new Error('Meet.bot client not configured. Please use configure_meetbot first.');
112
+ throw new Error('Meet.bot client not configured. Provide an Authorization: Bearer <token> header when connecting to the MCP server.');
134
113
  }
135
114
  if (!page) {
136
115
  throw new Error('Page URL is required');
@@ -156,19 +135,20 @@ export class MeetbotMCPStreamable {
156
135
  title: 'Book Meeting',
157
136
  description: 'Book a new meeting slot',
158
137
  inputSchema: {
159
- page: z.string().describe('The URL of the scheduling page'),
160
- guest_email: z.string().describe('Email address of the guest'),
161
- guest_name: z.string().describe('Name of the guest'),
162
- notes: z.string().optional().describe('Additional notes for the meeting'),
163
- start: z.string().describe('Start time in ISO 8601 format'),
138
+ page: z.string().describe('The URL of the scheduling page (e.g. https://meet.bot/your-page)'),
139
+ guest_email: z.string().describe('Email address of the guest (used for calendar invite and confirmation)'),
140
+ guest_name: z.string().describe('Full name of the guest'),
141
+ notes: z.string().optional().describe('Optional notes to include with the meeting (e.g. agenda, call details)'),
142
+ start: z.string().describe('Start time in ISO 8601 format (e.g. 2025-03-10T14:00:00Z); must be an available slot'),
164
143
  },
144
+ annotations: MeetbotMCPStreamable.TOOL_ANNOTATIONS,
165
145
  }, async ({ page, guest_email, guest_name, notes, start }, extra) => {
166
146
  if (!extra.sessionId) {
167
147
  throw new Error('Session ID is required');
168
148
  }
169
149
  const client = this.clients.get(extra.sessionId);
170
150
  if (!client) {
171
- throw new Error('Meet.bot client not configured. Please use configure_meetbot first.');
151
+ throw new Error('Meet.bot client not configured. Provide an Authorization: Bearer <token> header when connecting to the MCP server.');
172
152
  }
173
153
  if (!page || !guest_email || !guest_name || !start) {
174
154
  throw new Error('Required parameters missing: page, guest_email, guest_name, start');
@@ -186,15 +166,16 @@ export class MeetbotMCPStreamable {
186
166
  // Health check tool
187
167
  this.server.registerTool('health_check', {
188
168
  title: 'Health Check',
189
- description: 'Check if the Meet.bot API client is healthy',
169
+ description: 'Check if the Meet.bot API client is healthy and the Bearer token is valid',
190
170
  inputSchema: {},
171
+ annotations: MeetbotMCPStreamable.TOOL_ANNOTATIONS,
191
172
  }, async (_, extra) => {
192
173
  if (!extra.sessionId) {
193
174
  throw new Error('Session ID is required');
194
175
  }
195
176
  const client = this.clients.get(extra.sessionId);
196
177
  if (!client) {
197
- throw new Error('Meet.bot client not configured. Please use configure_meetbot first.');
178
+ throw new Error('Meet.bot client not configured. Provide an Authorization: Bearer <token> header when connecting to the MCP server.');
198
179
  }
199
180
  const isHealthy = await client.healthCheck();
200
181
  return {
@@ -208,6 +189,241 @@ export class MeetbotMCPStreamable {
208
189
  ],
209
190
  };
210
191
  });
192
+ // List webhooks tool
193
+ this.server.registerTool('list_webhooks', {
194
+ title: 'List Webhooks',
195
+ description: "List the authenticated user's outbound booking webhooks (fired on booking_received, booking_rescheduled and booking_cancelled)",
196
+ inputSchema: {},
197
+ annotations: MeetbotMCPStreamable.TOOL_ANNOTATIONS,
198
+ }, async (_, extra) => {
199
+ if (!extra.sessionId) {
200
+ throw new Error('Session ID is required');
201
+ }
202
+ const client = this.clients.get(extra.sessionId);
203
+ if (!client) {
204
+ throw new Error('Meet.bot client not configured. Provide an Authorization: Bearer <token> header when connecting to the MCP server.');
205
+ }
206
+ const webhooks = await client.listWebhooks();
207
+ if (webhooks.length === 0) {
208
+ return {
209
+ content: [{ type: 'text', text: 'No webhooks configured yet. Use set_webhook to add one.' }],
210
+ };
211
+ }
212
+ return {
213
+ content: [
214
+ {
215
+ type: 'text',
216
+ text: `Found ${webhooks.length} webhook(s):\n\n${webhooks
217
+ .map((webhook) => `• #${webhook.id} ${webhook.description || '(unnamed)'} -> ${webhook.webhook_url}\n coverage: ${webhook.coverage}, scope: ${webhook.scope}, active: ${webhook.is_active}`)
218
+ .join('\n')}`,
219
+ },
220
+ ],
221
+ };
222
+ });
223
+ // Set webhook tool (create or update)
224
+ this.server.registerTool('set_webhook', {
225
+ title: 'Set Webhook',
226
+ description: 'Create or update an outbound booking webhook. Omit id to create (webhook_url required); pass id to update. Meet.bot POSTs a JWT-signed (HS256) JSON payload to the URL on each booking event.',
227
+ inputSchema: {
228
+ id: z.number().optional().describe('Webhook id to update; omit to create a new one'),
229
+ webhook_url: z.string().optional().describe('HTTPS URL we POST booking events to (required when creating)'),
230
+ description: z.string().optional().describe('Optional label for the webhook'),
231
+ coverage: z.enum(['all', 'selected']).optional().describe("'all' (default) fires for every page including ones created later; 'selected' only for the pages in `pages`"),
232
+ scope: z.enum(['self', 'team']).optional().describe("'self' (default) your own pages; 'team' (team admins only) also fires for teammates' bookings"),
233
+ pages: z.array(z.number()).optional().describe("Page ids to cover when coverage='selected'"),
234
+ is_active: z.boolean().optional().describe('Whether the webhook is active (default true)'),
235
+ },
236
+ annotations: MeetbotMCPStreamable.TOOL_ANNOTATIONS,
237
+ }, async (args, extra) => {
238
+ if (!extra.sessionId) {
239
+ throw new Error('Session ID is required');
240
+ }
241
+ const client = this.clients.get(extra.sessionId);
242
+ if (!client) {
243
+ throw new Error('Meet.bot client not configured. Provide an Authorization: Bearer <token> header when connecting to the MCP server.');
244
+ }
245
+ const webhook = await client.setWebhook(args);
246
+ const secretLine = webhook.shared_secret
247
+ ? `\nShared secret (verify HS256 JWT signatures with this): ${webhook.shared_secret}`
248
+ : '';
249
+ return {
250
+ content: [
251
+ {
252
+ type: 'text',
253
+ text: `Webhook saved (#${webhook.id}).\nURL: ${webhook.webhook_url}\nCoverage: ${webhook.coverage}, Scope: ${webhook.scope}, Active: ${webhook.is_active}${secretLine}`,
254
+ },
255
+ ],
256
+ };
257
+ });
258
+ // Delete webhook tool
259
+ this.server.registerTool('delete_webhook', {
260
+ title: 'Delete Webhook',
261
+ description: "Delete one of the authenticated user's webhooks by id",
262
+ inputSchema: {
263
+ id: z.number().describe('The webhook id to delete'),
264
+ },
265
+ annotations: MeetbotMCPStreamable.TOOL_ANNOTATIONS,
266
+ }, async ({ id }, extra) => {
267
+ if (!extra.sessionId) {
268
+ throw new Error('Session ID is required');
269
+ }
270
+ const client = this.clients.get(extra.sessionId);
271
+ if (!client) {
272
+ throw new Error('Meet.bot client not configured. Provide an Authorization: Bearer <token> header when connecting to the MCP server.');
273
+ }
274
+ await client.deleteWebhook(id);
275
+ return {
276
+ content: [
277
+ {
278
+ type: 'text',
279
+ text: `Webhook #${id} deleted.`,
280
+ },
281
+ ],
282
+ };
283
+ });
284
+ }
285
+ setupPromptHandlers() {
286
+ // 1. schedule_meeting – full flow: list pages → slots → book (confirm with user first)
287
+ this.server.registerPrompt('schedule_meeting', {
288
+ title: 'Schedule a meeting',
289
+ description: 'Get step-by-step instructions to schedule a meeting using this server. Use this when the user wants to book a meeting or check availability.',
290
+ argsSchema: {},
291
+ }, () => ({
292
+ description: 'Instructions for scheduling a meeting with Meet.bot',
293
+ messages: [
294
+ {
295
+ role: 'user',
296
+ content: {
297
+ type: 'text',
298
+ text: 'I need to schedule a meeting. Please use the Meet.bot MCP tools to: 1) List my scheduling pages with get_scheduling_pages, 2) Get available slots for the chosen page with get_available_slots, and 3) Book the chosen slot with book_meeting (guest_email, guest_name, and start time required). Confirm the details with me before booking.',
299
+ },
300
+ },
301
+ ],
302
+ }));
303
+ // 2. check_availability – "When is [person] next free?" – availability only, no booking
304
+ this.server.registerPrompt('check_availability', {
305
+ title: 'Check availability',
306
+ description: 'When is this person next free? Checks availability only; no booking. Use for pre-meeting research or answering "when are they free?"',
307
+ argsSchema: {
308
+ page: z.string().describe('The scheduling page URL (e.g. https://meet.bot/your-page)'),
309
+ days_ahead: z.string().optional().describe('Number of days to look ahead (default: 7)'),
310
+ },
311
+ }, (args) => {
312
+ const days = typeof args.days_ahead === 'string' && args.days_ahead !== '' ? parseInt(args.days_ahead, 10) : 7;
313
+ const start = new Date();
314
+ const end = new Date();
315
+ end.setDate(end.getDate() + days);
316
+ const startStr = start.toISOString().slice(0, 10);
317
+ const endStr = end.toISOString().slice(0, 10);
318
+ return {
319
+ description: 'Check when this person is next available; do not book.',
320
+ messages: [
321
+ {
322
+ role: 'user',
323
+ content: {
324
+ type: 'text',
325
+ text: `Check availability only (no booking). Use get_available_slots with page "${args.page}", start "${startStr}", end "${endStr}", and a reasonable count (e.g. 10). Return when this person is next free. Do not book a meeting or collect guest details.`,
326
+ },
327
+ },
328
+ ],
329
+ };
330
+ });
331
+ // 3. book_for_guest – fast path when all details are already known
332
+ this.server.registerPrompt('book_for_guest', {
333
+ title: 'Book for guest',
334
+ description: 'Fast path when you already have all details. Books a meeting directly using the preferred time; useful when another system provides the slot.',
335
+ argsSchema: {
336
+ page: z.string().describe('The scheduling page URL'),
337
+ guest_name: z.string().describe('Full name of the guest'),
338
+ guest_email: z.string().describe('Email address of the guest'),
339
+ preferred_time: z.string().describe('Preferred start time in ISO 8601 format (e.g. 2025-03-10T14:00:00Z); should be an available slot'),
340
+ },
341
+ }, (args) => ({
342
+ description: 'Book a meeting with the given guest and time.',
343
+ messages: [
344
+ {
345
+ role: 'user',
346
+ content: {
347
+ type: 'text',
348
+ text: `Book a meeting now. Use book_meeting with: page "${args.page}", guest_name "${args.guest_name}", guest_email "${args.guest_email}", start "${args.preferred_time}". No need to discover slots first – book this exact time.`,
349
+ },
350
+ },
351
+ ],
352
+ }));
353
+ // 4. share_booking_link – "Send [person] a link to book" – returns shareable links only
354
+ this.server.registerPrompt('share_booking_link', {
355
+ title: 'Share booking link',
356
+ description: 'Send the user a link to book. Returns shareable booking links; the guest picks their own slot. No booking is performed.',
357
+ argsSchema: {
358
+ page: z.string().describe('The scheduling page URL'),
359
+ count: z.string().optional().describe('Number of slot links to return (default: 3)'),
360
+ },
361
+ }, (args) => {
362
+ const count = typeof args.count === 'string' && args.count !== '' ? parseInt(args.count, 10) : 3;
363
+ return {
364
+ description: 'Get shareable booking links to send to the guest.',
365
+ messages: [
366
+ {
367
+ role: 'user',
368
+ content: {
369
+ type: 'text',
370
+ text: `Get shareable booking links only (do not book). Use get_available_slots with page "${args.page}", booking_link: true, and count: ${count}. Return the first ${count} booking links formatted clearly so the user can copy or send them to their guest. The guest will choose their own slot.`,
371
+ },
372
+ },
373
+ ],
374
+ };
375
+ });
376
+ // 5. list_my_pages – starting point when the AI doesn't know which page to use
377
+ this.server.registerPrompt('list_my_pages', {
378
+ title: 'List my scheduling pages',
379
+ description: 'Starting point when you don\'t know which page to use. Lists the user\'s scheduling pages with a brief description of each.',
380
+ argsSchema: {},
381
+ }, () => ({
382
+ description: 'List the user’s scheduling pages.',
383
+ messages: [
384
+ {
385
+ role: 'user',
386
+ content: {
387
+ type: 'text',
388
+ text: 'List my scheduling pages. Use get_scheduling_pages and present the results clearly: for each page show title, duration, and URL, with a brief description so I (or the user) can choose which page to use next.',
389
+ },
390
+ },
391
+ ],
392
+ }));
393
+ // 6. suggest_times – offer N options for the user to pick
394
+ this.server.registerPrompt('suggest_times', {
395
+ title: 'Suggest times',
396
+ description: 'Offer the user some options. Returns a clean list of available slots for them to pick from; no booking until they choose.',
397
+ argsSchema: {
398
+ page: z.string().describe('The scheduling page URL'),
399
+ count: z.string().optional().describe('Number of slot options to return (default: 3)'),
400
+ timezone: z.string().optional().describe('IANA timezone for displaying times (e.g. America/New_York)'),
401
+ start_date: z.string().optional().describe('Start of range in YYYY-MM-DD format'),
402
+ end_date: z.string().optional().describe('End of range in YYYY-MM-DD format'),
403
+ },
404
+ }, (args) => {
405
+ const count = typeof args.count === 'string' && args.count !== '' ? parseInt(args.count, 10) : 3;
406
+ const parts = [`Use get_available_slots with page "${args.page}" and count: ${count}.`];
407
+ if (args.timezone)
408
+ parts.push(`Use timezone "${args.timezone}" for display.`);
409
+ if (args.start_date)
410
+ parts.push(`Restrict to start date ${args.start_date}.`);
411
+ if (args.end_date)
412
+ parts.push(`Restrict to end date ${args.end_date}.`);
413
+ parts.push('Format the slots as a clean, numbered list for the user to pick one. Do not book until they choose.');
414
+ return {
415
+ description: 'Present available times for the user to choose from.',
416
+ messages: [
417
+ {
418
+ role: 'user',
419
+ content: {
420
+ type: 'text',
421
+ text: parts.join(' '),
422
+ },
423
+ },
424
+ ],
425
+ };
426
+ });
211
427
  }
212
428
  /**
213
429
  * Create Express app with MCP endpoints
@@ -227,6 +443,202 @@ export class MeetbotMCPStreamable {
227
443
  }
228
444
  next();
229
445
  });
446
+ // MCP server card (well-known discovery) – structured for Smithery Tool Quality (annotations, param descriptions, title)
447
+ const toolAnnotations = {
448
+ audience: ['user', 'assistant'],
449
+ priority: 0.8,
450
+ lastModified: '2025-03-05T00:00:00Z',
451
+ };
452
+ const serverCard = {
453
+ serverInfo: {
454
+ name: 'meetbot-mcp',
455
+ version: '1.3.0',
456
+ description: 'Meet.bot MCP Server for scheduling and booking. Lets AI agents check availability, get scheduling page info, and book meetings on your behalf.',
457
+ },
458
+ authentication: {
459
+ required: true,
460
+ schemes: ['bearer'],
461
+ instructions: 'Provide your Meet.bot API key as a Bearer token in the Authorization header: Authorization: Bearer <your-api-key>',
462
+ },
463
+ tools: [
464
+ {
465
+ name: 'get_scheduling_pages',
466
+ title: 'Get Scheduling Pages',
467
+ description: 'Get all scheduling pages for the authenticated user',
468
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false },
469
+ annotations: toolAnnotations,
470
+ },
471
+ {
472
+ name: 'get_page_info',
473
+ title: 'Get Page Information',
474
+ description: 'Get information about a specific scheduling page',
475
+ inputSchema: {
476
+ type: 'object',
477
+ description: 'Parameters for fetching a single scheduling page.',
478
+ properties: {
479
+ page: { type: 'string', description: 'The URL of the scheduling page (e.g. https://meet.bot/your-page)' },
480
+ },
481
+ required: ['page'],
482
+ additionalProperties: false,
483
+ },
484
+ annotations: toolAnnotations,
485
+ },
486
+ {
487
+ name: 'get_available_slots',
488
+ title: 'Get Available Slots',
489
+ description: 'Get available booking slots for a scheduling page',
490
+ inputSchema: {
491
+ type: 'object',
492
+ description: 'Parameters for querying available slots (optional filters for count, date range, timezone, and booking links).',
493
+ properties: {
494
+ page: { type: 'string', description: 'The URL of the scheduling page (e.g. https://meet.bot/your-page)' },
495
+ count: { type: 'number', description: 'Maximum number of slots to return (defaults to server limit)' },
496
+ start: { type: 'string', description: 'Start date for the range in YYYY-MM-DD format' },
497
+ end: { type: 'string', description: 'End date for the range in YYYY-MM-DD format' },
498
+ timezone: { type: 'string', description: 'IANA timezone for slot times (e.g. America/New_York, Europe/London)' },
499
+ booking_link: { type: 'boolean', description: 'If true, include shareable booking links in the response' },
500
+ },
501
+ required: ['page'],
502
+ additionalProperties: false,
503
+ },
504
+ annotations: toolAnnotations,
505
+ },
506
+ {
507
+ name: 'book_meeting',
508
+ title: 'Book Meeting',
509
+ description: 'Book a new meeting slot',
510
+ inputSchema: {
511
+ type: 'object',
512
+ description: 'Parameters for booking a meeting (guest details and chosen slot).',
513
+ properties: {
514
+ page: { type: 'string', description: 'The URL of the scheduling page (e.g. https://meet.bot/your-page)' },
515
+ guest_email: { type: 'string', description: 'Email address of the guest (used for calendar invite and confirmation)' },
516
+ guest_name: { type: 'string', description: 'Full name of the guest' },
517
+ notes: { type: 'string', description: 'Optional notes to include with the meeting (e.g. agenda, call details)' },
518
+ start: { type: 'string', description: 'Start time in ISO 8601 format (e.g. 2025-03-10T14:00:00Z); must be an available slot' },
519
+ },
520
+ required: ['page', 'guest_email', 'guest_name', 'start'],
521
+ additionalProperties: false,
522
+ },
523
+ annotations: toolAnnotations,
524
+ },
525
+ {
526
+ name: 'health_check',
527
+ title: 'Health Check',
528
+ description: 'Check if the Meet.bot API client is healthy and the Bearer token is valid',
529
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false },
530
+ annotations: toolAnnotations,
531
+ },
532
+ {
533
+ name: 'list_webhooks',
534
+ title: 'List Webhooks',
535
+ description: "List the authenticated user's outbound booking webhooks (fired on booking_received, booking_rescheduled and booking_cancelled)",
536
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false },
537
+ annotations: toolAnnotations,
538
+ },
539
+ {
540
+ name: 'set_webhook',
541
+ title: 'Set Webhook',
542
+ description: 'Create or update an outbound booking webhook. Omit id to create (webhook_url required); pass id to update. Meet.bot POSTs a JWT-signed (HS256) JSON payload to the URL on each booking event.',
543
+ inputSchema: {
544
+ type: 'object',
545
+ description: 'Parameters for creating or updating a booking webhook.',
546
+ properties: {
547
+ id: { type: 'number', description: 'Webhook id to update; omit to create a new one' },
548
+ webhook_url: { type: 'string', description: 'HTTPS URL we POST booking events to (required when creating)' },
549
+ description: { type: 'string', description: 'Optional label for the webhook' },
550
+ coverage: { type: 'string', enum: ['all', 'selected'], description: "'all' (default) fires for every page including ones created later; 'selected' only for the pages in `pages`" },
551
+ scope: { type: 'string', enum: ['self', 'team'], description: "'self' (default) your own pages; 'team' (team admins only) also fires for teammates' bookings" },
552
+ pages: { type: 'array', items: { type: 'number' }, description: "Page ids to cover when coverage='selected'" },
553
+ is_active: { type: 'boolean', description: 'Whether the webhook is active (default true)' },
554
+ },
555
+ additionalProperties: false,
556
+ },
557
+ annotations: toolAnnotations,
558
+ },
559
+ {
560
+ name: 'delete_webhook',
561
+ title: 'Delete Webhook',
562
+ description: "Delete one of the authenticated user's webhooks by id",
563
+ inputSchema: {
564
+ type: 'object',
565
+ description: 'Parameters for deleting a webhook.',
566
+ properties: {
567
+ id: { type: 'number', description: 'The webhook id to delete' },
568
+ },
569
+ required: ['id'],
570
+ additionalProperties: false,
571
+ },
572
+ annotations: toolAnnotations,
573
+ },
574
+ ],
575
+ prompts: [
576
+ {
577
+ name: 'schedule_meeting',
578
+ title: 'Schedule a meeting',
579
+ description: 'Get step-by-step instructions to schedule a meeting using this server. Use this when the user wants to book a meeting or check availability.',
580
+ arguments: [],
581
+ },
582
+ {
583
+ name: 'check_availability',
584
+ title: 'Check availability',
585
+ description: 'When is this person next free? Checks availability only; no booking. Use for pre-meeting research or answering "when are they free?"',
586
+ arguments: [
587
+ { name: 'page', description: 'The scheduling page URL (e.g. https://meet.bot/your-page)', required: true },
588
+ { name: 'days_ahead', description: 'Number of days to look ahead (default: 7)', required: false },
589
+ ],
590
+ },
591
+ {
592
+ name: 'book_for_guest',
593
+ title: 'Book for guest',
594
+ description: 'Fast path when you already have all details. Books a meeting directly using the preferred time; useful when another system provides the slot.',
595
+ arguments: [
596
+ { name: 'page', description: 'The scheduling page URL', required: true },
597
+ { name: 'guest_name', description: 'Full name of the guest', required: true },
598
+ { name: 'guest_email', description: 'Email address of the guest', required: true },
599
+ { name: 'preferred_time', description: 'Preferred start time in ISO 8601 format (e.g. 2025-03-10T14:00:00Z); should be an available slot', required: true },
600
+ ],
601
+ },
602
+ {
603
+ name: 'share_booking_link',
604
+ title: 'Share booking link',
605
+ description: 'Send the user a link to book. Returns shareable booking links; the guest picks their own slot. No booking is performed.',
606
+ arguments: [
607
+ { name: 'page', description: 'The scheduling page URL', required: true },
608
+ { name: 'count', description: 'Number of slot links to return (default: 3)', required: false },
609
+ ],
610
+ },
611
+ {
612
+ name: 'list_my_pages',
613
+ title: 'List my scheduling pages',
614
+ description: "Starting point when you don't know which page to use. Lists the user's scheduling pages with a brief description of each.",
615
+ arguments: [],
616
+ },
617
+ {
618
+ name: 'suggest_times',
619
+ title: 'Suggest times',
620
+ description: 'Offer the user some options. Returns a clean list of available slots for them to pick from; no booking until they choose.',
621
+ arguments: [
622
+ { name: 'page', description: 'The scheduling page URL', required: true },
623
+ { name: 'count', description: 'Number of slot options to return (default: 3)', required: false },
624
+ { name: 'timezone', description: 'IANA timezone for displaying times (e.g. America/New_York)', required: false },
625
+ { name: 'start_date', description: 'Start of range in YYYY-MM-DD format', required: false },
626
+ { name: 'end_date', description: 'End of range in YYYY-MM-DD format', required: false },
627
+ ],
628
+ },
629
+ ],
630
+ };
631
+ app.get('/.well-known/mcp/server-card.json', (_req, res) => {
632
+ res.type('application/json').json(serverCard);
633
+ });
634
+ // Glama directory ownership claim — Glama verifies the maintainer email
635
+ // against the claiming account, then unlocks listing metadata/control.
636
+ app.get('/.well-known/glama.json', (_req, res) => {
637
+ res.type('application/json').json({
638
+ $schema: 'https://glama.ai/mcp/schemas/glama.json',
639
+ maintainers: [{ email: 'vincent@meet.bot' }],
640
+ });
641
+ });
230
642
  // MCP POST endpoint (naked path for dedicated MCP subdomain)
231
643
  app.post('/', async (req, res) => {
232
644
  const sessionId = req.headers['mcp-session-id'];