fangcloud-mcp 0.1.0__tar.gz → 0.1.1__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.
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Your Name
3
+ Copyright (c) 2025 FangCloud Developer
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,4 @@
1
+ include LICENSE
2
+ include README.md
3
+ include pyproject.toml
4
+ include setup.py
@@ -1,15 +1,24 @@
1
1
  Metadata-Version: 2.1
2
- Name: fangcloud-mcp
3
- Version: 0.1.0
2
+ Name: fangcloud_mcp
3
+ Version: 0.1.1
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 <repository-url>
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 <repository-url>
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,10 @@
1
+ """
2
+ FangCloud MCP - Model Context Protocol (MCP) 服务器实现,提供与 FangCloud 云存储服务的集成
3
+ """
4
+
5
+ __version__ = "0.1.1"
6
+
7
+ from .fangcloud import main
8
+ from .fangcloud_api import FangcloudAPI
9
+
10
+ __all__ = ["main", "FangcloudAPI"]
@@ -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()