Spaces:
Sleeping
Sleeping
| import type { FastMCP } from 'fastmcp'; | |
| import { UserError } from 'fastmcp'; | |
| import { z } from 'zod'; | |
| import { getDriveClient } from '../../clients.js'; | |
| import { escapeDriveQuery } from '../../driveQueryUtils.js'; | |
| export function register(server: FastMCP) { | |
| server.addTool({ | |
| name: 'listFolderContents', | |
| description: | |
| "Lists files and subfolders within a Drive folder. Use folderId='root' to browse the top-level of the Drive.", | |
| parameters: z.strictObject({ | |
| folderId: z | |
| .string() | |
| .describe('ID of the folder to list contents of. Use "root" for the root Drive folder.'), | |
| includeSubfolders: z | |
| .boolean() | |
| .optional() | |
| .default(true) | |
| .describe('Whether to include subfolders in results.'), | |
| includeFiles: z | |
| .boolean() | |
| .optional() | |
| .default(true) | |
| .describe('Whether to include files in results.'), | |
| maxResults: z | |
| .number() | |
| .int() | |
| .min(1) | |
| .max(100) | |
| .optional() | |
| .default(50) | |
| .describe('Maximum number of items to return.'), | |
| }), | |
| execute: async (args, { log }) => { | |
| const drive = await getDriveClient(); | |
| log.info(`Listing contents of folder: ${args.folderId}`); | |
| try { | |
| let queryString = `'${escapeDriveQuery(args.folderId)}' in parents and trashed=false`; | |
| // Filter by type if specified | |
| if (!args.includeSubfolders && !args.includeFiles) { | |
| throw new UserError('At least one of includeSubfolders or includeFiles must be true.'); | |
| } | |
| if (!args.includeSubfolders) { | |
| queryString += ` and mimeType!='application/vnd.google-apps.folder'`; | |
| } else if (!args.includeFiles) { | |
| queryString += ` and mimeType='application/vnd.google-apps.folder'`; | |
| } | |
| const response = await drive.files.list({ | |
| q: queryString, | |
| pageSize: args.maxResults, | |
| orderBy: 'folder,name', | |
| fields: 'files(id,name,mimeType,size,modifiedTime,webViewLink,owners(displayName))', | |
| supportsAllDrives: true, | |
| includeItemsFromAllDrives: true, | |
| }); | |
| const items = response.data.files || []; | |
| const folders = items | |
| .filter((f) => f.mimeType === 'application/vnd.google-apps.folder') | |
| .map((f) => ({ | |
| id: f.id, | |
| name: f.name, | |
| modifiedTime: f.modifiedTime, | |
| })); | |
| const files = items | |
| .filter((f) => f.mimeType !== 'application/vnd.google-apps.folder') | |
| .map((f) => ({ | |
| id: f.id, | |
| name: f.name, | |
| mimeType: f.mimeType, | |
| modifiedTime: f.modifiedTime, | |
| })); | |
| return JSON.stringify({ folders, files }, null, 2); | |
| } catch (error: any) { | |
| log.error(`Error listing folder contents: ${error.message || error}`); | |
| if (error.code === 404) throw new UserError('Folder not found. Check the folder ID.'); | |
| if (error.code === 403) | |
| throw new UserError('Permission denied. Make sure you have access to this folder.'); | |
| throw new UserError(`Failed to list folder contents: ${error.message || 'Unknown error'}`); | |
| } | |
| }, | |
| }); | |
| } | |