cloudshellgpt 1.0.0__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.
- cloudshellgpt/__init__.py +11 -0
- cloudshellgpt/audit.py +175 -0
- cloudshellgpt/bedrock_translator.py +525 -0
- cloudshellgpt/cli.py +612 -0
- cloudshellgpt/config.py +296 -0
- cloudshellgpt/cost.py +443 -0
- cloudshellgpt/executor.py +382 -0
- cloudshellgpt/formatter.py +328 -0
- cloudshellgpt/i18n.py +203 -0
- cloudshellgpt/intent.py +1080 -0
- cloudshellgpt/learning.py +969 -0
- cloudshellgpt/mcp_server.py +264 -0
- cloudshellgpt/safety.py +952 -0
- cloudshellgpt-1.0.0.dist-info/METADATA +426 -0
- cloudshellgpt-1.0.0.dist-info/RECORD +18 -0
- cloudshellgpt-1.0.0.dist-info/WHEEL +4 -0
- cloudshellgpt-1.0.0.dist-info/entry_points.txt +2 -0
- cloudshellgpt-1.0.0.dist-info/licenses/LICENSE +198 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""MCP server — exposes CloudShellGPT as Model Context Protocol tools.
|
|
2
|
+
|
|
3
|
+
This allows CloudShellGPT to be used as a tool from Kiro, Claude Desktop,
|
|
4
|
+
Cursor, and other MCP-compatible clients.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from mcp.server import Server
|
|
13
|
+
from mcp.server.stdio import stdio_server
|
|
14
|
+
from mcp.types import TextContent, Tool
|
|
15
|
+
|
|
16
|
+
from cloudshellgpt.bedrock_translator import BedrockTranslator
|
|
17
|
+
from cloudshellgpt.executor import AWSExecutor
|
|
18
|
+
from cloudshellgpt.intent import IntentParser
|
|
19
|
+
from cloudshellgpt.safety import SafetyLayer
|
|
20
|
+
|
|
21
|
+
server = Server("cloudshellgpt")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@server.list_tools() # type: ignore[no-untyped-call, untyped-decorator]
|
|
25
|
+
async def list_tools() -> list[Tool]:
|
|
26
|
+
"""List available MCP tools."""
|
|
27
|
+
return [
|
|
28
|
+
Tool(
|
|
29
|
+
name="aws_translate",
|
|
30
|
+
description=(
|
|
31
|
+
"Translate a natural language intent into an AWS CLI command. "
|
|
32
|
+
"Returns the command, explanation, risk level, and estimated cost. "
|
|
33
|
+
"Does NOT execute the command — use aws_execute for that."
|
|
34
|
+
),
|
|
35
|
+
inputSchema={
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"intent": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Natural language description of what you want to do (any language)",
|
|
41
|
+
},
|
|
42
|
+
"region": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Optional AWS region override",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
"required": ["intent"],
|
|
48
|
+
},
|
|
49
|
+
),
|
|
50
|
+
Tool(
|
|
51
|
+
name="aws_execute",
|
|
52
|
+
description=(
|
|
53
|
+
"Execute an AWS CLI command. Returns stdout, stderr, exit code, and duration. "
|
|
54
|
+
"ALWAYS show the user the command before calling this tool — they need to confirm."
|
|
55
|
+
),
|
|
56
|
+
inputSchema={
|
|
57
|
+
"type": "object",
|
|
58
|
+
"properties": {
|
|
59
|
+
"command": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"description": "The full AWS CLI command to execute",
|
|
62
|
+
},
|
|
63
|
+
"dry_run": {
|
|
64
|
+
"type": "boolean",
|
|
65
|
+
"description": "If true, add --dry-run flag where supported",
|
|
66
|
+
"default": False,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
"required": ["command"],
|
|
70
|
+
},
|
|
71
|
+
),
|
|
72
|
+
Tool(
|
|
73
|
+
name="aws_cost_preview",
|
|
74
|
+
description=(
|
|
75
|
+
"Estimate the cost of an AWS command before executing it. "
|
|
76
|
+
"Returns breakdown of cost components and warnings."
|
|
77
|
+
),
|
|
78
|
+
inputSchema={
|
|
79
|
+
"type": "object",
|
|
80
|
+
"properties": {
|
|
81
|
+
"command": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"description": "The AWS CLI command to evaluate",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
"required": ["command"],
|
|
87
|
+
},
|
|
88
|
+
),
|
|
89
|
+
Tool(
|
|
90
|
+
name="aws_explain",
|
|
91
|
+
description=(
|
|
92
|
+
"Explain what an AWS CLI command does in detail. "
|
|
93
|
+
"Breaks down each flag, describes the operation, and provides docs links."
|
|
94
|
+
),
|
|
95
|
+
inputSchema={
|
|
96
|
+
"type": "object",
|
|
97
|
+
"properties": {
|
|
98
|
+
"command": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "The AWS CLI command to explain",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
"required": ["command"],
|
|
104
|
+
},
|
|
105
|
+
),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@server.call_tool() # type: ignore[untyped-decorator]
|
|
110
|
+
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
111
|
+
"""Handle tool calls from MCP clients."""
|
|
112
|
+
try:
|
|
113
|
+
if name == "aws_translate":
|
|
114
|
+
return await _tool_translate(arguments)
|
|
115
|
+
elif name == "aws_execute":
|
|
116
|
+
return await _tool_execute(arguments)
|
|
117
|
+
elif name == "aws_cost_preview":
|
|
118
|
+
return await _tool_cost_preview(arguments)
|
|
119
|
+
elif name == "aws_explain":
|
|
120
|
+
return await _tool_explain(arguments)
|
|
121
|
+
else:
|
|
122
|
+
return [TextContent(type="text", text=f"Unknown tool: {name}")]
|
|
123
|
+
except Exception as e:
|
|
124
|
+
return [TextContent(type="text", text=f"Error: {type(e).__name__}: {e}")]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
async def _tool_translate(args: dict[str, Any]) -> list[TextContent]:
|
|
128
|
+
"""Handle aws_translate tool call."""
|
|
129
|
+
intent_text = args.get("intent", "")
|
|
130
|
+
region = args.get("region")
|
|
131
|
+
|
|
132
|
+
parser = IntentParser()
|
|
133
|
+
intent = parser.parse(intent_text, region=region)
|
|
134
|
+
|
|
135
|
+
translator = BedrockTranslator()
|
|
136
|
+
translation = translator.translate(intent)
|
|
137
|
+
|
|
138
|
+
result = {
|
|
139
|
+
"command": translation.command,
|
|
140
|
+
"explanation": translation.explanation,
|
|
141
|
+
"detailed_explanation": translation.detailed_explanation,
|
|
142
|
+
"risk_level": translation.risk_level,
|
|
143
|
+
"estimated_cost": translation.estimated_cost,
|
|
144
|
+
"requires_dry_run": translation.requires_dry_run,
|
|
145
|
+
"affected_resources": translation.affected_resources,
|
|
146
|
+
"flags_used": translation.flags_used,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2, ensure_ascii=False))]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
async def _tool_execute(args: dict[str, Any]) -> list[TextContent]:
|
|
153
|
+
"""Handle aws_execute tool call."""
|
|
154
|
+
from cloudshellgpt.config import Config
|
|
155
|
+
|
|
156
|
+
command = args.get("command", "")
|
|
157
|
+
dry_run = args.get("dry_run", False)
|
|
158
|
+
|
|
159
|
+
cfg = Config()
|
|
160
|
+
executor = AWSExecutor(dry_run=dry_run, timeout=cfg.timeout)
|
|
161
|
+
result = executor.run(command)
|
|
162
|
+
|
|
163
|
+
output = {
|
|
164
|
+
"command": result.command,
|
|
165
|
+
"exit_code": result.exit_code,
|
|
166
|
+
"stdout": result.stdout,
|
|
167
|
+
"stderr": result.stderr,
|
|
168
|
+
"duration_ms": result.duration_ms,
|
|
169
|
+
"dry_run": result.dry_run,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return [TextContent(type="text", text=json.dumps(output, indent=2))]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def _tool_cost_preview(args: dict[str, Any]) -> list[TextContent]:
|
|
176
|
+
"""Handle aws_cost_preview tool call.
|
|
177
|
+
|
|
178
|
+
Estimates the cost and risk level of an AWS CLI command before execution.
|
|
179
|
+
Uses CostEstimator for cost data and SafetyLayer for independent risk
|
|
180
|
+
classification. Each call instantiates fresh dependencies (stateless).
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
args: Dictionary with 'command' key containing the AWS CLI command.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
JSON with command, estimated_cost, risk_level, and warnings.
|
|
187
|
+
"""
|
|
188
|
+
from cloudshellgpt.bedrock_translator import Translation
|
|
189
|
+
from cloudshellgpt.cost import CostEstimator
|
|
190
|
+
|
|
191
|
+
command = args.get("command", "")
|
|
192
|
+
|
|
193
|
+
# Instantiate fresh dependencies per call (stateless requirement)
|
|
194
|
+
cost_estimator = CostEstimator()
|
|
195
|
+
safety = SafetyLayer()
|
|
196
|
+
|
|
197
|
+
# Get cost estimate from Cost Explorer API
|
|
198
|
+
cost_estimate = cost_estimator.estimate(command)
|
|
199
|
+
|
|
200
|
+
# Build a minimal Translation for the safety layer's risk assessment.
|
|
201
|
+
# The safety layer classifies risk independently via pattern matching,
|
|
202
|
+
# so we set risk_level="low" to let the rule-based system do its job
|
|
203
|
+
# without artificial inflation from the translation side.
|
|
204
|
+
mock_translation = Translation(
|
|
205
|
+
command=command,
|
|
206
|
+
explanation="Cost preview",
|
|
207
|
+
detailed_explanation="",
|
|
208
|
+
risk_level="low",
|
|
209
|
+
estimated_cost=cost_estimate.estimated_monthly_cost > 0
|
|
210
|
+
and f"${cost_estimate.estimated_monthly_cost:.2f}/month"
|
|
211
|
+
or "$0.00",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Assess risk with cost estimate integrated
|
|
215
|
+
safety_check = safety.assess(mock_translation, cost_estimate=cost_estimate)
|
|
216
|
+
|
|
217
|
+
# Format estimated_cost string
|
|
218
|
+
if cost_estimate.status == "estimated" and cost_estimate.estimated_monthly_cost > 0:
|
|
219
|
+
estimated_cost = f"${cost_estimate.estimated_monthly_cost:.2f}/month"
|
|
220
|
+
elif cost_estimate.status == "unknown":
|
|
221
|
+
estimated_cost = "unknown"
|
|
222
|
+
else:
|
|
223
|
+
estimated_cost = "$0.00"
|
|
224
|
+
|
|
225
|
+
# Combine warnings from both cost estimation and safety assessment
|
|
226
|
+
warnings: list[str] = []
|
|
227
|
+
warnings.extend(cost_estimate.warnings)
|
|
228
|
+
if safety_check.warnings:
|
|
229
|
+
warnings.extend(w for w in safety_check.warnings if w not in cost_estimate.warnings)
|
|
230
|
+
|
|
231
|
+
result = {
|
|
232
|
+
"command": command,
|
|
233
|
+
"estimated_cost": estimated_cost,
|
|
234
|
+
"risk_level": safety_check.risk_level,
|
|
235
|
+
"warnings": warnings,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2, ensure_ascii=False))]
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
async def _tool_explain(args: dict[str, Any]) -> list[TextContent]:
|
|
242
|
+
"""Handle aws_explain tool call."""
|
|
243
|
+
from cloudshellgpt.learning import Explainer
|
|
244
|
+
|
|
245
|
+
command = args.get("command", "")
|
|
246
|
+
explainer = Explainer()
|
|
247
|
+
explanation = explainer.explain_sync(command)
|
|
248
|
+
|
|
249
|
+
return [TextContent(type="text", text=explanation)]
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def serve_mcp() -> None:
|
|
253
|
+
"""Run the MCP server on stdio."""
|
|
254
|
+
import asyncio
|
|
255
|
+
|
|
256
|
+
async def _run() -> None:
|
|
257
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
258
|
+
await server.run(
|
|
259
|
+
read_stream,
|
|
260
|
+
write_stream,
|
|
261
|
+
server.create_initialization_options(),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
asyncio.run(_run())
|