google-tools-mcp 1.0.14 → 1.0.16
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/dist/auth.js
CHANGED
|
@@ -259,7 +259,7 @@ async function authenticate() {
|
|
|
259
259
|
// ---------------------------------------------------------------------------
|
|
260
260
|
// Public API
|
|
261
261
|
// ---------------------------------------------------------------------------
|
|
262
|
-
export { getTokenPath };
|
|
262
|
+
export { getTokenPath, getConfigDir, SCOPES };
|
|
263
263
|
|
|
264
264
|
export async function authorize() {
|
|
265
265
|
if (process.env.SERVICE_ACCOUNT_PATH) {
|
package/dist/clients.js
CHANGED
|
@@ -94,6 +94,11 @@ export async function initializeFormsClient() {
|
|
|
94
94
|
return { authClient, formsClient };
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// --- Get auth client without triggering init (for diagnostics) ---
|
|
98
|
+
export function getAuthClientIfReady() {
|
|
99
|
+
return authClient;
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
// --- Reset all clients (used by logout) ---
|
|
98
103
|
export function resetClients() {
|
|
99
104
|
authClient = null;
|
|
@@ -7,6 +7,7 @@ export function register(server) {
|
|
|
7
7
|
name: 'get_events',
|
|
8
8
|
description:
|
|
9
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
|
+
'By default only returns future events (time_min defaults to now). To retrieve past events, set time_min to a past date (e.g. "2025-01-01T00:00:00Z"). ' +
|
|
10
11
|
'Use calendar_id to query another user\'s calendar (requires sharing) or "primary" for the authenticated user.',
|
|
11
12
|
parameters: z.object({
|
|
12
13
|
calendar_id: z
|
|
@@ -8,7 +8,9 @@ export function register(server) {
|
|
|
8
8
|
description:
|
|
9
9
|
'Lists individual occurrences of a recurring event. Use this to find specific instances ' +
|
|
10
10
|
'you want to modify or cancel (e.g. "cancel next Tuesday\'s standup"). ' +
|
|
11
|
-
'Each instance has its own event ID that you can pass to manage_event.'
|
|
11
|
+
'Each instance has its own event ID that you can pass to manage_event. ' +
|
|
12
|
+
'By default only returns future instances (time_min defaults to now). To retrieve past instances, set time_min to a past date. ' +
|
|
13
|
+
'Note: all parameters use snake_case (event_id, time_min, time_max), not camelCase.',
|
|
12
14
|
parameters: z.object({
|
|
13
15
|
calendar_id: z
|
|
14
16
|
.string()
|
package/dist/tools/index.js
CHANGED
|
@@ -5,9 +5,11 @@ import { z } from 'zod';
|
|
|
5
5
|
import * as fs from 'fs/promises';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
import { getTokenPath, getConfigDir, SCOPES } from '../auth.js';
|
|
10
|
+
import { resetClients, withAuthRetry, getAuthClientIfReady } from '../clients.js';
|
|
10
11
|
import { logger } from '../logger.js';
|
|
12
|
+
import { google } from 'googleapis';
|
|
11
13
|
|
|
12
14
|
// ---------------------------------------------------------------------------
|
|
13
15
|
// Wrap server.addTool so every tool's execute() auto-retries on invalid_grant.
|
|
@@ -140,4 +142,214 @@ export async function registerAllTools(server) {
|
|
|
140
142
|
});
|
|
141
143
|
},
|
|
142
144
|
});
|
|
145
|
+
|
|
146
|
+
// --- Troubleshoot tool (always available) ---
|
|
147
|
+
server.addTool({
|
|
148
|
+
name: 'troubleshoot',
|
|
149
|
+
description:
|
|
150
|
+
'Run a health check on google-tools-mcp: verify OAuth token, test API connectivity, show config and recent logs. Call this when tools are failing or behaving unexpectedly.',
|
|
151
|
+
parameters: z.object({}),
|
|
152
|
+
execute: async () => {
|
|
153
|
+
const report = { auth: {}, services: {}, config: {}, recentLogs: null, environment: {} };
|
|
154
|
+
|
|
155
|
+
// --- Auth status ---
|
|
156
|
+
const tokenPath = getTokenPath();
|
|
157
|
+
try {
|
|
158
|
+
const tokenContent = await fs.readFile(tokenPath, 'utf8');
|
|
159
|
+
const token = JSON.parse(tokenContent);
|
|
160
|
+
report.auth.tokenFile = 'present';
|
|
161
|
+
report.auth.hasRefreshToken = !!token.refresh_token;
|
|
162
|
+
report.auth.type = token.type || 'unknown';
|
|
163
|
+
} catch (err) {
|
|
164
|
+
report.auth.tokenFile = err.code === 'ENOENT' ? 'missing' : 'unreadable';
|
|
165
|
+
report.auth.hasRefreshToken = false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Try refreshing token
|
|
169
|
+
const client = getAuthClientIfReady();
|
|
170
|
+
if (client) {
|
|
171
|
+
try {
|
|
172
|
+
const { credentials } = await client.refreshAccessToken();
|
|
173
|
+
client.setCredentials(credentials);
|
|
174
|
+
report.auth.status = 'valid';
|
|
175
|
+
report.auth.expiry = credentials.expiry_date
|
|
176
|
+
? new Date(credentials.expiry_date).toISOString()
|
|
177
|
+
: 'unknown';
|
|
178
|
+
} catch (err) {
|
|
179
|
+
report.auth.status = 'expired_or_revoked';
|
|
180
|
+
report.auth.refreshError = err.message;
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
report.auth.status = report.auth.tokenFile === 'present' ? 'not_initialized' : 'missing';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// --- Service probes ---
|
|
187
|
+
if (client && report.auth.status === 'valid') {
|
|
188
|
+
// Drive
|
|
189
|
+
try {
|
|
190
|
+
const drive = google.drive({ version: 'v3', auth: client });
|
|
191
|
+
const res = await drive.about.get({ fields: 'user' });
|
|
192
|
+
report.services.drive = { status: 'ok', account: res.data.user?.emailAddress || 'unknown' };
|
|
193
|
+
} catch (err) {
|
|
194
|
+
report.services.drive = { status: 'error', error: err.message };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Gmail
|
|
198
|
+
try {
|
|
199
|
+
const gmail = google.gmail({ version: 'v1', auth: client });
|
|
200
|
+
const res = await gmail.users.getProfile({ userId: 'me' });
|
|
201
|
+
report.services.gmail = { status: 'ok', email: res.data.emailAddress || 'unknown' };
|
|
202
|
+
} catch (err) {
|
|
203
|
+
report.services.gmail = { status: 'error', error: err.message };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Calendar
|
|
207
|
+
try {
|
|
208
|
+
const calendar = google.calendar({ version: 'v3', auth: client });
|
|
209
|
+
await calendar.calendarList.list({ maxResults: 1 });
|
|
210
|
+
report.services.calendar = { status: 'ok' };
|
|
211
|
+
} catch (err) {
|
|
212
|
+
report.services.calendar = { status: 'error', error: err.message };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Forms — just check scope presence
|
|
216
|
+
report.services.forms = {
|
|
217
|
+
status: SCOPES.some(s => s.includes('forms')) ? 'configured' : 'no_scope',
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Docs/Sheets — covered by Drive auth
|
|
221
|
+
report.services.docs = { status: report.services.drive.status === 'ok' ? 'ok (via Drive auth)' : 'unknown' };
|
|
222
|
+
report.services.sheets = { status: report.services.drive.status === 'ok' ? 'ok (via Drive auth)' : 'unknown' };
|
|
223
|
+
} else {
|
|
224
|
+
report.services = { note: 'Skipped — auth not available' };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// --- Config summary ---
|
|
228
|
+
const configDir = getConfigDir();
|
|
229
|
+
report.config = {
|
|
230
|
+
configDir,
|
|
231
|
+
profile: process.env.GOOGLE_MCP_PROFILE || '(default)',
|
|
232
|
+
tokenPath,
|
|
233
|
+
credentialSource: process.env.GOOGLE_CLIENT_ID ? 'environment' : 'file',
|
|
234
|
+
scopes: SCOPES,
|
|
235
|
+
logFile: process.env.GOOGLE_MCP_LOG_FILE || '(not set)',
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// --- Recent logs ---
|
|
239
|
+
const logFilePath = process.env.GOOGLE_MCP_LOG_FILE === '1'
|
|
240
|
+
? path.join(configDir, 'server.log')
|
|
241
|
+
: process.env.GOOGLE_MCP_LOG_FILE;
|
|
242
|
+
if (logFilePath) {
|
|
243
|
+
try {
|
|
244
|
+
const logContent = await fs.readFile(logFilePath, 'utf8');
|
|
245
|
+
const lines = logContent.trimEnd().split('\n');
|
|
246
|
+
report.recentLogs = lines.slice(-20);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
report.recentLogs = err.code === 'ENOENT' ? '(log file not found)' : `(error reading log: ${err.message})`;
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
report.recentLogs = '(file logging not enabled — set GOOGLE_MCP_LOG_FILE to enable)';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// --- Environment ---
|
|
255
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
256
|
+
let pkgVersion = 'unknown';
|
|
257
|
+
try {
|
|
258
|
+
const pkgPath = path.resolve(__dirname, '..', '..', 'package.json');
|
|
259
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
260
|
+
pkgVersion = pkg.version;
|
|
261
|
+
} catch {}
|
|
262
|
+
report.environment = {
|
|
263
|
+
serverVersion: pkgVersion,
|
|
264
|
+
nodeVersion: process.version,
|
|
265
|
+
platform: process.platform,
|
|
266
|
+
osRelease: os.release(),
|
|
267
|
+
arch: process.arch,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
return JSON.stringify(report, null, 2);
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// --- Feedback tool (always available) ---
|
|
275
|
+
server.addTool({
|
|
276
|
+
name: 'feedback',
|
|
277
|
+
description:
|
|
278
|
+
'Submit feedback or a bug report for google-tools-mcp. Automatically collects diagnostic info and generates a pre-filled GitHub issue URL.',
|
|
279
|
+
parameters: z.object({
|
|
280
|
+
type: z.enum(['bug', 'feature']).describe('Type of feedback'),
|
|
281
|
+
title: z.string().describe('Short summary'),
|
|
282
|
+
description: z.string().describe('Detailed description of the issue or feature request'),
|
|
283
|
+
}),
|
|
284
|
+
execute: async (args) => {
|
|
285
|
+
// Collect diagnostics
|
|
286
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
287
|
+
let pkgVersion = 'unknown';
|
|
288
|
+
try {
|
|
289
|
+
const pkgPath = path.resolve(__dirname, '..', '..', 'package.json');
|
|
290
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
291
|
+
pkgVersion = pkg.version;
|
|
292
|
+
} catch {}
|
|
293
|
+
|
|
294
|
+
let authStatus = 'unknown';
|
|
295
|
+
const tokenPath = getTokenPath();
|
|
296
|
+
try {
|
|
297
|
+
await fs.access(tokenPath);
|
|
298
|
+
const client = getAuthClientIfReady();
|
|
299
|
+
if (client) {
|
|
300
|
+
try {
|
|
301
|
+
await client.refreshAccessToken();
|
|
302
|
+
authStatus = 'valid';
|
|
303
|
+
} catch {
|
|
304
|
+
authStatus = 'expired_or_revoked';
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
authStatus = 'not_initialized';
|
|
308
|
+
}
|
|
309
|
+
} catch {
|
|
310
|
+
authStatus = 'missing';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const enabledScopes = SCOPES.map(s => s.split('/').pop());
|
|
314
|
+
|
|
315
|
+
// Build markdown body
|
|
316
|
+
const diagnostics = [
|
|
317
|
+
`- **Server version:** ${pkgVersion}`,
|
|
318
|
+
`- **Node version:** ${process.version}`,
|
|
319
|
+
`- **OS:** ${process.platform} ${os.release()} (${process.arch})`,
|
|
320
|
+
`- **Auth status:** ${authStatus}`,
|
|
321
|
+
`- **Scopes:** ${enabledScopes.join(', ')}`,
|
|
322
|
+
].join('\n');
|
|
323
|
+
|
|
324
|
+
const body = [
|
|
325
|
+
`## Description`,
|
|
326
|
+
``,
|
|
327
|
+
args.description,
|
|
328
|
+
``,
|
|
329
|
+
`<details>`,
|
|
330
|
+
`<summary>Diagnostic Info</summary>`,
|
|
331
|
+
``,
|
|
332
|
+
diagnostics,
|
|
333
|
+
``,
|
|
334
|
+
`</details>`,
|
|
335
|
+
].join('\n');
|
|
336
|
+
|
|
337
|
+
// Build GitHub issue URL
|
|
338
|
+
const label = args.type === 'bug' ? 'bug' : 'enhancement';
|
|
339
|
+
const params = new URLSearchParams({
|
|
340
|
+
title: args.title,
|
|
341
|
+
body,
|
|
342
|
+
labels: label,
|
|
343
|
+
});
|
|
344
|
+
const url = `https://github.com/karthikcsq/google-tools-mcp/issues/new?${params.toString()}`;
|
|
345
|
+
|
|
346
|
+
return JSON.stringify({
|
|
347
|
+
url,
|
|
348
|
+
markdown: body,
|
|
349
|
+
note: url.length > 8000
|
|
350
|
+
? 'The URL may be too long for some browsers. Use the markdown body below to create the issue manually.'
|
|
351
|
+
: 'Open the URL to create a pre-filled GitHub issue.',
|
|
352
|
+
}, null, 2);
|
|
353
|
+
},
|
|
354
|
+
});
|
|
143
355
|
}
|