| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- package storage
- import (
- "context"
- "fmt"
- "io"
- "strings"
- "github.com/mark3labs/mcp-go/mcp"
- "github.com/mark3labs/mcp-go/server"
- "github.com/minio/minio-go/v7"
- "fwh-toolbox-server/cmd/mcp/tools/helper"
- minioUtil "fwh-toolbox-server/utils/minio"
- )
- // minioAllowedBuckets 允许访问的 bucket 白名单
- var minioAllowedBuckets = []string{
- "fwh-toolbox",
- "fwh-toolbox-ai-file",
- }
- func registerMinioTools(s *server.MCPServer) {
- s.AddTool(
- mcp.NewTool("storage_minio_list_objects",
- mcp.WithDescription("列出 MinIO bucket 中的对象(只读),用于查看已下载的文件"),
- mcp.WithString("bucket", mcp.Required(), mcp.Description(
- fmt.Sprintf("Bucket 名称,可选: %v", minioAllowedBuckets),
- )),
- mcp.WithString("prefix", mcp.Description("对象前缀过滤(如 SN 号或日期路径)")),
- mcp.WithNumber("limit", mcp.Description("返回数量限制,默认20,最大100")),
- ),
- handleMinioListObjects,
- )
- s.AddTool(
- mcp.NewTool("storage_minio_read_object",
- mcp.WithDescription("读取 MinIO 中的文本文件内容(只读,仅支持文本类型文件,最大 1MB)"),
- mcp.WithString("bucket", mcp.Required(), mcp.Description(
- fmt.Sprintf("Bucket 名称,可选: %v", minioAllowedBuckets),
- )),
- mcp.WithString("key", mcp.Required(), mcp.Description("对象 key(完整路径)")),
- ),
- handleMinioReadObject,
- )
- }
- func handleMinioListObjects(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
- bucket, err := req.RequireString("bucket")
- if err != nil {
- return helper.ParamError("bucket"), nil
- }
- if !isAllowedBucket(bucket) {
- return helper.ParamInvalidError("bucket", fmt.Sprintf("不允许访问此 bucket,可选: %v", minioAllowedBuckets)), nil
- }
- prefix := req.GetString("prefix", "")
- limit := int(req.GetFloat("limit", 20))
- if limit > 100 {
- limit = 100
- }
- opts := minio.ListObjectsOptions{
- Prefix: prefix,
- Recursive: true,
- }
- type ObjectInfo struct {
- Key string `json:"key"`
- Size int64 `json:"size"`
- LastModified string `json:"lastModified"`
- ContentType string `json:"contentType,omitempty"`
- }
- var objects []ObjectInfo
- for obj := range minioUtil.Client.ListObjects(ctx, bucket, opts) {
- if obj.Err != nil {
- return helper.ServiceError("列出MinIO对象", obj.Err), nil
- }
- objects = append(objects, ObjectInfo{
- Key: obj.Key,
- Size: obj.Size,
- LastModified: obj.LastModified.Format("2006-01-02 15:04:05"),
- ContentType: obj.ContentType,
- })
- if len(objects) >= limit {
- break
- }
- }
- if len(objects) == 0 {
- return helper.EmptyResult(fmt.Sprintf("bucket %s 中无匹配对象 (prefix: %s)", bucket, prefix)), nil
- }
- return helper.JSONResultOrFileAuto(map[string]any{
- "bucket": bucket,
- "prefix": prefix,
- "count": len(objects),
- "objects": objects,
- }), nil
- }
- func handleMinioReadObject(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
- bucket, err := req.RequireString("bucket")
- if err != nil {
- return helper.ParamError("bucket"), nil
- }
- key, err := req.RequireString("key")
- if err != nil {
- return helper.ParamError("key"), nil
- }
- if !isAllowedBucket(bucket) {
- return helper.ParamInvalidError("bucket", fmt.Sprintf("不允许访问此 bucket,可选: %v", minioAllowedBuckets)), nil
- }
- // 获取对象信息
- stat, err := minioUtil.Client.StatObject(ctx, bucket, key, minio.StatObjectOptions{})
- if err != nil {
- return helper.ServiceError("获取对象信息", err), nil
- }
- // 限制大小:仅允许读取 1MB 以内的文本文件
- const maxSize = 1 * 1024 * 1024
- if stat.Size > maxSize {
- return helper.ParamInvalidError("key", fmt.Sprintf("文件过大 (%d bytes),仅支持读取 1MB 以内的文本文件", stat.Size)), nil
- }
- // 检查是否为文本类型
- if !isTextContentType(stat.ContentType) {
- return helper.ParamInvalidError("key", fmt.Sprintf("非文本文件 (content-type: %s),不支持读取", stat.ContentType)), nil
- }
- obj, err := minioUtil.Client.GetObject(ctx, bucket, key, minio.GetObjectOptions{})
- if err != nil {
- return helper.ServiceError("读取MinIO对象", err), nil
- }
- defer obj.Close()
- data, err := io.ReadAll(obj)
- if err != nil {
- return helper.ServiceError("读取对象内容", err), nil
- }
- return helper.TextResultOrFile(string(data), "minio_object"), nil
- }
- func isAllowedBucket(name string) bool {
- for _, b := range minioAllowedBuckets {
- if b == name {
- return true
- }
- }
- return false
- }
- func isTextContentType(ct string) bool {
- textTypes := []string{
- "text/",
- "application/json",
- "application/xml",
- "application/csv",
- "application/javascript",
- }
- for _, t := range textTypes {
- if strings.HasPrefix(ct, t) || strings.Contains(ct, t) {
- return true
- }
- }
- return false
- }
|