alibaba-cloud-ops-mcp-server 0.9.13__tar.gz → 0.9.15__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.
Files changed (44) hide show
  1. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/PKG-INFO +1 -1
  2. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/pyproject.toml +1 -1
  3. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/alibabacloud/utils.py +3 -6
  4. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/server.py +46 -17
  5. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/tools/api_tools.py +33 -8
  6. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/tools/application_management_tools.py +29 -8
  7. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/uv.lock +1 -3
  8. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/.github/workflows/python-ci.yml +0 -0
  9. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/.gitignore +0 -0
  10. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/Dockerfile +0 -0
  11. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/LICENSE +0 -0
  12. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/README.md +0 -0
  13. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/README_mcp_args.md +0 -0
  14. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/README_zh.md +0 -0
  15. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/examples/openapi_mcp_quickstart/server.py +0 -0
  16. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/image/Alibaba-Cloud-Ops-MCP-User-Group-en.png +0 -0
  17. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/image/Alibaba-Cloud-Ops-MCP-User-Group-zh.png +0 -0
  18. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/image/alibaba-cloud.png +0 -0
  19. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/image/qoder.svg +0 -0
  20. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/__init__.py +0 -0
  21. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/__init__.py +0 -0
  22. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/__main__.py +0 -0
  23. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/alibabacloud/__init__.py +0 -0
  24. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/alibabacloud/api_meta_client.py +0 -0
  25. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/alibabacloud/exception.py +0 -0
  26. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/alibabacloud/static/PROMPT_UNDERSTANDING.md +0 -0
  27. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/alibabacloud/static/__init__.py +0 -0
  28. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/config.py +0 -0
  29. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/settings.py +0 -0
  30. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/tools/__init__.py +0 -0
  31. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/tools/cms_tools.py +0 -0
  32. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/tools/common_api_tools.py +0 -0
  33. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/tools/local_tools.py +0 -0
  34. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/tools/oos_tools.py +0 -0
  35. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/src/alibaba_cloud_ops_mcp_server/tools/oss_tools.py +0 -0
  36. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/tests/alibabacloud/test_api_meta_client.py +0 -0
  37. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/tests/alibabacloud/test_exception.py +0 -0
  38. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/tests/alibabacloud/test_utils.py +0 -0
  39. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/tests/test_init.py +0 -0
  40. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/tests/test_server.py +0 -0
  41. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/tests/tools/test_api_tools.py +0 -0
  42. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/tests/tools/test_cms_tools.py +0 -0
  43. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/tests/tools/test_oos_tools.py +0 -0
  44. {alibaba_cloud_ops_mcp_server-0.9.13 → alibaba_cloud_ops_mcp_server-0.9.15}/tests/tools/test_oss_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alibaba-cloud-ops-mcp-server
3
- Version: 0.9.13
3
+ Version: 0.9.15
4
4
  Summary: A MCP server for Alibaba Cloud
5
5
  Author-email: Zheng Dayu <dayu.zdy@alibaba-inc.com>, Zhao Shuaibo <zhaoshuaibo.zsb@alibaba-inc.com>
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "alibaba-cloud-ops-mcp-server"
3
- version = "0.9.13"
3
+ version = "0.9.15"
4
4
  description = "A MCP server for Alibaba Cloud"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -192,7 +192,7 @@ def find_bucket_by_tag(client: oss.Client, tag_key: str, tag_value: str) -> Opti
192
192
  return buckets[0] if buckets else None
193
193
 
194
194
 
195
- def get_or_create_bucket_for_code_deploy(application_name: str, region_id: str) -> str:
195
+ def get_or_create_bucket_for_code_deploy(application_name: str) -> str:
196
196
  """
197
197
  Obtain or create an OSS bucket for code deployment
198
198
 
@@ -206,17 +206,14 @@ def get_or_create_bucket_for_code_deploy(application_name: str, region_id: str)
206
206
  """
207
207
  tag_key = 'app_management'
208
208
  tag_value = 'code_deploy'
209
- client = create_oss_client(region_id=region_id)
209
+ client = create_oss_client(region_id='cn-hangzhou')
210
210
 
211
211
  found_bucket = find_bucket_by_tag(client, tag_key, tag_value)
212
212
  if found_bucket:
213
213
  logger.info(f"[code_deploy] Found existing bucket by tag: {found_bucket}")
214
214
  return found_bucket
215
215
 
216
- safe_app_name = application_name.lower().replace('_', '-').replace(' ', '-')
217
-
218
- safe_app_name = ''.join(c if c.isalnum() or c == '-' else '' for c in safe_app_name)
219
- bucket_name = f'app-{safe_app_name}-code-deploy-{str(uuid.uuid4())[:8]}'
216
+ bucket_name = f'code-deploy-{str(uuid.uuid4())[:8]}'
220
217
 
221
218
  try:
222
219
  client.put_bucket(oss.PutBucketRequest(
@@ -80,7 +80,13 @@ SUPPORTED_SERVICES_MAP = {
80
80
  default="domestic",
81
81
  help="Environment type: 'domestic' for domestic, 'international' for overseas (default: domestic)",
82
82
  )
83
- def main(transport: str, port: int, host: str, services: str, headers_credential_only: bool, env: str):
83
+ @click.option(
84
+ "--code-deploy",
85
+ is_flag=True,
86
+ default=False,
87
+ help="Enable code deploy mode, only load 6 specific tools: OOS_CodeDeploy, OOS_GetDeployStatus, OOS_GetLastDeploymentInfo, LOCAL_ListDirectory, LOCAL_RunShellScript, LOCAL_AnalyzeDeployStack",
88
+ )
89
+ def main(transport: str, port: int, host: str, services: str, headers_credential_only: bool, env: str, code_deploy: bool):
84
90
  _setup_logging()
85
91
  # Create an MCP server
86
92
  mcp = FastMCP(
@@ -152,23 +158,46 @@ def main(transport: str, port: int, host: str, services: str, headers_credential
152
158
  settings.headers_credential_only = headers_credential_only
153
159
  if env:
154
160
  settings.env = env
155
- if services:
156
- service_keys = [s.strip().lower() for s in services.split(",")]
157
- service_list = [(key, SUPPORTED_SERVICES_MAP.get(key, key)) for key in service_keys]
158
- set_custom_service_list(service_list)
159
- for tool in common_api_tools.tools:
161
+ if code_deploy:
162
+ # Code deploy mode: only load 6 specific tools
163
+ code_deploy_tools = {
164
+ 'OOS_CodeDeploy',
165
+ 'OOS_GetDeployStatus',
166
+ 'OOS_GetLastDeploymentInfo',
167
+ 'ECS_DescribeInstances',
168
+ 'LOCAL_ListDirectory',
169
+ 'LOCAL_RunShellScript',
170
+ 'LOCAL_AnalyzeDeployStack'
171
+ }
172
+
173
+ # Load from application_management_tools
174
+ for tool in application_management_tools.tools:
175
+ if tool.__name__ in code_deploy_tools:
176
+ mcp.tool(tool)
177
+
178
+ # Load from local_tools
179
+ for tool in local_tools.tools:
180
+ if tool.__name__ in code_deploy_tools:
181
+ mcp.tool(tool)
182
+ else:
183
+ # Normal mode: load all tools
184
+ if services:
185
+ service_keys = [s.strip().lower() for s in services.split(",")]
186
+ service_list = [(key, SUPPORTED_SERVICES_MAP.get(key, key)) for key in service_keys]
187
+ set_custom_service_list(service_list)
188
+ for tool in common_api_tools.tools:
189
+ mcp.tool(tool)
190
+ for tool in oos_tools.tools:
191
+ mcp.tool(tool)
192
+ for tool in application_management_tools.tools:
193
+ mcp.tool(tool)
194
+ for tool in cms_tools.tools:
195
+ mcp.tool(tool)
196
+ for tool in oss_tools.tools:
197
+ mcp.tool(tool)
198
+ api_tools.create_api_tools(mcp, config)
199
+ for tool in local_tools.tools:
160
200
  mcp.tool(tool)
161
- for tool in oos_tools.tools:
162
- mcp.tool(tool)
163
- for tool in application_management_tools.tools:
164
- mcp.tool(tool)
165
- for tool in cms_tools.tools:
166
- mcp.tool(tool)
167
- for tool in oss_tools.tools:
168
- mcp.tool(tool)
169
- api_tools.create_api_tools(mcp, config)
170
- for tool in local_tools.tools:
171
- mcp.tool(tool)
172
201
 
173
202
  # Initialize and run the server
174
203
  logger.debug(f'mcp server is running on {transport} mode.')
@@ -1,4 +1,7 @@
1
1
  import os
2
+ import time
3
+
4
+ from Tea.exceptions import UnretryableException
2
5
  from mcp.server.fastmcp import FastMCP, Context
3
6
  from pydantic import Field
4
7
  import logging
@@ -90,9 +93,9 @@ def create_client(service: str, region_id: str) -> OpenApiClient:
90
93
 
91
94
  # JSON array parameter of type String
92
95
  ECS_LIST_PARAMETERS = {
93
- 'HpcClusterIds', 'DedicatedHostClusterIds', 'DedicatedHostIds',
94
- 'InstanceIds', 'DeploymentSetIds', 'KeyPairNames', 'SecurityGroupIds',
95
- 'diskIds', 'repeatWeekdays', 'timePoints', 'DiskIds', 'SnapshotLinkIds',
96
+ 'HpcClusterIds', 'DedicatedHostClusterIds', 'DedicatedHostIds',
97
+ 'InstanceIds', 'DeploymentSetIds', 'KeyPairNames', 'SecurityGroupIds',
98
+ 'diskIds', 'repeatWeekdays', 'timePoints', 'DiskIds', 'SnapshotLinkIds',
96
99
  'EipAddresses', 'PublicIpAddresses', 'PrivateIpAddresses'
97
100
  }
98
101
 
@@ -109,7 +112,7 @@ def _tools_api_call(service: str, api: str, parameters: dict, ctx: Context):
109
112
  method = 'POST' if api_meta.get('methods', ['post'])[0] == 'post' else 'GET'
110
113
  path = api_meta.get('path', '/')
111
114
  style = ApiMetaClient.get_service_style(service)
112
-
115
+
113
116
  # Handling special parameter formats
114
117
  processed_parameters = parameters.copy()
115
118
  processed_parameters = {k: v for k, v in processed_parameters.items() if v is not None}
@@ -117,7 +120,7 @@ def _tools_api_call(service: str, api: str, parameters: dict, ctx: Context):
117
120
  for param_name, param_value in parameters.items():
118
121
  if param_name in ECS_LIST_PARAMETERS and isinstance(param_value, list):
119
122
  processed_parameters[param_name] = json.dumps(param_value)
120
-
123
+
121
124
  req = open_api_models.OpenApiRequest(
122
125
  query=OpenApiUtilClient.query(processed_parameters)
123
126
  )
@@ -135,9 +138,31 @@ def _tools_api_call(service: str, api: str, parameters: dict, ctx: Context):
135
138
  logger.info(f'Call API Request: Service: {service} API: {api} Method: {method} Parameters: {processed_parameters}')
136
139
  client = create_client(service, processed_parameters.get('RegionId', 'cn-hangzhou'))
137
140
  runtime = util_models.RuntimeOptions()
138
- resp = client.call_api(params, req, runtime)
139
- logger.info(f'Call API Response: {resp}')
140
- return resp
141
+
142
+ max_retries = 3
143
+ last_exception = None
144
+
145
+ for attempt in range(max_retries):
146
+ try:
147
+ resp = client.call_api(params, req, runtime)
148
+ logger.info(f'Call API Response: {resp}')
149
+ return resp
150
+ except UnretryableException as e:
151
+ last_exception = e
152
+ error_msg = str(e)
153
+ has_bad_fd = '[Errno 9] Bad file descriptor' in error_msg
154
+
155
+ if has_bad_fd and attempt < max_retries - 1:
156
+ wait_time = (attempt + 1) * 0.5
157
+ logger.warning(f'[_tools_api_call] UnretryableException with [Errno 9] Bad file descriptor (attempt {attempt + 1}/{max_retries}), retrying after {wait_time}s: {e}')
158
+ time.sleep(wait_time)
159
+ else:
160
+ logger.error(f'Call API Error: {e}')
161
+ raise e
162
+
163
+ if last_exception:
164
+ logger.error(f'[_tools_api_call] All retries failed, raising last exception: {last_exception}')
165
+ raise last_exception
141
166
 
142
167
 
143
168
  def _create_parameter_schema(fields: dict):
@@ -5,7 +5,7 @@ from alibaba_cloud_ops_mcp_server.tools.api_tools import _tools_api_call
5
5
  from pathlib import Path
6
6
 
7
7
  from pydantic import Field
8
- from typing import Optional, Tuple
8
+ from typing import Optional, Tuple, List
9
9
  import json
10
10
  import time
11
11
  from alibabacloud_oos20190601.client import Client as oos20190601Client
@@ -47,13 +47,9 @@ def OOS_CodeDeploy(
47
47
  name: str = Field(description='name of the application'),
48
48
  deploy_region_id: str = Field(description='Region ID for deployment'),
49
49
  application_group_name: str = Field(description='name of the application group'),
50
- region_id_oss: str = Field(description='OSS region ID'),
51
50
  object_name: str = Field(description='OSS object name'),
52
51
  file_path: str = Field(description='Local file path to upload. If the file is not in '
53
52
  '.code_deploy/release directory, it will be copied there.'),
54
- is_internal_oss: bool = Field(description='Whether to download OSS files through internal network. Note: '
55
- 'If you choose internal network download, you must ensure that '
56
- 'the ECS to be deployed and OSS are in the same region.'),
57
53
  application_start: str = Field(
58
54
  description='Application start command script. IMPORTANT: If the uploaded artifact '
59
55
  'is a tar archive or compressed package (e.g., .tar, .tar.gz, .zip), '
@@ -78,7 +74,7 @@ def OOS_CodeDeploy(
78
74
 
79
75
  ):
80
76
  """
81
- 通过应用管理 API 部署应用到 ECS 实例。
77
+ 部署应用到阿里云ECS实例。
82
78
 
83
79
  完整部署流程(在调用此工具之前):
84
80
 
@@ -242,7 +238,8 @@ def OOS_CodeDeploy(
242
238
  file_path = str(release_path_resolved)
243
239
  else:
244
240
  logger.info(f"[code_deploy] File already in release directory: {file_path}")
245
-
241
+ region_id_oss = 'cn-hangzhou'
242
+ is_internal_oss = True if deploy_region_id.lower() == 'cn-hangzhou' else False
246
243
  # Log input parameters
247
244
  logger.info(f"[code_deploy] Input parameters: name={name}, deploy_region_id={deploy_region_id}, "
248
245
  f"application_group_name={application_group_name}, instance_ids={instance_ids}, "
@@ -250,7 +247,7 @@ def OOS_CodeDeploy(
250
247
  f"is_internal_oss={is_internal_oss}, port={port}")
251
248
 
252
249
  # Upload file to OSS
253
- bucket_name = get_or_create_bucket_for_code_deploy(name, region_id_oss)
250
+ bucket_name = get_or_create_bucket_for_code_deploy(name)
254
251
  logger.info(f"[code_deploy] Auto selected/created bucket: {bucket_name}")
255
252
 
256
253
  put_object_resp = oss_tools.OSS_PutObject(
@@ -379,6 +376,30 @@ def OOS_GetDeployStatus(
379
376
  return response
380
377
 
381
378
 
379
+ @_append_tool
380
+ def ECS_DescribeInstances(
381
+ instance_ids: List[str] = Field(description='AlibabaCloud ECS instance ID List (required)'),
382
+ region_id: str = Field(description='AlibabaCloud region ID', default='cn-hangzhou'),
383
+ ):
384
+ """
385
+ 查询指定ECS实例的详细信息。此工具要求必须提供实例ID列表,避免随意查询所有实例。
386
+ 注意:此工具仅用于查询用户明确指定的实例信息,不允许用于扫描或枚举所有实例。
387
+ """
388
+ logger.info(f"[ECS_DescribeInstances] Input parameters: region_id={region_id}, instance_ids={instance_ids}")
389
+
390
+ if not instance_ids:
391
+ raise ValueError("instance_ids is required and cannot be empty")
392
+
393
+ describe_instances_request = ecs_20140526_models.DescribeInstancesRequest(
394
+ region_id=region_id,
395
+ instance_ids=json.dumps(instance_ids)
396
+ )
397
+
398
+ response = _describe_instances_with_retry(region_id, describe_instances_request)
399
+ logger.info(f"[ECS_DescribeInstances] Response: {json.dumps(str(response), ensure_ascii=False)}")
400
+ return response
401
+
402
+
382
403
  def _handle_new_application_group(client, name, application_group_name, deploy_region_id,
383
404
  region_id_oss, bucket_name, object_name, version_id,
384
405
  is_internal_oss, port, instance_ids, application_start,
@@ -116,12 +116,11 @@ wheels = [
116
116
 
117
117
  [[package]]
118
118
  name = "alibaba-cloud-ops-mcp-server"
119
- version = "0.9.12"
119
+ version = "0.9.15"
120
120
  source = { editable = "." }
121
121
  dependencies = [
122
122
  { name = "alibabacloud-cms20190101" },
123
123
  { name = "alibabacloud-credentials" },
124
- { name = "alibabacloud-credentials-api" },
125
124
  { name = "alibabacloud-ecs20140526" },
126
125
  { name = "alibabacloud-oos20190601" },
127
126
  { name = "alibabacloud-oss-v2" },
@@ -140,7 +139,6 @@ dev = [
140
139
  requires-dist = [
141
140
  { name = "alibabacloud-cms20190101", specifier = ">=3.1.4" },
142
141
  { name = "alibabacloud-credentials", specifier = ">=1.0.3" },
143
- { name = "alibabacloud-credentials-api", specifier = "==1.0.1" },
144
142
  { name = "alibabacloud-ecs20140526", specifier = ">=6.1.0" },
145
143
  { name = "alibabacloud-oos20190601", specifier = ">=3.5.0" },
146
144
  { name = "alibabacloud-oss-v2", specifier = ">=1.1.0" },