Skip to content

Commit cd18746

Browse files
committed
add initila files
0 parents  commit cd18746

File tree

6 files changed

+311
-0
lines changed

6 files changed

+311
-0
lines changed

.gitignore

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
build/
8+
develop-eggs/
9+
dist/
10+
downloads/
11+
eggs/
12+
.eggs/
13+
lib/
14+
lib64/
15+
parts/
16+
sdist/
17+
var/
18+
wheels/
19+
*.egg-info/
20+
.installed.cfg
21+
*.egg
22+
23+
# Virtual Environment
24+
venv/
25+
env/
26+
ENV/
27+
.env
28+
.venv
29+
30+
# IDE
31+
.idea/
32+
.vscode/
33+
*.swp
34+
*.swo
35+
.DS_Store
36+
37+
# Unit test / coverage reports
38+
htmlcov/
39+
.tox/
40+
.nox/
41+
.coverage
42+
.coverage.*
43+
.cache
44+
nosetests.xml
45+
coverage.xml
46+
*.cover
47+
*.py,cover
48+
.hypothesis/
49+
.pytest_cache/
50+
cover/
51+
52+
# Jupyter Notebook
53+
.ipynb_checkpoints
54+
55+
# mypy
56+
.mypy_cache/
57+
.dmypy.json
58+
dmypy.json
59+
60+
# Distribution / packaging
61+
.Python
62+
build/
63+
develop-eggs/
64+
dist/
65+
downloads/
66+
eggs/
67+
.eggs/
68+
lib/
69+
lib64/
70+
parts/
71+
sdist/
72+
var/
73+
wheels/
74+
share/python-wheels/
75+
*.egg-info/
76+
.installed.cfg
77+
*.egg
78+
MANIFEST

README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# MCP Shell Server
2+
3+
A secure shell command execution server implementing the Model Context Protocol (MCP). This server allows remote execution of whitelisted shell commands with support for stdin input.
4+
5+
## Features
6+
7+
* **Secure Command Execution**: Only whitelisted commands can be executed
8+
* **Stdin Support**: Pass input to commands via stdin
9+
* **Comprehensive Output**: Returns stdout, stderr, exit status, and execution time
10+
* **Shell Operator Safety**: Validates commands after shell operators (; , &&, ||, |)
11+
12+
## Installation
13+
14+
```bash
15+
pip install mcp-shell-server
16+
```
17+
18+
Or install from source:
19+
20+
```bash
21+
git clone https://github.com/yourusername/mcp-shell-server.git
22+
cd mcp-shell-server
23+
pip install -e .
24+
```
25+
26+
## Usage
27+
28+
### Starting the Server
29+
30+
```bash
31+
ALLOW_COMMANDS="ls,cat,echo" uvx mcp-shell-server
32+
```
33+
34+
The `ALLOW_COMMANDS` environment variable specifies which commands are allowed to be executed.
35+
36+
### Making Requests
37+
38+
Example requests to the server:
39+
40+
```python
41+
# Basic command execution
42+
{
43+
"command": ["ls", "-l", "/tmp"]
44+
}
45+
46+
# Command with stdin input
47+
{
48+
"command": ["cat"],
49+
"stdin": "Hello, World!"
50+
}
51+
```
52+
53+
### Response Format
54+
55+
Successful response:
56+
57+
```json
58+
{
59+
"stdout": "command output",
60+
"stderr": "",
61+
"status": 0,
62+
"execution_time": 0.123
63+
}
64+
```
65+
66+
Error response:
67+
68+
```json
69+
{
70+
"error": "Command not allowed: rm",
71+
"status": 1,
72+
"stdout": "",
73+
"stderr": "Command not allowed: rm",
74+
"execution_time": 0
75+
}
76+
```
77+
78+
## Security
79+
80+
The server implements several security measures:
81+
82+
1. **Command Whitelisting**: Only explicitly allowed commands can be executed
83+
2. **Shell Operator Validation**: Commands after shell operators are also validated against the whitelist
84+
3. **No Shell Injection**: Commands are executed directly without shell interpretation
85+
86+
## Environment Variables
87+
88+
* `ALLOW_COMMANDS`: Comma-separated list of allowed commands (e.g., "ls, cat, echo")
89+
90+
## API Reference
91+
92+
### Request Format
93+
94+
| Field | Type | Description |
95+
|----------|------------|-----------------------------------------------|
96+
| command | string[] | Command and its arguments as array elements |
97+
| stdin | string | (Optional) Input to be passed to the command |
98+
99+
### Response Format
100+
101+
| Field | Type | Description |
102+
|----------------|---------|---------------------------------------------|
103+
| stdout | string | Standard output from the command |
104+
| stderr | string | Standard error output from the command |
105+
| status | integer | Exit status code |
106+
| execution_time | float | Time taken to execute (in seconds) |
107+
| error | string | (Optional) Error message if failed |
108+
109+
## Requirements
110+
111+
* Python 3.11 or higher
112+
* uvicorn
113+
* fastapi
114+
* mcp-core

mcp_shell_server/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .server import ShellServer, main
2+
from .shell_executor import ShellExecutor
3+
4+
__version__ = "0.1.0"
5+
__all__ = ["ShellServer", "ShellExecutor", "main"]

mcp_shell_server/server.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import os
2+
import asyncio
3+
from typing import Dict, List, Optional, Any
4+
from mcp_core import MCPServer, MCPRequest
5+
from .shell_executor import ShellExecutor
6+
7+
class ShellServer(MCPServer):
8+
def __init__(self):
9+
super().__init__()
10+
self.executor = ShellExecutor()
11+
12+
async def handle_request(self, request: MCPRequest) -> Dict[str, Any]:
13+
command: List[str] = request.args.get("command", [])
14+
stdin: Optional[str] = request.args.get("stdin")
15+
16+
if not command:
17+
return {
18+
"error": "No command provided",
19+
"status": 1
20+
}
21+
22+
result = await self.executor.execute(command, stdin)
23+
return result
24+
25+
def main():
26+
server = ShellServer()
27+
server.run()

mcp_shell_server/shell_executor.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import os
2+
import time
3+
import asyncio
4+
from typing import Dict, List, Optional, Any
5+
6+
class ShellExecutor:
7+
def __init__(self):
8+
# Allow whitespace in ALLOW_COMMANDS and trim each command
9+
allow_commands = os.environ.get("ALLOW_COMMANDS", "")
10+
self.allowed_commands = set(cmd.strip() for cmd in allow_commands.split(",") if cmd.strip())
11+
12+
def _validate_command(self, command: List[str]) -> None:
13+
if not command:
14+
raise ValueError("Empty command")
15+
16+
# Check first command
17+
if command[0] not in self.allowed_commands:
18+
raise ValueError(f"Command not allowed: {command[0]}")
19+
20+
# Check for shell operators and subsequent commands
21+
for arg in command[1:]:
22+
if arg in [";", "&&", "||", "|"]:
23+
next_cmd_idx = command.index(arg) + 1
24+
if next_cmd_idx < len(command):
25+
next_cmd = command[next_cmd_idx]
26+
if next_cmd not in self.allowed_commands:
27+
raise ValueError(f"Command not allowed: {next_cmd}")
28+
29+
async def execute(self, command: List[str], stdin: Optional[str] = None) -> Dict[str, Any]:
30+
try:
31+
self._validate_command(command)
32+
except ValueError as e:
33+
return {
34+
"error": str(e),
35+
"status": 1,
36+
"stdout": "",
37+
"stderr": str(e),
38+
"execution_time": 0
39+
}
40+
41+
start_time = time.time()
42+
43+
process = await asyncio.create_subprocess_exec(
44+
*command,
45+
stdin=asyncio.subprocess.PIPE if stdin else None,
46+
stdout=asyncio.subprocess.PIPE,
47+
stderr=asyncio.subprocess.PIPE
48+
)
49+
50+
if stdin:
51+
stdout, stderr = await process.communicate(stdin.encode())
52+
else:
53+
stdout, stderr = await process.communicate()
54+
55+
execution_time = time.time() - start_time
56+
57+
return {
58+
"stdout": stdout.decode() if stdout else "",
59+
"stderr": stderr.decode() if stderr else "",
60+
"status": process.returncode,
61+
"execution_time": execution_time
62+
}

pyproject.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[project]
2+
name = "mcp-shell-server"
3+
version = "0.1.0"
4+
description = "MCP Shell Server - Execute shell commands via MCP protocol"
5+
authors = [
6+
{ name = "tumf" }
7+
]
8+
dependencies = [
9+
"uvicorn>=0.27.0",
10+
"fastapi>=0.109.0",
11+
"mcp-core>=0.2.0",
12+
]
13+
requires-python = ">=3.11"
14+
readme = "README.md"
15+
license = { text = "MIT" }
16+
17+
[build-system]
18+
requires = ["hatchling"]
19+
build-backend = "hatchling.build"
20+
21+
[tool.hatch.metadata]
22+
allow-direct-references = true
23+
24+
[project.scripts]
25+
mcp-shell-server = "mcp_shell_server.server:main"

0 commit comments

Comments
 (0)