google-tools-mcp 1.2.11 → 2.0.0
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 -21
- package/README.md +555 -276
- package/dist/cachedToolsList.js +52 -52
- package/dist/googleDocsApiHelpers.js +76 -12
- package/dist/helpers.js +572 -252
- package/dist/httpAuth.js +296 -0
- package/dist/index.js +162 -11
- package/dist/logger.js +87 -87
- package/dist/markdown-transformer/docsToMarkdown.js +222 -21
- package/dist/markdown-transformer/index.js +19 -9
- package/dist/markdown-transformer/markdownToDocs.js +458 -28
- package/dist/readTracker.js +136 -17
- package/dist/sessionContext.js +37 -0
- package/dist/setup.js +187 -9
- package/dist/tools/calendar/getBusy.js +64 -64
- package/dist/tools/calendar/getEvents.js +141 -141
- package/dist/tools/calendar/getFree.js +225 -225
- package/dist/tools/calendar/index.js +19 -19
- package/dist/tools/calendar/listCalendars.js +38 -38
- package/dist/tools/calendar/listRecurringInstances.js +83 -83
- package/dist/tools/calendar/manageCalendar.js +121 -121
- package/dist/tools/calendar/manageEvent.js +258 -258
- package/dist/tools/calendar/moveEvent.js +66 -66
- package/dist/tools/docs/addTab.js +10 -12
- package/dist/tools/docs/appendToGoogleDoc.js +9 -4
- package/dist/tools/docs/deleteRange.js +9 -4
- package/dist/tools/docs/findAndReplace.js +9 -4
- package/dist/tools/docs/formatting/applyParagraphStyle.js +4 -1
- package/dist/tools/docs/formatting/getFormatting.js +171 -171
- package/dist/tools/docs/insertImage.js +15 -2
- package/dist/tools/docs/insertPageBreak.js +4 -1
- package/dist/tools/docs/insertTable.js +4 -1
- package/dist/tools/docs/insertTableWithData.js +4 -1
- package/dist/tools/docs/modifyText.js +14 -4
- package/dist/tools/docs/modifyText.test.js +84 -84
- package/dist/tools/docs/readGoogleDoc.js +71 -11
- package/dist/tools/docs/renameTab.js +12 -14
- package/dist/tools/drafts.js +165 -165
- package/dist/tools/drive/createDocument.js +13 -1
- package/dist/tools/drive/downloadFile.js +268 -268
- package/dist/tools/drive/getFileInfo.js +48 -48
- package/dist/tools/drive/getFilePath.js +50 -50
- package/dist/tools/drive/listDriveFiles.js +112 -112
- package/dist/tools/drive/listSharedDrives.js +52 -52
- package/dist/tools/drive/listSharedWithMe.js +97 -97
- package/dist/tools/drive/uploadFile.js +111 -111
- package/dist/tools/extras/index.js +9 -9
- package/dist/tools/extras/readDriveFile.js +212 -206
- package/dist/tools/extras/readFile.js +84 -84
- package/dist/tools/extras/searchFileContents.js +81 -81
- package/dist/tools/forms/batchUpdateForm.js +81 -81
- package/dist/tools/forms/createForm.js +54 -54
- package/dist/tools/forms/getForm.js +118 -118
- package/dist/tools/forms/getFormResponse.js +45 -45
- package/dist/tools/forms/index.js +15 -15
- package/dist/tools/forms/listFormResponses.js +56 -56
- package/dist/tools/forms/setPublishSettings.js +59 -59
- package/dist/tools/gmail/drafts.js +165 -165
- package/dist/tools/gmail/labels.js +54 -83
- package/dist/tools/gmail/messages.js +444 -441
- package/dist/tools/gmail/settings.js +358 -528
- package/dist/tools/gmail/threads.js +286 -164
- package/dist/tools/index.js +511 -473
- package/dist/tools/labels.js +103 -103
- package/dist/tools/legacyAliases.js +426 -0
- package/dist/tools/maps/directions.js +72 -0
- package/dist/tools/maps/geocode.js +16 -0
- package/dist/tools/maps/index.js +15 -0
- package/dist/tools/maps/mapsClient.js +83 -0
- package/dist/tools/maps/placeDetails.js +16 -0
- package/dist/tools/maps/reverseGeocode.js +19 -0
- package/dist/tools/maps/searchNearby.js +54 -0
- package/dist/tools/maps/searchPlaces.js +24 -0
- package/dist/tools/messages.js +448 -448
- package/dist/tools/settings.js +528 -528
- package/dist/tools/slides/createPresentation.js +104 -104
- package/dist/tools/slides/createShape.js +92 -92
- package/dist/tools/slides/createTextBox.js +82 -82
- package/dist/tools/slides/deleteSlide.js +30 -30
- package/dist/tools/slides/duplicateSlide.js +37 -37
- package/dist/tools/slides/exportThumbnail.js +42 -42
- package/dist/tools/slides/formatParagraph.js +72 -72
- package/dist/tools/slides/formatText.js +84 -84
- package/dist/tools/slides/getPresentation.js +87 -87
- package/dist/tools/slides/index.js +33 -33
- package/dist/tools/slides/reorderSlides.js +41 -41
- package/dist/tools/slides/replaceAllText.js +46 -46
- package/dist/tools/slides/setBackground.js +58 -58
- package/dist/tools/slides/speakerNotes.js +102 -102
- package/dist/tools/slides/styleShape.js +111 -111
- package/dist/tools/slides/updatePresentation.js +128 -128
- package/dist/tools/threads.js +145 -145
- package/dist/tools/utils/appendMarkdownToGoogleDoc.js +31 -7
- package/dist/tools/utils/replaceDocumentWithMarkdown.js +72 -9
- package/dist/updateCheck.js +202 -0
- package/dist/workspace.js +122 -0
- package/package.json +82 -80
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getDriveClient } from '../../clients.js';
|
|
4
|
-
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: 'getFilePath',
|
|
8
|
-
description: 'Returns the full folder path of a Drive file from root to the file itself (e.g. "My Drive/Projects/Docs/report.pdf"). Walks up the parent chain to reconstruct the complete path.',
|
|
9
|
-
parameters: z.object({
|
|
10
|
-
fileId: z.string().describe('The ID of the file or folder to get the path for.'),
|
|
11
|
-
}),
|
|
12
|
-
execute: async (args, { log }) => {
|
|
13
|
-
const drive = await getDriveClient();
|
|
14
|
-
log.info(`Getting full path for file: ${args.fileId}`);
|
|
15
|
-
try {
|
|
16
|
-
const pathParts = [];
|
|
17
|
-
let currentId = args.fileId;
|
|
18
|
-
|
|
19
|
-
while (currentId) {
|
|
20
|
-
const response = await drive.files.get({
|
|
21
|
-
fileId: currentId,
|
|
22
|
-
fields: 'id,name,parents',
|
|
23
|
-
supportsAllDrives: true,
|
|
24
|
-
});
|
|
25
|
-
const file = response.data;
|
|
26
|
-
if (!file) break;
|
|
27
|
-
|
|
28
|
-
pathParts.unshift(file.name);
|
|
29
|
-
|
|
30
|
-
if (file.parents && file.parents.length > 0) {
|
|
31
|
-
currentId = file.parents[0];
|
|
32
|
-
} else {
|
|
33
|
-
break;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const fullPath = pathParts.join('/');
|
|
38
|
-
|
|
39
|
-
return JSON.stringify({ fileId: args.fileId, path: fullPath }, null, 2);
|
|
40
|
-
} catch (error) {
|
|
41
|
-
log.error(`Error getting file path: ${error.message || error}`);
|
|
42
|
-
if (error.code === 404)
|
|
43
|
-
throw new UserError(`File not found (ID: ${args.fileId}).`);
|
|
44
|
-
if (error.code === 403)
|
|
45
|
-
throw new UserError('Permission denied. Make sure you have access to this file.');
|
|
46
|
-
throw new UserError(`Failed to get file path: ${error.message || 'Unknown error'}`);
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDriveClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'getFilePath',
|
|
8
|
+
description: 'Returns the full folder path of a Drive file from root to the file itself (e.g. "My Drive/Projects/Docs/report.pdf"). Walks up the parent chain to reconstruct the complete path.',
|
|
9
|
+
parameters: z.object({
|
|
10
|
+
fileId: z.string().describe('The ID of the file or folder to get the path for.'),
|
|
11
|
+
}),
|
|
12
|
+
execute: async (args, { log }) => {
|
|
13
|
+
const drive = await getDriveClient();
|
|
14
|
+
log.info(`Getting full path for file: ${args.fileId}`);
|
|
15
|
+
try {
|
|
16
|
+
const pathParts = [];
|
|
17
|
+
let currentId = args.fileId;
|
|
18
|
+
|
|
19
|
+
while (currentId) {
|
|
20
|
+
const response = await drive.files.get({
|
|
21
|
+
fileId: currentId,
|
|
22
|
+
fields: 'id,name,parents',
|
|
23
|
+
supportsAllDrives: true,
|
|
24
|
+
});
|
|
25
|
+
const file = response.data;
|
|
26
|
+
if (!file) break;
|
|
27
|
+
|
|
28
|
+
pathParts.unshift(file.name);
|
|
29
|
+
|
|
30
|
+
if (file.parents && file.parents.length > 0) {
|
|
31
|
+
currentId = file.parents[0];
|
|
32
|
+
} else {
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const fullPath = pathParts.join('/');
|
|
38
|
+
|
|
39
|
+
return JSON.stringify({ fileId: args.fileId, path: fullPath }, null, 2);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
log.error(`Error getting file path: ${error.message || error}`);
|
|
42
|
+
if (error.code === 404)
|
|
43
|
+
throw new UserError(`File not found (ID: ${args.fileId}).`);
|
|
44
|
+
if (error.code === 403)
|
|
45
|
+
throw new UserError('Permission denied. Make sure you have access to this file.');
|
|
46
|
+
throw new UserError(`Failed to get file path: ${error.message || 'Unknown error'}`);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -1,112 +1,112 @@
|
|
|
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
|
-
// Drive API does not allow orderBy when using fullText contains
|
|
77
|
-
const useFullText = !!args.query;
|
|
78
|
-
const listParams = {
|
|
79
|
-
q: queryParts.join(' and '),
|
|
80
|
-
pageSize: args.maxResults,
|
|
81
|
-
fields: 'files(id,name,mimeType,modifiedTime,createdTime,size,webViewLink,owners(displayName,emailAddress))',
|
|
82
|
-
supportsAllDrives: true,
|
|
83
|
-
includeItemsFromAllDrives: true,
|
|
84
|
-
};
|
|
85
|
-
if (!useFullText) {
|
|
86
|
-
listParams.orderBy = args.orderBy === 'name' ? 'name' : args.orderBy;
|
|
87
|
-
}
|
|
88
|
-
const response = await drive.files.list(listParams);
|
|
89
|
-
|
|
90
|
-
const files = (response.data.files || []).map((file) => ({
|
|
91
|
-
id: file.id,
|
|
92
|
-
name: file.name,
|
|
93
|
-
mimeType: file.mimeType,
|
|
94
|
-
modifiedTime: file.modifiedTime,
|
|
95
|
-
owner: file.owners?.[0]?.displayName || null,
|
|
96
|
-
url: file.webViewLink,
|
|
97
|
-
}));
|
|
98
|
-
|
|
99
|
-
return JSON.stringify({ files }, null, 2);
|
|
100
|
-
} catch (error) {
|
|
101
|
-
log.error(`Error listing Drive files: ${error.message || error}`);
|
|
102
|
-
if (error.code === 403)
|
|
103
|
-
throw new UserError(
|
|
104
|
-
'Permission denied. Make sure you have granted Google Drive access to the application.'
|
|
105
|
-
);
|
|
106
|
-
throw new UserError(
|
|
107
|
-
`Failed to list files: ${error.message || 'Unknown error'}`
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
}
|
|
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
|
+
// Drive API does not allow orderBy when using fullText contains
|
|
77
|
+
const useFullText = !!args.query;
|
|
78
|
+
const listParams = {
|
|
79
|
+
q: queryParts.join(' and '),
|
|
80
|
+
pageSize: args.maxResults,
|
|
81
|
+
fields: 'files(id,name,mimeType,modifiedTime,createdTime,size,webViewLink,owners(displayName,emailAddress))',
|
|
82
|
+
supportsAllDrives: true,
|
|
83
|
+
includeItemsFromAllDrives: true,
|
|
84
|
+
};
|
|
85
|
+
if (!useFullText) {
|
|
86
|
+
listParams.orderBy = args.orderBy === 'name' ? 'name' : args.orderBy;
|
|
87
|
+
}
|
|
88
|
+
const response = await drive.files.list(listParams);
|
|
89
|
+
|
|
90
|
+
const files = (response.data.files || []).map((file) => ({
|
|
91
|
+
id: file.id,
|
|
92
|
+
name: file.name,
|
|
93
|
+
mimeType: file.mimeType,
|
|
94
|
+
modifiedTime: file.modifiedTime,
|
|
95
|
+
owner: file.owners?.[0]?.displayName || null,
|
|
96
|
+
url: file.webViewLink,
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
return JSON.stringify({ files }, null, 2);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
log.error(`Error listing Drive files: ${error.message || error}`);
|
|
102
|
+
if (error.code === 403)
|
|
103
|
+
throw new UserError(
|
|
104
|
+
'Permission denied. Make sure you have granted Google Drive access to the application.'
|
|
105
|
+
);
|
|
106
|
+
throw new UserError(
|
|
107
|
+
`Failed to list files: ${error.message || 'Unknown error'}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
@@ -1,52 +1,52 @@
|
|
|
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: 'listSharedDrives',
|
|
7
|
-
description: 'Lists all Shared Drives (Team Drives) the user has access to. Use the returned drive ID with listFolderContents to browse its contents.',
|
|
8
|
-
parameters: z.object({
|
|
9
|
-
maxResults: z
|
|
10
|
-
.number()
|
|
11
|
-
.int()
|
|
12
|
-
.min(1)
|
|
13
|
-
.max(100)
|
|
14
|
-
.optional()
|
|
15
|
-
.default(50)
|
|
16
|
-
.describe('Maximum number of shared drives to return.'),
|
|
17
|
-
pageToken: z
|
|
18
|
-
.string()
|
|
19
|
-
.optional()
|
|
20
|
-
.describe('Token for fetching the next page of results.'),
|
|
21
|
-
}),
|
|
22
|
-
execute: async (args, { log }) => {
|
|
23
|
-
const drive = await getDriveClient();
|
|
24
|
-
log.info('Listing shared drives');
|
|
25
|
-
try {
|
|
26
|
-
const response = await drive.drives.list({
|
|
27
|
-
pageSize: args.maxResults,
|
|
28
|
-
pageToken: args.pageToken || undefined,
|
|
29
|
-
fields: 'nextPageToken,drives(id,name,createdTime,hidden)',
|
|
30
|
-
});
|
|
31
|
-
const drives = (response.data.drives || []).map((d) => ({
|
|
32
|
-
id: d.id,
|
|
33
|
-
name: d.name,
|
|
34
|
-
createdTime: d.createdTime,
|
|
35
|
-
hidden: d.hidden || false,
|
|
36
|
-
}));
|
|
37
|
-
const result = {
|
|
38
|
-
drives,
|
|
39
|
-
nextPageToken: response.data.nextPageToken || null,
|
|
40
|
-
totalCount: drives.length,
|
|
41
|
-
};
|
|
42
|
-
return JSON.stringify(result, null, 2);
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
log.error(`Error listing shared drives: ${error.message || error}`);
|
|
46
|
-
if (error.code === 403)
|
|
47
|
-
throw new UserError('Permission denied. Make sure the Drive API scope includes shared drives.');
|
|
48
|
-
throw new UserError(`Failed to list shared drives: ${error.message || 'Unknown error'}`);
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
}
|
|
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: 'listSharedDrives',
|
|
7
|
+
description: 'Lists all Shared Drives (Team Drives) the user has access to. Use the returned drive ID with listFolderContents to browse its contents.',
|
|
8
|
+
parameters: z.object({
|
|
9
|
+
maxResults: z
|
|
10
|
+
.number()
|
|
11
|
+
.int()
|
|
12
|
+
.min(1)
|
|
13
|
+
.max(100)
|
|
14
|
+
.optional()
|
|
15
|
+
.default(50)
|
|
16
|
+
.describe('Maximum number of shared drives to return.'),
|
|
17
|
+
pageToken: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Token for fetching the next page of results.'),
|
|
21
|
+
}),
|
|
22
|
+
execute: async (args, { log }) => {
|
|
23
|
+
const drive = await getDriveClient();
|
|
24
|
+
log.info('Listing shared drives');
|
|
25
|
+
try {
|
|
26
|
+
const response = await drive.drives.list({
|
|
27
|
+
pageSize: args.maxResults,
|
|
28
|
+
pageToken: args.pageToken || undefined,
|
|
29
|
+
fields: 'nextPageToken,drives(id,name,createdTime,hidden)',
|
|
30
|
+
});
|
|
31
|
+
const drives = (response.data.drives || []).map((d) => ({
|
|
32
|
+
id: d.id,
|
|
33
|
+
name: d.name,
|
|
34
|
+
createdTime: d.createdTime,
|
|
35
|
+
hidden: d.hidden || false,
|
|
36
|
+
}));
|
|
37
|
+
const result = {
|
|
38
|
+
drives,
|
|
39
|
+
nextPageToken: response.data.nextPageToken || null,
|
|
40
|
+
totalCount: drives.length,
|
|
41
|
+
};
|
|
42
|
+
return JSON.stringify(result, null, 2);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
log.error(`Error listing shared drives: ${error.message || error}`);
|
|
46
|
+
if (error.code === 403)
|
|
47
|
+
throw new UserError('Permission denied. Make sure the Drive API scope includes shared drives.');
|
|
48
|
+
throw new UserError(`Failed to list shared drives: ${error.message || 'Unknown error'}`);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -1,97 +1,97 @@
|
|
|
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: 'listSharedWithMe',
|
|
7
|
-
description: 'Lists files and folders that have been shared with the user ("Shared with me"). Returns items shared by others that are not in the user\'s own Drive. Use folder IDs with listFolderContents to navigate into shared folders.',
|
|
8
|
-
parameters: z.object({
|
|
9
|
-
mimeType: z
|
|
10
|
-
.string()
|
|
11
|
-
.optional()
|
|
12
|
-
.describe('Filter by MIME type. Use "application/vnd.google-apps.folder" for folders only, "application/vnd.google-apps.document" for Docs, "application/vnd.google-apps.spreadsheet" for Sheets, etc.'),
|
|
13
|
-
query: z
|
|
14
|
-
.string()
|
|
15
|
-
.optional()
|
|
16
|
-
.describe('Search query to filter shared items by name (uses "contains" matching).'),
|
|
17
|
-
maxResults: z
|
|
18
|
-
.number()
|
|
19
|
-
.int()
|
|
20
|
-
.min(1)
|
|
21
|
-
.max(100)
|
|
22
|
-
.optional()
|
|
23
|
-
.default(50)
|
|
24
|
-
.describe('Maximum number of items to return.'),
|
|
25
|
-
pageToken: z
|
|
26
|
-
.string()
|
|
27
|
-
.optional()
|
|
28
|
-
.describe('Token for fetching the next page of results.'),
|
|
29
|
-
orderBy: z
|
|
30
|
-
.enum(['name', 'modifiedTime', 'sharedWithMeTime'])
|
|
31
|
-
.optional()
|
|
32
|
-
.default('sharedWithMeTime')
|
|
33
|
-
.describe('How to sort results. Defaults to most recently shared first.'),
|
|
34
|
-
}),
|
|
35
|
-
execute: async (args, { log }) => {
|
|
36
|
-
const drive = await getDriveClient();
|
|
37
|
-
log.info('Listing shared with me');
|
|
38
|
-
try {
|
|
39
|
-
let queryParts = ['sharedWithMe=true', 'trashed=false'];
|
|
40
|
-
if (args.mimeType) {
|
|
41
|
-
queryParts.push(`mimeType='${args.mimeType}'`);
|
|
42
|
-
}
|
|
43
|
-
if (args.query) {
|
|
44
|
-
queryParts.push(`name contains '${args.query.replace(/'/g, "\\'")}'`);
|
|
45
|
-
}
|
|
46
|
-
const orderBy = args.orderBy === 'sharedWithMeTime'
|
|
47
|
-
? 'sharedWithMeTime desc'
|
|
48
|
-
: args.orderBy === 'modifiedTime'
|
|
49
|
-
? 'modifiedTime desc'
|
|
50
|
-
: 'name';
|
|
51
|
-
const response = await drive.files.list({
|
|
52
|
-
q: queryParts.join(' and '),
|
|
53
|
-
pageSize: args.maxResults,
|
|
54
|
-
pageToken: args.pageToken || undefined,
|
|
55
|
-
orderBy,
|
|
56
|
-
fields: 'nextPageToken,files(id,name,mimeType,modifiedTime,sharedWithMeTime,sharingUser(displayName,emailAddress),webViewLink,owners(displayName))',
|
|
57
|
-
supportsAllDrives: true,
|
|
58
|
-
includeItemsFromAllDrives: true,
|
|
59
|
-
});
|
|
60
|
-
const items = response.data.files || [];
|
|
61
|
-
const folders = items
|
|
62
|
-
.filter((f) => f.mimeType === 'application/vnd.google-apps.folder')
|
|
63
|
-
.map((f) => ({
|
|
64
|
-
id: f.id,
|
|
65
|
-
name: f.name,
|
|
66
|
-
sharedWithMeTime: f.sharedWithMeTime,
|
|
67
|
-
sharedBy: f.sharingUser?.displayName || f.owners?.[0]?.displayName || null,
|
|
68
|
-
sharedByEmail: f.sharingUser?.emailAddress || null,
|
|
69
|
-
}));
|
|
70
|
-
const files = items
|
|
71
|
-
.filter((f) => f.mimeType !== 'application/vnd.google-apps.folder')
|
|
72
|
-
.map((f) => ({
|
|
73
|
-
id: f.id,
|
|
74
|
-
name: f.name,
|
|
75
|
-
mimeType: f.mimeType,
|
|
76
|
-
modifiedTime: f.modifiedTime,
|
|
77
|
-
sharedWithMeTime: f.sharedWithMeTime,
|
|
78
|
-
sharedBy: f.sharingUser?.displayName || f.owners?.[0]?.displayName || null,
|
|
79
|
-
sharedByEmail: f.sharingUser?.emailAddress || null,
|
|
80
|
-
}));
|
|
81
|
-
const result = {
|
|
82
|
-
folders,
|
|
83
|
-
files,
|
|
84
|
-
nextPageToken: response.data.nextPageToken || null,
|
|
85
|
-
totalCount: items.length,
|
|
86
|
-
};
|
|
87
|
-
return JSON.stringify(result, null, 2);
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
log.error(`Error listing shared items: ${error.message || error}`);
|
|
91
|
-
if (error.code === 403)
|
|
92
|
-
throw new UserError('Permission denied. Make sure you have the correct Drive API scopes.');
|
|
93
|
-
throw new UserError(`Failed to list shared items: ${error.message || 'Unknown error'}`);
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
}
|
|
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: 'listSharedWithMe',
|
|
7
|
+
description: 'Lists files and folders that have been shared with the user ("Shared with me"). Returns items shared by others that are not in the user\'s own Drive. Use folder IDs with listFolderContents to navigate into shared folders.',
|
|
8
|
+
parameters: z.object({
|
|
9
|
+
mimeType: z
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe('Filter by MIME type. Use "application/vnd.google-apps.folder" for folders only, "application/vnd.google-apps.document" for Docs, "application/vnd.google-apps.spreadsheet" for Sheets, etc.'),
|
|
13
|
+
query: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Search query to filter shared items by name (uses "contains" matching).'),
|
|
17
|
+
maxResults: z
|
|
18
|
+
.number()
|
|
19
|
+
.int()
|
|
20
|
+
.min(1)
|
|
21
|
+
.max(100)
|
|
22
|
+
.optional()
|
|
23
|
+
.default(50)
|
|
24
|
+
.describe('Maximum number of items to return.'),
|
|
25
|
+
pageToken: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Token for fetching the next page of results.'),
|
|
29
|
+
orderBy: z
|
|
30
|
+
.enum(['name', 'modifiedTime', 'sharedWithMeTime'])
|
|
31
|
+
.optional()
|
|
32
|
+
.default('sharedWithMeTime')
|
|
33
|
+
.describe('How to sort results. Defaults to most recently shared first.'),
|
|
34
|
+
}),
|
|
35
|
+
execute: async (args, { log }) => {
|
|
36
|
+
const drive = await getDriveClient();
|
|
37
|
+
log.info('Listing shared with me');
|
|
38
|
+
try {
|
|
39
|
+
let queryParts = ['sharedWithMe=true', 'trashed=false'];
|
|
40
|
+
if (args.mimeType) {
|
|
41
|
+
queryParts.push(`mimeType='${args.mimeType}'`);
|
|
42
|
+
}
|
|
43
|
+
if (args.query) {
|
|
44
|
+
queryParts.push(`name contains '${args.query.replace(/'/g, "\\'")}'`);
|
|
45
|
+
}
|
|
46
|
+
const orderBy = args.orderBy === 'sharedWithMeTime'
|
|
47
|
+
? 'sharedWithMeTime desc'
|
|
48
|
+
: args.orderBy === 'modifiedTime'
|
|
49
|
+
? 'modifiedTime desc'
|
|
50
|
+
: 'name';
|
|
51
|
+
const response = await drive.files.list({
|
|
52
|
+
q: queryParts.join(' and '),
|
|
53
|
+
pageSize: args.maxResults,
|
|
54
|
+
pageToken: args.pageToken || undefined,
|
|
55
|
+
orderBy,
|
|
56
|
+
fields: 'nextPageToken,files(id,name,mimeType,modifiedTime,sharedWithMeTime,sharingUser(displayName,emailAddress),webViewLink,owners(displayName))',
|
|
57
|
+
supportsAllDrives: true,
|
|
58
|
+
includeItemsFromAllDrives: true,
|
|
59
|
+
});
|
|
60
|
+
const items = response.data.files || [];
|
|
61
|
+
const folders = items
|
|
62
|
+
.filter((f) => f.mimeType === 'application/vnd.google-apps.folder')
|
|
63
|
+
.map((f) => ({
|
|
64
|
+
id: f.id,
|
|
65
|
+
name: f.name,
|
|
66
|
+
sharedWithMeTime: f.sharedWithMeTime,
|
|
67
|
+
sharedBy: f.sharingUser?.displayName || f.owners?.[0]?.displayName || null,
|
|
68
|
+
sharedByEmail: f.sharingUser?.emailAddress || null,
|
|
69
|
+
}));
|
|
70
|
+
const files = items
|
|
71
|
+
.filter((f) => f.mimeType !== 'application/vnd.google-apps.folder')
|
|
72
|
+
.map((f) => ({
|
|
73
|
+
id: f.id,
|
|
74
|
+
name: f.name,
|
|
75
|
+
mimeType: f.mimeType,
|
|
76
|
+
modifiedTime: f.modifiedTime,
|
|
77
|
+
sharedWithMeTime: f.sharedWithMeTime,
|
|
78
|
+
sharedBy: f.sharingUser?.displayName || f.owners?.[0]?.displayName || null,
|
|
79
|
+
sharedByEmail: f.sharingUser?.emailAddress || null,
|
|
80
|
+
}));
|
|
81
|
+
const result = {
|
|
82
|
+
folders,
|
|
83
|
+
files,
|
|
84
|
+
nextPageToken: response.data.nextPageToken || null,
|
|
85
|
+
totalCount: items.length,
|
|
86
|
+
};
|
|
87
|
+
return JSON.stringify(result, null, 2);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
log.error(`Error listing shared items: ${error.message || error}`);
|
|
91
|
+
if (error.code === 403)
|
|
92
|
+
throw new UserError('Permission denied. Make sure you have the correct Drive API scopes.');
|
|
93
|
+
throw new UserError(`Failed to list shared items: ${error.message || 'Unknown error'}`);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|