Skip to content

Commit ed7a411

Browse files
author
YangSen-qn
committed
chore: change some desc
1 parent bf5e47c commit ed7a411

File tree

3 files changed

+444
-498
lines changed

3 files changed

+444
-498
lines changed

src/mcp_server/core/storage/storage.py

Lines changed: 31 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import aioboto3
2-
import asyncio
32
import logging
43
import qiniu
54

@@ -13,15 +12,7 @@
1312

1413

1514
class StorageService:
16-
"""
17-
S3 Resource provider that handles interactions with AWS S3 buckets.
18-
Part of a collection of resource providers (S3, DynamoDB, etc.) for the MCP server.
19-
"""
20-
2115
def __init__(self, cfg: config.Config = None):
22-
"""
23-
Initialize S3 resource provider
24-
"""
2516
# Configure boto3 with retries and timeouts
2617
self.s3_config = S3Config(
2718
retries=dict(max_attempts=3, mode="adaptive"),
@@ -37,15 +28,6 @@ def __init__(self, cfg: config.Config = None):
3728
def get_object_url(
3829
self, bucket: str, key: str, disable_ssl: bool = False, expires: int = 3600
3930
) -> list[dict[str:Any]]:
40-
"""
41-
获取对象
42-
:param disable_ssl:
43-
:param bucket:
44-
:param key:
45-
:param expires:
46-
:return: dict
47-
返回对象信息
48-
"""
4931
# 获取下载域名
5032
domains_getter = getattr(self.bucket_manager, "_BucketManager__uc_do_with_retrier")
5133
domains_list, domain_response = domains_getter('/v3/domains?tbl={0}'.format(bucket))
@@ -127,15 +109,6 @@ async def list_buckets(self, prefix: Optional[str] = None) -> List[dict]:
127109
async def list_objects(
128110
self, bucket: str, prefix: str = "", max_keys: int = 20, start_after: str = ""
129111
) -> List[dict]:
130-
"""
131-
List objects in a specific bucket using async client with pagination
132-
Args:
133-
bucket: Name of the S3 bucket
134-
prefix: Object prefix for filtering
135-
max_keys: Maximum number of keys to return
136-
start_after: the index that list from,can be last object key
137-
"""
138-
#
139112
if self.config.buckets and bucket not in self.config.buckets:
140113
logger.warning(f"Bucket {bucket} not in configured bucket list")
141114
return []
@@ -161,99 +134,73 @@ async def list_objects(
161134
)
162135
return response.get("Contents", [])
163136

164-
async def get_object(
165-
self, bucket: str, key: str, max_retries: int = 3
166-
) -> Dict[str, Any]:
167-
"""
168-
Get object from S3 using streaming to handle large files and PDFs reliably.
169-
The method reads the stream in chunks and concatenates them before returning.
170-
"""
137+
async def get_object(self, bucket: str, key: str) -> Dict[str, Any]:
171138
if self.config.buckets and bucket not in self.config.buckets:
172139
logger.warning(f"Bucket {bucket} not in configured bucket list")
173140
return {}
174141

175-
attempt = 0
176-
last_exception = None
177-
178-
while attempt < max_retries:
179-
try:
180-
async with self.s3_session.client(
181-
"s3",
182-
aws_access_key_id=self.config.access_key,
183-
aws_secret_access_key=self.config.secret_key,
184-
endpoint_url=self.config.endpoint_url,
185-
region_name=self.config.region_name,
186-
config=self.s3_config,
187-
) as s3:
188-
# Get the object and its stream
189-
response = await s3.get_object(Bucket=bucket, Key=key)
190-
stream = response["Body"]
191-
192-
# Read the entire stream in chunks
193-
chunks = []
194-
async for chunk in stream:
195-
chunks.append(chunk)
196-
197-
# Replace the stream with the complete data
198-
response["Body"] = b"".join(chunks)
199-
return response
142+
async with self.s3_session.client(
143+
"s3",
144+
aws_access_key_id=self.config.access_key,
145+
aws_secret_access_key=self.config.secret_key,
146+
endpoint_url=self.config.endpoint_url,
147+
region_name=self.config.region_name,
148+
config=self.s3_config,
149+
) as s3:
150+
# Get the object and its stream
151+
response = await s3.get_object(Bucket=bucket, Key=key)
152+
stream = response["Body"]
200153

201-
except Exception as e:
202-
last_exception = e
203-
if "NoSuchKey" in str(e):
204-
raise
154+
# Read the entire stream in chunks
155+
chunks = []
156+
async for chunk in stream:
157+
chunks.append(chunk)
205158

206-
attempt += 1
207-
if attempt < max_retries:
208-
wait_time = 2 ** attempt
209-
logger.warning(
210-
f"Attempt {attempt} failed, retrying in {wait_time} seconds: {str(e)}"
211-
)
212-
await asyncio.sleep(wait_time)
213-
continue
159+
# Replace the stream with the complete data
160+
response["Body"] = b"".join(chunks)
161+
return response
214162

215-
raise last_exception or Exception("Failed to get object after all retries")
216163

217164
def is_text_file(self, key: str) -> bool:
218-
"""Determine if a file is text-based by its extension"""
219165
text_extensions = {
166+
".ini",
167+
".conf",
168+
".py",
169+
".js",
170+
".xml",
171+
".yml",
172+
".properties",
220173
".txt",
221174
".log",
222175
".json",
223-
".xml",
224-
".yml",
225176
".yaml",
226177
".md",
227178
".csv",
228-
".ini",
229-
".conf",
230-
".py",
231-
".js",
232179
".html",
233180
".css",
234181
".sh",
235182
".bash",
236183
".cfg",
237-
".properties",
238184
}
239185
return any(key.lower().endswith(ext) for ext in text_extensions)
240186

187+
241188
def is_image_file(self, key: str) -> bool:
242189
"""Determine if a file is text-based by its extension"""
243190
text_extensions = {
191+
".gif",
244192
".png",
245-
".jpeg",
246193
".jpg",
247-
".gif",
248194
".bmp",
195+
".jpeg",
249196
".tiff",
250-
".svg",
251197
".webp",
198+
".svg",
252199
}
253200
return any(key.lower().endswith(ext) for ext in text_extensions)
254201

202+
255203
def is_markdown_file(self, key: str) -> bool:
256-
"""Determine if a file is text-based by its extension"""
257204
text_extensions = {
258205
".md",
259206
}

src/mcp_server/core/storage/tools.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010

1111
logger = logging.getLogger(consts.LOGGER_NAME)
1212

13-
_BUCKET_DESC = """When you use this operation with a directory bucket, you must use virtual-hosted-style requests in the format ${bucket_name}.s3.${region_id}.qiniucs.com. Path-style requests are not supported. Directory bucket names must be unique in the chosen Availability Zone.
14-
"""
13+
_BUCKET_DESC = "Qiniu Cloud Storage bucket Name"
1514

1615
class _ToolImpl:
1716
def __init__(self, storage: StorageService):
@@ -20,7 +19,7 @@ def __init__(self, storage: StorageService):
2019
@tools.tool_meta(
2120
types.Tool(
2221
name="ListBuckets",
23-
description="Returns a list of all buckets owned by the authenticated sender of the request. To grant IAM permission to use this operation, you must add the s3:ListAllMyBuckets policy action.",
22+
description="Return the Bucket you configured based on the conditions.",
2423
inputSchema={
2524
"type": "object",
2625
"properties": {
@@ -40,7 +39,7 @@ async def list_buckets(self, **kwargs) -> list[types.TextContent]:
4039
@tools.tool_meta(
4140
types.Tool(
4241
name="ListObjects",
43-
description="Each request will return some or all (up to 100) objects in the bucket. You can use request parameters as selection criteria to return some objects in the bucket. If you want to continue listing, set start_after to the key of the last file in the last listing result so that you can list new content. To get a list of buckets, see ListBuckets.",
42+
description="List objects in Qiniu Cloud, list a part each time, you can set start_after to continue listing, when the number of listed objects is less than max_keys, it means that all files are listed. start_after can be the key of the last file in the previous listing.",
4443
inputSchema={
4544
"type": "object",
4645
"properties": {
@@ -50,15 +49,15 @@ async def list_buckets(self, **kwargs) -> list[types.TextContent]:
5049
},
5150
"max_keys": {
5251
"type": "integer",
53-
"description": "Sets the maximum number of keys returned in the response. By default, the action returns up to 20 key names. The response might contain fewer keys but will never contain more.",
52+
"description": "Sets the max number of keys returned, default: 20",
5453
},
5554
"prefix": {
5655
"type": "string",
57-
"description": "Limits the response to keys that begin with the specified prefix.",
56+
"description": "Specify the prefix of the operation response key. Only keys that meet this prefix will be listed.",
5857
},
5958
"start_after": {
6059
"type": "string",
61-
"description": "start_after is where you want S3 to start listing from. S3 starts listing after this specified key. start_after can be any key in the bucket.",
60+
"description": "start_after is where you want Qiniu Cloud to start listing from. Qiniu Cloud starts listing after this specified key. start_after can be any key in the bucket.",
6261
},
6362
},
6463
"required": ["bucket"],
@@ -72,7 +71,7 @@ async def list_objects(self, **kwargs) -> list[types.TextContent]:
7271
@tools.tool_meta(
7372
types.Tool(
7473
name="GetObject",
75-
description="Retrieves an object from Qiniu bucket. In the GetObject request, specify the full key name for the object. Path-style requests are not supported.",
74+
description="Get an object contents from Qiniu Cloud bucket. In the GetObject request, specify the full key name for the object.",
7675
inputSchema={
7776
"type": "object",
7877
"properties": {
@@ -82,7 +81,7 @@ async def list_objects(self, **kwargs) -> list[types.TextContent]:
8281
},
8382
"key": {
8483
"type": "string",
85-
"description": "Key of the object to get. Length Constraints: Minimum length of 1.",
84+
"description": "Key of the object to get.",
8685
},
8786
},
8887
"required": ["bucket", "key"],
@@ -112,7 +111,7 @@ async def get_object(self, **kwargs) -> list[ImageContent] | list[TextContent]:
112111
@tools.tool_meta(
113112
types.Tool(
114113
name="GetObjectURL",
115-
description="Get the file download URL, and note that the Bucket where the file is located must be bound to a domain name. If using Qiniu's test domain, HTTPS access will not be available, and users need to make adjustments for this themselves.",
114+
description="Get the file download URL, and note that the Bucket where the file is located must be bound to a domain name. If using Qiniu Cloud test domain, HTTPS access will not be available, and users need to make adjustments for this themselves.",
116115
inputSchema={
117116
"type": "object",
118117
"properties": {

0 commit comments

Comments
 (0)