Skip to content

Commit b044a94

Browse files
committed
비동기 처리 오퍼레이션 응답 결과 강화 및 create_* 도구명 set_*으로 변경
1 parent 0022176 commit b044a94

File tree

3 files changed

+180
-24
lines changed

3 files changed

+180
-24
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919
-**Single Project Scope**: Operates within the configured `OS_PROJECT_NAME` project scope for complete tenant isolation. All operations are restricted to resources within the specified project, ensuring data privacy and security in multi-tenant environments.
2020
-**OpenStack SDK Integration**: Direct integration with OpenStack SDK for real-time project operations.
21-
-**Production-Safe Operations**: Built-in safety controls with `ALLOW_MODIFY_OPERATIONS` environment variable to prevent modification operations in production environments.
21+
-**Enhanced Operation Safety**: Built-in safety controls with `ALLOW_MODIFY_OPERATIONS` environment variable and comprehensive error handling that prevents false success claims and provides clear guidance for asynchronous operations.
22+
-**Smart Async Operation Handling**: Intelligent detection and guidance for OpenStack asynchronous operations (instance management, volume operations, network changes) with tool-specific timing expectations and verification commands.
2223
-**Enhanced Project Monitoring**: Comprehensive project status reports with health scoring system, resource utilization analysis, instance state tracking, and detailed health breakdown by service categories.
2324
-**Complete Service Coverage**: 93+ comprehensive tools covering Identity, Compute, Network, Storage, Image, Orchestration, Load Balancer, and Monitoring services within project scope.
2425
-**Advanced Instance Management**: Enhanced server lifecycle operations with backup, migration, rescue, and administrative functions including state analysis.
@@ -65,7 +66,7 @@
6566
| `openstack server create` | `set_instance` (action="create") || Instance creation |
6667
| `openstack server start/stop/reboot` | `set_instance` || Full lifecycle management |
6768
| `openstack server delete` | `set_instance` (action="delete") || Instance deletion |
68-
| `openstack server backup create` | `create_server_backup` || Backup creation with rotation |
69+
| `openstack server backup create` | `set_server_backup` || Backup creation with rotation |
6970
| `openstack server image create` | `set_instance` (action="snapshot") || Image/snapshot creation |
7071
| `openstack server shelve/unshelve` | `set_instance` || Instance shelving |
7172
| `openstack server lock/unlock` | `set_instance` || Instance locking |
@@ -97,7 +98,7 @@
9798
| `openstack server remove volume` | `set_server_volume` (action="detach") || Volume detachment |
9899
| `openstack server set` | `set_server_properties` (action="set") || Server property setting |
99100
| `openstack server unset` | `set_server_properties` (action="unset") || Server property unsetting |
100-
| `openstack server dump create` | `create_server_dump` || Server dump creation |
101+
| `openstack server dump create` | `set_server_dump` || Server dump creation |
101102
| `openstack server event list` | `get_server_events` || Server event tracking |
102103
| `openstack server group list` | `get_server_groups` || Server group listing |
103104
| `openstack server group create/delete` | `set_server_group` || Server group management |

src/mcp_openstack_ops/mcp_main.py

Lines changed: 115 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ def handle_operation_result(result: Dict[str, Any], operation_name: str, details
2121
Returns:
2222
Success JSON or clear error message
2323
"""
24+
# Check if result is None or empty
25+
if not result:
26+
error_response = f"❌ **{operation_name} Failed**\n\n**Error**: No response received from OpenStack API. The operation may have failed or timed out."
27+
28+
if details:
29+
details_str = '\n'.join([f"**{k}**: {v}" for k, v in details.items()])
30+
error_response += f"\n\n{details_str}"
31+
error_response += "\n\n**Recommendation**: Please verify the operation status and try again if needed."
32+
33+
return error_response
34+
2435
# Check if operation failed and return clear error message
2536
if isinstance(result, dict) and result.get('success') is False:
2637
error_message = result.get('message', 'Unknown error occurred')
@@ -33,16 +44,97 @@ def handle_operation_result(result: Dict[str, Any], operation_name: str, details
3344

3445
return error_response
3546

36-
# Return success response
37-
return result if isinstance(result, str) else json.dumps(result, indent=2, ensure_ascii=False)
47+
# Enhanced handling for successful operations with tool-specific async guidance
48+
if isinstance(result, dict) and result.get('success') is True:
49+
# Extract operation details from various parameter structures
50+
action = details.get('Action', 'operation') if details else 'operation'
51+
resource_name = (details.get('Instance') or details.get('Volume') or
52+
details.get('Network') or details.get('Stack') or
53+
details.get('Image') or details.get('Keypair') or 'resource') if details else 'resource'
54+
55+
# Tool-specific async handling and verification commands
56+
async_operations = {
57+
"Instance Management": {
58+
"async_actions": ["start", "stop", "restart", "reboot", "create", "delete", "resize", "rebuild"],
59+
"timing": "30-60 seconds",
60+
"verify_cmd": f"Show instance status for {resource_name}"
61+
},
62+
"Volume Management": {
63+
"async_actions": ["create", "delete", "extend", "attach", "detach"],
64+
"timing": "10-30 seconds",
65+
"verify_cmd": "List all volumes"
66+
},
67+
"Network Management": {
68+
"async_actions": ["create", "delete"],
69+
"timing": "5-15 seconds",
70+
"verify_cmd": "Show all networks"
71+
},
72+
"Heat Stack Management": {
73+
"async_actions": ["create", "update", "delete"],
74+
"timing": "2-10 minutes",
75+
"verify_cmd": "List all Heat stacks"
76+
},
77+
"Image Management": {
78+
"async_actions": ["create", "delete", "update"],
79+
"timing": "1-5 minutes",
80+
"verify_cmd": "List available images"
81+
},
82+
"Quota Management": {
83+
"async_actions": [], # Usually synchronous
84+
"timing": "immediate",
85+
"verify_cmd": "Show quota usage and limits"
86+
},
87+
"Keypair Management": {
88+
"async_actions": [], # Usually synchronous
89+
"timing": "immediate",
90+
"verify_cmd": "List all keypairs"
91+
}
92+
}
93+
94+
tool_config = async_operations.get(operation_name, {
95+
"async_actions": ["create", "delete", "update"],
96+
"timing": "variable",
97+
"verify_cmd": f"Check {operation_name.lower()} status"
98+
})
99+
100+
# Only add async note for operations that are actually asynchronous
101+
if action.lower() in tool_config.get("async_actions", []):
102+
if "message" in result:
103+
timing = tool_config.get("timing", "variable")
104+
verify_cmd = tool_config.get("verify_cmd", f"Check {operation_name.lower()} status")
105+
106+
result["message"] += f"\n\n📋 Note: This is an asynchronous operation. The {action} command has been initiated successfully (expected completion: {timing}). You can verify the status using '{verify_cmd}'."
107+
108+
result["operation_type"] = "asynchronous"
109+
result["verification_needed"] = True
110+
else:
111+
# For synchronous operations, just mark as completed
112+
result["operation_type"] = "synchronous"
113+
result["verification_needed"] = False
114+
115+
# Return formatted JSON response
116+
try:
117+
return json.dumps(result, indent=2, ensure_ascii=False)
118+
except Exception as e:
119+
# Fallback for JSON serialization issues
120+
return f"Operation completed but response formatting failed: {str(e)}"
121+
122+
# Return formatted JSON for other successful dict results
123+
try:
124+
return json.dumps(result, indent=2, ensure_ascii=False)
125+
except Exception as json_error:
126+
return f"✅ **{operation_name} Successful**\n\nOperation completed but response formatting failed: {str(json_error)}"
127+
128+
# Return string results as-is
129+
return str(result) if result else "❌ **Operation Failed**: Empty response"
38130

39131
from .connection import get_openstack_connection
132+
from .services.compute import search_instances as _search_instances
40133
from .functions import (
41134
get_service_status as _get_service_status,
42135
get_instance_details as _get_instance_details,
43136
get_instance_by_name as _get_instance_by_name,
44137
get_instance_by_id as _get_instance_by_id,
45-
search_instances as _search_instances,
46138
get_instances_by_status as _get_instances_by_status,
47139
get_network_details as _get_network_details,
48140
set_instance as _set_instance,
@@ -66,8 +158,8 @@ def handle_operation_result(result: Dict[str, Any], operation_name: str, details
66158
set_server_security_group as _set_server_security_group,
67159
set_server_migration as _set_server_migration,
68160
set_server_properties as _set_server_properties,
69-
create_server_backup as _create_server_backup,
70-
create_server_dump as _create_server_dump,
161+
create_server_backup as _set_server_backup,
162+
create_server_dump as _set_server_dump,
71163
# Network (Neutron) enhanced functions
72164
get_floating_ips as _get_floating_ips,
73165
set_floating_ip as _set_floating_ip,
@@ -422,20 +514,13 @@ async def search_instances(
422514

423515
search_result = _search_instances(
424516
search_term=search_term,
425-
search_in=search_in,
517+
search_fields=search_in.split(',') if search_in else ['name', 'id'],
426518
limit=limit,
427-
offset=offset,
428-
case_sensitive=case_sensitive
519+
include_inactive=True # 모든 인스턴스를 검색하기 위해
429520
)
430521

431522
# Handle both old return format (list) and new return format (dict)
432-
if isinstance(search_result, dict):
433-
instances = search_result.get('instances', [])
434-
search_info = search_result.get('search_info', {})
435-
pagination_info = search_result.get('pagination', {})
436-
performance_info = search_result.get('performance', {})
437-
else:
438-
# Backward compatibility with old list return format
523+
if isinstance(search_result, list):
439524
instances = search_result
440525
search_info = {
441526
'search_term': search_term,
@@ -445,6 +530,17 @@ async def search_instances(
445530
}
446531
pagination_info = {'limit': limit, 'offset': offset, 'has_more': False}
447532
performance_info = {}
533+
elif isinstance(search_result, dict):
534+
instances = search_result.get('instances', [])
535+
search_info = search_result.get('search_info', {})
536+
pagination_info = search_result.get('pagination', {})
537+
performance_info = search_result.get('performance', {})
538+
else:
539+
# Fallback for unexpected format
540+
instances = []
541+
search_info = {'search_term': search_term, 'matches_found': 0}
542+
pagination_info = {'limit': limit, 'offset': offset, 'has_more': False}
543+
performance_info = {}
448544

449545
result = {
450546
"timestamp": datetime.now().isoformat(),
@@ -1046,7 +1142,7 @@ async def set_server_properties(
10461142

10471143

10481144
@conditional_tool
1049-
async def create_server_backup(
1145+
async def set_server_backup(
10501146
instance_name: str,
10511147
backup_name: str,
10521148
backup_type: str = "daily",
@@ -1087,7 +1183,7 @@ async def create_server_backup(
10871183
'metadata': metadata
10881184
}
10891185

1090-
result = _create_server_backup(instance_name.strip(), backup_name.strip(), **kwargs)
1186+
result = _set_server_backup(instance_name.strip(), backup_name.strip(), **kwargs)
10911187

10921188
# Use centralized result handling
10931189
return handle_operation_result(
@@ -1112,7 +1208,7 @@ async def create_server_backup(
11121208

11131209

11141210
@conditional_tool
1115-
async def create_server_dump(instance_name: str) -> str:
1211+
async def set_server_dump(instance_name: str) -> str:
11161212
"""
11171213
Create a dump file for a server (vendor-specific feature).
11181214
@@ -1135,7 +1231,7 @@ async def create_server_dump(instance_name: str) -> str:
11351231
try:
11361232
logger.info(f"Attempting to create server dump: {instance_name}")
11371233

1138-
result = _create_server_dump(instance_name.strip())
1234+
result = _set_server_dump(instance_name.strip())
11391235

11401236
# Use centralized result handling
11411237
return handle_operation_result(

src/mcp_openstack_ops/prompt_template.md

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,65 @@
4949
-**WRONG**: "요청을 처리했습니다" (when required parameters missing)
5050
-**CORRECT**: Return the actual error message from the tool
5151

52+
### **🔍 Empty Response Detection and Handling**
53+
54+
**CRITICAL RULE**: If MCP tool returns empty, null, or "(응답 내용 없음)" response:
55+
56+
1. **NEVER assume operation succeeded**
57+
2. **NEVER make up success messages**
58+
3. **ALWAYS report the empty response issue**
59+
4. **Recommend verification steps**
60+
61+
**Proper Response Pattern for Empty Results**:
62+
```
63+
❌ The operation may not have completed successfully as no response was received from the OpenStack API.
64+
65+
🔍 **Recommended Next Steps**:
66+
1. Please verify the current status: "Show instance status for [instance-name]"
67+
2. Check recent events: "Show instance events for [instance-name]"
68+
3. Try the operation again if needed
69+
70+
This ensures we don't provide false success confirmations when operations may have actually failed.
71+
```
72+
73+
**Common Empty Response Scenarios**:
74+
- Instance start/stop/restart operations
75+
- Volume attach/detach operations
76+
- Network configuration changes
77+
- Security group modifications
78+
- Any OpenStack asynchronous operations
79+
80+
### **⚠️ Asynchronous Operation Awareness**
81+
82+
**For OpenStack asynchronous operations** (start, stop, restart, create, delete):
83+
84+
1. **Success message** = Command was **initiated**, not completed
85+
2. **Always inform user** about asynchronous nature
86+
3. **Provide status check guidance**
87+
88+
### **🔄 Enhanced Response Handling for All Operations**
89+
90+
**All `set_*` operations use enhanced response processing:**
91+
92+
**Success Response Patterns**:
93+
- **Instance Operations**: `✅ Instance [action] initiated. Verify: "Show instance status"`
94+
- **Volume Operations**: `✅ Volume [action] initiated. Verify: "List all volumes"`
95+
- **Network Operations**: `✅ Network [action] initiated. Verify: "Show all networks"`
96+
- **Image Operations**: `✅ Image [action] initiated. Verify: "List available images"`
97+
- **Stack Operations**: `✅ Stack [action] initiated. Verify: "List all Heat stacks"`
98+
- **Other Operations**: `✅ [Resource] [action] initiated. Verify with appropriate status command.`
99+
100+
**Universal Empty Response Pattern**:
101+
```
102+
❌ No response from OpenStack API - operation status unclear.
103+
Verify current state with appropriate status check command and retry if needed.
104+
```
105+
106+
**Application Rules**:
107+
- **Enhanced responses**: All `set_*` tools (modify operations)
108+
- **Standard responses**: All `get_*`, `search_*`, `monitor_*` tools (read-only)
109+
- **Async operations**: Always include verification guidance and expected timing
110+
52111
### **📋 Required Parameters for Create Operations**
53112

54113
**VM Creation (`set_instance` with action="create")**:
@@ -219,8 +278,8 @@ This approach provides **comprehensive 360-degree cluster visibility** with infr
219278
**Server Advanced Operations:**
220279
- `set_server_migration`: Live migrate/evacuate/confirm/abort (**Conditional Tool**)
221280
- `set_server_properties`: Set/unset metadata and properties (**Conditional Tool**)
222-
- `create_server_backup`: Create incremental backups (**Conditional Tool**)
223-
- `create_server_dump`: Trigger memory dumps (**Conditional Tool**)
281+
- `set_server_backup`: Create incremental backups (**Conditional Tool**)
282+
- `set_server_dump`: Trigger memory dumps (**Conditional Tool**)
224283

225284
**Server Information & Resources:**
226285
- `get_server_groups`: Affinity/anti-affinity policy information (always available)

0 commit comments

Comments
 (0)