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.
Files changed (97) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +555 -276
  3. package/dist/cachedToolsList.js +52 -52
  4. package/dist/googleDocsApiHelpers.js +76 -12
  5. package/dist/helpers.js +572 -252
  6. package/dist/httpAuth.js +296 -0
  7. package/dist/index.js +162 -11
  8. package/dist/logger.js +87 -87
  9. package/dist/markdown-transformer/docsToMarkdown.js +222 -21
  10. package/dist/markdown-transformer/index.js +19 -9
  11. package/dist/markdown-transformer/markdownToDocs.js +458 -28
  12. package/dist/readTracker.js +136 -17
  13. package/dist/sessionContext.js +37 -0
  14. package/dist/setup.js +187 -9
  15. package/dist/tools/calendar/getBusy.js +64 -64
  16. package/dist/tools/calendar/getEvents.js +141 -141
  17. package/dist/tools/calendar/getFree.js +225 -225
  18. package/dist/tools/calendar/index.js +19 -19
  19. package/dist/tools/calendar/listCalendars.js +38 -38
  20. package/dist/tools/calendar/listRecurringInstances.js +83 -83
  21. package/dist/tools/calendar/manageCalendar.js +121 -121
  22. package/dist/tools/calendar/manageEvent.js +258 -258
  23. package/dist/tools/calendar/moveEvent.js +66 -66
  24. package/dist/tools/docs/addTab.js +10 -12
  25. package/dist/tools/docs/appendToGoogleDoc.js +9 -4
  26. package/dist/tools/docs/deleteRange.js +9 -4
  27. package/dist/tools/docs/findAndReplace.js +9 -4
  28. package/dist/tools/docs/formatting/applyParagraphStyle.js +4 -1
  29. package/dist/tools/docs/formatting/getFormatting.js +171 -171
  30. package/dist/tools/docs/insertImage.js +15 -2
  31. package/dist/tools/docs/insertPageBreak.js +4 -1
  32. package/dist/tools/docs/insertTable.js +4 -1
  33. package/dist/tools/docs/insertTableWithData.js +4 -1
  34. package/dist/tools/docs/modifyText.js +14 -4
  35. package/dist/tools/docs/modifyText.test.js +84 -84
  36. package/dist/tools/docs/readGoogleDoc.js +71 -11
  37. package/dist/tools/docs/renameTab.js +12 -14
  38. package/dist/tools/drafts.js +165 -165
  39. package/dist/tools/drive/createDocument.js +13 -1
  40. package/dist/tools/drive/downloadFile.js +268 -268
  41. package/dist/tools/drive/getFileInfo.js +48 -48
  42. package/dist/tools/drive/getFilePath.js +50 -50
  43. package/dist/tools/drive/listDriveFiles.js +112 -112
  44. package/dist/tools/drive/listSharedDrives.js +52 -52
  45. package/dist/tools/drive/listSharedWithMe.js +97 -97
  46. package/dist/tools/drive/uploadFile.js +111 -111
  47. package/dist/tools/extras/index.js +9 -9
  48. package/dist/tools/extras/readDriveFile.js +212 -206
  49. package/dist/tools/extras/readFile.js +84 -84
  50. package/dist/tools/extras/searchFileContents.js +81 -81
  51. package/dist/tools/forms/batchUpdateForm.js +81 -81
  52. package/dist/tools/forms/createForm.js +54 -54
  53. package/dist/tools/forms/getForm.js +118 -118
  54. package/dist/tools/forms/getFormResponse.js +45 -45
  55. package/dist/tools/forms/index.js +15 -15
  56. package/dist/tools/forms/listFormResponses.js +56 -56
  57. package/dist/tools/forms/setPublishSettings.js +59 -59
  58. package/dist/tools/gmail/drafts.js +165 -165
  59. package/dist/tools/gmail/labels.js +54 -83
  60. package/dist/tools/gmail/messages.js +444 -441
  61. package/dist/tools/gmail/settings.js +358 -528
  62. package/dist/tools/gmail/threads.js +286 -164
  63. package/dist/tools/index.js +511 -473
  64. package/dist/tools/labels.js +103 -103
  65. package/dist/tools/legacyAliases.js +426 -0
  66. package/dist/tools/maps/directions.js +72 -0
  67. package/dist/tools/maps/geocode.js +16 -0
  68. package/dist/tools/maps/index.js +15 -0
  69. package/dist/tools/maps/mapsClient.js +83 -0
  70. package/dist/tools/maps/placeDetails.js +16 -0
  71. package/dist/tools/maps/reverseGeocode.js +19 -0
  72. package/dist/tools/maps/searchNearby.js +54 -0
  73. package/dist/tools/maps/searchPlaces.js +24 -0
  74. package/dist/tools/messages.js +448 -448
  75. package/dist/tools/settings.js +528 -528
  76. package/dist/tools/slides/createPresentation.js +104 -104
  77. package/dist/tools/slides/createShape.js +92 -92
  78. package/dist/tools/slides/createTextBox.js +82 -82
  79. package/dist/tools/slides/deleteSlide.js +30 -30
  80. package/dist/tools/slides/duplicateSlide.js +37 -37
  81. package/dist/tools/slides/exportThumbnail.js +42 -42
  82. package/dist/tools/slides/formatParagraph.js +72 -72
  83. package/dist/tools/slides/formatText.js +84 -84
  84. package/dist/tools/slides/getPresentation.js +87 -87
  85. package/dist/tools/slides/index.js +33 -33
  86. package/dist/tools/slides/reorderSlides.js +41 -41
  87. package/dist/tools/slides/replaceAllText.js +46 -46
  88. package/dist/tools/slides/setBackground.js +58 -58
  89. package/dist/tools/slides/speakerNotes.js +102 -102
  90. package/dist/tools/slides/styleShape.js +111 -111
  91. package/dist/tools/slides/updatePresentation.js +128 -128
  92. package/dist/tools/threads.js +145 -145
  93. package/dist/tools/utils/appendMarkdownToGoogleDoc.js +31 -7
  94. package/dist/tools/utils/replaceDocumentWithMarkdown.js +72 -9
  95. package/dist/updateCheck.js +202 -0
  96. package/dist/workspace.js +122 -0
  97. 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
- range: z
58
- .string()
59
- .optional()
60
- .describe("A1 notation range for Google Sheets (e.g., 'Sheet1!A1:C10'). Required for spreadsheets."),
61
- }),
62
- execute: async (args, { log }) => {
63
- const drive = await getDriveClient();
64
-
65
- // 1. Get file metadata to determine type
66
- let fileMeta;
67
- try {
68
- const res = await drive.files.get({
69
- fileId: args.fileId,
70
- fields: 'id,name,mimeType,modifiedTime,size',
71
- supportsAllDrives: true,
72
- });
73
- fileMeta = res.data;
74
- } catch (error) {
75
- if (error.code === 404) throw new UserError(`File not found: ${args.fileId}`);
76
- if (error.code === 403) throw new UserError('Permission denied. Check that the file is shared with this account.');
77
- throw new UserError(`Failed to get file info: ${error.message}`);
78
- }
79
-
80
- log.info(`File "${fileMeta.name}" is ${fileMeta.mimeType}`);
81
- trackRead(args.fileId, fileMeta.modifiedTime);
82
-
83
- // 2. Route based on MIME type
84
- if (fileMeta.mimeType === GOOGLE_DOC) {
85
- return await readGoogleDoc(args, fileMeta, log);
86
- }
87
-
88
- if (fileMeta.mimeType === GOOGLE_SHEET) {
89
- return await readGoogleSheet(args, fileMeta, log);
90
- }
91
-
92
- // Try reading as docx/pdf
93
- const text = await tryReadBinaryFile(drive, args.fileId, fileMeta, log);
94
- if (text !== null) {
95
- return text;
96
- }
97
-
98
- // Unsupported type
99
- const capInfo = CAPABILITIES.other;
100
- capInfo.fileType = fileMeta.mimeType;
101
- return JSON.stringify({
102
- file: { id: fileMeta.id, name: fileMeta.name, mimeType: fileMeta.mimeType },
103
- ...capInfo,
104
- content: null,
105
- }, null, 2);
106
- },
107
- });
108
- }
109
-
110
- async function readGoogleDoc(args, fileMeta, log) {
111
- const docs = await getDocsClient();
112
- const res = await docs.documents.get({
113
- documentId: args.fileId,
114
- fields: args.format === 'text' ? 'body(content(paragraph(elements(textRun(content)))))' : '*',
115
- });
116
-
117
- let content;
118
- if (args.format === 'json') {
119
- content = JSON.stringify(res.data, null, 2);
120
- } else if (args.format === 'markdown') {
121
- content = docsJsonToMarkdown(res.data);
122
- } else {
123
- // text
124
- let textContent = '';
125
- res.data.body?.content?.forEach((element) => {
126
- if (element.paragraph?.elements) {
127
- element.paragraph.elements.forEach((pe) => {
128
- if (pe.textRun?.content) textContent += pe.textRun.content;
129
- });
130
- }
131
- if (element.table?.tableRows) {
132
- element.table.tableRows.forEach((row) => {
133
- row.tableCells?.forEach((cell) => {
134
- cell.content?.forEach((cellElement) => {
135
- cellElement.paragraph?.elements?.forEach((pe) => {
136
- if (pe.textRun?.content) textContent += pe.textRun.content;
137
- });
138
- });
139
- });
140
- });
141
- }
142
- });
143
- content = textContent || '(empty document)';
144
- }
145
-
146
- return JSON.stringify({
147
- file: { id: fileMeta.id, name: fileMeta.name, mimeType: fileMeta.mimeType },
148
- ...CAPABILITIES.googleDoc,
149
- content,
150
- }, null, 2);
151
- }
152
-
153
- async function readGoogleSheet(args, fileMeta, log) {
154
- if (!args.range) {
155
- // Return sheet info without data
156
- const sheets = await getSheetsClient();
157
- const info = await sheets.spreadsheets.get({
158
- spreadsheetId: args.fileId,
159
- fields: 'sheets(properties(sheetId,title,gridProperties))',
160
- });
161
- const sheetList = info.data.sheets?.map(s => ({
162
- title: s.properties.title,
163
- sheetId: s.properties.sheetId,
164
- rows: s.properties.gridProperties?.rowCount,
165
- cols: s.properties.gridProperties?.columnCount,
166
- }));
167
- return JSON.stringify({
168
- file: { id: fileMeta.id, name: fileMeta.name, mimeType: fileMeta.mimeType },
169
- ...CAPABILITIES.googleSheet,
170
- note: 'Provide a range parameter (e.g., "Sheet1!A1:C10") to read data. Available sheets listed below.',
171
- sheets: sheetList,
172
- }, null, 2);
173
- }
174
-
175
- const sheets = await getSheetsClient();
176
- const response = await sheets.spreadsheets.values.get({
177
- spreadsheetId: args.fileId,
178
- range: args.range,
179
- });
180
- return JSON.stringify({
181
- file: { id: fileMeta.id, name: fileMeta.name, mimeType: fileMeta.mimeType },
182
- ...CAPABILITIES.googleSheet,
183
- range: args.range,
184
- values: response.data.values || [],
185
- }, null, 2);
186
- }
187
-
188
- async function tryReadBinaryFile(drive, fileId, fileMeta, log) {
189
- const { mimeType, name } = fileMeta;
190
- const isDocxFile = mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || name?.endsWith('.docx');
191
- const isPdfFile = mimeType === 'application/pdf' || name?.endsWith('.pdf');
192
-
193
- if (!isDocxFile && !isPdfFile) return null;
194
-
195
- const buffer = await downloadBuffer(drive, fileId);
196
- const text = await extractText(buffer, mimeType, name);
197
-
198
- if (text === null) return null;
199
-
200
- const capInfo = isDocxFile ? CAPABILITIES.docx : CAPABILITIES.pdf;
201
- return JSON.stringify({
202
- file: { id: fileMeta.id, name: fileMeta.name, mimeType: fileMeta.mimeType },
203
- ...capInfo,
204
- content: text,
205
- }, null, 2);
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
+ }