From 00d0f91985aadc87297cbedfd886b16d2727892a Mon Sep 17 00:00:00 2001 From: Kalvin Chau Date: Fri, 14 Mar 2025 14:14:27 -0700 Subject: [PATCH 1/5] feat(google_drive): set read/write scope on all commands to use the same token the readonly commands would re-initiate the oauth flow after a write call, and vice versa, since they only requested a single differing scope --- crates/goose-mcp/src/google_drive/mod.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/crates/goose-mcp/src/google_drive/mod.rs b/crates/goose-mcp/src/google_drive/mod.rs index 3bdeaf1e0feb..cc758d1001fe 100644 --- a/crates/goose-mcp/src/google_drive/mod.rs +++ b/crates/goose-mcp/src/google_drive/mod.rs @@ -36,6 +36,8 @@ pub const KEYCHAIN_SERVICE: &str = "mcp_google_drive"; pub const KEYCHAIN_USERNAME: &str = "oauth_credentials"; pub const KEYCHAIN_DISK_FALLBACK_ENV: &str = "GOOGLE_DRIVE_DISK_FALLBACK"; +const GOOGLE_DRIVE_SCOPES: Scope = Scope::Full; + #[derive(Debug)] enum FileOperation { Create { name: String }, @@ -659,7 +661,7 @@ impl GoogleDriveRouter { .supports_all_drives(true) .include_items_from_all_drives(true) .clear_scopes() // Scope::MeetReadonly is the default, remove it - .add_scope(Scope::Readonly) + .add_scope(GOOGLE_DRIVE_SCOPES) .doit() .await; @@ -698,7 +700,7 @@ impl GoogleDriveRouter { .param("fields", "mimeType") .supports_all_drives(true) .clear_scopes() - .add_scope(Scope::Readonly) + .add_scope(GOOGLE_DRIVE_SCOPES) .doit() .await .map_err(|e| { @@ -736,7 +738,7 @@ impl GoogleDriveRouter { .export(uri, export_mime_type) .param("alt", "media") .clear_scopes() - .add_scope(Scope::Readonly) + .add_scope(GOOGLE_DRIVE_SCOPES) .doit() .await; @@ -783,7 +785,7 @@ impl GoogleDriveRouter { .get(uri) .param("alt", "media") .clear_scopes() - .add_scope(Scope::Readonly) + .add_scope(GOOGLE_DRIVE_SCOPES) .doit() .await; @@ -878,7 +880,7 @@ impl GoogleDriveRouter { .spreadsheets() .get(spreadsheet_id) .clear_scopes() - .add_scope(Scope::Readonly) + .add_scope(GOOGLE_DRIVE_SCOPES) .doit() .await; @@ -925,7 +927,7 @@ impl GoogleDriveRouter { .spreadsheets() .values_get(spreadsheet_id, &sheet_name) .clear_scopes() - .add_scope(Scope::Readonly) + .add_scope(GOOGLE_DRIVE_SCOPES) .doit() .await; @@ -967,7 +969,7 @@ impl GoogleDriveRouter { .spreadsheets() .values_get(spreadsheet_id, range) .clear_scopes() - .add_scope(Scope::Readonly) + .add_scope(GOOGLE_DRIVE_SCOPES) .doit() .await; @@ -1032,7 +1034,7 @@ impl GoogleDriveRouter { .supports_all_drives(true) .include_items_from_all_drives(true) .clear_scopes() // Scope::MeetReadonly is the default, remove it - .add_scope(Scope::Readonly); + .add_scope(GOOGLE_DRIVE_SCOPES); // add a next token if we have one if let Some(token) = next_page_token { @@ -1092,6 +1094,8 @@ impl GoogleDriveRouter { .create(req) .use_content_as_indexable_text(true) .supports_all_drives(support_all_drives) + .clear_scopes() + .add_scope(GOOGLE_DRIVE_SCOPES) .upload(content, source_mime_type.parse().unwrap()) .await } @@ -1099,6 +1103,8 @@ impl GoogleDriveRouter { builder .update(req, file_id) .use_content_as_indexable_text(true) + .clear_scopes() + .add_scope(GOOGLE_DRIVE_SCOPES) .supports_all_drives(support_all_drives) .upload(content, source_mime_type.parse().unwrap()) .await @@ -1468,7 +1474,7 @@ impl GoogleDriveRouter { "comments(author, content, createdTime, modifiedTime, id, anchor, resolved)", ) .clear_scopes() - .add_scope(Scope::Readonly) + .add_scope(GOOGLE_DRIVE_SCOPES) .doit() .await; From 3afdc4bb748c5b171210f37ec3e3595c3998b536 Mon Sep 17 00:00:00 2001 From: Kalvin Chau Date: Mon, 17 Mar 2025 08:51:12 -0700 Subject: [PATCH 2/5] style: spacing for readability --- crates/goose-mcp/src/google_drive/mod.rs | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/crates/goose-mcp/src/google_drive/mod.rs b/crates/goose-mcp/src/google_drive/mod.rs index cc758d1001fe..ff2ad8c3d5df 100644 --- a/crates/goose-mcp/src/google_drive/mod.rs +++ b/crates/goose-mcp/src/google_drive/mod.rs @@ -1082,11 +1082,13 @@ impl GoogleDriveRouter { mime_type: Some(target_mime_type.to_string()), ..Default::default() }; + if let Some(p) = parent { req.parents = Some(vec![p.to_string()]); } let builder = self.drive.files(); + let result = match operation { FileOperation::Create { ref name } => { req.name = Some(name.to_string()); @@ -1110,6 +1112,7 @@ impl GoogleDriveRouter { .await } }; + match result { Err(e) => Err(ToolError::ExecutionError(format!( "Failed to upload google drive file {:?}, {}.", @@ -1132,6 +1135,7 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The name param is required".to_string(), ))?; + let mime_type = params .get("mimeType") @@ -1139,8 +1143,10 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The mimeType param is required".to_string(), ))?; + let body = params.get("body").and_then(|q| q.as_str()); let path = params.get("path").and_then(|q| q.as_str()); + let reader: Box = match (body, path) { (None, None) | (Some(_), Some(_)) => { return Err(ToolError::InvalidParameters( @@ -1152,11 +1158,14 @@ impl GoogleDriveRouter { ToolError::ExecutionError(format!("Error opening {}: {}", p, e).to_string()) })?), }; + let parent = params.get("parent").and_then(|q| q.as_str()); + let support_all_drives = params .get("supportAllDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); + self.upload_to_drive( FileOperation::Create { name: filename.to_string(), @@ -1178,6 +1187,7 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The name param is required".to_string(), ))?; + let body = params .get("body") @@ -1185,14 +1195,19 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The body param is required".to_string(), ))?; + let source_mime_type = "text/markdown"; let target_mime_type = "application/vnd.google-apps.document"; + let parent = params.get("parent").and_then(|q| q.as_str()); + let support_all_drives = params .get("supportAllDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); + let cursor = Box::new(Cursor::new(body.as_bytes().to_owned())); + self.upload_to_drive( FileOperation::Create { name: filename.to_string(), @@ -1214,6 +1229,7 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The name param is required".to_string(), ))?; + let body = params .get("body") @@ -1221,14 +1237,19 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The body param is required".to_string(), ))?; + let source_mime_type = "text/csv"; let target_mime_type = "application/vnd.google-apps.spreadsheet"; + let parent = params.get("parent").and_then(|q| q.as_str()); + let support_all_drives = params .get("supportAllDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); + let cursor = Box::new(Cursor::new(body.as_bytes().to_owned())); + self.upload_to_drive( FileOperation::Create { name: filename.to_string(), @@ -1250,6 +1271,7 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The name param is required".to_string(), ))?; + let path = params .get("path") @@ -1257,17 +1279,22 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The path param is required".to_string(), ))?; + let reader = Box::new(std::fs::File::open(path).map_err(|e| { ToolError::ExecutionError(format!("Error opening {}: {}", path, e).to_string()) })?); + let source_mime_type = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; let target_mime_type = "application/vnd.google-apps.presentation"; + let parent = params.get("parent").and_then(|q| q.as_str()); + let support_all_drives = params .get("supportAllDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); + self.upload_to_drive( FileOperation::Create { name: filename.to_string(), @@ -1289,6 +1316,7 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The fileId param is required".to_string(), ))?; + let mime_type = params .get("mimeType") @@ -1296,8 +1324,10 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The mimeType param is required".to_string(), ))?; + let body = params.get("body").and_then(|q| q.as_str()); let path = params.get("path").and_then(|q| q.as_str()); + let reader: Box = match (body, path) { (None, None) | (Some(_), Some(_)) => { return Err(ToolError::InvalidParameters( @@ -1309,6 +1339,7 @@ impl GoogleDriveRouter { ToolError::ExecutionError(format!("Error opening {}: {}", p, e).to_string()) })?), }; + let support_all_drives = params .get("supportAllDrives") .and_then(|q| q.as_bool()) @@ -1335,6 +1366,7 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The fileId param is required".to_string(), ))?; + let body = params .get("body") @@ -1342,13 +1374,17 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The body param is required".to_string(), ))?; + let source_mime_type = "text/markdown"; let target_mime_type = "application/vnd.google-apps.document"; + let support_all_drives = params .get("supportAllDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); + let cursor = Box::new(Cursor::new(body.as_bytes().to_owned())); + self.upload_to_drive( FileOperation::Update { file_id: file_id.to_string(), @@ -1370,6 +1406,7 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The fileId param is required".to_string(), ))?; + let body = params .get("body") @@ -1377,13 +1414,17 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The body param is required".to_string(), ))?; + let source_mime_type = "text/csv"; let target_mime_type = "application/vnd.google-apps.spreadsheet"; + let support_all_drives = params .get("supportAllDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); + let cursor = Box::new(Cursor::new(body.as_bytes().to_owned())); + self.upload_to_drive( FileOperation::Update { file_id: file_id.to_string(), @@ -1405,6 +1446,7 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The fileId param is required".to_string(), ))?; + let path = params .get("path") @@ -1412,16 +1454,20 @@ impl GoogleDriveRouter { .ok_or(ToolError::InvalidParameters( "The path param is required".to_string(), ))?; + let reader = Box::new(std::fs::File::open(path).map_err(|e| { ToolError::ExecutionError(format!("Error opening {}: {}", path, e).to_string()) })?); + let source_mime_type = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; let target_mime_type = "application/vnd.google-apps.presentation"; + let support_all_drives = params .get("supportAllDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); + self.upload_to_drive( FileOperation::Update { file_id: file_id.to_string(), From e659e10ff0bde5b3a4006f10034a680d56052fea Mon Sep 17 00:00:00 2001 From: Kalvin Chau Date: Mon, 17 Mar 2025 08:59:27 -0700 Subject: [PATCH 3/5] fix: extract correct and use snakeCase naming in tool parameters previously the parameters were exctracting parent and supportAllDrives, when the tool parameters were parent_id and allow_shared_drives --- crates/goose-mcp/src/google_drive/mod.rs | 88 ++++++++++++------------ 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/crates/goose-mcp/src/google_drive/mod.rs b/crates/goose-mcp/src/google_drive/mod.rs index ff2ad8c3d5df..040bebfb5ad2 100644 --- a/crates/goose-mcp/src/google_drive/mod.rs +++ b/crates/goose-mcp/src/google_drive/mod.rs @@ -240,11 +240,11 @@ impl GoogleDriveRouter { "type": "string", "description": "Path to the file to upload. Mutually exclusive with body.", }, - "parent_id": { + "parentId": { "type": "string", "description": "ID of the parent folder in which to create the file. (default: creates files in the root of 'My Drive')", }, - "allow_shared_drives": { + "allowSharedDrives": { "type": "boolean", "description": "Whether to allow access to shared drives or just your personal drive (default: false)", } @@ -270,11 +270,11 @@ impl GoogleDriveRouter { "type": "string", "description": "Markdown text of the file to create.", }, - "parent_id": { + "parentId": { "type": "string", "description": "ID of the parent folder in which to create the file. (default: creates files in the root of 'My Drive')", }, - "allow_shared_drives": { + "allowSharedDrives": { "type": "boolean", "description": "Whether to allow access to shared drives or just your personal drive (default: false)", } @@ -300,11 +300,11 @@ impl GoogleDriveRouter { "type": "string", "description": "CSV text of the file to create.", }, - "parent_id": { + "parentId": { "type": "string", "description": "ID of the parent folder in which to create the file. (default: creates files in the root of 'My Drive')", }, - "allow_shared_drives": { + "allowSharedDrives": { "type": "boolean", "description": "Whether to allow access to shared drives or just your personal drive (default: false)", } @@ -330,11 +330,11 @@ impl GoogleDriveRouter { "type": "string", "description": "Path to a PowerPoint file to upload.", }, - "parent_id": { + "parentId": { "type": "string", "description": "ID of the parent folder in which to create the file. (default: creates files in the root of 'My Drive')", }, - "allow_shared_drives": { + "allowSharedDrives": { "type": "boolean", "description": "Whether to allow access to shared drives or just your personal drive (default: false)", } @@ -368,7 +368,7 @@ impl GoogleDriveRouter { "type": "string", "description": "Path to a local file to use to update the Google Drive file. Mutually exclusive with body.", }, - "allow_shared_drives": { + "allowSharedDrives": { "type": "boolean", "description": "Whether to allow access to shared drives or just your personal drive (default: false)", } @@ -394,7 +394,7 @@ impl GoogleDriveRouter { "type": "string", "description": "Complete markdown text of the file to update.", }, - "allow_shared_drives": { + "allowSharedDrives": { "type": "boolean", "description": "Whether to allow access to shared drives or just your personal drive (default: false)", } @@ -420,7 +420,7 @@ impl GoogleDriveRouter { "type": "string", "description": "Complete CSV text of the updated file.", }, - "allow_shared_drives": { + "allowSharedDrives": { "type": "boolean", "description": "Whether to allow access to shared drives or just your personal drive (default: false)", } @@ -446,7 +446,7 @@ impl GoogleDriveRouter { "type": "string", "description": "Path to a PowerPoint file to upload to replace the existing file.", }, - "allow_shared_drives": { + "allowSharedDrives": { "type": "boolean", "description": "Whether to allow access to shared drives or just your personal drive (default: false)", } @@ -1159,10 +1159,10 @@ impl GoogleDriveRouter { })?), }; - let parent = params.get("parent").and_then(|q| q.as_str()); + let parent_id = params.get("parentId").and_then(|q| q.as_str()); - let support_all_drives = params - .get("supportAllDrives") + let allow_shared_drives = params + .get("allowSharedDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); @@ -1173,8 +1173,8 @@ impl GoogleDriveRouter { reader, mime_type, mime_type, - parent, - support_all_drives, + parent_id, + allow_shared_drives, ) .await } @@ -1199,10 +1199,10 @@ impl GoogleDriveRouter { let source_mime_type = "text/markdown"; let target_mime_type = "application/vnd.google-apps.document"; - let parent = params.get("parent").and_then(|q| q.as_str()); + let parent_id = params.get("parent").and_then(|q| q.as_str()); - let support_all_drives = params - .get("supportAllDrives") + let allow_shared_drives = params + .get("allowSharedDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); @@ -1215,8 +1215,8 @@ impl GoogleDriveRouter { cursor, source_mime_type, target_mime_type, - parent, - support_all_drives, + parent_id, + allow_shared_drives, ) .await } @@ -1241,10 +1241,10 @@ impl GoogleDriveRouter { let source_mime_type = "text/csv"; let target_mime_type = "application/vnd.google-apps.spreadsheet"; - let parent = params.get("parent").and_then(|q| q.as_str()); + let parent_id = params.get("parent_id").and_then(|q| q.as_str()); - let support_all_drives = params - .get("supportAllDrives") + let allow_shared_drives = params + .get("allowSharedDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); @@ -1257,8 +1257,8 @@ impl GoogleDriveRouter { cursor, source_mime_type, target_mime_type, - parent, - support_all_drives, + parent_id, + allow_shared_drives, ) .await } @@ -1288,10 +1288,10 @@ impl GoogleDriveRouter { "application/vnd.openxmlformats-officedocument.presentationml.presentation"; let target_mime_type = "application/vnd.google-apps.presentation"; - let parent = params.get("parent").and_then(|q| q.as_str()); + let parent_id = params.get("parent_id").and_then(|q| q.as_str()); - let support_all_drives = params - .get("supportAllDrives") + let allow_shared_drives = params + .get("allowSharedDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); @@ -1302,8 +1302,8 @@ impl GoogleDriveRouter { reader, source_mime_type, target_mime_type, - parent, - support_all_drives, + parent_id, + allow_shared_drives, ) .await } @@ -1340,8 +1340,8 @@ impl GoogleDriveRouter { })?), }; - let support_all_drives = params - .get("supportAllDrives") + let allow_shared_drives = params + .get("allowSharedDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); @@ -1353,7 +1353,7 @@ impl GoogleDriveRouter { mime_type, mime_type, None, - support_all_drives, + allow_shared_drives, ) .await } @@ -1378,8 +1378,8 @@ impl GoogleDriveRouter { let source_mime_type = "text/markdown"; let target_mime_type = "application/vnd.google-apps.document"; - let support_all_drives = params - .get("supportAllDrives") + let allow_shared_drives = params + .get("allowSharedDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); @@ -1393,7 +1393,7 @@ impl GoogleDriveRouter { source_mime_type, target_mime_type, None, - support_all_drives, + allow_shared_drives, ) .await } @@ -1418,8 +1418,8 @@ impl GoogleDriveRouter { let source_mime_type = "text/csv"; let target_mime_type = "application/vnd.google-apps.spreadsheet"; - let support_all_drives = params - .get("supportAllDrives") + let allow_shared_drives = params + .get("allowSharedDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); @@ -1433,7 +1433,7 @@ impl GoogleDriveRouter { source_mime_type, target_mime_type, None, - support_all_drives, + allow_shared_drives, ) .await } @@ -1463,8 +1463,8 @@ impl GoogleDriveRouter { "application/vnd.openxmlformats-officedocument.presentationml.presentation"; let target_mime_type = "application/vnd.google-apps.presentation"; - let support_all_drives = params - .get("supportAllDrives") + let allow_shared_drives = params + .get("allowSharedDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); @@ -1476,7 +1476,7 @@ impl GoogleDriveRouter { source_mime_type, target_mime_type, None, - support_all_drives, + allow_shared_drives, ) .await } From e2a74576350dfdf13c061be9f7ec3e24236d7e84 Mon Sep 17 00:00:00 2001 From: Kalvin Chau Date: Mon, 17 Mar 2025 10:18:05 -0700 Subject: [PATCH 4/5] fix(google_drive): correct parameters for parentId --- crates/goose-mcp/src/google_drive/mod.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/goose-mcp/src/google_drive/mod.rs b/crates/goose-mcp/src/google_drive/mod.rs index 040bebfb5ad2..0fa54bf4f681 100644 --- a/crates/goose-mcp/src/google_drive/mod.rs +++ b/crates/goose-mcp/src/google_drive/mod.rs @@ -1083,15 +1083,17 @@ impl GoogleDriveRouter { ..Default::default() }; - if let Some(p) = parent { - req.parents = Some(vec![p.to_string()]); - } - let builder = self.drive.files(); let result = match operation { FileOperation::Create { ref name } => { req.name = Some(name.to_string()); + + // we only accept parent_id from create tool calls + if let Some(p) = parent { + req.parents = Some(vec![p.to_string()]); + } + builder .create(req) .use_content_as_indexable_text(true) @@ -1199,7 +1201,7 @@ impl GoogleDriveRouter { let source_mime_type = "text/markdown"; let target_mime_type = "application/vnd.google-apps.document"; - let parent_id = params.get("parent").and_then(|q| q.as_str()); + let parent_id = params.get("parentId").and_then(|q| q.as_str()); let allow_shared_drives = params .get("allowSharedDrives") @@ -1241,7 +1243,7 @@ impl GoogleDriveRouter { let source_mime_type = "text/csv"; let target_mime_type = "application/vnd.google-apps.spreadsheet"; - let parent_id = params.get("parent_id").and_then(|q| q.as_str()); + let parent_id = params.get("parentId").and_then(|q| q.as_str()); let allow_shared_drives = params .get("allowSharedDrives") @@ -1288,7 +1290,7 @@ impl GoogleDriveRouter { "application/vnd.openxmlformats-officedocument.presentationml.presentation"; let target_mime_type = "application/vnd.google-apps.presentation"; - let parent_id = params.get("parent_id").and_then(|q| q.as_str()); + let parent_id = params.get("parentId").and_then(|q| q.as_str()); let allow_shared_drives = params .get("allowSharedDrives") From d96433e3b4dc9eb80e55bb9c8ea57f74c054435f Mon Sep 17 00:00:00 2001 From: Kalvin Chau Date: Mon, 17 Mar 2025 10:54:53 -0700 Subject: [PATCH 5/5] feat(google_drive): consolidate tool calls, update instructions --- crates/goose-mcp/src/google_drive/mod.rs | 487 ++++++++--------------- 1 file changed, 167 insertions(+), 320 deletions(-) diff --git a/crates/goose-mcp/src/google_drive/mod.rs b/crates/goose-mcp/src/google_drive/mod.rs index 0fa54bf4f681..02b5b9753ccc 100644 --- a/crates/goose-mcp/src/google_drive/mod.rs +++ b/crates/goose-mcp/src/google_drive/mod.rs @@ -253,10 +253,10 @@ impl GoogleDriveRouter { }), ); - let create_doc_tool = Tool::new( - "create_doc".to_string(), + let create_file_tool = Tool::new( + "create_file".to_string(), indoc! {r#" - Create a Google Doc from markdown text in Google Drive. + Create a Google file (Document, Spreadsheet, or Slides) in Google Drive. "#} .to_string(), json!({ @@ -266,80 +266,29 @@ impl GoogleDriveRouter { "type": "string", "description": "Name of the file to create", }, - "body": { - "type": "string", - "description": "Markdown text of the file to create.", - }, - "parentId": { - "type": "string", - "description": "ID of the parent folder in which to create the file. (default: creates files in the root of 'My Drive')", - }, - "allowSharedDrives": { - "type": "boolean", - "description": "Whether to allow access to shared drives or just your personal drive (default: false)", - } - }, - "required": ["name", "body"], - }), - ); - - let create_sheets_tool = Tool::new( - "create_sheets".to_string(), - indoc! {r#" - Create a Google Sheets document from csv text in Google Drive. - "#} - .to_string(), - json!({ - "type": "object", - "properties": { - "name": { + "fileType": { "type": "string", - "description": "Name of the file to create", + "enum": ["document", "spreadsheet", "slides"], + "description": "Type of Google file to create (document, spreadsheet, or slides)", }, "body": { "type": "string", - "description": "CSV text of the file to create.", - }, - "parentId": { - "type": "string", - "description": "ID of the parent folder in which to create the file. (default: creates files in the root of 'My Drive')", - }, - "allowSharedDrives": { - "type": "boolean", - "description": "Whether to allow access to shared drives or just your personal drive (default: false)", - } - }, - "required": ["name", "body"], - }), - ); - - let create_slides_tool = Tool::new( - "create_slides".to_string(), - indoc! {r#" - Create a Google Slides document in Google Drive by converting a PowerPoint file. - "#} - .to_string(), - json!({ - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the file to create", + "description": "Text content for the file (required for document and spreadsheet types)", }, "path": { "type": "string", - "description": "Path to a PowerPoint file to upload.", + "description": "Path to a file to upload (required for slides type)", }, "parentId": { "type": "string", - "description": "ID of the parent folder in which to create the file. (default: creates files in the root of 'My Drive')", + "description": "ID of the parent folder in which to create the file (default: creates files in the root of 'My Drive')", }, "allowSharedDrives": { "type": "boolean", "description": "Whether to allow access to shared drives or just your personal drive (default: false)", } }, - "required": ["name", "path"], + "required": ["name", "fileType"], }), ); @@ -377,10 +326,10 @@ impl GoogleDriveRouter { }), ); - let update_doc_tool = Tool::new( - "update_doc".to_string(), + let update_file_tool = Tool::new( + "update_file".to_string(), indoc! {r#" - Update a Google Doc from markdown text. + Update a Google file (Document, Spreadsheet, or Slides) in Google Drive. "#} .to_string(), json!({ @@ -390,68 +339,25 @@ impl GoogleDriveRouter { "type": "string", "description": "ID of the file to update", }, - "body": { - "type": "string", - "description": "Complete markdown text of the file to update.", - }, - "allowSharedDrives": { - "type": "boolean", - "description": "Whether to allow access to shared drives or just your personal drive (default: false)", - } - }, - "required": ["fileId", "body"], - }), - ); - - let update_sheets_tool = Tool::new( - "update_sheets".to_string(), - indoc! {r#" - Update a Google Sheets document from csv text. - "#} - .to_string(), - json!({ - "type": "object", - "properties": { - "fileId": { + "fileType": { "type": "string", - "description": "ID of the file to update", + "enum": ["document", "spreadsheet", "slides"], + "description": "Type of Google file to update (document, spreadsheet, or slides)", }, "body": { "type": "string", - "description": "Complete CSV text of the updated file.", - }, - "allowSharedDrives": { - "type": "boolean", - "description": "Whether to allow access to shared drives or just your personal drive (default: false)", - } - }, - "required": ["fileId", "body"], - }), - ); - - let update_slides_tool = Tool::new( - "update_slides".to_string(), - indoc! {r#" - Updatea Google Slides document in Google Drive by converting a PowerPoint file. - "#} - .to_string(), - json!({ - "type": "object", - "properties": { - "fileId": { - "type": "string", - "description": "ID of the file to update", + "description": "Text content for the file (required for document and spreadsheet types)", }, "path": { "type": "string", - "description": "Path to a PowerPoint file to upload to replace the existing file.", + "description": "Path to a file to upload (required for slides type)", }, "allowSharedDrives": { "type": "boolean", "description": "Whether to allow access to shared drives or just your personal drive (default: false)", } }, - "required": ["fileId", "path"], + "required": ["fileId", "fileType"], }), ); @@ -490,8 +396,8 @@ impl GoogleDriveRouter { }), ); - let comment_list_tool = Tool::new( - "comment_list".to_string(), + let list_comments_tool = Tool::new( + "list_comments".to_string(), indoc! {r#" List comments for a file in google drive by id, given an input file id. "#} @@ -544,6 +450,19 @@ impl GoogleDriveRouter { - get_columns: Get column headers from a specific sheet - get_values: Get values from a range + ### 4. Create File Tool + Create Google Workspace files (Docs, Sheets, or Slides) directly in Google Drive. + - For Google Docs: Converts Markdown text to a Google Document + - For Google Sheets: Converts CSV text to a Google Spreadsheet + - For Google Slides: Converts a PowerPoint file to Google Slides (requires a path to the powerpoint file) + + ### 5. Update File Tool + Update existing Google Workspace files (Docs, Sheets, or Slides) in Google Drive. + - For Google Docs: Updates with new Markdown text + - For Google Sheets: Updates with new CSV text + - For Google Slides: Updates with a new PowerPoint file (requires a path to the powerpoint file) + - Note: This functionally is an overwrite to the slides, warn the user before using this tool. + Parameters: - spreadsheetId: The ID of the spreadsheet (can be obtained from search results) - operation: The operation to perform (one of the operations listed above) @@ -585,15 +504,11 @@ impl GoogleDriveRouter { search_tool, read_tool, upload_tool, - create_doc_tool, - create_sheets_tool, - create_slides_tool, + create_file_tool, update_tool, - update_doc_tool, - update_sheets_tool, - update_slides_tool, + update_file_tool, sheets_tool, - comment_list_tool, + list_comments_tool, ], instructions, drive, @@ -1181,7 +1096,8 @@ impl GoogleDriveRouter { .await } - async fn create_doc(&self, params: Value) -> Result, ToolError> { + async fn create_file(&self, params: Value) -> Result, ToolError> { + // Extract common parameters let filename = params .get("name") @@ -1190,17 +1106,14 @@ impl GoogleDriveRouter { "The name param is required".to_string(), ))?; - let body = + let file_type = params - .get("body") + .get("fileType") .and_then(|q| q.as_str()) .ok_or(ToolError::InvalidParameters( - "The body param is required".to_string(), + "The fileType param is required".to_string(), ))?; - let source_mime_type = "text/markdown"; - let target_mime_type = "application/vnd.google-apps.document"; - let parent_id = params.get("parentId").and_then(|q| q.as_str()); let allow_shared_drives = params @@ -1208,102 +1121,70 @@ impl GoogleDriveRouter { .and_then(|q| q.as_bool()) .unwrap_or_default(); - let cursor = Box::new(Cursor::new(body.as_bytes().to_owned())); - - self.upload_to_drive( - FileOperation::Create { - name: filename.to_string(), - }, - cursor, - source_mime_type, - target_mime_type, - parent_id, - allow_shared_drives, - ) - .await - } - - async fn create_sheets(&self, params: Value) -> Result, ToolError> { - let filename = - params - .get("name") - .and_then(|q| q.as_str()) - .ok_or(ToolError::InvalidParameters( - "The name param is required".to_string(), - ))?; - - let body = - params - .get("body") - .and_then(|q| q.as_str()) - .ok_or(ToolError::InvalidParameters( - "The body param is required".to_string(), - ))?; - - let source_mime_type = "text/csv"; - let target_mime_type = "application/vnd.google-apps.spreadsheet"; - - let parent_id = params.get("parentId").and_then(|q| q.as_str()); - - let allow_shared_drives = params - .get("allowSharedDrives") - .and_then(|q| q.as_bool()) - .unwrap_or_default(); - - let cursor = Box::new(Cursor::new(body.as_bytes().to_owned())); - - self.upload_to_drive( - FileOperation::Create { - name: filename.to_string(), - }, - cursor, - source_mime_type, - target_mime_type, - parent_id, - allow_shared_drives, - ) - .await - } - - async fn create_slides(&self, params: Value) -> Result, ToolError> { - let filename = - params - .get("name") - .and_then(|q| q.as_str()) - .ok_or(ToolError::InvalidParameters( - "The name param is required".to_string(), - ))?; - - let path = - params - .get("path") - .and_then(|q| q.as_str()) - .ok_or(ToolError::InvalidParameters( - "The path param is required".to_string(), - ))?; - - let reader = Box::new(std::fs::File::open(path).map_err(|e| { - ToolError::ExecutionError(format!("Error opening {}: {}", path, e).to_string()) - })?); - - let source_mime_type = - "application/vnd.openxmlformats-officedocument.presentationml.presentation"; - let target_mime_type = "application/vnd.google-apps.presentation"; - - let parent_id = params.get("parentId").and_then(|q| q.as_str()); - - let allow_shared_drives = params - .get("allowSharedDrives") - .and_then(|q| q.as_bool()) - .unwrap_or_default(); + // Determine source and target MIME types based on file_type + let (source_mime_type, target_mime_type, reader): (String, String, Box) = + match file_type { + "document" => { + let body = params.get("body").and_then(|q| q.as_str()).ok_or( + ToolError::InvalidParameters( + "The body param is required for document file type".to_string(), + ), + )?; + + ( + "text/markdown".to_string(), + "application/vnd.google-apps.document".to_string(), + Box::new(Cursor::new(body.as_bytes().to_owned())), + ) + } + "spreadsheet" => { + let body = params.get("body").and_then(|q| q.as_str()).ok_or( + ToolError::InvalidParameters( + "The body param is required for spreadsheet file type".to_string(), + ), + )?; + ( + "text/csv".to_string(), + "application/vnd.google-apps.spreadsheet".to_string(), + Box::new(Cursor::new(body.as_bytes().to_owned())), + ) + } + "slides" => { + let path = params.get("path").and_then(|q| q.as_str()).ok_or( + ToolError::InvalidParameters( + "The path param is required for slides file type".to_string(), + ), + )?; + + let file = std::fs::File::open(path).map_err(|e| { + ToolError::ExecutionError( + format!("Error opening {}: {}", path, e).to_string(), + ) + })?; + + ( + "application/vnd.openxmlformats-officedocument.presentationml.presentation" + .to_string(), + "application/vnd.google-apps.presentation".to_string(), + Box::new(file), + ) + } + _ => { + return Err(ToolError::InvalidParameters(format!( + "Invalid fileType: {}. Supported types are: document, spreadsheet, slides", + file_type + ))) + } + }; + // Upload the file to Google Drive self.upload_to_drive( FileOperation::Create { name: filename.to_string(), }, reader, - source_mime_type, - target_mime_type, + &source_mime_type, + &target_mime_type, parent_id, allow_shared_drives, ) @@ -1360,7 +1241,8 @@ impl GoogleDriveRouter { .await } - async fn update_doc(&self, params: Value) -> Result, ToolError> { + async fn update_file(&self, params: Value) -> Result, ToolError> { + // Extract common parameters let file_id = params .get("fileId") @@ -1369,121 +1251,90 @@ impl GoogleDriveRouter { "The fileId param is required".to_string(), ))?; - let body = + let file_type = params - .get("body") + .get("fileType") .and_then(|q| q.as_str()) .ok_or(ToolError::InvalidParameters( - "The body param is required".to_string(), + "The fileType param is required".to_string(), ))?; - let source_mime_type = "text/markdown"; - let target_mime_type = "application/vnd.google-apps.document"; - let allow_shared_drives = params .get("allowSharedDrives") .and_then(|q| q.as_bool()) .unwrap_or_default(); - let cursor = Box::new(Cursor::new(body.as_bytes().to_owned())); - - self.upload_to_drive( - FileOperation::Update { - file_id: file_id.to_string(), - }, - cursor, - source_mime_type, - target_mime_type, - None, - allow_shared_drives, - ) - .await - } - - async fn update_sheets(&self, params: Value) -> Result, ToolError> { - let file_id = - params - .get("fileId") - .and_then(|q| q.as_str()) - .ok_or(ToolError::InvalidParameters( - "The fileId param is required".to_string(), - ))?; - - let body = - params - .get("body") - .and_then(|q| q.as_str()) - .ok_or(ToolError::InvalidParameters( - "The body param is required".to_string(), - ))?; - - let source_mime_type = "text/csv"; - let target_mime_type = "application/vnd.google-apps.spreadsheet"; - - let allow_shared_drives = params - .get("allowSharedDrives") - .and_then(|q| q.as_bool()) - .unwrap_or_default(); - - let cursor = Box::new(Cursor::new(body.as_bytes().to_owned())); - - self.upload_to_drive( - FileOperation::Update { - file_id: file_id.to_string(), - }, - cursor, - source_mime_type, - target_mime_type, - None, - allow_shared_drives, - ) - .await - } - - async fn update_slides(&self, params: Value) -> Result, ToolError> { - let file_id = - params - .get("fileId") - .and_then(|q| q.as_str()) - .ok_or(ToolError::InvalidParameters( - "The fileId param is required".to_string(), - ))?; - - let path = - params - .get("path") - .and_then(|q| q.as_str()) - .ok_or(ToolError::InvalidParameters( - "The path param is required".to_string(), - ))?; - - let reader = Box::new(std::fs::File::open(path).map_err(|e| { - ToolError::ExecutionError(format!("Error opening {}: {}", path, e).to_string()) - })?); - - let source_mime_type = - "application/vnd.openxmlformats-officedocument.presentationml.presentation"; - let target_mime_type = "application/vnd.google-apps.presentation"; - - let allow_shared_drives = params - .get("allowSharedDrives") - .and_then(|q| q.as_bool()) - .unwrap_or_default(); + // Determine source and target MIME types based on file_type + let (source_mime_type, target_mime_type, reader): (String, String, Box) = + match file_type { + "document" => { + let body = params.get("body").and_then(|q| q.as_str()).ok_or( + ToolError::InvalidParameters( + "The body param is required for document file type".to_string(), + ), + )?; + + ( + "text/markdown".to_string(), + "application/vnd.google-apps.document".to_string(), + Box::new(Cursor::new(body.as_bytes().to_owned())), + ) + } + "spreadsheet" => { + let body = params.get("body").and_then(|q| q.as_str()).ok_or( + ToolError::InvalidParameters( + "The body param is required for spreadsheet file type".to_string(), + ), + )?; + ( + "text/csv".to_string(), + "application/vnd.google-apps.spreadsheet".to_string(), + Box::new(Cursor::new(body.as_bytes().to_owned())), + ) + } + "slides" => { + let path = params.get("path").and_then(|q| q.as_str()).ok_or( + ToolError::InvalidParameters( + "The path param is required for slides file type".to_string(), + ), + )?; + + let file = std::fs::File::open(path).map_err(|e| { + ToolError::ExecutionError( + format!("Error opening {}: {}", path, e).to_string(), + ) + })?; + + ( + "application/vnd.openxmlformats-officedocument.presentationml.presentation" + .to_string(), + "application/vnd.google-apps.presentation".to_string(), + Box::new(file), + ) + } + _ => { + return Err(ToolError::InvalidParameters(format!( + "Invalid fileType: {}. Supported types are: document, spreadsheet, slides", + file_type + ))) + } + }; + // Upload the file to Google Drive self.upload_to_drive( FileOperation::Update { file_id: file_id.to_string(), }, reader, - source_mime_type, - target_mime_type, + &source_mime_type, + &target_mime_type, None, allow_shared_drives, ) .await } - async fn comment_list(&self, params: Value) -> Result, ToolError> { + async fn list_comments(&self, params: Value) -> Result, ToolError> { let file_id = params .get("fileId") @@ -1591,15 +1442,11 @@ impl Router for GoogleDriveRouter { "search" => this.search(arguments).await, "read" => this.read(arguments).await, "upload" => this.upload(arguments).await, - "create_doc" => this.create_doc(arguments).await, - "create_sheets" => this.create_sheets(arguments).await, - "create_slides" => this.create_slides(arguments).await, + "create_file" => this.create_file(arguments).await, "update" => this.update(arguments).await, - "update_doc" => this.update_doc(arguments).await, - "update_sheets" => this.update_sheets(arguments).await, - "update_slides" => this.update_slides(arguments).await, + "update_file" => this.update_file(arguments).await, "sheets_tool" => this.sheets_tool(arguments).await, - "comment_list" => this.comment_list(arguments).await, + "list_comments" => this.list_comments(arguments).await, _ => Err(ToolError::NotFound(format!("Tool {} not found", tool_name))), } })