@adrata/adrata-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +548 -0
  2. package/access/auth.js +289 -0
  3. package/access/oauth.js +1059 -0
  4. package/access/resource-metadata.js +167 -0
  5. package/access/tiers.js +422 -0
  6. package/analytics.js +634 -0
  7. package/api-bridge.js +499 -0
  8. package/governance/money.js +141 -0
  9. package/output-formatter.js +589 -0
  10. package/package.json +68 -0
  11. package/resources.js +246 -0
  12. package/security.js +690 -0
  13. package/server.js +2139 -0
  14. package/server.json +55 -0
  15. package/skills/backlog-triage/SKILL.md +115 -0
  16. package/skills/board-review/SKILL.md +96 -0
  17. package/skills/incident-to-card/SKILL.md +126 -0
  18. package/skills/log-outreach.md +62 -0
  19. package/skills/ship-the-card/SKILL.md +155 -0
  20. package/tool-annotations.js +269 -0
  21. package/tools/billing.js +149 -0
  22. package/tools/email-tools.js +652 -0
  23. package/tools/enterprise-tools.js +651 -0
  24. package/tools/free-search.js +160 -0
  25. package/tools/memory.js +440 -0
  26. package/tools/morning-brief.js +551 -0
  27. package/tools/paper-tools.js +563 -0
  28. package/tools/scheduling.js +322 -0
  29. package/tools/work-board-tools.js +758 -0
  30. package/toolsets/communications.js +276 -0
  31. package/toolsets/crm.js +495 -0
  32. package/toolsets/extensibility.js +1131 -0
  33. package/toolsets/infrastructure.js +757 -0
  34. package/toolsets/intelligence.js +232 -0
  35. package/toolsets/knowledge.js +154 -0
  36. package/toolsets/matrix.js +217 -0
  37. package/toolsets/outreach.js +432 -0
  38. package/toolsets/prospecting.js +314 -0
  39. package/toolsets/revenue/always-loaded.js +341 -0
  40. package/toolsets/revenue/sloan-tools.js +81 -0
  41. package/transport-http.js +505 -0
@@ -0,0 +1,322 @@
1
+ /**
2
+ * Demo Scheduling Module — Cal.com Integration
3
+ *
4
+ * Free tier: get_demo_availability, schedule_demo (no auth required)
5
+ * Enterprise tier: schedule_meeting (creates meeting on user's connected calendar)
6
+ *
7
+ * Environment:
8
+ * CAL_COM_API_KEY — Cal.com API key for fetching availability and creating bookings
9
+ *
10
+ * Fallback: if Cal.com API is unavailable, returns a direct booking link URL.
11
+ */
12
+
13
+ const CAL_COM_API_KEY = process.env.CAL_COM_API_KEY || '';
14
+ const CAL_COM_BASE = 'https://api.cal.com/v1';
15
+ const CAL_COM_BOOKING_LINK = 'https://cal.com/ross-adrata/demo';
16
+ const DEFAULT_EVENT_TYPE_ID = process.env.CAL_COM_EVENT_TYPE_ID || '';
17
+ const DEMO_DURATION_MINUTES = 30;
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Time zone detection
21
+ // ---------------------------------------------------------------------------
22
+
23
+ function detectTimezone() {
24
+ try {
25
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
26
+ } catch {
27
+ return 'America/Los_Angeles';
28
+ }
29
+ }
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Cal.com API helpers
33
+ // ---------------------------------------------------------------------------
34
+
35
+ async function calApi(method, path, { params, body } = {}) {
36
+ const url = new URL(path, CAL_COM_BASE);
37
+ url.searchParams.set('apiKey', CAL_COM_API_KEY);
38
+
39
+ if (params) {
40
+ for (const [k, v] of Object.entries(params)) {
41
+ if (v != null && v !== '') url.searchParams.set(k, String(v));
42
+ }
43
+ }
44
+
45
+ const res = await fetch(url.toString(), {
46
+ method,
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: body ? JSON.stringify(body) : undefined,
49
+ });
50
+
51
+ const text = await res.text();
52
+ let data;
53
+ try { data = JSON.parse(text); } catch { data = { raw: text }; }
54
+
55
+ if (!res.ok) {
56
+ throw new Error(`Cal.com ${method} ${path} -> ${res.status}: ${JSON.stringify(data).slice(0, 300)}`);
57
+ }
58
+ return data;
59
+ }
60
+
61
+ /**
62
+ * Check whether the Cal.com API is reachable and configured.
63
+ */
64
+ function isCalComConfigured() {
65
+ return CAL_COM_API_KEY.length > 0;
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // get_demo_availability
70
+ // ---------------------------------------------------------------------------
71
+
72
+ /**
73
+ * Fetch next available demo slots from Cal.com.
74
+ * Returns up to 5 slots with date, time, and duration.
75
+ * Falls back to booking link if API is unavailable.
76
+ *
77
+ * @param {string} [timezone] - IANA timezone string
78
+ * @returns {object} MCP response with availability data
79
+ */
80
+ export async function getDemoAvailability(timezone) {
81
+ const tz = timezone || detectTimezone();
82
+
83
+ if (!isCalComConfigured()) {
84
+ return {
85
+ available: false,
86
+ fallback: true,
87
+ bookingLink: CAL_COM_BOOKING_LINK,
88
+ timezone: tz,
89
+ message: `Cal.com API not configured. Book a demo directly: ${CAL_COM_BOOKING_LINK}`,
90
+ };
91
+ }
92
+
93
+ try {
94
+ // Cal.com v1: GET /availability with dateFrom/dateTo
95
+ const now = new Date();
96
+ const dateFrom = now.toISOString().split('T')[0];
97
+ const dateTo = new Date(now.getTime() + 14 * 86400000).toISOString().split('T')[0];
98
+
99
+ const data = await calApi('GET', '/availability', {
100
+ params: {
101
+ eventTypeId: DEFAULT_EVENT_TYPE_ID || undefined,
102
+ dateFrom,
103
+ dateTo,
104
+ timeZone: tz,
105
+ },
106
+ });
107
+
108
+ // Cal.com returns { slots: { "2024-01-15": ["09:00", "09:30", ...] } }
109
+ // or { availability: [...] } depending on endpoint version
110
+ const slots = [];
111
+
112
+ if (data.slots && typeof data.slots === 'object') {
113
+ for (const [date, times] of Object.entries(data.slots)) {
114
+ if (!Array.isArray(times)) continue;
115
+ for (const time of times) {
116
+ const timeStr = typeof time === 'string' ? time : time?.time;
117
+ if (timeStr) {
118
+ slots.push({
119
+ date,
120
+ time: timeStr,
121
+ duration: `${DEMO_DURATION_MINUTES} min`,
122
+ });
123
+ }
124
+ if (slots.length >= 5) break;
125
+ }
126
+ if (slots.length >= 5) break;
127
+ }
128
+ } else if (Array.isArray(data.availability)) {
129
+ for (const slot of data.availability) {
130
+ slots.push({
131
+ date: slot.date || slot.start?.split('T')[0],
132
+ time: slot.time || slot.start?.split('T')[1]?.slice(0, 5),
133
+ duration: `${DEMO_DURATION_MINUTES} min`,
134
+ });
135
+ if (slots.length >= 5) break;
136
+ }
137
+ }
138
+
139
+ if (slots.length === 0) {
140
+ return {
141
+ available: false,
142
+ fallback: true,
143
+ bookingLink: CAL_COM_BOOKING_LINK,
144
+ timezone: tz,
145
+ message: `No available slots in the next 14 days. Book directly: ${CAL_COM_BOOKING_LINK}`,
146
+ };
147
+ }
148
+
149
+ return {
150
+ available: true,
151
+ slots,
152
+ timezone: tz,
153
+ bookingLink: CAL_COM_BOOKING_LINK,
154
+ note: 'Reply with a slot number or date/time to book.',
155
+ };
156
+ } catch (err) {
157
+ return {
158
+ available: false,
159
+ fallback: true,
160
+ bookingLink: CAL_COM_BOOKING_LINK,
161
+ timezone: tz,
162
+ message: `Could not reach Cal.com API. Book a demo directly: ${CAL_COM_BOOKING_LINK}`,
163
+ error: err.message,
164
+ };
165
+ }
166
+ }
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // schedule_demo
170
+ // ---------------------------------------------------------------------------
171
+
172
+ /**
173
+ * Book a demo meeting via Cal.com.
174
+ * Falls back to booking link if API is unavailable.
175
+ *
176
+ * @param {string} date - Date in YYYY-MM-DD format
177
+ * @param {string} time - Time in HH:MM format (24h)
178
+ * @param {string} name - Attendee full name
179
+ * @param {string} email - Attendee email address
180
+ * @param {string} [company] - Attendee company name
181
+ * @param {string} [timezone] - IANA timezone string
182
+ * @returns {object} MCP response with booking confirmation or fallback
183
+ */
184
+ export async function scheduleDemo({ date, time, name, email, company, timezone }) {
185
+ const tz = timezone || detectTimezone();
186
+
187
+ if (!isCalComConfigured()) {
188
+ return {
189
+ booked: false,
190
+ fallback: true,
191
+ bookingLink: CAL_COM_BOOKING_LINK,
192
+ message: `Cal.com API not configured. Book directly: ${CAL_COM_BOOKING_LINK}`,
193
+ attendee: { name, email, company },
194
+ };
195
+ }
196
+
197
+ try {
198
+ // Construct ISO start time
199
+ const startTime = `${date}T${time}:00`;
200
+
201
+ const bookingPayload = {
202
+ eventTypeId: DEFAULT_EVENT_TYPE_ID ? Number(DEFAULT_EVENT_TYPE_ID) : undefined,
203
+ start: startTime,
204
+ end: undefined, // Cal.com computes from event type duration
205
+ responses: {
206
+ name,
207
+ email,
208
+ ...(company ? { company } : {}),
209
+ },
210
+ timeZone: tz,
211
+ language: 'en',
212
+ metadata: {
213
+ source: 'adrata-mcp',
214
+ },
215
+ };
216
+
217
+ // Clean undefined keys
218
+ Object.keys(bookingPayload).forEach(k => {
219
+ if (bookingPayload[k] === undefined) delete bookingPayload[k];
220
+ });
221
+
222
+ const data = await calApi('POST', '/bookings', { body: bookingPayload });
223
+
224
+ return {
225
+ booked: true,
226
+ booking: {
227
+ id: data.id || data.uid,
228
+ title: data.title || 'Adrata Demo',
229
+ startTime: data.startTime || data.start || startTime,
230
+ endTime: data.endTime || data.end,
231
+ attendee: { name, email, company },
232
+ timezone: tz,
233
+ meetingUrl: data.metadata?.videoCallUrl || data.meetingUrl || null,
234
+ },
235
+ message: `Demo booked for ${name} (${email}) on ${date} at ${time} ${tz}. A confirmation email has been sent.`,
236
+ };
237
+ } catch (err) {
238
+ return {
239
+ booked: false,
240
+ fallback: true,
241
+ bookingLink: CAL_COM_BOOKING_LINK,
242
+ message: `Could not create booking via Cal.com. Book directly: ${CAL_COM_BOOKING_LINK}`,
243
+ error: err.message,
244
+ attendee: { name, email, company },
245
+ };
246
+ }
247
+ }
248
+
249
+ // ---------------------------------------------------------------------------
250
+ // schedule_meeting (enterprise)
251
+ // ---------------------------------------------------------------------------
252
+
253
+ /**
254
+ * Create a meeting on the user's connected calendar via the Rust API.
255
+ * Enterprise tier only — requires OAuth connection.
256
+ *
257
+ * @param {object} params - Meeting parameters
258
+ * @param {Function} api - The server's api() helper
259
+ * @returns {object} MCP response with meeting details
260
+ */
261
+ export async function scheduleMeeting({ title, eventId, date, time, duration, attendees, description, timezone, dryRun, reason, confirmationToken }, api) {
262
+ const tz = timezone || detectTimezone();
263
+
264
+ const startTime = `${date}T${time}:00`;
265
+ const durationMinutes = duration || 30;
266
+
267
+ // Calculate end time
268
+ const startDate = new Date(`${startTime}`);
269
+ const endDate = new Date(startDate.getTime() + durationMinutes * 60000);
270
+ const endTime = endDate.toISOString();
271
+
272
+ const meetingBody = {
273
+ ...(eventId ? { eventId } : {}),
274
+ title: title || 'Meeting',
275
+ startTime,
276
+ endTime,
277
+ duration: durationMinutes,
278
+ timezone: tz,
279
+ description: description || '',
280
+ attendees: Array.isArray(attendees) ? attendees : (attendees ? [attendees] : []),
281
+ };
282
+
283
+ const data = await api('POST', '/api/v1/ai-crm-tools/execute', {
284
+ body: {
285
+ toolName: 'schedule_meeting',
286
+ arguments: meetingBody,
287
+ dryRun: dryRun ?? true,
288
+ reason,
289
+ confirmationToken,
290
+ },
291
+ });
292
+
293
+ const result = data?.data || data;
294
+ const scheduled = result?.data || result;
295
+ const preview = scheduled?.dryRun === true;
296
+ const needsConfirmation = Boolean(scheduled?.confirmationToken);
297
+
298
+ return {
299
+ created: Boolean(scheduled?.id),
300
+ dryRun: preview,
301
+ confirmationRequired: needsConfirmation,
302
+ confirmationToken: scheduled?.confirmationToken,
303
+ meeting: {
304
+ id: scheduled?.id,
305
+ title: meetingBody.title,
306
+ startTime,
307
+ endTime,
308
+ duration: durationMinutes,
309
+ timezone: tz,
310
+ attendees: meetingBody.attendees,
311
+ meetingUrl: scheduled?.meetingUrl || null,
312
+ calendarSyncStatus: scheduled?.calendarSyncStatus || null,
313
+ invitationDeliveryStatus: scheduled?.invitationDeliveryStatus || null,
314
+ invitesSent: scheduled?.invitesSent === true,
315
+ },
316
+ message: scheduled?.message || (preview
317
+ ? `Meeting "${meetingBody.title}" previewed for ${date} at ${time} ${tz}; nothing was written.`
318
+ : needsConfirmation
319
+ ? 'Approval captured. Re-run with the returned confirmation token to create and queue the event.'
320
+ : `Meeting "${meetingBody.title}" was submitted. Check calendarSyncStatus before claiming invitations were sent.`),
321
+ };
322
+ }