google-tools-mcp 1.0.3 → 1.0.5
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/tools/docs/formatting/index.js +0 -2
- package/dist/tools/docs/index.js +0 -2
- package/dist/tools/drive/{getDocumentInfo.js → getFileInfo.js} +48 -48
- package/dist/tools/drive/index.js +4 -4
- package/dist/tools/drive/listDriveFiles.js +107 -0
- package/dist/tools/sheets/index.js +0 -2
- package/package.json +1 -1
- package/dist/tools/docs/formatting/applyTextStyle.js +0 -49
- package/dist/tools/docs/insertText.js +0 -61
- package/dist/tools/drive/listGoogleDocs.js +0 -67
- package/dist/tools/sheets/listGoogleSheets.js +0 -62
package/dist/tools/docs/index.js
CHANGED
|
@@ -4,7 +4,6 @@ import { register as listDocumentTabs } from './listDocumentTabs.js';
|
|
|
4
4
|
import { register as renameTab } from './renameTab.js';
|
|
5
5
|
import { register as addTab } from './addTab.js';
|
|
6
6
|
import { register as appendToGoogleDoc } from './appendToGoogleDoc.js';
|
|
7
|
-
import { register as insertText } from './insertText.js';
|
|
8
7
|
import { register as deleteRange } from './deleteRange.js';
|
|
9
8
|
import { register as modifyText } from './modifyText.js';
|
|
10
9
|
import { register as findAndReplace } from './findAndReplace.js';
|
|
@@ -23,7 +22,6 @@ export function registerDocsTools(server) {
|
|
|
23
22
|
renameTab(server);
|
|
24
23
|
addTab(server);
|
|
25
24
|
appendToGoogleDoc(server);
|
|
26
|
-
insertText(server);
|
|
27
25
|
deleteRange(server);
|
|
28
26
|
modifyText(server);
|
|
29
27
|
findAndReplace(server);
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
export function register(server) {
|
|
5
|
-
server.addTool({
|
|
6
|
-
name: '
|
|
7
|
-
description: 'Gets metadata about
|
|
8
|
-
parameters:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
fields: 'id,name,description,mimeType,size,createdTime,modifiedTime,webViewLink,owners(displayName,emailAddress),lastModifyingUser(displayName,emailAddress),shared,parents,version',
|
|
18
|
-
supportsAllDrives: true,
|
|
19
|
-
});
|
|
20
|
-
const file = response.data;
|
|
21
|
-
if (!file) {
|
|
22
|
-
throw new UserError(`
|
|
23
|
-
}
|
|
24
|
-
const info = {
|
|
25
|
-
id: file.id,
|
|
26
|
-
name: file.name,
|
|
27
|
-
mimeType: file.mimeType,
|
|
28
|
-
createdTime: file.createdTime,
|
|
29
|
-
modifiedTime: file.modifiedTime,
|
|
30
|
-
owner: file.owners?.[0]?.displayName || null,
|
|
31
|
-
lastModifyingUser: file.lastModifyingUser?.displayName || null,
|
|
32
|
-
shared: file.shared || false,
|
|
33
|
-
url: file.webViewLink,
|
|
34
|
-
description: file.description || null,
|
|
35
|
-
};
|
|
36
|
-
return JSON.stringify(info, null, 2);
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
log.error(`Error getting
|
|
40
|
-
if (error.code === 404)
|
|
41
|
-
throw new UserError(`
|
|
42
|
-
if (error.code === 403)
|
|
43
|
-
throw new UserError('Permission denied. Make sure you have access to this
|
|
44
|
-
throw new UserError(`Failed to get
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDriveClient } from '../../clients.js';
|
|
4
|
+
export function register(server) {
|
|
5
|
+
server.addTool({
|
|
6
|
+
name: 'getFileInfo',
|
|
7
|
+
description: 'Gets metadata about any Drive file (document, spreadsheet, PDF, etc.) including its name, type, owner, sharing status, and modification history.',
|
|
8
|
+
parameters: z.object({
|
|
9
|
+
fileId: z.string().describe('The ID of the file to get information about.'),
|
|
10
|
+
}),
|
|
11
|
+
execute: async (args, { log }) => {
|
|
12
|
+
const drive = await getDriveClient();
|
|
13
|
+
log.info(`Getting info for file: ${args.fileId}`);
|
|
14
|
+
try {
|
|
15
|
+
const response = await drive.files.get({
|
|
16
|
+
fileId: args.fileId,
|
|
17
|
+
fields: 'id,name,description,mimeType,size,createdTime,modifiedTime,webViewLink,owners(displayName,emailAddress),lastModifyingUser(displayName,emailAddress),shared,parents,version',
|
|
18
|
+
supportsAllDrives: true,
|
|
19
|
+
});
|
|
20
|
+
const file = response.data;
|
|
21
|
+
if (!file) {
|
|
22
|
+
throw new UserError(`File with ID ${args.fileId} not found.`);
|
|
23
|
+
}
|
|
24
|
+
const info = {
|
|
25
|
+
id: file.id,
|
|
26
|
+
name: file.name,
|
|
27
|
+
mimeType: file.mimeType,
|
|
28
|
+
createdTime: file.createdTime,
|
|
29
|
+
modifiedTime: file.modifiedTime,
|
|
30
|
+
owner: file.owners?.[0]?.displayName || null,
|
|
31
|
+
lastModifyingUser: file.lastModifyingUser?.displayName || null,
|
|
32
|
+
shared: file.shared || false,
|
|
33
|
+
url: file.webViewLink,
|
|
34
|
+
description: file.description || null,
|
|
35
|
+
};
|
|
36
|
+
return JSON.stringify(info, null, 2);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
log.error(`Error getting file info: ${error.message || error}`);
|
|
40
|
+
if (error.code === 404)
|
|
41
|
+
throw new UserError(`File not found (ID: ${args.fileId}).`);
|
|
42
|
+
if (error.code === 403)
|
|
43
|
+
throw new UserError('Permission denied. Make sure you have access to this file.');
|
|
44
|
+
throw new UserError(`Failed to get file info: ${error.message || 'Unknown error'}`);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { register as
|
|
1
|
+
import { register as listDriveFiles } from './listDriveFiles.js';
|
|
2
2
|
import { register as searchGoogleDocs } from './searchGoogleDocs.js';
|
|
3
|
-
import { register as
|
|
3
|
+
import { register as getFileInfo } from './getFileInfo.js';
|
|
4
4
|
import { register as createFolder } from './createFolder.js';
|
|
5
5
|
import { register as listFolderContents } from './listFolderContents.js';
|
|
6
6
|
import { register as getFolderInfo } from './getFolderInfo.js';
|
|
@@ -13,9 +13,9 @@ import { register as createFromTemplate } from './createFromTemplate.js';
|
|
|
13
13
|
import { register as listSharedDrives } from './listSharedDrives.js';
|
|
14
14
|
import { register as listSharedWithMe } from './listSharedWithMe.js';
|
|
15
15
|
export function registerDriveTools(server) {
|
|
16
|
-
|
|
16
|
+
listDriveFiles(server);
|
|
17
17
|
searchGoogleDocs(server);
|
|
18
|
-
|
|
18
|
+
getFileInfo(server);
|
|
19
19
|
createFolder(server);
|
|
20
20
|
listFolderContents(server);
|
|
21
21
|
getFolderInfo(server);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDriveClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
const MIME_TYPES = {
|
|
6
|
+
document: 'application/vnd.google-apps.document',
|
|
7
|
+
spreadsheet: 'application/vnd.google-apps.spreadsheet',
|
|
8
|
+
presentation: 'application/vnd.google-apps.presentation',
|
|
9
|
+
form: 'application/vnd.google-apps.form',
|
|
10
|
+
pdf: 'application/pdf',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function register(server) {
|
|
14
|
+
server.addTool({
|
|
15
|
+
name: 'listDriveFiles',
|
|
16
|
+
description:
|
|
17
|
+
'Lists files in your Drive, optionally filtered by type (document, spreadsheet, presentation, form, pdf), name/content query, or modification date.',
|
|
18
|
+
parameters: z.object({
|
|
19
|
+
fileType: z
|
|
20
|
+
.enum(['document', 'spreadsheet', 'presentation', 'form', 'pdf', 'all'])
|
|
21
|
+
.optional()
|
|
22
|
+
.default('all')
|
|
23
|
+
.describe('Filter by file type. Defaults to all types.'),
|
|
24
|
+
maxResults: z
|
|
25
|
+
.number()
|
|
26
|
+
.int()
|
|
27
|
+
.min(1)
|
|
28
|
+
.max(100)
|
|
29
|
+
.optional()
|
|
30
|
+
.default(20)
|
|
31
|
+
.describe('Maximum number of files to return (1-100).'),
|
|
32
|
+
query: z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Search query to filter files by name or content.'),
|
|
36
|
+
orderBy: z
|
|
37
|
+
.enum(['name', 'modifiedTime', 'createdTime'])
|
|
38
|
+
.optional()
|
|
39
|
+
.default('modifiedTime')
|
|
40
|
+
.describe('Sort order for results.'),
|
|
41
|
+
modifiedAfter: z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe(
|
|
45
|
+
'Only return files modified after this date (ISO 8601 format, e.g., "2024-01-01").'
|
|
46
|
+
),
|
|
47
|
+
}),
|
|
48
|
+
execute: async (args, { log }) => {
|
|
49
|
+
const drive = await getDriveClient();
|
|
50
|
+
log.info(
|
|
51
|
+
`Listing Drive files. Type: ${args.fileType}, Query: ${args.query || 'none'}, Max: ${args.maxResults}, Order: ${args.orderBy}`
|
|
52
|
+
);
|
|
53
|
+
try {
|
|
54
|
+
let queryParts = ['trashed=false'];
|
|
55
|
+
|
|
56
|
+
// Filter by mime type
|
|
57
|
+
if (args.fileType !== 'all') {
|
|
58
|
+
queryParts.push(`mimeType='${MIME_TYPES[args.fileType]}'`);
|
|
59
|
+
} else if (!args.query) {
|
|
60
|
+
// Exclude folders from "all" results only when not doing fullText search
|
|
61
|
+
// (Drive API doesn't allow fullText contains with negated mimeType filters)
|
|
62
|
+
queryParts.push("mimeType!='application/vnd.google-apps.folder'");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (args.query) {
|
|
66
|
+
queryParts.push(
|
|
67
|
+
`(name contains '${args.query}' or fullText contains '${args.query}')`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (args.modifiedAfter) {
|
|
72
|
+
const cutoffDate = new Date(args.modifiedAfter).toISOString();
|
|
73
|
+
queryParts.push(`modifiedTime > '${cutoffDate}'`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const response = await drive.files.list({
|
|
77
|
+
q: queryParts.join(' and '),
|
|
78
|
+
pageSize: args.maxResults,
|
|
79
|
+
orderBy: args.orderBy === 'name' ? 'name' : args.orderBy,
|
|
80
|
+
fields: 'files(id,name,mimeType,modifiedTime,createdTime,size,webViewLink,owners(displayName,emailAddress))',
|
|
81
|
+
supportsAllDrives: true,
|
|
82
|
+
includeItemsFromAllDrives: true,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const files = (response.data.files || []).map((file) => ({
|
|
86
|
+
id: file.id,
|
|
87
|
+
name: file.name,
|
|
88
|
+
mimeType: file.mimeType,
|
|
89
|
+
modifiedTime: file.modifiedTime,
|
|
90
|
+
owner: file.owners?.[0]?.displayName || null,
|
|
91
|
+
url: file.webViewLink,
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
return JSON.stringify({ files }, null, 2);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
log.error(`Error listing Drive files: ${error.message || error}`);
|
|
97
|
+
if (error.code === 403)
|
|
98
|
+
throw new UserError(
|
|
99
|
+
'Permission denied. Make sure you have granted Google Drive access to the application.'
|
|
100
|
+
);
|
|
101
|
+
throw new UserError(
|
|
102
|
+
`Failed to list files: ${error.message || 'Unknown error'}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -6,7 +6,6 @@ import { register as clearSpreadsheetRange } from './clearSpreadsheetRange.js';
|
|
|
6
6
|
import { register as getSpreadsheetInfo } from './getSpreadsheetInfo.js';
|
|
7
7
|
import { register as addSpreadsheetSheet } from './addSpreadsheetSheet.js';
|
|
8
8
|
import { register as createSpreadsheet } from './createSpreadsheet.js';
|
|
9
|
-
import { register as listGoogleSheets } from './listGoogleSheets.js';
|
|
10
9
|
import { register as deleteSheet } from './deleteSheet.js';
|
|
11
10
|
import { register as renameSheet } from './renameSheet.js';
|
|
12
11
|
import { register as duplicateSheet } from './duplicateSheet.js';
|
|
@@ -39,7 +38,6 @@ export function registerSheetsTools(server) {
|
|
|
39
38
|
getSpreadsheetInfo(server);
|
|
40
39
|
addSpreadsheetSheet(server);
|
|
41
40
|
createSpreadsheet(server);
|
|
42
|
-
listGoogleSheets(server);
|
|
43
41
|
deleteSheet(server);
|
|
44
42
|
renameSheet(server);
|
|
45
43
|
duplicateSheet(server);
|
package/package.json
CHANGED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { getDocsClient } from '../../../clients.js';
|
|
3
|
-
import { ApplyTextStyleToolParameters, NotImplementedError, } from '../../../types.js';
|
|
4
|
-
import * as GDocsHelpers from '../../../googleDocsApiHelpers.js';
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: 'applyTextStyle',
|
|
8
|
-
description: 'Applies character-level formatting (bold, italic, color, font, etc.) to text identified by a character range or by searching for a text string. This is the primary tool for styling text in a document.',
|
|
9
|
-
parameters: ApplyTextStyleToolParameters,
|
|
10
|
-
execute: async (args, { log }) => {
|
|
11
|
-
const docs = await getDocsClient();
|
|
12
|
-
let { startIndex, endIndex } = args.target; // Will be updated if target is text
|
|
13
|
-
log.info(`Applying text style in doc ${args.documentId}${args.tabId ? ` (tab: ${args.tabId})` : ''}. Target: ${JSON.stringify(args.target)}, Style: ${JSON.stringify(args.style)}`);
|
|
14
|
-
try {
|
|
15
|
-
// Determine target range
|
|
16
|
-
if ('textToFind' in args.target) {
|
|
17
|
-
const range = await GDocsHelpers.findTextRange(docs, args.documentId, args.target.textToFind, args.target.matchInstance, args.tabId);
|
|
18
|
-
if (!range) {
|
|
19
|
-
throw new UserError(`Could not find instance ${args.target.matchInstance} of text "${args.target.textToFind}"${args.tabId ? ` in tab ${args.tabId}` : ''}.`);
|
|
20
|
-
}
|
|
21
|
-
startIndex = range.startIndex;
|
|
22
|
-
endIndex = range.endIndex;
|
|
23
|
-
log.info(`Found text "${args.target.textToFind}" (instance ${args.target.matchInstance}) at range ${startIndex}-${endIndex}`);
|
|
24
|
-
}
|
|
25
|
-
if (startIndex === undefined || endIndex === undefined) {
|
|
26
|
-
throw new UserError('Target range could not be determined.');
|
|
27
|
-
}
|
|
28
|
-
if (endIndex <= startIndex) {
|
|
29
|
-
throw new UserError('End index must be greater than start index for styling.');
|
|
30
|
-
}
|
|
31
|
-
// Build the request
|
|
32
|
-
const requestInfo = GDocsHelpers.buildUpdateTextStyleRequest(startIndex, endIndex, args.style, args.tabId);
|
|
33
|
-
if (!requestInfo) {
|
|
34
|
-
return 'No valid text styling options were provided.';
|
|
35
|
-
}
|
|
36
|
-
await GDocsHelpers.executeBatchUpdate(docs, args.documentId, [requestInfo.request]);
|
|
37
|
-
return `Successfully applied text style (${requestInfo.fields.join(', ')}) to range ${startIndex}-${endIndex}${args.tabId ? ` in tab ${args.tabId}` : ''}.`;
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
log.error(`Error applying text style in doc ${args.documentId}: ${error.message || error}`);
|
|
41
|
-
if (error instanceof UserError)
|
|
42
|
-
throw error;
|
|
43
|
-
if (error instanceof NotImplementedError)
|
|
44
|
-
throw error; // Should not happen here
|
|
45
|
-
throw new UserError(`Failed to apply text style: ${error.message || 'Unknown error'}`);
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getDocsClient } from '../../clients.js';
|
|
4
|
-
import { DocumentIdParameter } from '../../types.js';
|
|
5
|
-
import * as GDocsHelpers from '../../googleDocsApiHelpers.js';
|
|
6
|
-
export function register(server) {
|
|
7
|
-
server.addTool({
|
|
8
|
-
name: 'insertText',
|
|
9
|
-
description: "Inserts text at a specific character index within a document. Use readDocument with format='json' to determine the correct index.",
|
|
10
|
-
parameters: DocumentIdParameter.extend({
|
|
11
|
-
text: z.string().min(1).describe('The text to insert.'),
|
|
12
|
-
index: z
|
|
13
|
-
.number()
|
|
14
|
-
.int()
|
|
15
|
-
.min(1)
|
|
16
|
-
.describe("1-based character index within the document body. Use readDocument with format='json' to inspect indices."),
|
|
17
|
-
tabId: z
|
|
18
|
-
.string()
|
|
19
|
-
.optional()
|
|
20
|
-
.describe('The ID of the specific tab to insert into. If not specified, inserts into the first tab (or legacy document.body for documents without tabs).'),
|
|
21
|
-
}),
|
|
22
|
-
execute: async (args, { log }) => {
|
|
23
|
-
const docs = await getDocsClient();
|
|
24
|
-
log.info(`Inserting text in doc ${args.documentId} at index ${args.index}${args.tabId ? ` (tab: ${args.tabId})` : ''}`);
|
|
25
|
-
try {
|
|
26
|
-
if (args.tabId) {
|
|
27
|
-
// For tab-specific inserts, we need to verify the tab exists first
|
|
28
|
-
const docInfo = await docs.documents.get({
|
|
29
|
-
documentId: args.documentId,
|
|
30
|
-
includeTabsContent: true,
|
|
31
|
-
fields: 'tabs(tabProperties,documentTab)',
|
|
32
|
-
});
|
|
33
|
-
const targetTab = GDocsHelpers.findTabById(docInfo.data, args.tabId);
|
|
34
|
-
if (!targetTab) {
|
|
35
|
-
throw new UserError(`Tab with ID "${args.tabId}" not found in document.`);
|
|
36
|
-
}
|
|
37
|
-
if (!targetTab.documentTab) {
|
|
38
|
-
throw new UserError(`Tab "${args.tabId}" does not have content (may not be a document tab).`);
|
|
39
|
-
}
|
|
40
|
-
// Insert with tabId
|
|
41
|
-
const location = { index: args.index, tabId: args.tabId };
|
|
42
|
-
const request = {
|
|
43
|
-
insertText: { location, text: args.text },
|
|
44
|
-
};
|
|
45
|
-
await GDocsHelpers.executeBatchUpdate(docs, args.documentId, [request]);
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
// Use existing helper for backward compatibility
|
|
49
|
-
await GDocsHelpers.insertText(docs, args.documentId, args.text, args.index);
|
|
50
|
-
}
|
|
51
|
-
return `Successfully inserted text at index ${args.index}${args.tabId ? ` in tab ${args.tabId}` : ''}.`;
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
log.error(`Error inserting text in doc ${args.documentId}: ${error.message || error}`);
|
|
55
|
-
if (error instanceof UserError)
|
|
56
|
-
throw error;
|
|
57
|
-
throw new UserError(`Failed to insert text: ${error.message || 'Unknown error'}`);
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getDriveClient } from '../../clients.js';
|
|
4
|
-
export function register(server) {
|
|
5
|
-
server.addTool({
|
|
6
|
-
name: 'listDocuments',
|
|
7
|
-
description: 'Lists Google Documents in your Drive, optionally filtered by name or content. Use modifiedAfter to find recently changed documents.',
|
|
8
|
-
parameters: z.object({
|
|
9
|
-
maxResults: z
|
|
10
|
-
.number()
|
|
11
|
-
.int()
|
|
12
|
-
.min(1)
|
|
13
|
-
.max(100)
|
|
14
|
-
.optional()
|
|
15
|
-
.default(20)
|
|
16
|
-
.describe('Maximum number of documents to return (1-100).'),
|
|
17
|
-
query: z.string().optional().describe('Search query to filter documents by name or content.'),
|
|
18
|
-
orderBy: z
|
|
19
|
-
.enum(['name', 'modifiedTime', 'createdTime'])
|
|
20
|
-
.optional()
|
|
21
|
-
.default('modifiedTime')
|
|
22
|
-
.describe('Sort order for results.'),
|
|
23
|
-
modifiedAfter: z
|
|
24
|
-
.string()
|
|
25
|
-
.optional()
|
|
26
|
-
.describe('Only return documents modified after this date (ISO 8601 format, e.g., "2024-01-01").'),
|
|
27
|
-
}),
|
|
28
|
-
execute: async (args, { log }) => {
|
|
29
|
-
const drive = await getDriveClient();
|
|
30
|
-
log.info(`Listing Google Docs. Query: ${args.query || 'none'}, Max: ${args.maxResults}, Order: ${args.orderBy}`);
|
|
31
|
-
try {
|
|
32
|
-
// Build the query string for Google Drive API
|
|
33
|
-
let queryString = "mimeType='application/vnd.google-apps.document' and trashed=false";
|
|
34
|
-
if (args.query) {
|
|
35
|
-
queryString += ` and (name contains '${args.query}' or fullText contains '${args.query}')`;
|
|
36
|
-
}
|
|
37
|
-
if (args.modifiedAfter) {
|
|
38
|
-
const cutoffDate = new Date(args.modifiedAfter).toISOString();
|
|
39
|
-
queryString += ` and modifiedTime > '${cutoffDate}'`;
|
|
40
|
-
}
|
|
41
|
-
const response = await drive.files.list({
|
|
42
|
-
q: queryString,
|
|
43
|
-
pageSize: args.maxResults,
|
|
44
|
-
orderBy: args.orderBy === 'name' ? 'name' : args.orderBy,
|
|
45
|
-
fields: 'files(id,name,modifiedTime,createdTime,size,webViewLink,owners(displayName,emailAddress))',
|
|
46
|
-
supportsAllDrives: true,
|
|
47
|
-
includeItemsFromAllDrives: true,
|
|
48
|
-
});
|
|
49
|
-
const files = response.data.files || [];
|
|
50
|
-
const documents = files.map((file) => ({
|
|
51
|
-
id: file.id,
|
|
52
|
-
name: file.name,
|
|
53
|
-
modifiedTime: file.modifiedTime,
|
|
54
|
-
owner: file.owners?.[0]?.displayName || null,
|
|
55
|
-
url: file.webViewLink,
|
|
56
|
-
}));
|
|
57
|
-
return JSON.stringify({ documents }, null, 2);
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
log.error(`Error listing Google Docs: ${error.message || error}`);
|
|
61
|
-
if (error.code === 403)
|
|
62
|
-
throw new UserError('Permission denied. Make sure you have granted Google Drive access to the application.');
|
|
63
|
-
throw new UserError(`Failed to list documents: ${error.message || 'Unknown error'}`);
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getDriveClient } from '../../clients.js';
|
|
4
|
-
export function register(server) {
|
|
5
|
-
server.addTool({
|
|
6
|
-
name: 'listSpreadsheets',
|
|
7
|
-
description: 'Lists spreadsheets in your Drive, optionally filtered by name or content.',
|
|
8
|
-
parameters: z.object({
|
|
9
|
-
maxResults: z
|
|
10
|
-
.number()
|
|
11
|
-
.int()
|
|
12
|
-
.min(1)
|
|
13
|
-
.max(100)
|
|
14
|
-
.optional()
|
|
15
|
-
.default(20)
|
|
16
|
-
.describe('Maximum number of spreadsheets to return (1-100).'),
|
|
17
|
-
query: z
|
|
18
|
-
.string()
|
|
19
|
-
.optional()
|
|
20
|
-
.describe('Search query to filter spreadsheets by name or content.'),
|
|
21
|
-
orderBy: z
|
|
22
|
-
.enum(['name', 'modifiedTime', 'createdTime'])
|
|
23
|
-
.optional()
|
|
24
|
-
.default('modifiedTime')
|
|
25
|
-
.describe('Sort order for results.'),
|
|
26
|
-
}),
|
|
27
|
-
execute: async (args, { log }) => {
|
|
28
|
-
const drive = await getDriveClient();
|
|
29
|
-
log.info(`Listing Google Sheets. Query: ${args.query || 'none'}, Max: ${args.maxResults}, Order: ${args.orderBy}`);
|
|
30
|
-
try {
|
|
31
|
-
// Build the query string for Google Drive API
|
|
32
|
-
let queryString = "mimeType='application/vnd.google-apps.spreadsheet' and trashed=false";
|
|
33
|
-
if (args.query) {
|
|
34
|
-
queryString += ` and (name contains '${args.query}' or fullText contains '${args.query}')`;
|
|
35
|
-
}
|
|
36
|
-
const response = await drive.files.list({
|
|
37
|
-
q: queryString,
|
|
38
|
-
pageSize: args.maxResults,
|
|
39
|
-
orderBy: args.orderBy === 'name' ? 'name' : args.orderBy,
|
|
40
|
-
fields: 'files(id,name,modifiedTime,createdTime,size,webViewLink,owners(displayName,emailAddress))',
|
|
41
|
-
supportsAllDrives: true,
|
|
42
|
-
includeItemsFromAllDrives: true,
|
|
43
|
-
});
|
|
44
|
-
const files = response.data.files || [];
|
|
45
|
-
const spreadsheets = files.map((file) => ({
|
|
46
|
-
id: file.id,
|
|
47
|
-
name: file.name,
|
|
48
|
-
modifiedTime: file.modifiedTime,
|
|
49
|
-
owner: file.owners?.[0]?.displayName || null,
|
|
50
|
-
url: file.webViewLink,
|
|
51
|
-
}));
|
|
52
|
-
return JSON.stringify({ spreadsheets }, null, 2);
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
log.error(`Error listing Google Sheets: ${error.message || error}`);
|
|
56
|
-
if (error.code === 403)
|
|
57
|
-
throw new UserError('Permission denied. Make sure you have granted Google Drive access to the application.');
|
|
58
|
-
throw new UserError(`Failed to list spreadsheets: ${error.message || 'Unknown error'}`);
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
}
|