forked from codelion/dynamic-shell-server
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdynamic_shell_server.py
More file actions
122 lines (107 loc) · 4.06 KB
/
dynamic_shell_server.py
File metadata and controls
122 lines (107 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from mcp.server.fastmcp import FastMCP, Context
import asyncio
from typing import List, Optional, Dict, Union
import os
# Initialize the MCP server
mcp = FastMCP("Shell Commander")
@mcp.tool()
async def execute_command(command: str, args: Optional[List[str]] = None, shell: bool = True, timeout: Optional[int] = None) -> Dict[str, List[Dict[str, str]]]:
"""
Execute a shell command asynchronously and return its output.
Args:
command: The command to execute
args: Optional list of command arguments
shell: Whether to use shell execution (default: True)
timeout: Optional timeout in seconds (default: None, meaning no timeout)
"""
try:
# Handle both string and list commands
if args:
if shell:
cmd = f"{command} {' '.join(args)}"
else:
cmd = [command] + args
else:
cmd = command
# Create and run the process asynchronously
if shell:
process = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
else:
process = await asyncio.create_subprocess_exec(
command,
*args if args else [],
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
try:
# Wait for the process to complete with optional timeout
stdout, stderr = await asyncio.wait_for(
process.communicate(),
timeout=timeout
)
# Decode output
stdout_str = stdout.decode('utf-8') if stdout else ""
stderr_str = stderr.decode('utf-8') if stderr else ""
# Check return code
if process.returncode == 0:
return {
"content": [{
"type": "text",
"text": stdout_str if stdout_str else "Command completed successfully"
}],
"isError": False
}
else:
return {
"content": [{
"type": "text",
"text": f"Error: {stderr_str}" if stderr_str else "Command failed with no error message"
}],
"isError": True
}
except asyncio.TimeoutError:
# Try to terminate the process if it times out
try:
process.terminate()
await asyncio.sleep(0.1)
process.kill()
except:
pass
return {
"content": [{
"type": "text",
"text": f"Error: Command timed out after {timeout} seconds"
}],
"isError": True
}
except Exception as e:
return {
"content": [{
"type": "text",
"text": f"Unexpected Error: {str(e)}"
}],
"isError": True
}
@mcp.tool()
async def run_in_venv(venv_path: str, command: str, timeout: Optional[int] = None) -> Dict[str, List[Dict[str, str]]]:
"""
Run a command in a specific virtual environment.
Args:
venv_path: Path to the virtual environment
command: Command to execute in the venv
timeout: Optional timeout in seconds (default: None, meaning no timeout)
"""
# Construct the command to activate venv and run command
# Use the full path to activate script
activation_command = f"source {os.path.join(venv_path, 'bin', 'activate')} && {command}"
return await execute_command("/bin/bash", ["-c", activation_command], timeout=timeout)
@mcp.resource("process://status")
def get_process_status() -> str:
"""Resource that provides status of currently running processes."""
return "Process status information"
if __name__ == "__main__":
mcp.run()