fangcloud-mcp 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fangcloud-mcp-0.1.0 → fangcloud-mcp-0.1.2}/LICENSE +1 -1
- fangcloud-mcp-0.1.2/MANIFEST.in +4 -0
- {fangcloud-mcp-0.1.0 → fangcloud-mcp-0.1.2}/PKG-INFO +28 -3
- {fangcloud-mcp-0.1.0 → fangcloud-mcp-0.1.2}/README.md +18 -2
- fangcloud-mcp-0.1.2/fangcloud-mcp/__init__.py +10 -0
- fangcloud-mcp-0.1.2/fangcloud-mcp/fangcloud.py +391 -0
- fangcloud-mcp-0.1.2/fangcloud-mcp/fangcloud_api.py +629 -0
- {fangcloud-mcp-0.1.0 → fangcloud-mcp-0.1.2}/fangcloud_mcp.egg-info/PKG-INFO +28 -3
- {fangcloud-mcp-0.1.0 → fangcloud-mcp-0.1.2}/fangcloud_mcp.egg-info/SOURCES.txt +5 -0
- fangcloud-mcp-0.1.2/fangcloud_mcp.egg-info/entry_points.txt +3 -0
- fangcloud-mcp-0.1.2/fangcloud_mcp.egg-info/top_level.txt +1 -0
- fangcloud-mcp-0.1.2/pyproject.toml +40 -0
- fangcloud-mcp-0.1.2/setup.py +40 -0
- fangcloud-mcp-0.1.0/fangcloud_mcp.egg-info/top_level.txt +0 -1
- fangcloud-mcp-0.1.0/pyproject.toml +0 -23
- fangcloud-mcp-0.1.0/setup.py +0 -23
- {fangcloud-mcp-0.1.0 → fangcloud-mcp-0.1.2}/fangcloud_mcp.egg-info/dependency_links.txt +0 -0
- {fangcloud-mcp-0.1.0 → fangcloud-mcp-0.1.2}/fangcloud_mcp.egg-info/requires.txt +0 -0
- {fangcloud-mcp-0.1.0 → fangcloud-mcp-0.1.2}/setup.cfg +0 -0
@@ -1,15 +1,24 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fangcloud-mcp
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.2
|
4
4
|
Summary: FangCloud MCP 是一个 Model Context Protocol (MCP) 服务器实现,提供与 FangCloud 云存储服务的集成
|
5
5
|
Home-page: https://github.com/example/fangcloud-mcp
|
6
6
|
Author: FangCloud Developer
|
7
7
|
Author-email: dev@example.com
|
8
8
|
License: UNKNOWN
|
9
|
+
Project-URL: Bug Reports, https://github.com/example/fangcloud-mcp/issues
|
10
|
+
Project-URL: Source, https://github.com/example/fangcloud-mcp
|
11
|
+
Project-URL: Documentation, https://github.com/example/fangcloud-mcp#readme
|
12
|
+
Keywords: fangcloud,mcp,cloud storage,api
|
9
13
|
Platform: UNKNOWN
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
15
|
+
Classifier: Intended Audience :: Developers
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
10
17
|
Classifier: Programming Language :: Python :: 3.12
|
11
18
|
Classifier: License :: OSI Approved :: MIT License
|
12
19
|
Classifier: Operating System :: OS Independent
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
13
22
|
Requires-Python: >=3.12
|
14
23
|
Description-Content-Type: text/markdown
|
15
24
|
License-File: LICENSE
|
@@ -45,12 +54,28 @@ FangCloud MCP 提供以下功能:
|
|
45
54
|
|
46
55
|
## 安装方法
|
47
56
|
|
57
|
+
### 从 PyPI 安装(推荐)
|
58
|
+
|
59
|
+
```bash
|
60
|
+
pip install fangcloud-mcp
|
61
|
+
```
|
62
|
+
|
63
|
+
### 从源码安装
|
64
|
+
|
48
65
|
```bash
|
49
66
|
# 克隆仓库
|
50
|
-
git clone
|
67
|
+
git clone https://github.com/example/fangcloud-mcp.git
|
51
68
|
cd fangcloud-mcp
|
52
69
|
|
53
|
-
# 使用pip
|
70
|
+
# 使用pip安装
|
71
|
+
pip install .
|
72
|
+
```
|
73
|
+
|
74
|
+
### 开发模式安装
|
75
|
+
|
76
|
+
```bash
|
77
|
+
git clone https://github.com/example/fangcloud-mcp.git
|
78
|
+
cd fangcloud-mcp
|
54
79
|
pip install -e .
|
55
80
|
```
|
56
81
|
|
@@ -29,12 +29,28 @@ FangCloud MCP 提供以下功能:
|
|
29
29
|
|
30
30
|
## 安装方法
|
31
31
|
|
32
|
+
### 从 PyPI 安装(推荐)
|
33
|
+
|
34
|
+
```bash
|
35
|
+
pip install fangcloud-mcp
|
36
|
+
```
|
37
|
+
|
38
|
+
### 从源码安装
|
39
|
+
|
32
40
|
```bash
|
33
41
|
# 克隆仓库
|
34
|
-
git clone
|
42
|
+
git clone https://github.com/example/fangcloud-mcp.git
|
35
43
|
cd fangcloud-mcp
|
36
44
|
|
37
|
-
# 使用pip
|
45
|
+
# 使用pip安装
|
46
|
+
pip install .
|
47
|
+
```
|
48
|
+
|
49
|
+
### 开发模式安装
|
50
|
+
|
51
|
+
```bash
|
52
|
+
git clone https://github.com/example/fangcloud-mcp.git
|
53
|
+
cd fangcloud-mcp
|
38
54
|
pip install -e .
|
39
55
|
```
|
40
56
|
|
@@ -0,0 +1,391 @@
|
|
1
|
+
"""
|
2
|
+
#!/usr/bin/env python3
|
3
|
+
FangCloud MCP Main Module
|
4
|
+
|
5
|
+
This is the main entry point for the FangCloud MCP server.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
import argparse
|
10
|
+
import asyncio
|
11
|
+
import sys
|
12
|
+
import os
|
13
|
+
from typing import Dict, Any, Optional
|
14
|
+
from mcp.server.fastmcp import FastMCP
|
15
|
+
from .fangcloud_api import FangcloudAPI
|
16
|
+
|
17
|
+
|
18
|
+
# Configure logging
|
19
|
+
DEFAULT_LOG_FILE = "fangcloud_mcp.log"
|
20
|
+
logging.basicConfig(
|
21
|
+
level=logging.INFO,
|
22
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
23
|
+
handlers=[
|
24
|
+
logging.FileHandler(DEFAULT_LOG_FILE),
|
25
|
+
logging.StreamHandler()
|
26
|
+
]
|
27
|
+
)
|
28
|
+
logger = logging.getLogger("FangCloud.Main")
|
29
|
+
|
30
|
+
|
31
|
+
# Initialize MCP server
|
32
|
+
mcp = FastMCP("fangcloud")
|
33
|
+
api = FangcloudAPI()
|
34
|
+
|
35
|
+
@mcp.tool()
|
36
|
+
async def get_file_info(file_id: str) -> Dict[str, Any]:
|
37
|
+
"""
|
38
|
+
Get file information
|
39
|
+
|
40
|
+
Args:
|
41
|
+
file_id: File ID
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
File information (JSON format) or error message
|
45
|
+
"""
|
46
|
+
|
47
|
+
if not file_id:
|
48
|
+
return {"status": "error", "message": "file_id is required"}
|
49
|
+
|
50
|
+
try:
|
51
|
+
result = await api.get_file_info(file_id)
|
52
|
+
if result:
|
53
|
+
return {"status": "success", "data": result}
|
54
|
+
else:
|
55
|
+
return {"status": "error", "message": f"Failed to get file info for file ID {file_id}"}
|
56
|
+
except Exception as e:
|
57
|
+
error_msg = f"Get file info operation failed - {str(e)}"
|
58
|
+
logger.error(error_msg, exc_info=True)
|
59
|
+
return {"status": "error", "message": error_msg}
|
60
|
+
|
61
|
+
@mcp.tool()
|
62
|
+
async def get_folder_info(folder_id: str) -> Dict[str, Any]:
|
63
|
+
"""
|
64
|
+
Get folder information
|
65
|
+
|
66
|
+
Args:
|
67
|
+
folder_id: Folder ID
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
Folder information (JSON format) or error message
|
71
|
+
"""
|
72
|
+
if not folder_id:
|
73
|
+
return {"status": "error", "message": "folder_id is required"}
|
74
|
+
|
75
|
+
try:
|
76
|
+
result = await api.get_folder_info(folder_id)
|
77
|
+
if result:
|
78
|
+
return {"status": "success", "data": result}
|
79
|
+
else:
|
80
|
+
return {"status": "error", "message": f"Failed to get folder info for folder ID {folder_id}"}
|
81
|
+
except Exception as e:
|
82
|
+
error_msg = f"Get folder info operation failed - {str(e)}"
|
83
|
+
logger.error(error_msg, exc_info=True)
|
84
|
+
return {"status": "error", "message": error_msg}
|
85
|
+
|
86
|
+
@mcp.tool()
|
87
|
+
async def create_folder(name: str, parent_id: str,
|
88
|
+
target_space_type: Optional[str] = None,
|
89
|
+
target_space_id: Optional[str] = None) -> Dict[str, Any]:
|
90
|
+
"""
|
91
|
+
Create a new folder
|
92
|
+
|
93
|
+
Args:
|
94
|
+
name: Folder name (1-222 characters, cannot contain / ? : * " > <)
|
95
|
+
parent_id: Parent folder ID
|
96
|
+
target_space_type: Space type - "department" or "personal" (optional, effective when parent_id is 0)
|
97
|
+
target_space_id: Space ID (required when target_space_type is "department")
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
Folder creation result (JSON format) or error message
|
101
|
+
"""
|
102
|
+
if not name:
|
103
|
+
return {"status": "error", "message": "name is required"}
|
104
|
+
|
105
|
+
if not parent_id:
|
106
|
+
return {"status": "error", "message": "parent_id is required"}
|
107
|
+
|
108
|
+
try:
|
109
|
+
# Validate folder name length
|
110
|
+
if len(name) < 1 or len(name) > 222:
|
111
|
+
return {"status": "error", "message": "Folder name must be between 1 and 222 characters"}
|
112
|
+
|
113
|
+
# Check for invalid characters in folder name
|
114
|
+
invalid_chars = ['/', '?', ':', '*', '"', '>', '<']
|
115
|
+
if any(char in name for char in invalid_chars):
|
116
|
+
return {"status": "error", "message": "Folder name contains invalid characters (/ ? : * \" > <)"}
|
117
|
+
|
118
|
+
# Check if target_space_id is provided when needed
|
119
|
+
if parent_id == "0" or parent_id == 0:
|
120
|
+
if target_space_type == "department" and not target_space_id:
|
121
|
+
return {"status": "error", "message": "target_space_id is required when target_space_type is department"}
|
122
|
+
|
123
|
+
result = await api.create_folder(
|
124
|
+
name, parent_id, target_space_type, target_space_id
|
125
|
+
)
|
126
|
+
if result:
|
127
|
+
return {"status": "success", "data": result}
|
128
|
+
else:
|
129
|
+
return {"status": "error", "message": f"Failed to create folder '{name}' in parent folder {parent_id}"}
|
130
|
+
except Exception as e:
|
131
|
+
error_msg = f"Create folder operation failed - {str(e)}"
|
132
|
+
logger.error(error_msg, exc_info=True)
|
133
|
+
return {"status": "error", "message": error_msg}
|
134
|
+
|
135
|
+
@mcp.tool()
|
136
|
+
async def upload_file(parent_folder_id: str, local_file_path: str) -> Dict[str, Any]:
|
137
|
+
"""
|
138
|
+
Upload file to FangCloud
|
139
|
+
|
140
|
+
Args:
|
141
|
+
parent_folder_id: Target folder ID in FangCloud
|
142
|
+
local_file_path: Local file path to upload
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
Upload result message
|
146
|
+
"""
|
147
|
+
if not parent_folder_id:
|
148
|
+
return {"status": "error", "message": "parent_folder_id is required"}
|
149
|
+
|
150
|
+
if not local_file_path:
|
151
|
+
return {"status": "error", "message": "local_file_path is required"}
|
152
|
+
|
153
|
+
try:
|
154
|
+
# 获取文件名
|
155
|
+
file_name = os.path.basename(local_file_path)
|
156
|
+
|
157
|
+
# 获取上传URL
|
158
|
+
upload_url = await api.get_file_upload_url(parent_folder_id, file_name)
|
159
|
+
if not upload_url:
|
160
|
+
return {"status": "error", "message": f"Failed to get upload URL for {file_name}"}
|
161
|
+
|
162
|
+
# 上传文件
|
163
|
+
result = await api.upload_file(upload_url, local_file_path)
|
164
|
+
|
165
|
+
if result:
|
166
|
+
return {
|
167
|
+
"status": "success",
|
168
|
+
"message": f"File uploaded successfully - {local_file_path} -> Folder ID {parent_folder_id}",
|
169
|
+
"file_name": file_name,
|
170
|
+
"parent_folder_id": parent_folder_id
|
171
|
+
}
|
172
|
+
else:
|
173
|
+
return {"status": "error", "message": f"File upload failed - {local_file_path}"}
|
174
|
+
except Exception as e:
|
175
|
+
error_msg = f"Upload operation failed - {str(e)}"
|
176
|
+
logger.error(error_msg, exc_info=True)
|
177
|
+
return {"status": "error", "message": error_msg}
|
178
|
+
|
179
|
+
@mcp.tool()
|
180
|
+
async def update_file(file_id: str, name: Optional[str] = None,
|
181
|
+
description: Optional[str] = None) -> Dict[str, Any]:
|
182
|
+
"""
|
183
|
+
Update file name and/or description
|
184
|
+
|
185
|
+
Args:
|
186
|
+
file_id: File ID
|
187
|
+
name: New file name (optional)
|
188
|
+
description: New file description (optional)
|
189
|
+
|
190
|
+
Returns:
|
191
|
+
Update result message
|
192
|
+
|
193
|
+
Note:
|
194
|
+
At least one of name or description must be provided
|
195
|
+
"""
|
196
|
+
if not file_id:
|
197
|
+
return {"status": "error", "message": "file_id is required"}
|
198
|
+
|
199
|
+
if not name and not description:
|
200
|
+
return {"status": "error", "message": "At least one of name or description must be provided"}
|
201
|
+
|
202
|
+
try:
|
203
|
+
result = await api.update_file(file_id, name, description)
|
204
|
+
if result:
|
205
|
+
updated_fields = []
|
206
|
+
if name:
|
207
|
+
updated_fields.append("name")
|
208
|
+
if description:
|
209
|
+
updated_fields.append("description")
|
210
|
+
|
211
|
+
fields_str = " and ".join(updated_fields)
|
212
|
+
return {
|
213
|
+
"status": "success",
|
214
|
+
"message": f"File {file_id} {fields_str} updated successfully",
|
215
|
+
"file_id": file_id,
|
216
|
+
"updated_fields": updated_fields
|
217
|
+
}
|
218
|
+
else:
|
219
|
+
return {"status": "error", "message": f"Failed to update file ID {file_id}"}
|
220
|
+
except Exception as e:
|
221
|
+
error_msg = f"Update operation failed - {str(e)}"
|
222
|
+
logger.error(error_msg, exc_info=True)
|
223
|
+
return {"status": "error", "message": error_msg}
|
224
|
+
|
225
|
+
@mcp.tool()
|
226
|
+
async def download_file(file_id: str, local_path: str) -> Dict[str, Any]:
|
227
|
+
"""
|
228
|
+
Download file from FangCloud
|
229
|
+
|
230
|
+
Args:
|
231
|
+
file_id: File ID
|
232
|
+
local_path: Local path to save the file
|
233
|
+
|
234
|
+
Returns:
|
235
|
+
local file path or error message
|
236
|
+
"""
|
237
|
+
if not file_id:
|
238
|
+
return {"status": "error", "message": "file_id is required"}
|
239
|
+
|
240
|
+
try:
|
241
|
+
# Download file to local path
|
242
|
+
result = await api.download_file_to_local(file_id, local_path)
|
243
|
+
if result:
|
244
|
+
return {
|
245
|
+
"status": "success",
|
246
|
+
"message": f"File downloaded to {result}",
|
247
|
+
"file_path": result
|
248
|
+
}
|
249
|
+
else:
|
250
|
+
return {"status": "error", "message": f"Failed to download file ID {file_id} to {local_path}"}
|
251
|
+
except Exception as e:
|
252
|
+
error_msg = f"Download operation failed - {str(e)}"
|
253
|
+
logger.error(error_msg, exc_info=True)
|
254
|
+
return {"status": "error", "message": error_msg}
|
255
|
+
|
256
|
+
@mcp.tool()
|
257
|
+
async def list_folder_contents(folder_id: str, page_id: Optional[int] = 0,
|
258
|
+
page_capacity: Optional[int] = 20, type_filter: Optional[str] = "all",
|
259
|
+
sort_by: Optional[str] = "date",
|
260
|
+
sort_direction: Optional[str] = "desc") -> Dict[str, Any]:
|
261
|
+
"""
|
262
|
+
List folder contents (files and subfolders)
|
263
|
+
|
264
|
+
Args:
|
265
|
+
folder_id: Folder ID
|
266
|
+
page_id: Page number (optional, default 0)
|
267
|
+
page_capacity: Page capacity (optional, default 20)
|
268
|
+
type_filter: Filter by type - "file", "folder", or "all" (optional, default "all")
|
269
|
+
sort_by: Sort by - "name", "date", or "size" (optional, default "date")
|
270
|
+
sort_direction: Sort direction - "desc" or "asc" (optional, default "desc")
|
271
|
+
|
272
|
+
Returns:
|
273
|
+
Folder contents (JSON format) or error message
|
274
|
+
"""
|
275
|
+
if not folder_id:
|
276
|
+
return {"status": "error", "message": "folder_id is required"}
|
277
|
+
|
278
|
+
try:
|
279
|
+
result = await api.get_folder_children(
|
280
|
+
folder_id, page_id, page_capacity, type_filter, sort_by, sort_direction
|
281
|
+
)
|
282
|
+
if result:
|
283
|
+
return {"status": "success", "data": result}
|
284
|
+
else:
|
285
|
+
return {"status": "error", "message": f"Failed to get contents for folder ID {folder_id}"}
|
286
|
+
except Exception as e:
|
287
|
+
error_msg = f"List folder contents operation failed - {str(e)}"
|
288
|
+
logger.error(error_msg, exc_info=True)
|
289
|
+
return {"status": "error", "message": error_msg}
|
290
|
+
|
291
|
+
@mcp.tool()
|
292
|
+
async def list_personal_items(page_id: Optional[int] = 0,
|
293
|
+
page_capacity: Optional[int] = 20, type_filter: Optional[str] = "all",
|
294
|
+
sort_by: Optional[str] = "date",
|
295
|
+
sort_direction: Optional[str] = "desc") -> Dict[str, Any]:
|
296
|
+
"""
|
297
|
+
List personal items (files and folders in personal space)
|
298
|
+
|
299
|
+
Args:
|
300
|
+
page_id: Page number (optional, default 0)
|
301
|
+
page_capacity: Page capacity (optional, default 20)
|
302
|
+
type_filter: Filter by type - "file", "folder", or "all" (optional, default "all")
|
303
|
+
sort_by: Sort by - "name", "date", or "size" (optional, default "date")
|
304
|
+
sort_direction: Sort direction - "desc" or "asc" (optional, default "desc")
|
305
|
+
|
306
|
+
Returns:
|
307
|
+
Personal items (JSON format) or error message
|
308
|
+
"""
|
309
|
+
try:
|
310
|
+
result = await api.get_personal_items(
|
311
|
+
page_id, page_capacity, type_filter, sort_by, sort_direction
|
312
|
+
)
|
313
|
+
if result:
|
314
|
+
return {"status": "success", "data": result}
|
315
|
+
else:
|
316
|
+
return {"status": "error", "message": "Failed to get personal items"}
|
317
|
+
except Exception as e:
|
318
|
+
error_msg = f"List personal items operation failed - {str(e)}"
|
319
|
+
logger.error(error_msg, exc_info=True)
|
320
|
+
return {"status": "error", "message": error_msg}
|
321
|
+
|
322
|
+
@mcp.tool()
|
323
|
+
async def search_items(query_words: str, search_type: Optional[str] = "all",
|
324
|
+
page_id: Optional[int] = 0, search_in_folder: Optional[str] = None,
|
325
|
+
query_filter: Optional[str] = "all",
|
326
|
+
updated_time_range: Optional[str] = None) -> Dict[str, Any]:
|
327
|
+
"""
|
328
|
+
Search for files and folders
|
329
|
+
|
330
|
+
Args:
|
331
|
+
query_words: Search keywords (required)
|
332
|
+
search_type: Search type - "file", "folder", or "all" (optional, default "all")
|
333
|
+
page_id: Page number (optional, default 0)
|
334
|
+
search_in_folder: Parent folder ID to search within (optional)
|
335
|
+
query_filter: Search filter - "file_name", "content", "creator", or "all" (optional, default "all")
|
336
|
+
updated_time_range: Updated time range in format "start_timestamp,end_timestamp" (optional)
|
337
|
+
Both timestamps can be empty, but comma is required
|
338
|
+
|
339
|
+
Returns:
|
340
|
+
Search results (JSON format) or error message
|
341
|
+
"""
|
342
|
+
if not query_words:
|
343
|
+
return {"status": "error", "message": "query_words is required"}
|
344
|
+
|
345
|
+
try:
|
346
|
+
result = await api.search_items(
|
347
|
+
query_words, search_type, page_id, search_in_folder, query_filter, updated_time_range
|
348
|
+
)
|
349
|
+
if result:
|
350
|
+
return {"status": "success", "data": result}
|
351
|
+
else:
|
352
|
+
return {"status": "error", "message": f"Failed to search for items with query: {query_words}"}
|
353
|
+
except Exception as e:
|
354
|
+
error_msg = f"Search operation failed - {str(e)}"
|
355
|
+
logger.error(error_msg, exc_info=True)
|
356
|
+
return {"status": "error", "message": error_msg}
|
357
|
+
|
358
|
+
|
359
|
+
def main():
|
360
|
+
"""Main entry point for the FangCloud MCP server"""
|
361
|
+
try:
|
362
|
+
logger.info("=== Starting FangCloud MCP ===")
|
363
|
+
|
364
|
+
# Parse command line arguments
|
365
|
+
parser = argparse.ArgumentParser(description='FangCloud MCP')
|
366
|
+
parser.add_argument('--access_token', '-c', type=str, required=True, help='FangCloud API access token is required')
|
367
|
+
args = parser.parse_args()
|
368
|
+
|
369
|
+
# Validate access_token
|
370
|
+
if not args.access_token:
|
371
|
+
logger.error("Access token is required")
|
372
|
+
sys.exit(1)
|
373
|
+
|
374
|
+
logger.info(f"access_token: {args.access_token}")
|
375
|
+
|
376
|
+
# set access_token
|
377
|
+
api.set_access_token(args.access_token)
|
378
|
+
|
379
|
+
# Run MCP server
|
380
|
+
logger.info("Starting MCP server")
|
381
|
+
mcp.run(transport='stdio')
|
382
|
+
|
383
|
+
except Exception as e:
|
384
|
+
logger.error(f"Initialization failed: {str(e)}", exc_info=True)
|
385
|
+
sys.exit(1)
|
386
|
+
finally:
|
387
|
+
if 'api' in locals():
|
388
|
+
asyncio.run(api.cleanup())
|
389
|
+
|
390
|
+
if __name__ == "__main__":
|
391
|
+
main()
|