alibaba-cloud-ops-mcp-server 0.9.10__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/oss_tools.py +68 -3
- {alibaba_cloud_ops_mcp_server-0.9.10.dist-info → alibaba_cloud_ops_mcp_server-0.9.12.dist-info}/METADATA +39 -7
- {alibaba_cloud_ops_mcp_server-0.9.10.dist-info → alibaba_cloud_ops_mcp_server-0.9.12.dist-info}/RECORD +11 -9
- {alibaba_cloud_ops_mcp_server-0.9.10.dist-info → alibaba_cloud_ops_mcp_server-0.9.12.dist-info}/WHEEL +0 -0
- {alibaba_cloud_ops_mcp_server-0.9.10.dist-info → alibaba_cloud_ops_mcp_server-0.9.12.dist-info}/entry_points.txt +0 -0
- {alibaba_cloud_ops_mcp_server-0.9.10.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
|
+
|
|
@@ -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
|
|
17
|
+
alibaba_cloud_ops_mcp_server/tools/local_tools.py,sha256=Sl7Vl2mVQMLyMIZQDLoE-xVF0vNz_haMEspyYPDe6Sg,13133
|
|
16
18
|
alibaba_cloud_ops_mcp_server/tools/oos_tools.py,sha256=cPEl05Y0rlNvCeO2SF46Y7Ewky4LXZ8__sD0_JCi8ek,13569
|
|
17
|
-
alibaba_cloud_ops_mcp_server/tools/oss_tools.py,sha256=
|
|
18
|
-
alibaba_cloud_ops_mcp_server-0.9.
|
|
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.
|
|
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
|
|
File without changes
|