alibaba-cloud-ops-mcp-server 0.9.9__py3-none-any.whl → 0.9.12__py3-none-any.whl
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.
- alibaba_cloud_ops_mcp_server/alibabacloud/utils.py +183 -0
- alibaba_cloud_ops_mcp_server/server.py +84 -1
- alibaba_cloud_ops_mcp_server/tools/api_tools.py +8 -2
- alibaba_cloud_ops_mcp_server/tools/application_management_tools.py +748 -0
- alibaba_cloud_ops_mcp_server/tools/local_tools.py +323 -0
- alibaba_cloud_ops_mcp_server/tools/oos_tools.py +18 -8
- alibaba_cloud_ops_mcp_server/tools/oss_tools.py +68 -3
- {alibaba_cloud_ops_mcp_server-0.9.9.dist-info → alibaba_cloud_ops_mcp_server-0.9.12.dist-info}/METADATA +39 -7
- {alibaba_cloud_ops_mcp_server-0.9.9.dist-info → alibaba_cloud_ops_mcp_server-0.9.12.dist-info}/RECORD +12 -10
- {alibaba_cloud_ops_mcp_server-0.9.9.dist-info → alibaba_cloud_ops_mcp_server-0.9.12.dist-info}/WHEEL +1 -1
- {alibaba_cloud_ops_mcp_server-0.9.9.dist-info → alibaba_cloud_ops_mcp_server-0.9.12.dist-info}/entry_points.txt +0 -0
- {alibaba_cloud_ops_mcp_server-0.9.9.dist-info → alibaba_cloud_ops_mcp_server-0.9.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional, Dict, List, Any
|
|
8
|
+
from pydantic import Field
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ToolsList:
|
|
14
|
+
"""A tool list that can be used as both a list and a decorator"""
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self._list = []
|
|
17
|
+
|
|
18
|
+
def append(self, func):
|
|
19
|
+
"""Decorator: Add function to the list and return the function itself"""
|
|
20
|
+
self._list.append(func)
|
|
21
|
+
return func
|
|
22
|
+
|
|
23
|
+
def __iter__(self):
|
|
24
|
+
return iter(self._list)
|
|
25
|
+
|
|
26
|
+
def __len__(self):
|
|
27
|
+
return len(self._list)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
tools = ToolsList()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _validate_path(path: str) -> Path:
|
|
34
|
+
"""Validate and normalize path to prevent path traversal attacks"""
|
|
35
|
+
try:
|
|
36
|
+
# Convert to absolute path
|
|
37
|
+
abs_path = Path(path).resolve()
|
|
38
|
+
# Ensure path exists and is a file or directory
|
|
39
|
+
if not abs_path.exists():
|
|
40
|
+
raise FileNotFoundError(f"Path does not exist: {path}")
|
|
41
|
+
return abs_path
|
|
42
|
+
except (OSError, ValueError) as e:
|
|
43
|
+
raise ValueError(f"Invalid path: {path}") from e
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@tools.append
|
|
47
|
+
def LOCAL_ListDirectory(
|
|
48
|
+
path: str = Field(description="Directory path to list (absolute or relative path)"),
|
|
49
|
+
recursive: bool = Field(description="Whether to recursively list subdirectories", default=False)
|
|
50
|
+
):
|
|
51
|
+
"""列出指定目录中的文件和子目录。返回包含名称、类型和大小等信息的目录和文件列表。"""
|
|
52
|
+
logger.info(f"[ListDirectory] Input parameters: path={path}, recursive={recursive}")
|
|
53
|
+
try:
|
|
54
|
+
dir_path = _validate_path(path)
|
|
55
|
+
|
|
56
|
+
# Ensure it's a directory, not a file
|
|
57
|
+
if not dir_path.is_dir():
|
|
58
|
+
raise ValueError(f"Path is not a directory: {path}")
|
|
59
|
+
|
|
60
|
+
results = []
|
|
61
|
+
|
|
62
|
+
if recursive:
|
|
63
|
+
# Recursively list
|
|
64
|
+
for item in dir_path.rglob('*'):
|
|
65
|
+
try:
|
|
66
|
+
item_stat = item.stat()
|
|
67
|
+
results.append({
|
|
68
|
+
"name": item.name,
|
|
69
|
+
"path": str(item),
|
|
70
|
+
"type": "directory" if item.is_dir() else "file",
|
|
71
|
+
"size": item_stat.st_size if item.is_file() else None,
|
|
72
|
+
"modified": item_stat.st_mtime
|
|
73
|
+
})
|
|
74
|
+
except (OSError, PermissionError):
|
|
75
|
+
# Skip inaccessible files/directories
|
|
76
|
+
continue
|
|
77
|
+
else:
|
|
78
|
+
# List only current directory
|
|
79
|
+
for item in dir_path.iterdir():
|
|
80
|
+
try:
|
|
81
|
+
item_stat = item.stat()
|
|
82
|
+
results.append({
|
|
83
|
+
"name": item.name,
|
|
84
|
+
"path": str(item),
|
|
85
|
+
"type": "directory" if item.is_dir() else "file",
|
|
86
|
+
"size": item_stat.st_size if item.is_file() else None,
|
|
87
|
+
"modified": item_stat.st_mtime
|
|
88
|
+
})
|
|
89
|
+
except (OSError, PermissionError):
|
|
90
|
+
# Skip inaccessible files/directories
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# Sort by name
|
|
94
|
+
results.sort(key=lambda x: x["name"])
|
|
95
|
+
|
|
96
|
+
response = {
|
|
97
|
+
"path": str(dir_path),
|
|
98
|
+
"items": results,
|
|
99
|
+
"count": len(results)
|
|
100
|
+
}
|
|
101
|
+
logger.info(f"[ListDirectory] Response: {json.dumps(response, ensure_ascii=False, default=str)}")
|
|
102
|
+
return response
|
|
103
|
+
except Exception as e:
|
|
104
|
+
raise ValueError(f"Failed to list directory: {str(e)}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@tools.append
|
|
108
|
+
def LOCAL_RunShellScript(
|
|
109
|
+
script: str = Field(description="Shell script content or command to execute"),
|
|
110
|
+
working_directory: Optional[str] = Field(description="Working directory for command execution", default=None),
|
|
111
|
+
timeout: int = Field(description="Command execution timeout in seconds", default=300),
|
|
112
|
+
shell: bool = Field(description="Whether to use shell execution (True uses /bin/sh, False executes command directly)", default=True)
|
|
113
|
+
):
|
|
114
|
+
"""执行 shell 脚本或命令。返回命令输出、错误信息和退出代码。注意:执行任意命令可能存在安全风险,请谨慎使用。"""
|
|
115
|
+
logger.info(f"[RunShellScript] Input parameters: script={script}, working_directory={working_directory}, timeout={timeout}, shell={shell}")
|
|
116
|
+
try:
|
|
117
|
+
# Set working directory
|
|
118
|
+
cwd = None
|
|
119
|
+
if working_directory:
|
|
120
|
+
cwd = _validate_path(working_directory)
|
|
121
|
+
if not cwd.is_dir():
|
|
122
|
+
raise ValueError(f"Working directory is not a directory: {working_directory}")
|
|
123
|
+
cwd = str(cwd)
|
|
124
|
+
|
|
125
|
+
# Execute command
|
|
126
|
+
if shell:
|
|
127
|
+
# Execute using shell
|
|
128
|
+
process = subprocess.run(
|
|
129
|
+
script,
|
|
130
|
+
shell=True,
|
|
131
|
+
capture_output=True,
|
|
132
|
+
text=True,
|
|
133
|
+
timeout=timeout,
|
|
134
|
+
cwd=cwd,
|
|
135
|
+
executable='/bin/sh' if os.name != 'nt' else None
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
# Execute directly (need to split command into list)
|
|
139
|
+
import shlex
|
|
140
|
+
cmd_list = shlex.split(script)
|
|
141
|
+
process = subprocess.run(
|
|
142
|
+
cmd_list,
|
|
143
|
+
shell=False,
|
|
144
|
+
capture_output=True,
|
|
145
|
+
text=True,
|
|
146
|
+
timeout=timeout,
|
|
147
|
+
cwd=cwd
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
response = {
|
|
151
|
+
"command": script,
|
|
152
|
+
"exit_code": process.returncode,
|
|
153
|
+
"stdout": process.stdout,
|
|
154
|
+
"stderr": process.stderr,
|
|
155
|
+
"success": process.returncode == 0
|
|
156
|
+
}
|
|
157
|
+
logger.info(f"[RunShellScript] Response: {json.dumps(response, ensure_ascii=False, default=str)}")
|
|
158
|
+
return response
|
|
159
|
+
except subprocess.TimeoutExpired:
|
|
160
|
+
raise ValueError(f"Command execution timeout (exceeded {timeout} seconds)")
|
|
161
|
+
except FileNotFoundError:
|
|
162
|
+
raise ValueError(f"Command or script not found: {script}")
|
|
163
|
+
except Exception as e:
|
|
164
|
+
raise ValueError(f"Failed to execute command: {str(e)}")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@tools.append
|
|
168
|
+
def LOCAL_AnalyzeDeployStack(
|
|
169
|
+
directory: str = Field(description="Directory path to analyze (absolute or relative path)")
|
|
170
|
+
):
|
|
171
|
+
"""识别项目部署方式和技术栈。支持识别 npm、Python、Java、Go、Docker 等部署方式。"""
|
|
172
|
+
logger.info(f"[AnalyzeDeployStack] Input parameters: directory={directory}")
|
|
173
|
+
try:
|
|
174
|
+
dir_path = _validate_path(directory)
|
|
175
|
+
|
|
176
|
+
if not dir_path.is_dir():
|
|
177
|
+
raise ValueError(f"路径不是目录: {directory}")
|
|
178
|
+
|
|
179
|
+
detection_results = {
|
|
180
|
+
"directory": str(dir_path),
|
|
181
|
+
"deployment_methods": [],
|
|
182
|
+
"package_managers": [],
|
|
183
|
+
"frameworks": [],
|
|
184
|
+
"runtime_versions": {},
|
|
185
|
+
"config_files": {},
|
|
186
|
+
"detected": False
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# List of files to check
|
|
190
|
+
files_to_check = {
|
|
191
|
+
# Node.js / npm
|
|
192
|
+
"package.json": ("npm", "nodejs"),
|
|
193
|
+
"package-lock.json": ("npm", "nodejs"),
|
|
194
|
+
"yarn.lock": ("yarn", "nodejs"),
|
|
195
|
+
"pnpm-lock.yaml": ("pnpm", "nodejs"),
|
|
196
|
+
".nvmrc": ("nodejs", None),
|
|
197
|
+
|
|
198
|
+
# Python
|
|
199
|
+
"requirements.txt": ("pip", "python"),
|
|
200
|
+
"Pipfile": ("pipenv", "python"),
|
|
201
|
+
"pyproject.toml": ("poetry", "python"),
|
|
202
|
+
"setup.py": ("setuptools", "python"),
|
|
203
|
+
"environment.yml": ("conda", "python"),
|
|
204
|
+
".python-version": ("python", None),
|
|
205
|
+
|
|
206
|
+
# Java
|
|
207
|
+
"pom.xml": ("maven", "java"),
|
|
208
|
+
"build.gradle": ("gradle", "java"),
|
|
209
|
+
"build.gradle.kts": ("gradle", "java"),
|
|
210
|
+
|
|
211
|
+
# Go
|
|
212
|
+
"go.mod": ("go", "go"),
|
|
213
|
+
"go.sum": ("go", "go"),
|
|
214
|
+
"Gopkg.toml": ("dep", "go"),
|
|
215
|
+
|
|
216
|
+
# Rust
|
|
217
|
+
"Cargo.toml": ("cargo", "rust"),
|
|
218
|
+
"Cargo.lock": ("cargo", "rust"),
|
|
219
|
+
|
|
220
|
+
# PHP
|
|
221
|
+
"composer.json": ("composer", "php"),
|
|
222
|
+
|
|
223
|
+
# Ruby
|
|
224
|
+
"Gemfile": ("bundler", "ruby"),
|
|
225
|
+
|
|
226
|
+
# Docker
|
|
227
|
+
"Dockerfile": ("docker", "docker"),
|
|
228
|
+
"docker-compose.yml": ("docker-compose", "docker"),
|
|
229
|
+
"docker-compose.yaml": ("docker-compose", "docker"),
|
|
230
|
+
".dockerignore": ("docker", "docker"),
|
|
231
|
+
|
|
232
|
+
# 其他
|
|
233
|
+
"Makefile": ("make", None),
|
|
234
|
+
"CMakeLists.txt": ("cmake", "cpp"),
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# Check if files exist
|
|
238
|
+
for filename, (package_manager, framework) in files_to_check.items():
|
|
239
|
+
file_path = dir_path / filename
|
|
240
|
+
if file_path.exists():
|
|
241
|
+
if package_manager:
|
|
242
|
+
if package_manager not in detection_results["package_managers"]:
|
|
243
|
+
detection_results["package_managers"].append(package_manager)
|
|
244
|
+
if framework:
|
|
245
|
+
if framework not in detection_results["frameworks"]:
|
|
246
|
+
detection_results["frameworks"].append(framework)
|
|
247
|
+
detection_results["config_files"][filename] = str(file_path)
|
|
248
|
+
|
|
249
|
+
# Read key files to get more information
|
|
250
|
+
# Read package.json
|
|
251
|
+
package_json = dir_path / "package.json"
|
|
252
|
+
if package_json.exists():
|
|
253
|
+
try:
|
|
254
|
+
with open(package_json, 'r', encoding='utf-8') as f:
|
|
255
|
+
pkg_data = json.load(f)
|
|
256
|
+
if "engines" in pkg_data and "node" in pkg_data["engines"]:
|
|
257
|
+
detection_results["runtime_versions"]["node"] = pkg_data["engines"]["node"]
|
|
258
|
+
except Exception:
|
|
259
|
+
pass
|
|
260
|
+
|
|
261
|
+
# Read requirements.txt or pyproject.toml to get Python version information
|
|
262
|
+
if (dir_path / "requirements.txt").exists() or (dir_path / "pyproject.toml").exists():
|
|
263
|
+
# Try to read from .python-version
|
|
264
|
+
py_version_file = dir_path / ".python-version"
|
|
265
|
+
if py_version_file.exists():
|
|
266
|
+
try:
|
|
267
|
+
version = py_version_file.read_text().strip()
|
|
268
|
+
detection_results["runtime_versions"]["python"] = version
|
|
269
|
+
except Exception:
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
# Read go.mod to get Go version
|
|
273
|
+
go_mod = dir_path / "go.mod"
|
|
274
|
+
if go_mod.exists():
|
|
275
|
+
try:
|
|
276
|
+
content = go_mod.read_text()
|
|
277
|
+
match = re.search(r'go\s+(\d+\.\d+)', content)
|
|
278
|
+
if match:
|
|
279
|
+
detection_results["runtime_versions"]["go"] = match.group(1)
|
|
280
|
+
except Exception:
|
|
281
|
+
pass
|
|
282
|
+
|
|
283
|
+
# Read Dockerfile to detect base image
|
|
284
|
+
dockerfile = dir_path / "Dockerfile"
|
|
285
|
+
if dockerfile.exists():
|
|
286
|
+
try:
|
|
287
|
+
content = dockerfile.read_text()
|
|
288
|
+
# Detect common base images
|
|
289
|
+
if "FROM node" in content or "FROM node:" in content:
|
|
290
|
+
match = re.search(r'FROM node:?(\S+)', content)
|
|
291
|
+
if match:
|
|
292
|
+
detection_results["runtime_versions"]["node_docker"] = match.group(1)
|
|
293
|
+
elif "FROM python" in content or "FROM python:" in content:
|
|
294
|
+
match = re.search(r'FROM python:?(\S+)', content)
|
|
295
|
+
if match:
|
|
296
|
+
detection_results["runtime_versions"]["python_docker"] = match.group(1)
|
|
297
|
+
except Exception:
|
|
298
|
+
pass
|
|
299
|
+
|
|
300
|
+
# Determine main deployment method
|
|
301
|
+
if detection_results["package_managers"] or detection_results["frameworks"]:
|
|
302
|
+
detection_results["detected"] = True
|
|
303
|
+
# Infer deployment method based on detected package managers
|
|
304
|
+
if "npm" in detection_results["package_managers"] or "yarn" in detection_results["package_managers"] or "pnpm" in detection_results["package_managers"]:
|
|
305
|
+
detection_results["deployment_methods"].append("npm")
|
|
306
|
+
if "pip" in detection_results["package_managers"] or "pipenv" in detection_results["package_managers"] or "poetry" in detection_results["package_managers"]:
|
|
307
|
+
detection_results["deployment_methods"].append("python")
|
|
308
|
+
if "maven" in detection_results["package_managers"] or "gradle" in detection_results["package_managers"]:
|
|
309
|
+
detection_results["deployment_methods"].append("java")
|
|
310
|
+
if "go" in detection_results["package_managers"]:
|
|
311
|
+
detection_results["deployment_methods"].append("go")
|
|
312
|
+
if "docker" in detection_results["package_managers"]:
|
|
313
|
+
detection_results["deployment_methods"].append("docker")
|
|
314
|
+
|
|
315
|
+
# If nothing detected, return unknown
|
|
316
|
+
if not detection_results["detected"]:
|
|
317
|
+
detection_results["deployment_methods"].append("unknown")
|
|
318
|
+
|
|
319
|
+
logger.info(f"[IdentifyDeploymentMethod] Response: {json.dumps(detection_results, ensure_ascii=False, default=str)}")
|
|
320
|
+
return detection_results
|
|
321
|
+
except Exception as e:
|
|
322
|
+
raise ValueError(f"Failed to identify deployment method: {str(e)}")
|
|
323
|
+
|
|
@@ -150,7 +150,12 @@ def OOS_RunInstances(
|
|
|
150
150
|
SystemDiskPerformanceLevel: str = Field(description='The performance level of the ESSD used as the system disk. Valid values: PL0, PL1, PL2, PL3', default='PL1'),
|
|
151
151
|
PrivateIpAddress: str = Field(description='The private IP address of the instance. For VPC type ECS instances, the private IP address must be selected from the available IP range of the VSwitch', default=''),
|
|
152
152
|
SystemDiskAutoSnapshotPolicyId: str = Field(description='The ID of the automatic snapshot policy to apply to the system disk', default=''),
|
|
153
|
-
DataDiskParameters: str = Field(description='Data disk configuration in JSON format. Example: [{"Size":"100","DiskName":"data-disk-1","Description":"","Category":"cloud_essd","PerformanceLevel":"PL1","AutoSnapshotPolicyId":""}]', default='')
|
|
153
|
+
DataDiskParameters: str = Field(description='Data disk configuration in JSON format. Example: [{"Size":"100","DiskName":"data-disk-1","Description":"","Category":"cloud_essd","PerformanceLevel":"PL1","AutoSnapshotPolicyId":""}]', default=''),
|
|
154
|
+
Tags: list = Field(description='The tag of an ECS instance, for example: [{"Key":"oos","Value":"test"}]', default=[]),
|
|
155
|
+
ResourceGroupId: str = Field(description='Resource group ID', default=''),
|
|
156
|
+
Description: str = Field(description='The description of the ECS instances', default=''),
|
|
157
|
+
HostName: str = Field(description='The host name of the ECS instance', default=''),
|
|
158
|
+
ZoneId: str = Field(description='The ID of the zone where the ECS instances are deployed', default=''),
|
|
154
159
|
):
|
|
155
160
|
"""批量创建ECS实例,适用于需要同时创建多台ECS实例的场景,例如应用部署和高可用性场景。"""
|
|
156
161
|
|
|
@@ -164,8 +169,7 @@ def OOS_RunInstances(
|
|
|
164
169
|
'amount': Amount,
|
|
165
170
|
'instanceName': InstanceName
|
|
166
171
|
}
|
|
167
|
-
|
|
168
|
-
# 添加系统盘相关参数
|
|
172
|
+
|
|
169
173
|
if SystemDiskCategory:
|
|
170
174
|
parameters['systemDiskCategory'] = SystemDiskCategory
|
|
171
175
|
if SystemDiskSize:
|
|
@@ -180,17 +184,23 @@ def OOS_RunInstances(
|
|
|
180
184
|
parameters['privateIpAddress'] = PrivateIpAddress
|
|
181
185
|
if SystemDiskAutoSnapshotPolicyId:
|
|
182
186
|
parameters['systemDiskAutoSnapshotPolicyId'] = SystemDiskAutoSnapshotPolicyId
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
if Tags:
|
|
188
|
+
parameters['tags'] = Tags
|
|
189
|
+
if ResourceGroupId:
|
|
190
|
+
parameters['resourceGroupId'] = ResourceGroupId
|
|
191
|
+
if Description:
|
|
192
|
+
parameters['description'] = Description
|
|
193
|
+
if HostName:
|
|
194
|
+
parameters['hostName'] = HostName
|
|
195
|
+
if ZoneId:
|
|
196
|
+
parameters['zoneId'] = ZoneId
|
|
197
|
+
|
|
185
198
|
if DataDiskParameters:
|
|
186
199
|
try:
|
|
187
|
-
# 解析 JSON 字符串为列表
|
|
188
200
|
data_disks = json.loads(DataDiskParameters) if isinstance(DataDiskParameters, str) else DataDiskParameters
|
|
189
201
|
if isinstance(data_disks, list) and len(data_disks) > 0:
|
|
190
|
-
# 将数据盘列表转换为 OOS 模板需要的格式
|
|
191
202
|
parameters['dataDiskParameters'] = data_disks
|
|
192
203
|
except (json.JSONDecodeError, TypeError) as e:
|
|
193
|
-
# 如果解析失败,忽略数据盘参数
|
|
194
204
|
pass
|
|
195
205
|
|
|
196
206
|
return _start_execution_sync(region_id=RegionId, template_name='ACS-ECS-RunInstances', parameters=parameters)
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
# oss_tools.py
|
|
2
1
|
import os
|
|
2
|
+
import mimetypes
|
|
3
|
+
import logging
|
|
4
|
+
import json
|
|
3
5
|
import alibabacloud_oss_v2 as oss
|
|
4
|
-
from alibaba_cloud_ops_mcp_server.alibabacloud.utils import get_credentials_from_header
|
|
5
6
|
|
|
6
7
|
from pydantic import Field
|
|
7
8
|
from alibabacloud_oss_v2 import Credentials
|
|
8
9
|
from alibabacloud_oss_v2.credentials import EnvironmentVariableCredentialsProvider
|
|
9
10
|
from alibabacloud_credentials.client import Client as CredClient
|
|
10
11
|
|
|
11
|
-
|
|
12
12
|
tools = []
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
15
18
|
class CredentialsProvider(EnvironmentVariableCredentialsProvider):
|
|
16
19
|
def __init__(self) -> None:
|
|
20
|
+
from alibaba_cloud_ops_mcp_server.alibabacloud.utils import get_credentials_from_header
|
|
17
21
|
credentials = get_credentials_from_header()
|
|
18
22
|
if credentials:
|
|
19
23
|
access_key_id = credentials.get('AccessKeyId', None)
|
|
@@ -114,3 +118,64 @@ def OSS_DeleteBucket(
|
|
|
114
118
|
client = create_client(region_id=RegionId)
|
|
115
119
|
result = client.delete_bucket(oss.DeleteBucketRequest(bucket=BucketName))
|
|
116
120
|
return result.__str__()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def OSS_PutObject(
|
|
125
|
+
BucketName: str = Field(description='AlibabaCloud OSS Bucket Name'),
|
|
126
|
+
ObjectKey: str = Field(description='AlibabaCloud OSS Object Key (file path/name in OSS)'),
|
|
127
|
+
FilePath: str = Field(description='Local file path to upload'),
|
|
128
|
+
RegionId: str = Field(description='AlibabaCloud region ID', default='cn-hangzhou'),
|
|
129
|
+
ContentType: str = Field(description='Content type of the object (e.g., text/plain, application/json). If not provided, will be inferred from file extension.', default=None)
|
|
130
|
+
):
|
|
131
|
+
"""上传本地文件到指定的OSS存储空间。"""
|
|
132
|
+
logger.info(f"[OSS_PutObject] Input parameters: BucketName={BucketName}, ObjectKey={ObjectKey}, "
|
|
133
|
+
f"FilePath={FilePath}, RegionId={RegionId}, ContentType={ContentType}")
|
|
134
|
+
if not BucketName:
|
|
135
|
+
raise ValueError("Bucket name is required")
|
|
136
|
+
if not ObjectKey:
|
|
137
|
+
raise ValueError("Object key is required")
|
|
138
|
+
if not FilePath:
|
|
139
|
+
raise ValueError("File path is required")
|
|
140
|
+
|
|
141
|
+
# Check if file exists
|
|
142
|
+
if not os.path.exists(FilePath):
|
|
143
|
+
raise FileNotFoundError(f"File not found: {FilePath}")
|
|
144
|
+
|
|
145
|
+
if not os.path.isfile(FilePath):
|
|
146
|
+
raise ValueError(f"Path is not a file: {FilePath}")
|
|
147
|
+
|
|
148
|
+
client = create_client(region_id=RegionId)
|
|
149
|
+
|
|
150
|
+
# Read file content in binary mode
|
|
151
|
+
with open(FilePath, 'rb') as f:
|
|
152
|
+
body = f.read()
|
|
153
|
+
|
|
154
|
+
# Infer content type from file extension if not provided
|
|
155
|
+
if not ContentType:
|
|
156
|
+
ContentType, _ = mimetypes.guess_type(FilePath)
|
|
157
|
+
if not ContentType:
|
|
158
|
+
ContentType = 'application/octet-stream' # Default to binary
|
|
159
|
+
|
|
160
|
+
# Prepare put object request
|
|
161
|
+
request = oss.PutObjectRequest(
|
|
162
|
+
bucket=BucketName,
|
|
163
|
+
key=ObjectKey,
|
|
164
|
+
body=body
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Set content type
|
|
168
|
+
request.content_type = ContentType
|
|
169
|
+
|
|
170
|
+
result = client.put_object(request)
|
|
171
|
+
version_id = result.version_id
|
|
172
|
+
response = {
|
|
173
|
+
'status_code': result.status_code,
|
|
174
|
+
'etag': result.etag if hasattr(result, 'etag') else None,
|
|
175
|
+
'file_size': len(body),
|
|
176
|
+
'content_type': ContentType,
|
|
177
|
+
'version_id': version_id,
|
|
178
|
+
'message': f'Successfully uploaded file {FilePath} as {ObjectKey} to bucket {BucketName}'
|
|
179
|
+
}
|
|
180
|
+
logger.info(f"[OSS_PutObject] Response: {json.dumps(response, ensure_ascii=False)}")
|
|
181
|
+
return response
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alibaba-cloud-ops-mcp-server
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.12
|
|
4
4
|
Summary: A MCP server for Alibaba Cloud
|
|
5
|
-
Author-email: Zheng Dayu <dayu.zdy@alibaba-inc.com>
|
|
5
|
+
Author-email: Zheng Dayu <dayu.zdy@alibaba-inc.com>, Zhao Shuaibo <zhaoshuaibo.zsb@alibaba-inc.com>
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Requires-Python: >=3.10
|
|
8
8
|
Requires-Dist: alibabacloud-cms20190101>=3.1.4
|
|
9
|
-
Requires-Dist: alibabacloud-credentials
|
|
9
|
+
Requires-Dist: alibabacloud-credentials-api==1.0.1
|
|
10
|
+
Requires-Dist: alibabacloud-credentials>=1.0.3
|
|
10
11
|
Requires-Dist: alibabacloud-ecs20140526>=6.1.0
|
|
11
|
-
Requires-Dist: alibabacloud-oos20190601>=3.
|
|
12
|
+
Requires-Dist: alibabacloud-oos20190601>=3.5.0
|
|
12
13
|
Requires-Dist: alibabacloud-oss-v2>=1.1.0
|
|
13
14
|
Requires-Dist: click>=8.1.8
|
|
14
15
|
Requires-Dist: fastmcp==2.8.0
|
|
@@ -21,7 +22,19 @@ Description-Content-Type: text/markdown
|
|
|
21
22
|
|
|
22
23
|
[中文版本](./README_zh.md)
|
|
23
24
|
|
|
24
|
-
Alibaba Cloud Ops MCP Server is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server that provides seamless integration with Alibaba Cloud APIs, enabling AI assistants to
|
|
25
|
+
Alibaba Cloud Ops MCP Server is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server that provides seamless integration with Alibaba Cloud APIs, enabling AI assistants to operate resources on Alibaba Cloud, supporting ECS, Cloud Monitor, OOS, OSS, VPC, RDS and other widely used cloud products. It also enables AI assistants to analyze, build, and deploy applications to Alibaba Cloud ECS instances.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **ECS Management**: Create, start, stop, reboot, delete instances, run commands, view instances, regions, zones, images, security groups, and more
|
|
30
|
+
- **VPC Management**: View VPCs and VSwitches
|
|
31
|
+
- **RDS Management**: List, start, stop, and restart RDS instances
|
|
32
|
+
- **OSS Management**: List, create, delete buckets, and view objects
|
|
33
|
+
- **Cloud Monitor**: Get CPU usage, load average, memory usage, and disk usage metrics for ECS instances
|
|
34
|
+
- **Application Deployment**: Deploy applications to ECS instances with automatic application and application group management
|
|
35
|
+
- **Project Analysis**: Automatically identify project technology stack and deployment methods (npm, Python, Java, Go, Docker, etc.)
|
|
36
|
+
- **Local File Operations**: List directories, run shell scripts, and analyze project structures
|
|
37
|
+
- **Dynamic API Tools**: Support for Alibaba Cloud OpenAPI operations
|
|
25
38
|
|
|
26
39
|
## Prepare
|
|
27
40
|
|
|
@@ -60,8 +73,9 @@ To use `alibaba-cloud-ops-mcp-server` MCP Server with any other MCP Client, you
|
|
|
60
73
|
|
|
61
74
|
## MCP Maketplace Integration
|
|
62
75
|
|
|
63
|
-
* [
|
|
76
|
+
* [Qoder](https://qoder.com) <a href="qoder://aicoding.aicoding-deeplink/mcp/add?name=alibaba-cloud-ops-mcp-server&config=JTdCJTIyY29tbWFuZCUyMiUzQSUyMnV2eCUyMiUyQyUyMmFyZ3MlMjIlM0ElNUIlMjJhbGliYWJhLWNsb3VkLW9wcy1tY3Atc2VydmVyJTQwbGF0ZXN0JTIyJTVEJTJDJTIyZW52JTIyJTNBJTdCJTIyQUxJQkFCQV9DTE9VRF9BQ0NFU1NfS0VZX0lEJTIyJTNBJTIyWW91ciUyMEFjY2VzcyUyMEtleSUyMElkJTIyJTJDJTIyQUxJQkFCQV9DTE9VRF9BQ0NFU1NfS0VZX1NFQ1JFVCUyMiUzQSUyMllvdXIlMjBBY2Nlc3MlMjBLZXklMjBTRUNSRVQlMjIlN0QlN0Q%3D"><img src="./image/qoder.svg" alt="Install MCP Server" height="20"></a>
|
|
64
77
|
* [Cursor](https://docs.cursor.com/tools) [](https://cursor.com/en/install-mcp?name=alibaba-cloud-ops-mcp-server&config=eyJ0aW1lb3V0Ijo2MDAsImNvbW1hbmQiOiJ1dnggYWxpYmFiYS1jbG91ZC1vcHMtbWNwLXNlcnZlckBsYXRlc3QiLCJlbnYiOnsiQUxJQkFCQV9DTE9VRF9BQ0NFU1NfS0VZX0lEIjoiWW91ciBBY2Nlc3MgS2V5IElkIiwiQUxJQkFCQV9DTE9VRF9BQ0NFU1NfS0VZX1NFQ1JFVCI6IllvdXIgQWNjZXNzIEtleSBTZWNyZXQifX0%3D)
|
|
78
|
+
* [Cline](https://cline.bot/mcp-marketplace)
|
|
65
79
|
* [ModelScope](https://www.modelscope.cn/mcp/servers/@aliyun/alibaba-cloud-ops-mcp-server?lang=en_US)
|
|
66
80
|
* [Lingma](https://lingma.aliyun.com/)
|
|
67
81
|
* [Smithery AI](https://smithery.ai/server/@aliyun/alibaba-cloud-ops-mcp-server)
|
|
@@ -77,7 +91,7 @@ To use `alibaba-cloud-ops-mcp-server` MCP Server with any other MCP Client, you
|
|
|
77
91
|
|
|
78
92
|
## Tools
|
|
79
93
|
|
|
80
|
-
| **Product** | **Tool** | **Function** | **
|
|
94
|
+
| **Product** | **Tool** | **Function** | **Implementation** | **Status** |
|
|
81
95
|
| --- | --- | --- | --- | --- |
|
|
82
96
|
| ECS | RunCommand | Run Command | OOS | Done |
|
|
83
97
|
| | StartInstances | Start Instances | OOS | Done |
|
|
@@ -112,6 +126,24 @@ To use `alibaba-cloud-ops-mcp-server` MCP Server with any other MCP Client, you
|
|
|
112
126
|
| | GetDiskUsageData | Get Disk Utilization Metric Data | API | Done |
|
|
113
127
|
| | GetDiskTotalData | Get Total Disk Partition Capacity Metric Data | API | Done |
|
|
114
128
|
| | GetDiskUsedData | Get Disk Partition Usage Metric Data | API | Done |
|
|
129
|
+
| Application Management | OOS_CodeDeploy | Deploy applications to ECS instances with automatic artifact upload to OSS | OOS | Done |
|
|
130
|
+
| | OOS_GetDeployStatus | Query deployment status of application groups | API | Done |
|
|
131
|
+
| | OOS_GetLastDeploymentInfo | Retrieve information about the last deployment | API | Done |
|
|
132
|
+
| Local | LOCAL_ListDirectory | List files and subdirectories in a directory | Local | Done |
|
|
133
|
+
| | LOCAL_RunShellScript | Execute shell scripts or commands | Local | Done |
|
|
134
|
+
| | LOCAL_AnalyzeDeployStack | Identify project deployment methods and technology stack | Local | Done |
|
|
135
|
+
|
|
136
|
+
## Deployment Workflow
|
|
137
|
+
|
|
138
|
+
The typical deployment workflow includes:
|
|
139
|
+
|
|
140
|
+
1. **Project Analysis**: Use `LOCAL_AnalyzeDeployStack` to identify the project's technology stack and deployment method
|
|
141
|
+
2. **Build Artifacts**: Build or package the application locally (e.g., create tar.gz or zip files)
|
|
142
|
+
3. **Deploy Application**: Use `OOS_CodeDeploy` to deploy the application to ECS instances
|
|
143
|
+
- Automatically creates application and application group if they don't exist
|
|
144
|
+
- Uploads artifacts to OSS
|
|
145
|
+
- Deploys to specified ECS instances
|
|
146
|
+
4. **Monitor Deployment**: Use `OOS_GetDeployStatus` to check deployment status
|
|
115
147
|
|
|
116
148
|
## Contact us
|
|
117
149
|
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
alibaba_cloud_ops_mcp_server/__init__.py,sha256=BaluUNyRz8Qw-X7Y0ywDezwbkqiSvWlSYn2452XeGcA,213
|
|
2
2
|
alibaba_cloud_ops_mcp_server/__main__.py,sha256=Q40p2HtWGvxj1JLvS7dn95NLzDhJNQ6JAgLLyCb4Y50,63
|
|
3
3
|
alibaba_cloud_ops_mcp_server/config.py,sha256=PizctjXsQUWoMWBY1dFjNffVlZr9K6hNvqA4DpayR_o,513
|
|
4
|
-
alibaba_cloud_ops_mcp_server/server.py,sha256=
|
|
4
|
+
alibaba_cloud_ops_mcp_server/server.py,sha256=C3vb1VlVo2P_yJ1LjWiRMI8FAMkujUdD6j2NmB36kWs,8005
|
|
5
5
|
alibaba_cloud_ops_mcp_server/settings.py,sha256=R1jvMtgErWn_1MZ2Gq3xzBZeMtkGpSHvVutInh0Ix4s,167
|
|
6
6
|
alibaba_cloud_ops_mcp_server/alibabacloud/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
alibaba_cloud_ops_mcp_server/alibabacloud/api_meta_client.py,sha256=t2TSc0Gzcy_uEcaCgiHHuLoMiEGu3-NCtYmwYjyPWsY,7973
|
|
8
8
|
alibaba_cloud_ops_mcp_server/alibabacloud/exception.py,sha256=7PdhgqgXEGrTPL1cj98h9EH-RrM6-2TT89PDtcmlpmU,1230
|
|
9
|
-
alibaba_cloud_ops_mcp_server/alibabacloud/utils.py,sha256=
|
|
9
|
+
alibaba_cloud_ops_mcp_server/alibabacloud/utils.py,sha256=J3fywpnqMvIX19Fw87sKlKSxwWGjQcje8sRTMVAUZvg,8271
|
|
10
10
|
alibaba_cloud_ops_mcp_server/alibabacloud/static/PROMPT_UNDERSTANDING.md,sha256=QPubudP1bwDbWu0Js6MYb4cJd1B2zM_JGp53twYv5yc,3611
|
|
11
11
|
alibaba_cloud_ops_mcp_server/alibabacloud/static/__init__.py,sha256=wJrYamaIb7e_kA4ILZpdP1f1TUUTXMGqEhA4IbSZ2Ts,230
|
|
12
12
|
alibaba_cloud_ops_mcp_server/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
alibaba_cloud_ops_mcp_server/tools/api_tools.py,sha256=
|
|
13
|
+
alibaba_cloud_ops_mcp_server/tools/api_tools.py,sha256=nEtrjp-hFYJs47rFreXJoQ25OSdAUDODSv8GqBtP54Q,9687
|
|
14
|
+
alibaba_cloud_ops_mcp_server/tools/application_management_tools.py,sha256=Y-YidjZ5wwNRE_FMb6_bD7WDav4XjsMa3fbAIusrPRc,34284
|
|
14
15
|
alibaba_cloud_ops_mcp_server/tools/cms_tools.py,sha256=BmPTiP8wu9DsEHBQsvR7JH9nFkcKMTBuNuafFqSfVxU,4308
|
|
15
16
|
alibaba_cloud_ops_mcp_server/tools/common_api_tools.py,sha256=ccQAWqS1I9F-fdOdjLcXN-dIhNqSbZV8T5ODuGXlfXM,2711
|
|
16
|
-
alibaba_cloud_ops_mcp_server/tools/
|
|
17
|
-
alibaba_cloud_ops_mcp_server/tools/
|
|
18
|
-
alibaba_cloud_ops_mcp_server
|
|
19
|
-
alibaba_cloud_ops_mcp_server-0.9.
|
|
20
|
-
alibaba_cloud_ops_mcp_server-0.9.
|
|
21
|
-
alibaba_cloud_ops_mcp_server-0.9.
|
|
22
|
-
alibaba_cloud_ops_mcp_server-0.9.
|
|
17
|
+
alibaba_cloud_ops_mcp_server/tools/local_tools.py,sha256=Sl7Vl2mVQMLyMIZQDLoE-xVF0vNz_haMEspyYPDe6Sg,13133
|
|
18
|
+
alibaba_cloud_ops_mcp_server/tools/oos_tools.py,sha256=cPEl05Y0rlNvCeO2SF46Y7Ewky4LXZ8__sD0_JCi8ek,13569
|
|
19
|
+
alibaba_cloud_ops_mcp_server/tools/oss_tools.py,sha256=6yKo1FqQN3n9I-eDUW8MrnIZTHthy-worKc8XIsn-Nw,7427
|
|
20
|
+
alibaba_cloud_ops_mcp_server-0.9.12.dist-info/METADATA,sha256=fdcTjd-aehrJyuNJvHrc9Owb90cr7O7jlNCS3BNMqHU,8845
|
|
21
|
+
alibaba_cloud_ops_mcp_server-0.9.12.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
22
|
+
alibaba_cloud_ops_mcp_server-0.9.12.dist-info/entry_points.txt,sha256=ESGAWXKEp184forhs7VzTD4P1AUdZz6vCW6hRUKITGw,83
|
|
23
|
+
alibaba_cloud_ops_mcp_server-0.9.12.dist-info/licenses/LICENSE,sha256=gQgVkp2ttRCjodiPpXZZR-d7JnrYIYNiHk1YDUYgpa4,11331
|
|
24
|
+
alibaba_cloud_ops_mcp_server-0.9.12.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|