google-tools-mcp 1.0.4 → 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.
@@ -56,8 +56,9 @@ export function register(server) {
56
56
  // Filter by mime type
57
57
  if (args.fileType !== 'all') {
58
58
  queryParts.push(`mimeType='${MIME_TYPES[args.fileType]}'`);
59
- } else {
60
- // Exclude folders from "all" results
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)
61
62
  queryParts.push("mimeType!='application/vnd.google-apps.folder'");
62
63
  }
63
64
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google-tools-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Combined Google Workspace MCP server (Drive, Docs, Sheets, Gmail) with lazy-loaded tool categories",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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,48 +0,0 @@
1
- import { UserError } from 'fastmcp';
2
- import { getDriveClient } from '../../clients.js';
3
- import { DocumentIdParameter } from '../../types.js';
4
- export function register(server) {
5
- server.addTool({
6
- name: 'getDocumentInfo',
7
- description: 'Gets metadata about a document including its name, owner, sharing status, and modification history.',
8
- parameters: DocumentIdParameter,
9
- execute: async (args, { log }) => {
10
- const drive = await getDriveClient();
11
- log.info(`Getting info for document: ${args.documentId}`);
12
- try {
13
- const response = await drive.files.get({
14
- fileId: args.documentId,
15
- // Note: 'permissions' and 'alternateLink' fields removed - they cause
16
- // "Invalid field selection" errors for Google Docs files
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(`Document with ID ${args.documentId} 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 document info: ${error.message || error}`);
40
- if (error.code === 404)
41
- throw new UserError(`Document not found (ID: ${args.documentId}).`);
42
- if (error.code === 403)
43
- throw new UserError('Permission denied. Make sure you have access to this document.');
44
- throw new UserError(`Failed to get document info: ${error.message || 'Unknown error'}`);
45
- }
46
- },
47
- });
48
- }
@@ -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
- }