Преглед на файлове

feat:mcp的专用桶添加写权限

Jerry Liang преди 2 месеца
родител
ревизия
8c9d4c6106
променени са 3 файла, в които са добавени 56 реда и са изтрити 14 реда
  1. 11 9
      service/cmd/mcp/DESIGN.md
  2. 7 5
      service/cmd/mcp/main.go
  3. 38 0
      service/cmd/mcp/tools/storage/file.go

+ 11 - 9
service/cmd/mcp/DESIGN.md

@@ -106,7 +106,7 @@ service/cmd/mcp/
 │  直接读取 DB/Cache,跳过业务逻辑,仅限只读           │
 ├───────────────────────────────────────────────────┤
 │  Layer 3: storage_*  — 对象存储层(文件操作)        │
-│  MinIO 对象浏览和用户上传文件读取,仅限只读           │
+│  file_* 支持读写,minio_* 仅只读浏览和读取           │
 ├───────────────────────────────────────────────────┤
 │  Layer 4: util_*     — 辅助工具层(纯函数)          │
 │  格式解析等不涉及数据持久化的工具                     │
@@ -146,12 +146,13 @@ service/cmd/mcp/
 
 ### 4.3 Storage 层(对象存储)
 
-| 工具名 | 说明 | 参数 |
-|--------|------|------|
-| `storage_file_read` | 读取用户上传文件 | `path` (必填) |
-| `storage_file_list` | 列出用户上传文件 | (无参数) |
-| `storage_minio_list_objects` | 列出 MinIO 对象 | `bucket` (必填), `prefix`, `limit` |
-| `storage_minio_read_object` | 读取 MinIO 文本文件 | `bucket` (必填), `key` (必填) |
+| 工具名 | 说明 | 参数 | 读写 |
+|--------|------|------|------|
+| `storage_file_read` | 读取用户上传文件 | `path` (必填) | 读 |
+| `storage_file_write` | 写入文件到存储 | `path` (必填), `content` (必填) | 写 |
+| `storage_file_list` | 列出用户上传文件 | (无参数) | 读 |
+| `storage_minio_list_objects` | 列出 MinIO 对象 | `bucket` (必填), `prefix`, `limit` | 读 |
+| `storage_minio_read_object` | 读取 MinIO 文本文件 | `bucket` (必填), `key` (必填) | 读 |
 
 ### 4.4 Util 层(辅助工具)
 
@@ -204,9 +205,10 @@ var minioAllowedBuckets = []string{
 
 | 层级 | 写操作 | 说明 |
 |------|--------|------|
-| service | `service_feedback_create` | 唯一写操作,仅创建 Plane Intake Issue |
+| service | `service_feedback_create` | 创建 Plane Intake Issue |
 | data | 无 | 全部只读 |
-| storage | 无 | 全部只读(文件上传通过 HTTP API,不通过 MCP) |
+| storage | `storage_file_write` | 写入文件到 MCP FileStore(MinIO),最大 2MB |
+| storage | `storage_minio_*` | 仅只读(列出、读取),不支持写入 |
 | util | 无 | 纯函数 |
 
 ---

+ 7 - 5
service/cmd/mcp/main.go

@@ -84,9 +84,9 @@ const serverInstructions = `FranklinWH 工具箱 MCP Server — 内部工具平
 - data_redis_*: Redis 缓存查看
 
 ### storage_* — 对象存储层
-读取 MinIO 对象存储或用户上传的文件。仅限只读操作。
-- storage_file_*: 用户上传文件读取
-- storage_minio_*: MinIO bucket 对象浏览和读取
+文件和对象存储操作。
+- storage_file_*: 用户上传文件的读取和写入
+- storage_minio_*: MinIO bucket 对象浏览和读取(只读)
 
 ### util_* — 辅助工具
 纯函数工具,不涉及数据持久化。
@@ -95,5 +95,7 @@ const serverInstructions = `FranklinWH 工具箱 MCP Server — 内部工具平
 ## 使用原则
 1. 优先使用 service_* 工具,它们包含完整的业务逻辑
 2. 仅当 service 层无法满足时才使用 data_* 直查
-3. 所有 data_* 和 storage_* 工具仅支持只读操作
-4. 查询 MongoDB 时注意 collection 白名单限制`
+3. data_* 工具仅支持只读操作
+4. storage_file_* 支持读写,storage_minio_* 仅只读
+5. 查询 MongoDB 时注意 collection 白名单限制
+6. MinIO 操作受 bucket 白名单限制`

+ 38 - 0
service/cmd/mcp/tools/storage/file.go

@@ -33,6 +33,15 @@ func registerFileTools(s *server.MCPServer) {
 		handleFileRead,
 	)
 
+	s.AddTool(
+		mcp.NewTool("storage_file_write",
+			mcp.WithDescription("将文本内容写入文件存储(MinIO)。可用于保存分析结果、生成报告等。文件路径会自动添加 /uploads/ 前缀。"),
+			mcp.WithString("path", mcp.Required(), mcp.Description("文件路径,格式为 /uploads/{filename}(如 /uploads/report.csv)")),
+			mcp.WithString("content", mcp.Required(), mcp.Description("要写入的文本内容")),
+		),
+		handleFileWrite,
+	)
+
 	s.AddTool(
 		mcp.NewTool("storage_file_list",
 			mcp.WithDescription("列出当前会话中用户上传的所有文件,返回文件名、路径、大小等信息。"),
@@ -75,3 +84,32 @@ func handleFileList(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
 
 	return helper.JSONResult(files), nil
 }
+
+func handleFileWrite(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+	path, err := req.RequireString("path")
+	if err != nil {
+		return helper.ParamError("path"), nil
+	}
+	content, err := req.RequireString("content")
+	if err != nil {
+		return helper.ParamError("content"), nil
+	}
+
+	if mcpFileStore == nil {
+		return helper.ServiceError("文件存储", fmt.Errorf("未初始化")), nil
+	}
+
+	// 写入文件(userId 和 sessionId 使用固定值,MCP 模式下无用户上下文)
+	record, err := mcpFileStore.WriteFile("mcp-agent", "mcp-session", path, content)
+	if err != nil {
+		return helper.ServiceError("写入文件", err), nil
+	}
+
+	return helper.JSONResult(map[string]any{
+		"message":  "文件写入成功",
+		"path":     record.Path,
+		"fileName": record.FileName,
+		"size":     record.Size,
+		"key":      record.Key,
+	}), nil
+}