google-tools-mcp 1.0.14 → 1.0.15
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 +1 -1
- package/dist/clients.js +5 -0
- package/dist/tools/index.js +214 -2
- package/package.json +1 -1
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;
|
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
|
}
|