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,206 +1,212 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getDriveClient, getDocsClient, getSheetsClient } from '../../clients.js';
|
|
4
|
-
import { docsJsonToMarkdown } from '../../markdown-transformer/index.js';
|
|
5
|
-
import * as GDocsHelpers from '../../googleDocsApiHelpers.js';
|
|
6
|
-
import { extractText, downloadBuffer } from './readFile.js';
|
|
7
|
-
import { trackRead } from '../../readTracker.js';
|
|
8
|
-
|
|
9
|
-
// Google-native MIME types
|
|
10
|
-
const GOOGLE_DOC = 'application/vnd.google-apps.document';
|
|
11
|
-
const GOOGLE_SHEET = 'application/vnd.google-apps.spreadsheet';
|
|
12
|
-
const GOOGLE_SLIDES = 'application/vnd.google-apps.presentation';
|
|
13
|
-
|
|
14
|
-
// Capabilities by file type category
|
|
15
|
-
const CAPABILITIES = {
|
|
16
|
-
googleDoc: {
|
|
17
|
-
fileType: 'Google Doc',
|
|
18
|
-
capabilities: ['read', 'readDocument', 'modifyText', 'replaceDocumentWithMarkdown', 'appendMarkdown', 'appendText', 'findAndReplace', 'deleteRange'],
|
|
19
|
-
},
|
|
20
|
-
googleSheet: {
|
|
21
|
-
fileType: 'Google Sheet',
|
|
22
|
-
capabilities: ['read', 'readSpreadsheet', 'writeSpreadsheet', 'appendRows', 'formatCells'],
|
|
23
|
-
},
|
|
24
|
-
docx: {
|
|
25
|
-
fileType: 'Word Document (.docx)',
|
|
26
|
-
capabilities: ['read'],
|
|
27
|
-
note: 'This is a Word document. Editing tools (modifyText, appendMarkdown, etc.) are not available unless converted to a Google Doc via copyFile or Drive UI.',
|
|
28
|
-
},
|
|
29
|
-
pdf: {
|
|
30
|
-
fileType: 'PDF',
|
|
31
|
-
capabilities: ['read'],
|
|
32
|
-
note: 'This is a PDF file. Editing tools are not available. Convert to a Google Doc first if editing is needed.',
|
|
33
|
-
},
|
|
34
|
-
other: {
|
|
35
|
-
fileType: 'Unknown',
|
|
36
|
-
capabilities: ['download'],
|
|
37
|
-
note: 'This file type cannot be read as text. Use downloadFile to download it.',
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export function register(server) {
|
|
42
|
-
server.addTool({
|
|
43
|
-
name: 'readDriveFile',
|
|
44
|
-
description:
|
|
45
|
-
'Unified read tool: accepts any Google Drive file ID, auto-detects the file type (Google Doc, Sheet, Word .docx, PDF), ' +
|
|
46
|
-
'and routes to the appropriate reader. Returns content along with metadata about available capabilities. ' +
|
|
47
|
-
'Use this when you have a file ID but don\'t know the file type, or when readDocument fails with a 400 error.',
|
|
48
|
-
parameters: z.object({
|
|
49
|
-
fileId: z
|
|
50
|
-
.string()
|
|
51
|
-
.describe('The Google Drive file ID (the long string between /d/ and /edit in a Drive URL).'),
|
|
52
|
-
format: z
|
|
53
|
-
.enum(['text', 'json', 'markdown'])
|
|
54
|
-
.optional()
|
|
55
|
-
.default('markdown')
|
|
56
|
-
.describe("Output format for Google Docs: 'markdown' (default), 'text' (plain text), 'json' (raw structure). Ignored for non-Doc files."),
|
|
57
|
-
|
|
58
|
-
.
|
|
59
|
-
.optional()
|
|
60
|
-
.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (element.
|
|
132
|
-
element.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDriveClient, getDocsClient, getSheetsClient } from '../../clients.js';
|
|
4
|
+
import { docsJsonToMarkdown } from '../../markdown-transformer/index.js';
|
|
5
|
+
import * as GDocsHelpers from '../../googleDocsApiHelpers.js';
|
|
6
|
+
import { extractText, downloadBuffer } from './readFile.js';
|
|
7
|
+
import { trackRead } from '../../readTracker.js';
|
|
8
|
+
|
|
9
|
+
// Google-native MIME types
|
|
10
|
+
const GOOGLE_DOC = 'application/vnd.google-apps.document';
|
|
11
|
+
const GOOGLE_SHEET = 'application/vnd.google-apps.spreadsheet';
|
|
12
|
+
const GOOGLE_SLIDES = 'application/vnd.google-apps.presentation';
|
|
13
|
+
|
|
14
|
+
// Capabilities by file type category
|
|
15
|
+
const CAPABILITIES = {
|
|
16
|
+
googleDoc: {
|
|
17
|
+
fileType: 'Google Doc',
|
|
18
|
+
capabilities: ['read', 'readDocument', 'modifyText', 'replaceDocumentWithMarkdown', 'appendMarkdown', 'appendText', 'findAndReplace', 'deleteRange'],
|
|
19
|
+
},
|
|
20
|
+
googleSheet: {
|
|
21
|
+
fileType: 'Google Sheet',
|
|
22
|
+
capabilities: ['read', 'readSpreadsheet', 'writeSpreadsheet', 'appendRows', 'formatCells'],
|
|
23
|
+
},
|
|
24
|
+
docx: {
|
|
25
|
+
fileType: 'Word Document (.docx)',
|
|
26
|
+
capabilities: ['read'],
|
|
27
|
+
note: 'This is a Word document. Editing tools (modifyText, appendMarkdown, etc.) are not available unless converted to a Google Doc via copyFile or Drive UI.',
|
|
28
|
+
},
|
|
29
|
+
pdf: {
|
|
30
|
+
fileType: 'PDF',
|
|
31
|
+
capabilities: ['read'],
|
|
32
|
+
note: 'This is a PDF file. Editing tools are not available. Convert to a Google Doc first if editing is needed.',
|
|
33
|
+
},
|
|
34
|
+
other: {
|
|
35
|
+
fileType: 'Unknown',
|
|
36
|
+
capabilities: ['download'],
|
|
37
|
+
note: 'This file type cannot be read as text. Use downloadFile to download it.',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function register(server) {
|
|
42
|
+
server.addTool({
|
|
43
|
+
name: 'readDriveFile',
|
|
44
|
+
description:
|
|
45
|
+
'Unified read tool: accepts any Google Drive file ID, auto-detects the file type (Google Doc, Sheet, Word .docx, PDF), ' +
|
|
46
|
+
'and routes to the appropriate reader. Returns content along with metadata about available capabilities. ' +
|
|
47
|
+
'Use this when you have a file ID but don\'t know the file type, or when readDocument fails with a 400 error.',
|
|
48
|
+
parameters: z.object({
|
|
49
|
+
fileId: z
|
|
50
|
+
.string()
|
|
51
|
+
.describe('The Google Drive file ID (the long string between /d/ and /edit in a Drive URL).'),
|
|
52
|
+
format: z
|
|
53
|
+
.enum(['text', 'json', 'markdown'])
|
|
54
|
+
.optional()
|
|
55
|
+
.default('markdown')
|
|
56
|
+
.describe("Output format for Google Docs: 'markdown' (default, preserves rich text/paragraph formatting with a small HTML allowlist), 'text' (plain text), 'json' (raw structure). Ignored for non-Doc files."),
|
|
57
|
+
plainMarkdown: z
|
|
58
|
+
.boolean()
|
|
59
|
+
.optional()
|
|
60
|
+
.default(false)
|
|
61
|
+
.describe('For Google Docs markdown output only. If true, suppresses rich HTML-style formatting extensions and returns cleaner portable markdown.'),
|
|
62
|
+
range: z
|
|
63
|
+
.string()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("A1 notation range for Google Sheets (e.g., 'Sheet1!A1:C10'). Required for spreadsheets."),
|
|
66
|
+
}),
|
|
67
|
+
execute: async (args, { log }) => {
|
|
68
|
+
const drive = await getDriveClient();
|
|
69
|
+
|
|
70
|
+
// 1. Get file metadata to determine type
|
|
71
|
+
let fileMeta;
|
|
72
|
+
try {
|
|
73
|
+
const res = await drive.files.get({
|
|
74
|
+
fileId: args.fileId,
|
|
75
|
+
fields: 'id,name,mimeType,modifiedTime,size',
|
|
76
|
+
supportsAllDrives: true,
|
|
77
|
+
});
|
|
78
|
+
fileMeta = res.data;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error.code === 404) throw new UserError(`File not found: ${args.fileId}`);
|
|
81
|
+
if (error.code === 403) throw new UserError('Permission denied. Check that the file is shared with this account.');
|
|
82
|
+
throw new UserError(`Failed to get file info: ${error.message}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
log.info(`File "${fileMeta.name}" is ${fileMeta.mimeType}`);
|
|
86
|
+
trackRead(args.fileId, fileMeta.modifiedTime);
|
|
87
|
+
|
|
88
|
+
// 2. Route based on MIME type
|
|
89
|
+
if (fileMeta.mimeType === GOOGLE_DOC) {
|
|
90
|
+
return await readGoogleDoc(args, fileMeta, log);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (fileMeta.mimeType === GOOGLE_SHEET) {
|
|
94
|
+
return await readGoogleSheet(args, fileMeta, log);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Try reading as docx/pdf
|
|
98
|
+
const text = await tryReadBinaryFile(drive, args.fileId, fileMeta, log);
|
|
99
|
+
if (text !== null) {
|
|
100
|
+
return text;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Unsupported type
|
|
104
|
+
const capInfo = CAPABILITIES.other;
|
|
105
|
+
capInfo.fileType = fileMeta.mimeType;
|
|
106
|
+
return JSON.stringify({
|
|
107
|
+
file: { id: fileMeta.id, name: fileMeta.name, mimeType: fileMeta.mimeType },
|
|
108
|
+
...capInfo,
|
|
109
|
+
content: null,
|
|
110
|
+
}, null, 2);
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function readGoogleDoc(args, fileMeta, log) {
|
|
116
|
+
const docs = await getDocsClient();
|
|
117
|
+
const res = await docs.documents.get({
|
|
118
|
+
documentId: args.fileId,
|
|
119
|
+
fields: args.format === 'text' ? 'revisionId,body(content(paragraph(elements(textRun(content)))))' : '*',
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
let content;
|
|
123
|
+
if (args.format === 'json') {
|
|
124
|
+
content = JSON.stringify(res.data, null, 2);
|
|
125
|
+
} else if (args.format === 'markdown') {
|
|
126
|
+
content = docsJsonToMarkdown(res.data, { plainMarkdown: args.plainMarkdown });
|
|
127
|
+
} else {
|
|
128
|
+
// text
|
|
129
|
+
let textContent = '';
|
|
130
|
+
res.data.body?.content?.forEach((element) => {
|
|
131
|
+
if (element.paragraph?.elements) {
|
|
132
|
+
element.paragraph.elements.forEach((pe) => {
|
|
133
|
+
if (pe.textRun?.content) textContent += pe.textRun.content;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (element.table?.tableRows) {
|
|
137
|
+
element.table.tableRows.forEach((row) => {
|
|
138
|
+
row.tableCells?.forEach((cell) => {
|
|
139
|
+
cell.content?.forEach((cellElement) => {
|
|
140
|
+
cellElement.paragraph?.elements?.forEach((pe) => {
|
|
141
|
+
if (pe.textRun?.content) textContent += pe.textRun.content;
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
content = textContent || '(empty document)';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
trackRead(args.fileId, fileMeta.modifiedTime, args.format === 'markdown' ? content : undefined, res.data.revisionId);
|
|
152
|
+
return JSON.stringify({
|
|
153
|
+
file: { id: fileMeta.id, name: fileMeta.name, mimeType: fileMeta.mimeType },
|
|
154
|
+
...CAPABILITIES.googleDoc,
|
|
155
|
+
content,
|
|
156
|
+
}, null, 2);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function readGoogleSheet(args, fileMeta, log) {
|
|
160
|
+
if (!args.range) {
|
|
161
|
+
// Return sheet info without data
|
|
162
|
+
const sheets = await getSheetsClient();
|
|
163
|
+
const info = await sheets.spreadsheets.get({
|
|
164
|
+
spreadsheetId: args.fileId,
|
|
165
|
+
fields: 'sheets(properties(sheetId,title,gridProperties))',
|
|
166
|
+
});
|
|
167
|
+
const sheetList = info.data.sheets?.map(s => ({
|
|
168
|
+
title: s.properties.title,
|
|
169
|
+
sheetId: s.properties.sheetId,
|
|
170
|
+
rows: s.properties.gridProperties?.rowCount,
|
|
171
|
+
cols: s.properties.gridProperties?.columnCount,
|
|
172
|
+
}));
|
|
173
|
+
return JSON.stringify({
|
|
174
|
+
file: { id: fileMeta.id, name: fileMeta.name, mimeType: fileMeta.mimeType },
|
|
175
|
+
...CAPABILITIES.googleSheet,
|
|
176
|
+
note: 'Provide a range parameter (e.g., "Sheet1!A1:C10") to read data. Available sheets listed below.',
|
|
177
|
+
sheets: sheetList,
|
|
178
|
+
}, null, 2);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const sheets = await getSheetsClient();
|
|
182
|
+
const response = await sheets.spreadsheets.values.get({
|
|
183
|
+
spreadsheetId: args.fileId,
|
|
184
|
+
range: args.range,
|
|
185
|
+
});
|
|
186
|
+
return JSON.stringify({
|
|
187
|
+
file: { id: fileMeta.id, name: fileMeta.name, mimeType: fileMeta.mimeType },
|
|
188
|
+
...CAPABILITIES.googleSheet,
|
|
189
|
+
range: args.range,
|
|
190
|
+
values: response.data.values || [],
|
|
191
|
+
}, null, 2);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function tryReadBinaryFile(drive, fileId, fileMeta, log) {
|
|
195
|
+
const { mimeType, name } = fileMeta;
|
|
196
|
+
const isDocxFile = mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || name?.endsWith('.docx');
|
|
197
|
+
const isPdfFile = mimeType === 'application/pdf' || name?.endsWith('.pdf');
|
|
198
|
+
|
|
199
|
+
if (!isDocxFile && !isPdfFile) return null;
|
|
200
|
+
|
|
201
|
+
const buffer = await downloadBuffer(drive, fileId);
|
|
202
|
+
const text = await extractText(buffer, mimeType, name);
|
|
203
|
+
|
|
204
|
+
if (text === null) return null;
|
|
205
|
+
|
|
206
|
+
const capInfo = isDocxFile ? CAPABILITIES.docx : CAPABILITIES.pdf;
|
|
207
|
+
return JSON.stringify({
|
|
208
|
+
file: { id: fileMeta.id, name: fileMeta.name, mimeType: fileMeta.mimeType },
|
|
209
|
+
...capInfo,
|
|
210
|
+
content: text,
|
|
211
|
+
}, null, 2);
|
|
212
|
+
}
|
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getDriveClient } from '../../clients.js';
|
|
4
|
-
import { trackRead } from '../../readTracker.js';
|
|
5
|
-
import mammoth from 'mammoth';
|
|
6
|
-
|
|
7
|
-
function isDocx(mimeType, name) {
|
|
8
|
-
return (
|
|
9
|
-
mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
|
|
10
|
-
(name && name.endsWith('.docx'))
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function isPdf(mimeType, name) {
|
|
15
|
-
return mimeType === 'application/pdf' || (name && name.endsWith('.pdf'));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async function downloadBuffer(drive, fileId) {
|
|
19
|
-
const response = await drive.files.get(
|
|
20
|
-
{ fileId, alt: 'media', supportsAllDrives: true },
|
|
21
|
-
{ responseType: 'arraybuffer' }
|
|
22
|
-
);
|
|
23
|
-
return Buffer.from(response.data);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function extractText(buffer, mimeType, name) {
|
|
27
|
-
if (isDocx(mimeType, name)) {
|
|
28
|
-
const result = await mammoth.extractRawText({ buffer });
|
|
29
|
-
return result.value;
|
|
30
|
-
}
|
|
31
|
-
if (isPdf(mimeType, name)) {
|
|
32
|
-
const { default: pdfParse } = await import('pdf-parse/lib/pdf-parse.js');
|
|
33
|
-
const result = await pdfParse(buffer);
|
|
34
|
-
return result.text;
|
|
35
|
-
}
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export { isDocx, isPdf, downloadBuffer };
|
|
40
|
-
|
|
41
|
-
export function register(server) {
|
|
42
|
-
server.addTool({
|
|
43
|
-
name: 'readFile',
|
|
44
|
-
description:
|
|
45
|
-
'Read the full text content of a .docx or .pdf file from Google Drive by file ID. ' +
|
|
46
|
-
'Use this for Word documents and PDFs that cannot be read with readDocument.',
|
|
47
|
-
parameters: z.object({
|
|
48
|
-
fileId: z.string().describe('The Google Drive file ID'),
|
|
49
|
-
}),
|
|
50
|
-
execute: async ({ fileId }, { log }) => {
|
|
51
|
-
const drive = await getDriveClient();
|
|
52
|
-
log.info(`Reading file ${fileId}`);
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const meta = await drive.files.get({
|
|
56
|
-
fileId,
|
|
57
|
-
fields: 'name,mimeType',
|
|
58
|
-
supportsAllDrives: true,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const { name, mimeType } = meta.data;
|
|
62
|
-
trackRead(fileId);
|
|
63
|
-
const buffer = await downloadBuffer(drive, fileId);
|
|
64
|
-
const text = await extractText(buffer, mimeType, name);
|
|
65
|
-
|
|
66
|
-
if (text === null) {
|
|
67
|
-
throw new UserError(
|
|
68
|
-
`Unsupported file type: ${mimeType} (${name}). Only .docx and .pdf are supported.`
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return `# ${name}\n\n${text}`;
|
|
73
|
-
} catch (error) {
|
|
74
|
-
if (error instanceof UserError) throw error;
|
|
75
|
-
log.error(`Error reading file: ${error.message}`);
|
|
76
|
-
if (error.code === 404)
|
|
77
|
-
throw new UserError(`File not found: ${fileId}`);
|
|
78
|
-
if (error.code === 403)
|
|
79
|
-
throw new UserError('Permission denied. Check that the file is shared with this account.');
|
|
80
|
-
throw new UserError(`Failed to read file: ${error.message}`);
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDriveClient } from '../../clients.js';
|
|
4
|
+
import { trackRead } from '../../readTracker.js';
|
|
5
|
+
import mammoth from 'mammoth';
|
|
6
|
+
|
|
7
|
+
function isDocx(mimeType, name) {
|
|
8
|
+
return (
|
|
9
|
+
mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
|
|
10
|
+
(name && name.endsWith('.docx'))
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isPdf(mimeType, name) {
|
|
15
|
+
return mimeType === 'application/pdf' || (name && name.endsWith('.pdf'));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function downloadBuffer(drive, fileId) {
|
|
19
|
+
const response = await drive.files.get(
|
|
20
|
+
{ fileId, alt: 'media', supportsAllDrives: true },
|
|
21
|
+
{ responseType: 'arraybuffer' }
|
|
22
|
+
);
|
|
23
|
+
return Buffer.from(response.data);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function extractText(buffer, mimeType, name) {
|
|
27
|
+
if (isDocx(mimeType, name)) {
|
|
28
|
+
const result = await mammoth.extractRawText({ buffer });
|
|
29
|
+
return result.value;
|
|
30
|
+
}
|
|
31
|
+
if (isPdf(mimeType, name)) {
|
|
32
|
+
const { default: pdfParse } = await import('pdf-parse/lib/pdf-parse.js');
|
|
33
|
+
const result = await pdfParse(buffer);
|
|
34
|
+
return result.text;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { isDocx, isPdf, downloadBuffer };
|
|
40
|
+
|
|
41
|
+
export function register(server) {
|
|
42
|
+
server.addTool({
|
|
43
|
+
name: 'readFile',
|
|
44
|
+
description:
|
|
45
|
+
'Read the full text content of a .docx or .pdf file from Google Drive by file ID. ' +
|
|
46
|
+
'Use this for Word documents and PDFs that cannot be read with readDocument.',
|
|
47
|
+
parameters: z.object({
|
|
48
|
+
fileId: z.string().describe('The Google Drive file ID'),
|
|
49
|
+
}),
|
|
50
|
+
execute: async ({ fileId }, { log }) => {
|
|
51
|
+
const drive = await getDriveClient();
|
|
52
|
+
log.info(`Reading file ${fileId}`);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const meta = await drive.files.get({
|
|
56
|
+
fileId,
|
|
57
|
+
fields: 'name,mimeType',
|
|
58
|
+
supportsAllDrives: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const { name, mimeType } = meta.data;
|
|
62
|
+
trackRead(fileId);
|
|
63
|
+
const buffer = await downloadBuffer(drive, fileId);
|
|
64
|
+
const text = await extractText(buffer, mimeType, name);
|
|
65
|
+
|
|
66
|
+
if (text === null) {
|
|
67
|
+
throw new UserError(
|
|
68
|
+
`Unsupported file type: ${mimeType} (${name}). Only .docx and .pdf are supported.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return `# ${name}\n\n${text}`;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error instanceof UserError) throw error;
|
|
75
|
+
log.error(`Error reading file: ${error.message}`);
|
|
76
|
+
if (error.code === 404)
|
|
77
|
+
throw new UserError(`File not found: ${fileId}`);
|
|
78
|
+
if (error.code === 403)
|
|
79
|
+
throw new UserError('Permission denied. Check that the file is shared with this account.');
|
|
80
|
+
throw new UserError(`Failed to read file: ${error.message}`);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|