google-tools-mcp 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Karthik Senthil
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,233 @@
1
+ # google-tools-mcp
2
+
3
+ A unified MCP server for Google Workspace — Drive, Docs, Sheets, Gmail, and Calendar — with **146 tools** across 8 categories.
4
+
5
+ All tools are loaded at startup so they're immediately available to your AI agent. No discovery step needed.
6
+
7
+ ## Why This Exists
8
+
9
+ Most Google MCP servers split functionality across separate packages. This server combines everything into one — single auth token, single process, single config.
10
+
11
+ ## Features
12
+
13
+ - **146 tools** across 8 categories, all available immediately
14
+ - **Single auth token** — one OAuth flow covers Drive, Docs, Sheets, Gmail, and Calendar
15
+ - **Lazy-loading auth** — no browser popup until your first tool call
16
+ - **Multi-profile support** — separate tokens per Google account
17
+ - **No telemetry**
18
+
19
+ ## Getting Started
20
+
21
+ ### Step 1: Create Google OAuth Credentials
22
+
23
+ 1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
24
+ 2. Create a project (or use an existing one)
25
+ 3. Enable the **Google Docs API**, **Google Sheets API**, **Google Drive API**, **Gmail API**, and **Google Calendar API**
26
+ 4. Go to **Credentials** → **Create Credentials** → **OAuth Client ID**
27
+ 5. Select **Desktop application** as the application type
28
+ 6. Download the credentials or note your **Client ID** and **Client Secret**
29
+
30
+ ### Step 2: Provide Your Credentials
31
+
32
+ Choose **one** of the following methods (whichever you prefer):
33
+
34
+ #### Option A: Use `credentials.json`
35
+
36
+ Download the JSON file from Google Cloud Console and place it in either location:
37
+
38
+ ```
39
+ ~/.config/google-tools-mcp/credentials.json (recommended — shared across projects)
40
+ ./credentials.json (local to your project)
41
+ ```
42
+
43
+ That's it — no env vars needed. The server will find it automatically.
44
+
45
+ #### Option B: Create a `.env` file
46
+
47
+ Create a `.env` file in either location:
48
+
49
+ ```
50
+ ~/.config/google-tools-mcp/.env (recommended — shared across projects)
51
+ ./.env (local to your project)
52
+ ```
53
+
54
+ With the following contents:
55
+
56
+ ```env
57
+ GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
58
+ GOOGLE_CLIENT_SECRET=your-client-secret
59
+ ```
60
+
61
+ #### Option C: Set env vars in your MCP config
62
+
63
+ Add the credentials directly to your MCP configuration:
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "google": {
69
+ "command": "npx",
70
+ "args": ["-y", "google-tools-mcp"],
71
+ "env": {
72
+ "GOOGLE_CLIENT_ID": "your-client-id",
73
+ "GOOGLE_CLIENT_SECRET": "your-client-secret"
74
+ }
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ > **Credential lookup order:** env vars → `~/.config/google-tools-mcp/.env` → project root `.env` → `~/.config/google-tools-mcp/credentials.json` → project root `credentials.json`
81
+
82
+ ### Step 3: Add to Your MCP Client
83
+
84
+ #### Claude Code (recommended)
85
+
86
+ If you used Option A or B above:
87
+
88
+ ```bash
89
+ claude mcp add google -- npx -y google-tools-mcp
90
+ ```
91
+
92
+ Or with env vars (Option C):
93
+
94
+ ```bash
95
+ claude mcp add google \
96
+ -e GOOGLE_CLIENT_ID=your-client-id \
97
+ -e GOOGLE_CLIENT_SECRET=your-client-secret \
98
+ -- npx -y google-tools-mcp
99
+ ```
100
+
101
+ #### Project-Local Installation (with profile)
102
+
103
+ Via the `claude` CLI:
104
+
105
+ ```bash
106
+ claude mcp add google \
107
+ -e GOOGLE_MCP_PROFILE=myprofile \
108
+ -- npx -y google-tools-mcp
109
+ ```
110
+
111
+ Or manually in your `.mcp.json`:
112
+
113
+ ```json
114
+ {
115
+ "mcpServers": {
116
+ "google": {
117
+ "command": "npx",
118
+ "args": ["-y", "google-tools-mcp"],
119
+ "env": {
120
+ "GOOGLE_MCP_PROFILE": "myprofile"
121
+ }
122
+ }
123
+ }
124
+ }
125
+ ```
126
+
127
+ #### Other MCP clients
128
+
129
+ Add this to your MCP configuration (e.g., `.mcp.json`, `claude_desktop_config.json`):
130
+
131
+ ```json
132
+ {
133
+ "mcpServers": {
134
+ "google": {
135
+ "command": "npx",
136
+ "args": ["-y", "google-tools-mcp"]
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ If using Option C, add an `"env"` block with your `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`.
143
+
144
+ ### Step 4: Authenticate
145
+
146
+ On your first tool call, the server will automatically open your browser for Google OAuth consent. Sign in and grant access — the token is saved to `~/.config/google-tools-mcp/token.json` for future use.
147
+
148
+ You can also run the auth flow manually anytime:
149
+
150
+ ```bash
151
+ npx google-tools-mcp auth
152
+ ```
153
+
154
+ ### Multi-Account Support
155
+
156
+ Set the `GOOGLE_MCP_PROFILE` env var to use separate tokens per profile:
157
+
158
+ ```json
159
+ {
160
+ "env": {
161
+ "GOOGLE_MCP_PROFILE": "work"
162
+ }
163
+ }
164
+ ```
165
+
166
+ This stores tokens in `~/.config/google-tools-mcp/work/` instead of the default directory.
167
+
168
+ ## Tool Categories
169
+
170
+ ### `files` (16 tools)
171
+ Google Drive file management and content reading.
172
+
173
+ `listDocuments`, `searchDocuments`, `getDocumentInfo`, `createFolder`, `listFolderContents`, `getFolderInfo`, `moveFile`, `copyFile`, `renameFile`, `deleteFile`, `createDocument`, `createDocumentFromTemplate`, `listSharedDrives`, `listSharedWithMe`, `readFile`, `searchFileContents`
174
+
175
+ ### `documents` (23 tools)
176
+ Google Docs read/write/format with markdown support.
177
+
178
+ `readDocument`, `appendText`, `insertText`, `deleteRange`, `modifyText`, `findAndReplace`, `insertTable`, `insertTableWithData`, `insertPageBreak`, `insertImage`, `listTabs`, `addTab`, `renameTab`, `applyTextStyle`, `applyParagraphStyle`, `addComment`, `deleteComment`, `getComment`, `listComments`, `replyToComment`, `resolveComment`, `appendMarkdown`, `replaceDocumentWithMarkdown`
179
+
180
+ ### `spreadsheets` (30 tools)
181
+ Google Sheets operations.
182
+
183
+ `readSpreadsheet`, `writeSpreadsheet`, `batchWrite`, `appendRows`, `clearRange`, `createSpreadsheet`, `getSpreadsheetInfo`, `addSheet`, `deleteSheet`, `duplicateSheet`, `renameSheet`, `formatCells`, `readCellFormat`, `autoResizeColumns`, `freezeRowsAndColumns`, `setColumnWidths`, `addConditionalFormatting`, `copyFormatting`, `setDropdownValidation`, `createTable`, `deleteTable`, `getTable`, `listTables`, `appendTableRows`, `updateTableRange`, `insertChart`, `deleteChart`, `groupRows`, `ungroupAllRows`, `listSpreadsheets`
184
+
185
+ ### `email` (19 tools)
186
+ Gmail messages and drafts.
187
+
188
+ `send_message`, `reply_message`, `forward_message`, `get_message`, `list_messages`, `modify_message`, `delete_message`, `trash_message`, `untrash_message`, `batch_delete_messages`, `batch_modify_messages`, `batch_get_messages`, `get_attachment`, `create_draft`, `update_draft`, `delete_draft`, `get_draft`, `list_drafts`, `send_draft`
189
+
190
+ ### `email_threads` (7 tools)
191
+ Gmail thread-level operations.
192
+
193
+ `get_thread`, `list_threads`, `batch_get_threads`, `modify_thread`, `delete_thread`, `trash_thread`, `untrash_thread`
194
+
195
+ ### `email_labels` (6 tools)
196
+ Gmail label management.
197
+
198
+ `create_label`, `delete_label`, `get_label`, `list_labels`, `patch_label`, `update_label`
199
+
200
+ ### `email_settings` (37 tools)
201
+ Gmail admin and configuration.
202
+
203
+ `get_auto_forwarding`, `update_auto_forwarding`, `get_imap`, `update_imap`, `get_language`, `update_language`, `get_pop`, `update_pop`, `get_vacation`, `update_vacation`, `add_delegate`, `remove_delegate`, `get_delegate`, `list_delegates`, `create_filter`, `delete_filter`, `get_filter`, `list_filters`, `create_forwarding_address`, `delete_forwarding_address`, `get_forwarding_address`, `list_forwarding_addresses`, `create_send_as`, `delete_send_as`, `get_send_as`, `list_send_as`, `patch_send_as`, `update_send_as`, `verify_send_as`, `delete_smime_info`, `get_smime_info`, `insert_smime_info`, `list_smime_info`, `set_default_smime_info`, `get_profile`, `watch_mailbox`, `stop_mail_watch`
204
+
205
+ ### `calendar` (8 tools)
206
+ Google Calendar — events, availability, and calendar management.
207
+
208
+ `list_calendars`, `get_events`, `manage_event`, `get_busy`, `get_free`, `move_event`, `list_recurring_event_instances`, `manage_calendar`
209
+
210
+ ## Environment Variables
211
+
212
+ | Variable | Required | Description |
213
+ |---|---|---|
214
+ | `GOOGLE_CLIENT_ID` | No* | OAuth 2.0 Client ID |
215
+ | `GOOGLE_CLIENT_SECRET` | No* | OAuth 2.0 Client Secret |
216
+ | `GOOGLE_MCP_PROFILE` | No | Profile name for multi-account support (see above) |
217
+ | `LOG_LEVEL` | No | `debug`, `info`, `warn`, `error`, or `silent` |
218
+ | `SERVICE_ACCOUNT_PATH` | No | Path to service account JSON key (alternative to OAuth) |
219
+ | `GOOGLE_IMPERSONATE_USER` | No | Email to impersonate with service account |
220
+
221
+ \* Not required as env vars if you provide credentials via `.env` file or `credentials.json` (see [Step 2](#step-2-provide-your-credentials)).
222
+
223
+ ## Migrating from gdrive-tools-mcp / gmail-tools-mcp
224
+
225
+ This package replaces both [`gdrive-tools-mcp`](https://www.npmjs.com/package/gdrive-tools-mcp) and [`gmail-tools-mcp`](https://www.npmjs.com/package/gmail-tools-mcp). To migrate:
226
+
227
+ 1. Replace both MCP server entries with a single `google-tools-mcp` entry
228
+ 2. Re-authenticate (the combined server uses its own config dir at `~/.config/google-tools-mcp/`)
229
+ 3. All tools are available immediately — no discovery step needed
230
+
231
+ ## License
232
+
233
+ MIT
package/dist/auth.js CHANGED
@@ -46,6 +46,8 @@ const SCOPES = [
46
46
  'https://www.googleapis.com/auth/gmail.send',
47
47
  'https://www.googleapis.com/auth/gmail.settings.basic',
48
48
  'https://www.googleapis.com/auth/gmail.settings.sharing',
49
+ // Calendar
50
+ 'https://www.googleapis.com/auth/calendar',
49
51
  ];
50
52
 
51
53
  // ---------------------------------------------------------------------------
package/dist/clients.js CHANGED
@@ -11,6 +11,7 @@ let googleDrive = null;
11
11
  let googleSheets = null;
12
12
  let googleScript = null;
13
13
  let gmailClient = null;
14
+ let calendarClient = null;
14
15
 
15
16
  async function ensureAuth() {
16
17
  if (authClient) return;
@@ -49,6 +50,14 @@ export async function initializeGmailClient() {
49
50
  return { authClient, gmailClient };
50
51
  }
51
52
 
53
+ // --- Calendar client ---
54
+ export async function initializeCalendarClient() {
55
+ if (calendarClient) return { authClient, calendarClient };
56
+ await ensureAuth();
57
+ if (!calendarClient) calendarClient = google.calendar({ version: 'v3', auth: authClient });
58
+ return { authClient, calendarClient };
59
+ }
60
+
52
61
  // --- Reset all clients (used by logout) ---
53
62
  export function resetClients() {
54
63
  authClient = null;
@@ -57,6 +66,7 @@ export function resetClients() {
57
66
  googleSheets = null;
58
67
  googleScript = null;
59
68
  gmailClient = null;
69
+ calendarClient = null;
60
70
  }
61
71
 
62
72
  // --- Individual client getters ---
@@ -95,3 +105,9 @@ export async function getGmailClient() {
95
105
  if (!gmail) throw new UserError('Gmail client is not initialized.');
96
106
  return gmail;
97
107
  }
108
+
109
+ export async function getCalendarClient() {
110
+ const { calendarClient: calendar } = await initializeCalendarClient();
111
+ if (!calendar) throw new UserError('Google Calendar client is not initialized.');
112
+ return calendar;
113
+ }
package/dist/index.js CHANGED
@@ -1,14 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  // google-tools-mcp — Combined Google Workspace MCP server
3
3
  //
4
- // Provides lazy-loaded tool categories for Drive, Docs, Sheets, and Gmail.
5
- // Only a discovery tool is exposed at startup; individual tools are loaded on demand.
4
+ // All tool categories (Drive, Docs, Sheets, Gmail, Calendar) are loaded at
5
+ // startup so they're available in the initial tools/list response.
6
6
  //
7
7
  // Usage:
8
8
  // google-tools-mcp Start the MCP server (default)
9
9
  // google-tools-mcp auth Run the interactive OAuth flow
10
10
  import { FastMCP } from 'fastmcp';
11
- import { collectToolsWhileRegistering, installCachedToolsListHandler } from './cachedToolsList.js';
12
11
  import { registerAllTools } from './tools/index.js';
13
12
  import { logger } from './logger.js';
14
13
 
@@ -38,17 +37,13 @@ const server = new FastMCP({
38
37
  version: '1.0.0',
39
38
  });
40
39
 
41
- const registeredTools = [];
42
- collectToolsWhileRegistering(server, registeredTools);
43
- registerAllTools(server);
40
+ await registerAllTools(server);
44
41
 
45
42
  try {
46
43
  logger.info('Starting google-tools-mcp server...');
47
44
  await server.start({ transportType: 'stdio' });
48
- installCachedToolsListHandler(server, registeredTools);
49
45
  logger.info('MCP Server running using stdio. Awaiting client connection...');
50
46
  logger.info('Google auth will run automatically on first tool call.');
51
- logger.info(`${registeredTools.length} tools registered at startup (discovery + logout). Call load_google_tools to load more.`);
52
47
  } catch (startError) {
53
48
  logger.error('FATAL: Server failed to start:', startError.message || startError);
54
49
  process.exit(1);
@@ -0,0 +1,64 @@
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_busy',
8
+ description:
9
+ 'Returns busy time blocks for one or more people in a given time range. ' +
10
+ 'Works across your Google Workspace org without needing calendar sharing — you see when people are busy but not event details. ' +
11
+ 'Pass email addresses as calendar_ids to check other people.',
12
+ parameters: z.object({
13
+ time_min: z
14
+ .string()
15
+ .describe('Start of query range in RFC3339 format (e.g. "2025-03-15T00:00:00Z").'),
16
+ time_max: z
17
+ .string()
18
+ .describe('End of query range in RFC3339 format (e.g. "2025-03-16T23:59:59Z").'),
19
+ calendar_ids: z
20
+ .array(z.string())
21
+ .optional()
22
+ .default(['primary'])
23
+ .describe('Calendar IDs or email addresses to check. Defaults to ["primary"].'),
24
+ }),
25
+ execute: async (args, { log }) => {
26
+ const calendar = await getCalendarClient();
27
+ log.info(`Checking busy times for ${args.calendar_ids.join(', ')}`);
28
+
29
+ try {
30
+ const response = await calendar.freebusy.query({
31
+ requestBody: {
32
+ timeMin: args.time_min,
33
+ timeMax: args.time_max,
34
+ items: args.calendar_ids.map((id) => ({ id })),
35
+ },
36
+ });
37
+
38
+ const calendars = response.data.calendars || {};
39
+ const results = {};
40
+
41
+ for (const [calId, data] of Object.entries(calendars)) {
42
+ if (data.errors?.length) {
43
+ results[calId] = {
44
+ error: data.errors[0].reason,
45
+ busy: [],
46
+ };
47
+ } else {
48
+ results[calId] = {
49
+ busy: (data.busy || []).map((block) => ({
50
+ start: block.start,
51
+ end: block.end,
52
+ })),
53
+ };
54
+ }
55
+ }
56
+
57
+ return JSON.stringify(results, null, 2);
58
+ } catch (error) {
59
+ log.error(`Error querying freebusy: ${error.message || error}`);
60
+ throw new UserError(`Failed to query busy times: ${error.message || 'Unknown error'}`);
61
+ }
62
+ },
63
+ });
64
+ }
@@ -0,0 +1,140 @@
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_events',
8
+ description:
9
+ 'Retrieves events from a Google Calendar. Can get a single event by ID, or list events in a time range with optional search. ' +
10
+ 'Use calendar_id to query another user\'s calendar (requires sharing) or "primary" for the authenticated user.',
11
+ parameters: z.object({
12
+ calendar_id: z
13
+ .string()
14
+ .optional()
15
+ .default('primary')
16
+ .describe('Calendar ID to query. Use "primary" for the main calendar, or an email address for a shared calendar.'),
17
+ event_id: z
18
+ .string()
19
+ .optional()
20
+ .describe('Specific event ID to retrieve. If provided, other filter params are ignored.'),
21
+ time_min: z
22
+ .string()
23
+ .optional()
24
+ .describe('Start of time range in RFC3339 format (e.g. "2025-01-01T00:00:00Z"). Defaults to now if not specified.'),
25
+ time_max: z
26
+ .string()
27
+ .optional()
28
+ .describe('End of time range in RFC3339 format (e.g. "2025-01-31T23:59:59Z").'),
29
+ max_results: z
30
+ .number()
31
+ .optional()
32
+ .default(25)
33
+ .describe('Maximum number of events to return (default 25, max 2500).'),
34
+ query: z
35
+ .string()
36
+ .optional()
37
+ .describe('Free-text search across event summary, description, location, attendees, etc.'),
38
+ detailed: z
39
+ .boolean()
40
+ .optional()
41
+ .default(false)
42
+ .describe('If true, includes attendees, attachments, conferencing details, and extended properties.'),
43
+ }),
44
+ execute: async (args, { log }) => {
45
+ const calendar = await getCalendarClient();
46
+
47
+ try {
48
+ // Single event fetch
49
+ if (args.event_id) {
50
+ log.info(`Getting event ${args.event_id} from calendar ${args.calendar_id}`);
51
+ const response = await calendar.events.get({
52
+ calendarId: args.calendar_id,
53
+ eventId: args.event_id,
54
+ });
55
+ return JSON.stringify(formatEvent(response.data, true), null, 2);
56
+ }
57
+
58
+ // List events
59
+ log.info(`Listing events from calendar ${args.calendar_id}`);
60
+ const params = {
61
+ calendarId: args.calendar_id,
62
+ maxResults: Math.min(args.max_results, 2500),
63
+ singleEvents: true,
64
+ orderBy: 'startTime',
65
+ };
66
+
67
+ if (args.time_min) params.timeMin = args.time_min;
68
+ else params.timeMin = new Date().toISOString();
69
+
70
+ if (args.time_max) params.timeMax = args.time_max;
71
+ if (args.query) params.q = args.query;
72
+
73
+ const response = await calendar.events.list(params);
74
+ const events = response.data.items || [];
75
+
76
+ return JSON.stringify(
77
+ events.map((e) => formatEvent(e, args.detailed)),
78
+ null,
79
+ 2
80
+ );
81
+ } catch (error) {
82
+ log.error(`Error getting events: ${error.message || error}`);
83
+ if (error.code === 404)
84
+ throw new UserError('Calendar or event not found. Check the calendar_id or event_id.');
85
+ if (error.code === 403)
86
+ throw new UserError('Permission denied. The calendar may not be shared with you.');
87
+ throw new UserError(`Failed to get events: ${error.message || 'Unknown error'}`);
88
+ }
89
+ },
90
+ });
91
+ }
92
+
93
+ function formatEvent(event, detailed) {
94
+ const result = {
95
+ id: event.id,
96
+ summary: event.summary || '(No title)',
97
+ start: event.start?.dateTime || event.start?.date || null,
98
+ end: event.end?.dateTime || event.end?.date || null,
99
+ status: event.status,
100
+ htmlLink: event.htmlLink,
101
+ };
102
+
103
+ if (event.location) result.location = event.location;
104
+ if (event.description) result.description = event.description;
105
+ if (event.recurringEventId) result.recurringEventId = event.recurringEventId;
106
+
107
+ if (detailed) {
108
+ if (event.attendees?.length) {
109
+ result.attendees = event.attendees.map((a) => ({
110
+ email: a.email,
111
+ displayName: a.displayName || null,
112
+ responseStatus: a.responseStatus,
113
+ organizer: a.organizer || false,
114
+ self: a.self || false,
115
+ }));
116
+ }
117
+ if (event.conferenceData) {
118
+ const entryPoints = event.conferenceData.entryPoints || [];
119
+ result.conferencing = entryPoints.map((ep) => ({
120
+ type: ep.entryPointType,
121
+ uri: ep.uri,
122
+ label: ep.label || null,
123
+ }));
124
+ }
125
+ if (event.attachments?.length) {
126
+ result.attachments = event.attachments.map((a) => ({
127
+ title: a.title,
128
+ fileUrl: a.fileUrl,
129
+ mimeType: a.mimeType,
130
+ }));
131
+ }
132
+ if (event.creator) result.creator = event.creator;
133
+ if (event.organizer) result.organizer = { email: event.organizer.email, displayName: event.organizer.displayName || null };
134
+ result.visibility = event.visibility || 'default';
135
+ result.transparency = event.transparency || 'opaque';
136
+ if (event.reminders) result.reminders = event.reminders;
137
+ }
138
+
139
+ return result;
140
+ }