feat: extend rustfs mcp with bucket creation and deletion (#416)

* feat: extend rustfs mcp with bucket creation and deletion

* update file to fix pipeline error

* change variable name to fix pipeline error
This commit is contained in:
majinghe
2025-08-18 09:06:55 +08:00
committed by GitHub
parent c7c149975b
commit c5f6c66f72
3 changed files with 119 additions and 0 deletions
+14
View File
@@ -231,6 +231,20 @@ Retrieve an object from S3 with two operation modes: read content directly or do
- `local_path` (string, optional): Local file path (required when mode is "download")
- `max_content_size` (number, optional): Maximum content size in bytes for read mode (default: 1MB)
### `create_bucket`
Create a new S3 bucket with the specified name.
**Parameters:**
- `bucket_name` (string): Source S3 bucket.
### `delete_bucket`
Delete the specified S3 bucket. If the bucket is not empty, the deletion will fail. You should delete all objects and objects inside them before calling this method.**WARNING: This operation will permanently delete the bucket and all objects within it!**
- `bucket_name` (string): Source S3 bucket.
## Architecture
The MCP server is built with a modular architecture:
+30
View File
@@ -151,6 +151,36 @@ impl S3Client {
Ok(Self { client })
}
pub async fn create_bucket(&self, bucket_name: &str) -> Result<BucketInfo> {
info!("Creating S3 bucket: {}", bucket_name);
self.client
.create_bucket()
.bucket(bucket_name)
.send()
.await
.context(format!("Failed to create S3 bucket: {bucket_name}"))?;
info!("Bucket '{}' created successfully", bucket_name);
Ok(BucketInfo {
name: bucket_name.to_string(),
creation_date: None, // Creation date not returned by create_bucket
})
}
pub async fn delete_bucket(&self, bucket_name: &str) -> Result<()> {
info!("Deleting S3 bucket: {}", bucket_name);
self.client
.delete_bucket()
.bucket(bucket_name)
.send()
.await
.context(format!("Failed to delete S3 bucket: {bucket_name}"))?;
info!("Bucket '{}' deleted successfully", bucket_name);
Ok(())
}
pub async fn list_buckets(&self) -> Result<Vec<BucketInfo>> {
debug!("Listing S3 buckets");
+75
View File
@@ -54,6 +54,18 @@ pub struct UploadFileRequest {
pub cache_control: Option<String>,
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct CreateBucketReqeust {
#[schemars(description = "Name of the S3 bucket to create")]
pub bucket_name: String,
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct DeleteBucketReqeust {
#[schemars(description = "Name of the S3 bucket to delete")]
pub bucket_name: String,
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct GetObjectRequest {
#[schemars(description = "Name of the S3 bucket")]
@@ -110,6 +122,53 @@ impl RustfsMcpServer {
})
}
#[tool(description = "Create a new S3 bucket with the specified name")]
pub async fn create_bucket(&self, Parameters(req): Parameters<CreateBucketReqeust>) -> String {
info!("Executing create_bucket tool for bucket: {}", req.bucket_name);
match self.s3_client.create_bucket(&req.bucket_name).await {
Ok(_) => {
format!("Successfully created bucket: {}", req.bucket_name)
}
Err(e) => {
format!("Failed to create bucket '{}': {:?}", req.bucket_name, e)
}
}
}
#[tool(description = "Delete an existing S3 bucket with the specified name")]
pub async fn delete_bucket(&self, Parameters(req): Parameters<DeleteBucketReqeust>) -> String {
info!("Executing delete_bucket tool for bucket: {}", req.bucket_name);
// check if bucket is empty, if not, can not delete bucket directly.
let object_result = match self
.s3_client
.list_objects_v2(&req.bucket_name, ListObjectsOptions::default())
.await
{
Ok(result) => result,
Err(e) => {
error!("Failed to list objects in bucket '{}': {:?}", req.bucket_name, e);
return format!("Failed to list objects in bucket '{}': {:?}", req.bucket_name, e);
}
};
if !object_result.objects.is_empty() {
error!("Bucket '{}' is not empty", req.bucket_name);
return format!("Failed to delete bucket '{}': bucket is not empty", req.bucket_name);
}
// delete the bucket.
match self.s3_client.delete_bucket(&req.bucket_name).await {
Ok(_) => {
format!("Successfully deleted bucket: {}", req.bucket_name)
}
Err(e) => {
format!("Failed to delete bucket '{}': {:?}", req.bucket_name, e)
}
}
}
#[tool(description = "List all S3 buckets accessible with the configured credentials")]
pub async fn list_buckets(&self) -> String {
info!("Executing list_buckets tool");
@@ -667,4 +726,20 @@ mod tests {
assert_eq!(read_mode_deser, GetObjectMode::Read);
assert_eq!(download_mode_deser, GetObjectMode::Download);
}
#[test]
fn test_bucket_creation() {
let request = CreateBucketReqeust {
bucket_name: "test-bucket".to_string(),
};
assert_eq!(request.bucket_name, "test-bucket");
}
#[test]
fn test_bucket_deletion() {
let request = DeleteBucketReqeust {
bucket_name: "test-bucket".to_string(),
};
assert_eq!(request.bucket_name, "test-bucket");
}
}