google-tools-mcp 1.2.12 → 2.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 (97) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +537 -276
  3. package/dist/cachedToolsList.js +52 -52
  4. package/dist/googleDocsApiHelpers.js +76 -12
  5. package/dist/helpers.js +572 -252
  6. package/dist/httpAuth.js +296 -0
  7. package/dist/index.js +162 -11
  8. package/dist/logger.js +87 -87
  9. package/dist/markdown-transformer/docsToMarkdown.js +128 -0
  10. package/dist/markdown-transformer/index.js +17 -7
  11. package/dist/markdown-transformer/markdownToDocs.js +185 -27
  12. package/dist/readTracker.js +136 -17
  13. package/dist/sessionContext.js +37 -0
  14. package/dist/setup.js +187 -9
  15. package/dist/tools/calendar/getBusy.js +64 -64
  16. package/dist/tools/calendar/getEvents.js +141 -141
  17. package/dist/tools/calendar/getFree.js +225 -225
  18. package/dist/tools/calendar/index.js +19 -19
  19. package/dist/tools/calendar/listCalendars.js +38 -38
  20. package/dist/tools/calendar/listRecurringInstances.js +83 -83
  21. package/dist/tools/calendar/manageCalendar.js +121 -121
  22. package/dist/tools/calendar/manageEvent.js +258 -258
  23. package/dist/tools/calendar/moveEvent.js +66 -66
  24. package/dist/tools/docs/addTab.js +10 -12
  25. package/dist/tools/docs/appendToGoogleDoc.js +9 -4
  26. package/dist/tools/docs/deleteRange.js +9 -4
  27. package/dist/tools/docs/findAndReplace.js +9 -4
  28. package/dist/tools/docs/formatting/applyParagraphStyle.js +4 -1
  29. package/dist/tools/docs/formatting/getFormatting.js +171 -171
  30. package/dist/tools/docs/insertImage.js +15 -2
  31. package/dist/tools/docs/insertPageBreak.js +4 -1
  32. package/dist/tools/docs/insertTable.js +4 -1
  33. package/dist/tools/docs/insertTableWithData.js +4 -1
  34. package/dist/tools/docs/modifyText.js +14 -4
  35. package/dist/tools/docs/modifyText.test.js +84 -84
  36. package/dist/tools/docs/readGoogleDoc.js +71 -11
  37. package/dist/tools/docs/renameTab.js +12 -14
  38. package/dist/tools/drafts.js +165 -165
  39. package/dist/tools/drive/createDocument.js +12 -0
  40. package/dist/tools/drive/downloadFile.js +268 -268
  41. package/dist/tools/drive/getFileInfo.js +48 -48
  42. package/dist/tools/drive/getFilePath.js +50 -50
  43. package/dist/tools/drive/listDriveFiles.js +112 -112
  44. package/dist/tools/drive/listSharedDrives.js +52 -52
  45. package/dist/tools/drive/listSharedWithMe.js +97 -97
  46. package/dist/tools/drive/uploadFile.js +111 -111
  47. package/dist/tools/extras/index.js +9 -9
  48. package/dist/tools/extras/readDriveFile.js +197 -196
  49. package/dist/tools/extras/readFile.js +84 -84
  50. package/dist/tools/extras/searchFileContents.js +81 -81
  51. package/dist/tools/forms/batchUpdateForm.js +81 -81
  52. package/dist/tools/forms/createForm.js +54 -54
  53. package/dist/tools/forms/getForm.js +118 -118
  54. package/dist/tools/forms/getFormResponse.js +45 -45
  55. package/dist/tools/forms/index.js +15 -15
  56. package/dist/tools/forms/listFormResponses.js +56 -56
  57. package/dist/tools/forms/setPublishSettings.js +59 -59
  58. package/dist/tools/gmail/drafts.js +165 -165
  59. package/dist/tools/gmail/labels.js +54 -83
  60. package/dist/tools/gmail/messages.js +444 -441
  61. package/dist/tools/gmail/settings.js +358 -528
  62. package/dist/tools/gmail/threads.js +286 -164
  63. package/dist/tools/index.js +511 -473
  64. package/dist/tools/labels.js +103 -103
  65. package/dist/tools/legacyAliases.js +426 -0
  66. package/dist/tools/maps/directions.js +72 -0
  67. package/dist/tools/maps/geocode.js +16 -0
  68. package/dist/tools/maps/index.js +15 -0
  69. package/dist/tools/maps/mapsClient.js +83 -0
  70. package/dist/tools/maps/placeDetails.js +16 -0
  71. package/dist/tools/maps/reverseGeocode.js +19 -0
  72. package/dist/tools/maps/searchNearby.js +54 -0
  73. package/dist/tools/maps/searchPlaces.js +24 -0
  74. package/dist/tools/messages.js +448 -448
  75. package/dist/tools/settings.js +528 -528
  76. package/dist/tools/slides/createPresentation.js +104 -104
  77. package/dist/tools/slides/createShape.js +92 -92
  78. package/dist/tools/slides/createTextBox.js +82 -82
  79. package/dist/tools/slides/deleteSlide.js +30 -30
  80. package/dist/tools/slides/duplicateSlide.js +37 -37
  81. package/dist/tools/slides/exportThumbnail.js +42 -42
  82. package/dist/tools/slides/formatParagraph.js +72 -72
  83. package/dist/tools/slides/formatText.js +84 -84
  84. package/dist/tools/slides/getPresentation.js +87 -87
  85. package/dist/tools/slides/index.js +33 -33
  86. package/dist/tools/slides/reorderSlides.js +41 -41
  87. package/dist/tools/slides/replaceAllText.js +46 -46
  88. package/dist/tools/slides/setBackground.js +58 -58
  89. package/dist/tools/slides/speakerNotes.js +102 -102
  90. package/dist/tools/slides/styleShape.js +111 -111
  91. package/dist/tools/slides/updatePresentation.js +128 -128
  92. package/dist/tools/threads.js +145 -145
  93. package/dist/tools/utils/appendMarkdownToGoogleDoc.js +30 -6
  94. package/dist/tools/utils/replaceDocumentWithMarkdown.js +71 -8
  95. package/dist/updateCheck.js +202 -0
  96. package/dist/workspace.js +122 -0
  97. package/package.json +82 -81
@@ -1,225 +1,225 @@
1
- import { UserError } from 'fastmcp';
2
- import { z } from 'zod';
3
- import { getCalendarClient } from '../../clients.js';
4
-
5
- export function register(server) {
6
- server.addTool({
7
- name: 'get_free',
8
- description:
9
- 'Finds available time slots where all specified people are free. ' +
10
- 'Takes a duration and working hours as boundaries, then returns open slots. ' +
11
- 'Uses the freebusy API under the hood — works across your org without calendar sharing.',
12
- parameters: z.object({
13
- time_min: z
14
- .string()
15
- .describe('Start of search range in RFC3339 format (e.g. "2025-03-15T00:00:00Z").'),
16
- time_max: z
17
- .string()
18
- .describe('End of search range in RFC3339 format (e.g. "2025-03-21T23:59:59Z").'),
19
- duration_minutes: z
20
- .number()
21
- .describe('Required slot duration in minutes (e.g. 30, 60).'),
22
- calendar_ids: z
23
- .array(z.string())
24
- .optional()
25
- .default(['primary'])
26
- .describe('Calendar IDs or email addresses to check mutual availability. Defaults to ["primary"].'),
27
- working_hours_start: z
28
- .string()
29
- .optional()
30
- .default('09:00')
31
- .describe('Start of working hours in HH:MM format (e.g. "09:00"). Defaults to "09:00".'),
32
- working_hours_end: z
33
- .string()
34
- .optional()
35
- .default('17:00')
36
- .describe('End of working hours in HH:MM format (e.g. "17:00"). Defaults to "17:00".'),
37
- timezone: z
38
- .string()
39
- .optional()
40
- .default('UTC')
41
- .describe('Timezone for working hours (e.g. "America/New_York"). Defaults to "UTC".'),
42
- max_results: z
43
- .number()
44
- .optional()
45
- .default(10)
46
- .describe('Maximum number of available slots to return. Defaults to 10.'),
47
- }),
48
- execute: async (args, { log }) => {
49
- const calendar = await getCalendarClient();
50
- log.info(
51
- `Finding ${args.duration_minutes}min free slots for ${args.calendar_ids.join(', ')} ` +
52
- `between ${args.working_hours_start}-${args.working_hours_end} ${args.timezone}`
53
- );
54
-
55
- try {
56
- // 1. Query freebusy for all calendars
57
- const response = await calendar.freebusy.query({
58
- requestBody: {
59
- timeMin: args.time_min,
60
- timeMax: args.time_max,
61
- timeZone: args.timezone,
62
- items: args.calendar_ids.map((id) => ({ id })),
63
- },
64
- });
65
-
66
- const calendars = response.data.calendars || {};
67
-
68
- // Check for errors
69
- const errors = [];
70
- for (const [calId, data] of Object.entries(calendars)) {
71
- if (data.errors?.length) {
72
- errors.push(`${calId}: ${data.errors[0].reason}`);
73
- }
74
- }
75
- if (errors.length) {
76
- throw new UserError(
77
- `Could not check availability for some calendars:\n${errors.join('\n')}`
78
- );
79
- }
80
-
81
- // 2. Merge all busy blocks
82
- const allBusy = [];
83
- for (const data of Object.values(calendars)) {
84
- for (const block of data.busy || []) {
85
- allBusy.push({
86
- start: new Date(block.start).getTime(),
87
- end: new Date(block.end).getTime(),
88
- });
89
- }
90
- }
91
-
92
- // Sort and merge overlapping busy blocks
93
- allBusy.sort((a, b) => a.start - b.start);
94
- const merged = [];
95
- for (const block of allBusy) {
96
- if (merged.length && block.start <= merged[merged.length - 1].end) {
97
- merged[merged.length - 1].end = Math.max(merged[merged.length - 1].end, block.end);
98
- } else {
99
- merged.push({ ...block });
100
- }
101
- }
102
-
103
- // 3. Find free slots within working hours
104
- const rangeStart = new Date(args.time_min);
105
- const rangeEnd = new Date(args.time_max);
106
- const durationMs = args.duration_minutes * 60 * 1000;
107
- const [whStartH, whStartM] = args.working_hours_start.split(':').map(Number);
108
- const [whEndH, whEndM] = args.working_hours_end.split(':').map(Number);
109
-
110
- const freeSlots = [];
111
- const currentDay = new Date(rangeStart);
112
-
113
- // Iterate day by day
114
- while (currentDay < rangeEnd && freeSlots.length < args.max_results) {
115
- const dayStart = getTimeInTimezone(currentDay, whStartH, whStartM, args.timezone);
116
- const dayEnd = getTimeInTimezone(currentDay, whEndH, whEndM, args.timezone);
117
-
118
- if (dayEnd > rangeStart.getTime() && dayStart < rangeEnd.getTime()) {
119
- const effectiveStart = Math.max(dayStart, rangeStart.getTime());
120
- const effectiveEnd = Math.min(dayEnd, rangeEnd.getTime());
121
-
122
- // Find free windows in this day
123
- const dayBusy = merged.filter(
124
- (b) => b.start < effectiveEnd && b.end > effectiveStart
125
- );
126
-
127
- let cursor = effectiveStart;
128
- for (const block of dayBusy) {
129
- if (block.start > cursor) {
130
- // Free gap before this busy block
131
- const gapEnd = Math.min(block.start, effectiveEnd);
132
- if (gapEnd - cursor >= durationMs && freeSlots.length < args.max_results) {
133
- freeSlots.push({
134
- start: new Date(cursor).toISOString(),
135
- end: new Date(gapEnd).toISOString(),
136
- duration_minutes: Math.round((gapEnd - cursor) / 60000),
137
- });
138
- }
139
- }
140
- cursor = Math.max(cursor, block.end);
141
- }
142
- // Free gap after last busy block
143
- if (effectiveEnd - cursor >= durationMs && freeSlots.length < args.max_results) {
144
- freeSlots.push({
145
- start: new Date(cursor).toISOString(),
146
- end: new Date(effectiveEnd).toISOString(),
147
- duration_minutes: Math.round((effectiveEnd - cursor) / 60000),
148
- });
149
- }
150
- }
151
-
152
- // Move to next day
153
- currentDay.setDate(currentDay.getDate() + 1);
154
- }
155
-
156
- return JSON.stringify(
157
- {
158
- query: {
159
- calendars: args.calendar_ids,
160
- duration_minutes: args.duration_minutes,
161
- working_hours: `${args.working_hours_start}-${args.working_hours_end} ${args.timezone}`,
162
- },
163
- available_slots: freeSlots,
164
- total_found: freeSlots.length,
165
- },
166
- null,
167
- 2
168
- );
169
- } catch (error) {
170
- if (error instanceof UserError) throw error;
171
- log.error(`Error finding free slots: ${error.message || error}`);
172
- throw new UserError(`Failed to find available slots: ${error.message || 'Unknown error'}`);
173
- }
174
- },
175
- });
176
- }
177
-
178
- /**
179
- * Get a timestamp for a specific time-of-day on a given date in a timezone.
180
- * Uses Intl.DateTimeFormat to resolve the timezone offset.
181
- */
182
- function getTimeInTimezone(date, hours, minutes, timezone) {
183
- // Build a date string for the target day
184
- const year = date.getFullYear();
185
- const month = String(date.getMonth() + 1).padStart(2, '0');
186
- const day = String(date.getDate()).padStart(2, '0');
187
- const hh = String(hours).padStart(2, '0');
188
- const mm = String(minutes).padStart(2, '0');
189
-
190
- // Create a date in the local timezone of the server, then adjust
191
- // We use a formatter to figure out the offset
192
- try {
193
- const formatter = new Intl.DateTimeFormat('en-US', {
194
- timeZone: timezone,
195
- year: 'numeric',
196
- month: '2-digit',
197
- day: '2-digit',
198
- hour: '2-digit',
199
- minute: '2-digit',
200
- second: '2-digit',
201
- hour12: false,
202
- });
203
-
204
- // Create a reference point at midnight UTC for this date
205
- const refUtc = new Date(`${year}-${month}-${day}T${hh}:${mm}:00Z`);
206
-
207
- // Format that UTC time in the target timezone to see what time it appears as
208
- const parts = formatter.formatToParts(refUtc);
209
- const tzParts = {};
210
- for (const p of parts) {
211
- if (p.type !== 'literal') tzParts[p.type] = parseInt(p.value, 10);
212
- }
213
-
214
- // Calculate the offset: what we wanted vs what we got
215
- const gotMinutes = tzParts.hour * 60 + tzParts.minute;
216
- const wantedMinutes = hours * 60 + minutes;
217
- const diffMs = (gotMinutes - wantedMinutes) * 60 * 1000;
218
-
219
- // Adjust: if the timezone shows a later time, the UTC equivalent is earlier
220
- return refUtc.getTime() - diffMs;
221
- } catch {
222
- // Fallback: treat as UTC
223
- return new Date(`${year}-${month}-${day}T${hh}:${mm}:00Z`).getTime();
224
- }
225
- }
1
+ import { UserError } from 'fastmcp';
2
+ import { z } from 'zod';
3
+ import { getCalendarClient } from '../../clients.js';
4
+
5
+ export function register(server) {
6
+ server.addTool({
7
+ name: 'getFree',
8
+ description:
9
+ 'Finds available time slots where all specified people are free. ' +
10
+ 'Takes a duration and working hours as boundaries, then returns open slots. ' +
11
+ 'Uses the freebusy API under the hood — works across your org without calendar sharing.',
12
+ parameters: z.object({
13
+ time_min: z
14
+ .string()
15
+ .describe('Start of search range in RFC3339 format (e.g. "2025-03-15T00:00:00Z").'),
16
+ time_max: z
17
+ .string()
18
+ .describe('End of search range in RFC3339 format (e.g. "2025-03-21T23:59:59Z").'),
19
+ duration_minutes: z
20
+ .number()
21
+ .describe('Required slot duration in minutes (e.g. 30, 60).'),
22
+ calendar_ids: z
23
+ .array(z.string())
24
+ .optional()
25
+ .default(['primary'])
26
+ .describe('Calendar IDs or email addresses to check mutual availability. Defaults to ["primary"].'),
27
+ working_hours_start: z
28
+ .string()
29
+ .optional()
30
+ .default('09:00')
31
+ .describe('Start of working hours in HH:MM format (e.g. "09:00"). Defaults to "09:00".'),
32
+ working_hours_end: z
33
+ .string()
34
+ .optional()
35
+ .default('17:00')
36
+ .describe('End of working hours in HH:MM format (e.g. "17:00"). Defaults to "17:00".'),
37
+ timezone: z
38
+ .string()
39
+ .optional()
40
+ .default('UTC')
41
+ .describe('Timezone for working hours (e.g. "America/New_York"). Defaults to "UTC".'),
42
+ max_results: z
43
+ .number()
44
+ .optional()
45
+ .default(10)
46
+ .describe('Maximum number of available slots to return. Defaults to 10.'),
47
+ }),
48
+ execute: async (args, { log }) => {
49
+ const calendar = await getCalendarClient();
50
+ log.info(
51
+ `Finding ${args.duration_minutes}min free slots for ${args.calendar_ids.join(', ')} ` +
52
+ `between ${args.working_hours_start}-${args.working_hours_end} ${args.timezone}`
53
+ );
54
+
55
+ try {
56
+ // 1. Query freebusy for all calendars
57
+ const response = await calendar.freebusy.query({
58
+ requestBody: {
59
+ timeMin: args.time_min,
60
+ timeMax: args.time_max,
61
+ timeZone: args.timezone,
62
+ items: args.calendar_ids.map((id) => ({ id })),
63
+ },
64
+ });
65
+
66
+ const calendars = response.data.calendars || {};
67
+
68
+ // Check for errors
69
+ const errors = [];
70
+ for (const [calId, data] of Object.entries(calendars)) {
71
+ if (data.errors?.length) {
72
+ errors.push(`${calId}: ${data.errors[0].reason}`);
73
+ }
74
+ }
75
+ if (errors.length) {
76
+ throw new UserError(
77
+ `Could not check availability for some calendars:\n${errors.join('\n')}`
78
+ );
79
+ }
80
+
81
+ // 2. Merge all busy blocks
82
+ const allBusy = [];
83
+ for (const data of Object.values(calendars)) {
84
+ for (const block of data.busy || []) {
85
+ allBusy.push({
86
+ start: new Date(block.start).getTime(),
87
+ end: new Date(block.end).getTime(),
88
+ });
89
+ }
90
+ }
91
+
92
+ // Sort and merge overlapping busy blocks
93
+ allBusy.sort((a, b) => a.start - b.start);
94
+ const merged = [];
95
+ for (const block of allBusy) {
96
+ if (merged.length && block.start <= merged[merged.length - 1].end) {
97
+ merged[merged.length - 1].end = Math.max(merged[merged.length - 1].end, block.end);
98
+ } else {
99
+ merged.push({ ...block });
100
+ }
101
+ }
102
+
103
+ // 3. Find free slots within working hours
104
+ const rangeStart = new Date(args.time_min);
105
+ const rangeEnd = new Date(args.time_max);
106
+ const durationMs = args.duration_minutes * 60 * 1000;
107
+ const [whStartH, whStartM] = args.working_hours_start.split(':').map(Number);
108
+ const [whEndH, whEndM] = args.working_hours_end.split(':').map(Number);
109
+
110
+ const freeSlots = [];
111
+ const currentDay = new Date(rangeStart);
112
+
113
+ // Iterate day by day
114
+ while (currentDay < rangeEnd && freeSlots.length < args.max_results) {
115
+ const dayStart = getTimeInTimezone(currentDay, whStartH, whStartM, args.timezone);
116
+ const dayEnd = getTimeInTimezone(currentDay, whEndH, whEndM, args.timezone);
117
+
118
+ if (dayEnd > rangeStart.getTime() && dayStart < rangeEnd.getTime()) {
119
+ const effectiveStart = Math.max(dayStart, rangeStart.getTime());
120
+ const effectiveEnd = Math.min(dayEnd, rangeEnd.getTime());
121
+
122
+ // Find free windows in this day
123
+ const dayBusy = merged.filter(
124
+ (b) => b.start < effectiveEnd && b.end > effectiveStart
125
+ );
126
+
127
+ let cursor = effectiveStart;
128
+ for (const block of dayBusy) {
129
+ if (block.start > cursor) {
130
+ // Free gap before this busy block
131
+ const gapEnd = Math.min(block.start, effectiveEnd);
132
+ if (gapEnd - cursor >= durationMs && freeSlots.length < args.max_results) {
133
+ freeSlots.push({
134
+ start: new Date(cursor).toISOString(),
135
+ end: new Date(gapEnd).toISOString(),
136
+ duration_minutes: Math.round((gapEnd - cursor) / 60000),
137
+ });
138
+ }
139
+ }
140
+ cursor = Math.max(cursor, block.end);
141
+ }
142
+ // Free gap after last busy block
143
+ if (effectiveEnd - cursor >= durationMs && freeSlots.length < args.max_results) {
144
+ freeSlots.push({
145
+ start: new Date(cursor).toISOString(),
146
+ end: new Date(effectiveEnd).toISOString(),
147
+ duration_minutes: Math.round((effectiveEnd - cursor) / 60000),
148
+ });
149
+ }
150
+ }
151
+
152
+ // Move to next day
153
+ currentDay.setDate(currentDay.getDate() + 1);
154
+ }
155
+
156
+ return JSON.stringify(
157
+ {
158
+ query: {
159
+ calendars: args.calendar_ids,
160
+ duration_minutes: args.duration_minutes,
161
+ working_hours: `${args.working_hours_start}-${args.working_hours_end} ${args.timezone}`,
162
+ },
163
+ available_slots: freeSlots,
164
+ total_found: freeSlots.length,
165
+ },
166
+ null,
167
+ 2
168
+ );
169
+ } catch (error) {
170
+ if (error instanceof UserError) throw error;
171
+ log.error(`Error finding free slots: ${error.message || error}`);
172
+ throw new UserError(`Failed to find available slots: ${error.message || 'Unknown error'}`);
173
+ }
174
+ },
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Get a timestamp for a specific time-of-day on a given date in a timezone.
180
+ * Uses Intl.DateTimeFormat to resolve the timezone offset.
181
+ */
182
+ function getTimeInTimezone(date, hours, minutes, timezone) {
183
+ // Build a date string for the target day
184
+ const year = date.getFullYear();
185
+ const month = String(date.getMonth() + 1).padStart(2, '0');
186
+ const day = String(date.getDate()).padStart(2, '0');
187
+ const hh = String(hours).padStart(2, '0');
188
+ const mm = String(minutes).padStart(2, '0');
189
+
190
+ // Create a date in the local timezone of the server, then adjust
191
+ // We use a formatter to figure out the offset
192
+ try {
193
+ const formatter = new Intl.DateTimeFormat('en-US', {
194
+ timeZone: timezone,
195
+ year: 'numeric',
196
+ month: '2-digit',
197
+ day: '2-digit',
198
+ hour: '2-digit',
199
+ minute: '2-digit',
200
+ second: '2-digit',
201
+ hour12: false,
202
+ });
203
+
204
+ // Create a reference point at midnight UTC for this date
205
+ const refUtc = new Date(`${year}-${month}-${day}T${hh}:${mm}:00Z`);
206
+
207
+ // Format that UTC time in the target timezone to see what time it appears as
208
+ const parts = formatter.formatToParts(refUtc);
209
+ const tzParts = {};
210
+ for (const p of parts) {
211
+ if (p.type !== 'literal') tzParts[p.type] = parseInt(p.value, 10);
212
+ }
213
+
214
+ // Calculate the offset: what we wanted vs what we got
215
+ const gotMinutes = tzParts.hour * 60 + tzParts.minute;
216
+ const wantedMinutes = hours * 60 + minutes;
217
+ const diffMs = (gotMinutes - wantedMinutes) * 60 * 1000;
218
+
219
+ // Adjust: if the timezone shows a later time, the UTC equivalent is earlier
220
+ return refUtc.getTime() - diffMs;
221
+ } catch {
222
+ // Fallback: treat as UTC
223
+ return new Date(`${year}-${month}-${day}T${hh}:${mm}:00Z`).getTime();
224
+ }
225
+ }
@@ -1,19 +1,19 @@
1
- import { register as listCalendars } from './listCalendars.js';
2
- import { register as getEvents } from './getEvents.js';
3
- import { register as manageEvent } from './manageEvent.js';
4
- import { register as getBusy } from './getBusy.js';
5
- import { register as getFree } from './getFree.js';
6
- import { register as moveEvent } from './moveEvent.js';
7
- import { register as listRecurringInstances } from './listRecurringInstances.js';
8
- import { register as manageCalendar } from './manageCalendar.js';
9
-
10
- export function registerCalendarTools(server) {
11
- listCalendars(server);
12
- getEvents(server);
13
- manageEvent(server);
14
- getBusy(server);
15
- getFree(server);
16
- moveEvent(server);
17
- listRecurringInstances(server);
18
- manageCalendar(server);
19
- }
1
+ import { register as listCalendars } from './listCalendars.js';
2
+ import { register as getEvents } from './getEvents.js';
3
+ import { register as manageEvent } from './manageEvent.js';
4
+ import { register as getBusy } from './getBusy.js';
5
+ import { register as getFree } from './getFree.js';
6
+ import { register as moveEvent } from './moveEvent.js';
7
+ import { register as listRecurringInstances } from './listRecurringInstances.js';
8
+ import { register as manageCalendar } from './manageCalendar.js';
9
+
10
+ export function registerCalendarTools(server) {
11
+ listCalendars(server);
12
+ getEvents(server);
13
+ manageEvent(server);
14
+ getBusy(server);
15
+ getFree(server);
16
+ moveEvent(server);
17
+ listRecurringInstances(server);
18
+ manageCalendar(server);
19
+ }
@@ -1,38 +1,38 @@
1
- import { UserError } from 'fastmcp';
2
- import { z } from 'zod';
3
- import { getCalendarClient } from '../../clients.js';
4
-
5
- export function register(server) {
6
- server.addTool({
7
- name: 'list_calendars',
8
- description:
9
- 'Lists all calendars accessible to the authenticated user. Returns calendar name, ID, access role, and whether it is the primary calendar.',
10
- parameters: z.object({}),
11
- execute: async (_args, { log }) => {
12
- const calendar = await getCalendarClient();
13
- log.info('Listing calendars');
14
-
15
- try {
16
- const response = await calendar.calendarList.list();
17
- const calendars = response.data.items || [];
18
-
19
- const results = calendars.map((cal) => ({
20
- id: cal.id,
21
- summary: cal.summary || cal.id,
22
- description: cal.description || null,
23
- primary: cal.primary || false,
24
- accessRole: cal.accessRole,
25
- timeZone: cal.timeZone,
26
- backgroundColor: cal.backgroundColor || null,
27
- }));
28
-
29
- return JSON.stringify(results, null, 2);
30
- } catch (error) {
31
- log.error(`Error listing calendars: ${error.message || error}`);
32
- if (error.code === 401)
33
- throw new UserError('Authentication failed. Try logging out and re-authenticating.');
34
- throw new UserError(`Failed to list calendars: ${error.message || 'Unknown error'}`);
35
- }
36
- },
37
- });
38
- }
1
+ import { UserError } from 'fastmcp';
2
+ import { z } from 'zod';
3
+ import { getCalendarClient } from '../../clients.js';
4
+
5
+ export function register(server) {
6
+ server.addTool({
7
+ name: 'listCalendars',
8
+ description:
9
+ 'Lists all calendars accessible to the authenticated user. Returns calendar name, ID, access role, and whether it is the primary calendar.',
10
+ parameters: z.object({}),
11
+ execute: async (_args, { log }) => {
12
+ const calendar = await getCalendarClient();
13
+ log.info('Listing calendars');
14
+
15
+ try {
16
+ const response = await calendar.calendarList.list();
17
+ const calendars = response.data.items || [];
18
+
19
+ const results = calendars.map((cal) => ({
20
+ id: cal.id,
21
+ summary: cal.summary || cal.id,
22
+ description: cal.description || null,
23
+ primary: cal.primary || false,
24
+ accessRole: cal.accessRole,
25
+ timeZone: cal.timeZone,
26
+ backgroundColor: cal.backgroundColor || null,
27
+ }));
28
+
29
+ return JSON.stringify(results, null, 2);
30
+ } catch (error) {
31
+ log.error(`Error listing calendars: ${error.message || error}`);
32
+ if (error.code === 401)
33
+ throw new UserError('Authentication failed. Try logging out and re-authenticating.');
34
+ throw new UserError(`Failed to list calendars: ${error.message || 'Unknown error'}`);
35
+ }
36
+ },
37
+ });
38
+ }