mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-18 22:54:24 +01:00
Shea/gdrive perms (#2229)
This commit is contained in:
@@ -28,7 +28,7 @@ use google_docs1::{self, Docs};
|
|||||||
use google_drive3::common::ReadSeek;
|
use google_drive3::common::ReadSeek;
|
||||||
use google_drive3::{
|
use google_drive3::{
|
||||||
self,
|
self,
|
||||||
api::{Comment, File, FileShortcutDetails, Reply, Scope},
|
api::{Comment, File, FileShortcutDetails, Permission, Reply, Scope},
|
||||||
hyper_rustls::{self, HttpsConnector},
|
hyper_rustls::{self, HttpsConnector},
|
||||||
hyper_util::{self, client::legacy::connect::HttpConnector},
|
hyper_util::{self, client::legacy::connect::HttpConnector},
|
||||||
DriveHub,
|
DriveHub,
|
||||||
@@ -54,6 +54,15 @@ enum PaginationState {
|
|||||||
Next(String),
|
Next(String),
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
const PERMISSIONTYPE: &[&str] = &["user", "group", "domain", "anyone"];
|
||||||
|
const ROLES: &[&str] = &[
|
||||||
|
"owner",
|
||||||
|
"organizer",
|
||||||
|
"fileOrganizer",
|
||||||
|
"writer",
|
||||||
|
"commenter",
|
||||||
|
"reader",
|
||||||
|
];
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref GOOGLE_DRIVE_ID_REGEX: Regex =
|
static ref GOOGLE_DRIVE_ID_REGEX: Regex =
|
||||||
@@ -714,6 +723,88 @@ impl GoogleDriveRouter {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let get_permissions_tool = Tool::new(
|
||||||
|
"get_permissions".to_string(),
|
||||||
|
indoc! {r#"
|
||||||
|
List sharing permissions for a file, folder, or shared drive.
|
||||||
|
"#}
|
||||||
|
.to_string(),
|
||||||
|
json!({
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"fileId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Id of the file, folder, or shared drive.",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["fileId"],
|
||||||
|
}),
|
||||||
|
Some(ToolAnnotations {
|
||||||
|
title: Some("List sharing permissions".to_string()),
|
||||||
|
read_only_hint: true,
|
||||||
|
destructive_hint: false,
|
||||||
|
idempotent_hint: false,
|
||||||
|
open_world_hint: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let sharing_tool = Tool::new(
|
||||||
|
"sharing".to_string(),
|
||||||
|
indoc! {r#"
|
||||||
|
Manage sharing for a Google Drive file or folder.
|
||||||
|
|
||||||
|
Supports the operations:
|
||||||
|
- create: Create a new permission for a 'type' identified by the 'target' param to have the 'role' privileges.
|
||||||
|
- update: Update an existing permission to a different role. (You cannot change the type or to whom it is targeted).
|
||||||
|
- delete: Delete an existing permission.
|
||||||
|
"#}
|
||||||
|
.to_string(),
|
||||||
|
json!({
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"fileId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Id of the file or folder.",
|
||||||
|
},
|
||||||
|
"operation": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Desired sharing operation.",
|
||||||
|
"enum": ["create", "update", "delete"],
|
||||||
|
},
|
||||||
|
"permissionId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Permission Id for delete or update operations.",
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Role to apply to permission for create or update operations.",
|
||||||
|
"enum": ["owner", "organizer", "fileOrganizer", "writer", "commenter", "reader"]
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Type of permission to create or update.",
|
||||||
|
"enum": ["user", "group", "domain", "anyone"],
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "For the user and group types, the email address. For a domain type, the domain name. (The anyone type does not require a target). Required for the create operation.",
|
||||||
|
},
|
||||||
|
"emailMessage": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Email notification message to send to users and groups.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["fileId", "operation"],
|
||||||
|
}),
|
||||||
|
Some(ToolAnnotations {
|
||||||
|
title: Some("Manage file sharing".to_string()),
|
||||||
|
read_only_hint: false,
|
||||||
|
destructive_hint: false,
|
||||||
|
idempotent_hint: false,
|
||||||
|
open_world_hint: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
let instructions = indoc::formatdoc! {r#"
|
let instructions = indoc::formatdoc! {r#"
|
||||||
Google Drive MCP Server Instructions
|
Google Drive MCP Server Instructions
|
||||||
|
|
||||||
@@ -862,6 +953,8 @@ impl GoogleDriveRouter {
|
|||||||
create_comment_tool,
|
create_comment_tool,
|
||||||
reply_tool,
|
reply_tool,
|
||||||
list_drives_tool,
|
list_drives_tool,
|
||||||
|
get_permissions_tool,
|
||||||
|
sharing_tool,
|
||||||
],
|
],
|
||||||
instructions,
|
instructions,
|
||||||
drive,
|
drive,
|
||||||
@@ -1216,7 +1309,7 @@ impl GoogleDriveRouter {
|
|||||||
// Validation: check for / path separators as invalid uris
|
// Validation: check for / path separators as invalid uris
|
||||||
if drive_uri.contains('/') {
|
if drive_uri.contains('/') {
|
||||||
return Err(ToolError::InvalidParameters(format!(
|
return Err(ToolError::InvalidParameters(format!(
|
||||||
"The uri '{}' conatins extra '/'. Only the base URI is allowed.",
|
"The uri '{}' contains extra '/'. Only the base URI is allowed.",
|
||||||
uri
|
uri
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@@ -2746,6 +2839,225 @@ impl GoogleDriveRouter {
|
|||||||
}
|
}
|
||||||
Ok(vec![Content::text(results.join("\n"))])
|
Ok(vec![Content::text(results.join("\n"))])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn output_permission(&self, p: Permission) -> String {
|
||||||
|
format!(
|
||||||
|
"(display_name: {}) (domain: {}) (email_address: {}) (expiration_time: {}) (permission_details: {:?}) (role: {}) (type: {}) (uri: {})",
|
||||||
|
p.display_name.unwrap_or_default(),
|
||||||
|
p.domain.unwrap_or_default(),
|
||||||
|
p.email_address.unwrap_or_default(),
|
||||||
|
p.expiration_time.unwrap_or_default(),
|
||||||
|
p.permission_details.unwrap_or_default(),
|
||||||
|
p.role.unwrap_or_default(),
|
||||||
|
p.type_.unwrap_or_default(),
|
||||||
|
p.id.unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_permissions(&self, params: Value) -> Result<Vec<Content>, 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 mut results: Vec<String> = Vec::new();
|
||||||
|
let mut state = PaginationState::Start;
|
||||||
|
while state != PaginationState::End {
|
||||||
|
let mut builder = self
|
||||||
|
.drive
|
||||||
|
.permissions()
|
||||||
|
.list(file_id)
|
||||||
|
.param("fields", "permissions(displayName, domain, emailAddress, expirationTime, permissionDetails, role, type, id)")
|
||||||
|
.supports_all_drives(true)
|
||||||
|
.page_size(100)
|
||||||
|
.clear_scopes() // Scope::MeetReadonly is the default, remove it
|
||||||
|
.add_scope(GOOGLE_DRIVE_SCOPES);
|
||||||
|
if let PaginationState::Next(pt) = state {
|
||||||
|
builder = builder.page_token(&pt);
|
||||||
|
}
|
||||||
|
let result = builder.doit().await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Err(e) => {
|
||||||
|
return Err(ToolError::ExecutionError(format!(
|
||||||
|
"Failed to execute google drive list, {}.",
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
Ok(r) => {
|
||||||
|
let mut content =
|
||||||
|
r.1.permissions
|
||||||
|
.map(|perms| perms.into_iter().map(|p| self.output_permission(p)))
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
results.append(&mut content);
|
||||||
|
state = match r.1.next_page_token {
|
||||||
|
Some(npt) => PaginationState::Next(npt),
|
||||||
|
None => PaginationState::End,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(vec![Content::text(results.join("\n"))])
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sharing(&self, params: Value) -> Result<Vec<Content>, 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 operation = params.get("operation").and_then(|q| q.as_str()).ok_or(
|
||||||
|
ToolError::InvalidParameters("The operation is required".to_string()),
|
||||||
|
)?;
|
||||||
|
let permission_id = params.get("permissionId").and_then(|q| q.as_str());
|
||||||
|
let role = params.get("role").and_then(|s| {
|
||||||
|
s.as_str().map(|s| {
|
||||||
|
if ROLES.contains(&s) {
|
||||||
|
Ok(s)
|
||||||
|
} else {
|
||||||
|
Err(ToolError::InvalidParameters("Invalid role: must be one of ('owner', 'organizer', 'fileOrganizer', 'writer', 'commenter', 'reader')".to_string()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).transpose()?;
|
||||||
|
let permission_type = params.get("type").and_then(|s|
|
||||||
|
s.as_str().map(|s| {
|
||||||
|
if PERMISSIONTYPE.contains(&s) {
|
||||||
|
Ok(s)
|
||||||
|
} else {
|
||||||
|
Err(ToolError::InvalidParameters("Invalid permission type: must be one of ('user', 'group', 'domain', 'anyone')".to_string()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).transpose()?;
|
||||||
|
let target = params.get("target").and_then(|s| s.as_str());
|
||||||
|
let email_message = params.get("emailMessage").and_then(|s| s.as_str());
|
||||||
|
|
||||||
|
match operation {
|
||||||
|
"create" => {
|
||||||
|
let (role, permission_type) = match (role, permission_type) {
|
||||||
|
(Some(r), Some(t)) => (r, t),
|
||||||
|
_ => {
|
||||||
|
return Err(ToolError::InvalidParameters(
|
||||||
|
"The 'create' operation requires the 'role' and 'type' parameters."
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut req = Permission {
|
||||||
|
role: Some(role.to_string()),
|
||||||
|
type_: Some(permission_type.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
match (permission_type, target) {
|
||||||
|
("user", Some(t)) | ("group", Some(t)) => {
|
||||||
|
req.email_address = Some(t.to_string())
|
||||||
|
}
|
||||||
|
("domain", Some(d)) => req.domain = Some(d.to_string()),
|
||||||
|
("anyone", None) => {}
|
||||||
|
(_, _) => {
|
||||||
|
return Err(ToolError::InvalidParameters(format!(
|
||||||
|
"The '{}' operation for type '{}' requires the 'target' parameter.",
|
||||||
|
operation, permission_type
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut builder = self
|
||||||
|
.drive
|
||||||
|
.permissions()
|
||||||
|
.create(req, file_id)
|
||||||
|
.supports_all_drives(true)
|
||||||
|
.clear_scopes()
|
||||||
|
.add_scope(GOOGLE_DRIVE_SCOPES);
|
||||||
|
if let Some(msg) = email_message {
|
||||||
|
builder = builder.email_message(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = builder.doit().await;
|
||||||
|
match result {
|
||||||
|
Err(e) => Err(ToolError::ExecutionError(format!(
|
||||||
|
"Failed to manage sharing for google drive file {}, {}.",
|
||||||
|
file_id, e
|
||||||
|
))),
|
||||||
|
Ok(r) => Ok(vec![Content::text(self.output_permission(r.1))]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"update" => {
|
||||||
|
let (permission_id, role) = match (permission_id, role) {
|
||||||
|
(Some(p), Some(r)) => (p, r),
|
||||||
|
_ => {
|
||||||
|
return Err(ToolError::InvalidParameters(
|
||||||
|
"The 'update' operation requires the 'permissionId', and 'role'."
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// A permission update requires a permissionId, which is also
|
||||||
|
// the ID for that user, group, or domain. We don't _use_ the
|
||||||
|
// permission type in the Permission req body, because the
|
||||||
|
// update uses "patch semantics", and you can't patch a
|
||||||
|
// permission from one user to another without changing its ID.
|
||||||
|
let req = Permission {
|
||||||
|
role: Some(role.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = self
|
||||||
|
.drive
|
||||||
|
.permissions()
|
||||||
|
.update(req, file_id, permission_id)
|
||||||
|
.supports_all_drives(true)
|
||||||
|
.clear_scopes()
|
||||||
|
.add_scope(GOOGLE_DRIVE_SCOPES)
|
||||||
|
.doit()
|
||||||
|
.await;
|
||||||
|
match result {
|
||||||
|
Err(e) => Err(ToolError::ExecutionError(format!(
|
||||||
|
"Failed to manage sharing for google drive file {}, {}.",
|
||||||
|
file_id, e
|
||||||
|
))),
|
||||||
|
Ok(r) => Ok(vec![Content::text(self.output_permission(r.1))]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"delete" => {
|
||||||
|
let permission_id = permission_id.ok_or(ToolError::InvalidParameters(
|
||||||
|
"The 'delete' operation requires the 'permissionId'.".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let result = self
|
||||||
|
.drive
|
||||||
|
.permissions()
|
||||||
|
.delete(file_id, permission_id)
|
||||||
|
.supports_all_drives(true)
|
||||||
|
.clear_scopes()
|
||||||
|
.add_scope(GOOGLE_DRIVE_SCOPES)
|
||||||
|
.doit()
|
||||||
|
.await;
|
||||||
|
match result {
|
||||||
|
Err(e) => Err(ToolError::ExecutionError(format!(
|
||||||
|
"Failed to manage sharing for google drive file {}, {}.",
|
||||||
|
file_id, e
|
||||||
|
))),
|
||||||
|
Ok(_) => Ok(vec![Content::text(format!(
|
||||||
|
"Deleted permission: {} from file: {}",
|
||||||
|
file_id, permission_id
|
||||||
|
))]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s => Err(ToolError::InvalidParameters(
|
||||||
|
format!(
|
||||||
|
"Parameter 'operation' must be one of ('create', 'update', 'delete'); given {}",
|
||||||
|
s
|
||||||
|
)
|
||||||
|
.to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Router for GoogleDriveRouter {
|
impl Router for GoogleDriveRouter {
|
||||||
@@ -2790,6 +3102,8 @@ impl Router for GoogleDriveRouter {
|
|||||||
"get_comments" => this.get_comments(arguments).await,
|
"get_comments" => this.get_comments(arguments).await,
|
||||||
"reply" => this.reply(arguments).await,
|
"reply" => this.reply(arguments).await,
|
||||||
"list_drives" => this.list_drives(arguments).await,
|
"list_drives" => this.list_drives(arguments).await,
|
||||||
|
"get_permissions" => this.get_permissions(arguments).await,
|
||||||
|
"sharing" => this.sharing(arguments).await,
|
||||||
_ => Err(ToolError::NotFound(format!("Tool {} not found", tool_name))),
|
_ => Err(ToolError::NotFound(format!("Tool {} not found", tool_name))),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user