Spaces:
Sleeping
Sleeping
File size: 5,827 Bytes
7dc28be | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 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 132 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 | import type { FastMCP } from 'fastmcp';
import { UserError } from 'fastmcp';
import { z } from 'zod';
import { getDriveClient } from '../../clients.js';
import { escapeDriveQuery } from '../../driveQueryUtils.js';
/**
* Convenience shortcuts for common MIME types.
* Users can also pass any full MIME type string directly.
*/
const MIME_TYPE_SHORTCUTS: Record<string, string> = {
document: 'application/vnd.google-apps.document',
spreadsheet: 'application/vnd.google-apps.spreadsheet',
presentation: 'application/vnd.google-apps.presentation',
folder: 'application/vnd.google-apps.folder',
form: 'application/vnd.google-apps.form',
pdf: 'application/pdf',
zip: 'application/zip',
};
export function register(server: FastMCP) {
server.addTool({
name: 'listDriveFiles',
description:
'Lists files across Google Drive with optional filtering by type, folder, and ownership. ' +
'Unlike listDocuments (which only returns Google Docs), this tool works with all file types ' +
'(Sheets, PDFs, images, folders, etc.) and supports sort direction and size-based ordering. ' +
'Use mimeType shortcuts: "document", "spreadsheet", "presentation", "folder", "form", "pdf", "zip" ' +
'or pass any full MIME type string.',
parameters: z.strictObject({
maxResults: z
.number()
.int()
.min(1)
.max(100)
.optional()
.default(20)
.describe('Maximum number of files to return (1-100).'),
mimeType: z
.string()
.optional()
.describe(
'Filter by file type. Shortcuts: "document", "spreadsheet", "presentation", ' +
'"folder", "form", "pdf", "zip". Or pass a full MIME type (e.g. "image/png").'
),
folderId: z
.string()
.optional()
.describe(
'Only return files directly inside this folder. Use "root" for the top-level Drive. ' +
'Omit to search across all folders.'
),
orderBy: z
.enum(['name', 'modifiedTime', 'createdTime', 'quotaBytesUsed'])
.optional()
.default('modifiedTime')
.describe(
'Field to sort results by. "quotaBytesUsed" sorts by file size (note: Google-native files report 0).'
),
sortDirection: z
.enum(['asc', 'desc'])
.optional()
.default('desc')
.describe(
'Sort direction: "asc" for oldest/smallest first, "desc" for newest/largest first.'
),
ownedByMe: z
.boolean()
.optional()
.describe('If true, only return files owned by the authenticated user.'),
sharedWithMe: z
.boolean()
.optional()
.describe(
'If true, only return files shared with the authenticated user (excludes files they own). ' +
'Cannot be combined with ownedByMe.'
),
modifiedAfter: z
.string()
.optional()
.describe(
'Only return files modified after this date (ISO 8601 format, e.g. "2024-01-01").'
),
}),
execute: async (args, { log }) => {
if (args.ownedByMe && args.sharedWithMe) {
throw new UserError('ownedByMe and sharedWithMe cannot both be true.');
}
const drive = await getDriveClient();
log.info(
`Listing Drive files. mimeType=${args.mimeType || 'any'}, folder=${args.folderId || 'all'}, ` +
`orderBy=${args.orderBy} ${args.sortDirection}, ownedByMe=${args.ownedByMe}, sharedWithMe=${args.sharedWithMe}`
);
try {
const conditions: string[] = ['trashed=false'];
// Resolve MIME type shortcut or use value as-is
if (args.mimeType) {
const resolved = MIME_TYPE_SHORTCUTS[args.mimeType] ?? args.mimeType;
conditions.push(`mimeType='${escapeDriveQuery(resolved)}'`);
}
// Scope to a specific folder
if (args.folderId) {
conditions.push(`'${escapeDriveQuery(args.folderId)}' in parents`);
}
// Ownership filter
if (args.ownedByMe) {
conditions.push(`'me' in owners`);
} else if (args.sharedWithMe) {
conditions.push(`sharedWithMe=true`);
}
// Date filter
if (args.modifiedAfter) {
const cutoff = new Date(args.modifiedAfter).toISOString();
conditions.push(`modifiedTime > '${escapeDriveQuery(cutoff)}'`);
}
const queryString = conditions.join(' and ');
const orderByParam = args.sortDirection === 'desc' ? `${args.orderBy} desc` : args.orderBy;
const response = await drive.files.list({
q: queryString,
pageSize: args.maxResults,
orderBy: orderByParam,
fields:
'files(id,name,mimeType,size,modifiedTime,createdTime,webViewLink,owners(displayName,emailAddress))',
supportsAllDrives: true,
includeItemsFromAllDrives: true,
});
const files = (response.data.files || []).map((file) => ({
id: file.id,
name: file.name,
mimeType: file.mimeType,
size: file.size != null ? Number(file.size) : null,
modifiedTime: file.modifiedTime,
createdTime: file.createdTime,
owner: file.owners?.[0]?.displayName || null,
url: file.webViewLink,
}));
return JSON.stringify({ files, total: files.length }, null, 2);
} catch (error: any) {
log.error(`Error listing Drive files: ${error.message || error}`);
if (error.code === 403)
throw new UserError(
'Permission denied. Make sure you have granted Google Drive access to the application.'
);
throw new UserError(`Failed to list files: ${error.message || 'Unknown error'}`);
}
},
});
}
|