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 +21 -0
- package/README.md +233 -0
- package/dist/auth.js +2 -0
- package/dist/clients.js +16 -0
- package/dist/index.js +3 -8
- package/dist/tools/calendar/getBusy.js +64 -0
- package/dist/tools/calendar/getEvents.js +140 -0
- package/dist/tools/calendar/getFree.js +225 -0
- package/dist/tools/calendar/index.js +19 -0
- package/dist/tools/calendar/listCalendars.js +38 -0
- package/dist/tools/calendar/listRecurringInstances.js +81 -0
- package/dist/tools/calendar/manageCalendar.js +121 -0
- package/dist/tools/calendar/manageEvent.js +250 -0
- package/dist/tools/calendar/moveEvent.js +60 -0
- package/dist/tools/drafts.js +165 -0
- package/dist/tools/gmail/drafts.js +165 -165
- package/dist/tools/gmail/labels.js +103 -103
- package/dist/tools/gmail/messages.js +448 -448
- package/dist/tools/gmail/settings.js +528 -528
- package/dist/tools/gmail/threads.js +145 -145
- package/dist/tools/index.js +15 -80
- package/dist/tools/labels.js +103 -0
- package/dist/tools/messages.js +448 -0
- package/dist/tools/settings.js +528 -0
- package/dist/tools/threads.js +145 -0
- package/package.json +1 -1
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
|
-
//
|
|
5
|
-
//
|
|
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
|
-
|
|
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
|
+
}
|