@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.
- package/README.md +53 -21
- package/dist/cli-http.js +1 -1
- package/dist/cli-http.js.map +1 -1
- package/dist/cli-streamable.js +1 -1
- package/dist/cli-streamable.js.map +1 -1
- package/dist/mcp-server-streamable.d.ts +3 -0
- package/dist/mcp-server-streamable.d.ts.map +1 -1
- package/dist/mcp-server-streamable.js +462 -50
- package/dist/mcp-server-streamable.js.map +1 -1
- package/dist/mcp-server.d.ts +3 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +141 -47
- package/dist/mcp-server.js.map +1 -1
- package/dist/meetbot-client.d.ts +13 -1
- package/dist/meetbot-client.d.ts.map +1 -1
- package/dist/meetbot-client.js +28 -1
- package/dist/meetbot-client.js.map +1 -1
- package/dist/schemas.d.ts +61 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +21 -0
- package/dist/schemas.js.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -16,48 +16,25 @@ export class MeetbotMCPStreamable {
|
|
|
16
16
|
constructor() {
|
|
17
17
|
this.server = new McpServer({
|
|
18
18
|
name: 'meetbot-mcp',
|
|
19
|
-
version: '1.
|
|
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.
|
|
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.
|
|
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('
|
|
125
|
-
booking_link: z.boolean().optional().describe('
|
|
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.
|
|
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('
|
|
162
|
-
notes: z.string().optional().describe('
|
|
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.
|
|
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.
|
|
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'];
|