mcp-shell-server 0.1.0__tar.gz
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.
- mcp_shell_server-0.1.0/.github/dependabot.yml +13 -0
- mcp_shell_server-0.1.0/.github/workflows/test.yml +52 -0
- mcp_shell_server-0.1.0/.gitignore +78 -0
- mcp_shell_server-0.1.0/.python-version +1 -0
- mcp_shell_server-0.1.0/Makefile +23 -0
- mcp_shell_server-0.1.0/PKG-INFO +180 -0
- mcp_shell_server-0.1.0/README.md +158 -0
- mcp_shell_server-0.1.0/mcp_shell_server/__init__.py +15 -0
- mcp_shell_server-0.1.0/mcp_shell_server/server.py +123 -0
- mcp_shell_server-0.1.0/mcp_shell_server/shell_executor.py +175 -0
- mcp_shell_server-0.1.0/pyproject.toml +65 -0
- mcp_shell_server-0.1.0/tests/conftest.py +4 -0
- mcp_shell_server-0.1.0/tests/test_server.py +170 -0
- mcp_shell_server-0.1.0/tests/test_shell_executor.py +165 -0
- mcp_shell_server-0.1.0/uv.lock +697 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "pip"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
open-pull-requests-limit: 10
|
|
8
|
+
|
|
9
|
+
- package-ecosystem: "github-actions"
|
|
10
|
+
directory: "/"
|
|
11
|
+
schedule:
|
|
12
|
+
interval: "weekly"
|
|
13
|
+
open-pull-requests-limit: 10
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
pip install uv
|
|
28
|
+
|
|
29
|
+
- name: Install dev/test dependencies
|
|
30
|
+
run: |
|
|
31
|
+
pip install -e ".[dev]"
|
|
32
|
+
pip install -e ".[test]"
|
|
33
|
+
|
|
34
|
+
- name: Run tests
|
|
35
|
+
run: |
|
|
36
|
+
uv run pytest
|
|
37
|
+
|
|
38
|
+
- name: Run ruff
|
|
39
|
+
run: |
|
|
40
|
+
uv run ruff check .
|
|
41
|
+
|
|
42
|
+
- name: Run black
|
|
43
|
+
run: |
|
|
44
|
+
uv run black . --check
|
|
45
|
+
|
|
46
|
+
- name: Run isort
|
|
47
|
+
run: |
|
|
48
|
+
uv run isort . --check-only
|
|
49
|
+
|
|
50
|
+
- name: Run mypy
|
|
51
|
+
run: |
|
|
52
|
+
uv run mypy mcp_shell_server tests
|
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.PHONY: test format lint typecheck check
|
|
2
|
+
|
|
3
|
+
test:
|
|
4
|
+
pytest
|
|
5
|
+
|
|
6
|
+
format:
|
|
7
|
+
black .
|
|
8
|
+
isort .
|
|
9
|
+
ruff check --fix .
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
lint:
|
|
13
|
+
black --check .
|
|
14
|
+
isort --check .
|
|
15
|
+
ruff check .
|
|
16
|
+
|
|
17
|
+
typecheck:
|
|
18
|
+
mypy mcp_shell_server tests
|
|
19
|
+
|
|
20
|
+
# Run all checks required before pushing
|
|
21
|
+
check: lint typecheck test
|
|
22
|
+
fix: check format
|
|
23
|
+
all: check
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: mcp-shell-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP Shell Server - Execute shell commands via MCP protocol
|
|
5
|
+
Author: tumf
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: asyncio>=3.4.3
|
|
9
|
+
Requires-Dist: mcp>=1.1.0
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: black>=23.3.0; extra == 'dev'
|
|
12
|
+
Requires-Dist: isort>=5.12.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: mypy>=1.2.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: pre-commit>=3.2.2; extra == 'dev'
|
|
15
|
+
Requires-Dist: ruff>=0.0.262; extra == 'dev'
|
|
16
|
+
Provides-Extra: test
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
|
|
18
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
|
|
19
|
+
Requires-Dist: pytest-env>=1.1.0; extra == 'test'
|
|
20
|
+
Requires-Dist: pytest>=7.4.0; extra == 'test'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# MCP Shell Server
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
* **Secure Command Execution**: Only whitelisted commands can be executed
|
|
30
|
+
* **Standard Input Support**: Pass input to commands via stdin
|
|
31
|
+
* **Comprehensive Output**: Returns stdout, stderr, exit status, and execution time
|
|
32
|
+
* **Shell Operator Safety**: Validates commands after shell operators (; , &&, ||, |)
|
|
33
|
+
|
|
34
|
+
## MCP client setting in your Claude.app
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"shell": {
|
|
44
|
+
"command": "uv",
|
|
45
|
+
"args": [
|
|
46
|
+
"--directory",
|
|
47
|
+
".",
|
|
48
|
+
"run",
|
|
49
|
+
"mcp-shell-server"
|
|
50
|
+
],
|
|
51
|
+
"env": {
|
|
52
|
+
"ALLOW_COMMANDS": "ls,cat,pwd,grep,wc,touch,find"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install mcp-shell-server
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
### Starting the Server
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
ALLOW_COMMANDS="ls,cat,echo" uvx mcp-shell-server
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The `ALLOW_COMMANDS` environment variable specifies which commands are allowed to be executed. Commands can be separated by commas with optional spaces around them.
|
|
74
|
+
|
|
75
|
+
Valid formats for ALLOW_COMMANDS:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
ALLOW_COMMANDS="ls,cat,echo" # Basic format
|
|
79
|
+
ALLOW_COMMANDS="ls ,echo, cat" # With spaces
|
|
80
|
+
ALLOW_COMMANDS="ls, cat , echo" # Multiple spaces
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Request Format
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# Basic command execution
|
|
87
|
+
{
|
|
88
|
+
"command": ["ls", "-l", "/tmp"]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Command with stdin input
|
|
92
|
+
{
|
|
93
|
+
"command": ["cat"],
|
|
94
|
+
"stdin": "Hello, World!"
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Response Format
|
|
99
|
+
|
|
100
|
+
Successful response:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"stdout": "command output",
|
|
105
|
+
"stderr": "",
|
|
106
|
+
"status": 0,
|
|
107
|
+
"execution_time": 0.123
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Error response:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"error": "Command not allowed: rm",
|
|
116
|
+
"status": 1,
|
|
117
|
+
"stdout": "",
|
|
118
|
+
"stderr": "Command not allowed: rm",
|
|
119
|
+
"execution_time": 0
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Security
|
|
124
|
+
|
|
125
|
+
The server implements several security measures:
|
|
126
|
+
|
|
127
|
+
1. **Command Whitelisting**: Only explicitly allowed commands can be executed
|
|
128
|
+
2. **Shell Operator Validation**: Commands after shell operators (;, &&, ||, |) are also validated against the whitelist
|
|
129
|
+
3. **No Shell Injection**: Commands are executed directly without shell interpretation
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
### Setting up Development Environment
|
|
134
|
+
|
|
135
|
+
1. Clone the repository
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
git clone https://github.com/yourusername/mcp-shell-server.git
|
|
139
|
+
cd mcp-shell-server
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
2. Install dependencies including test requirements
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
pip install -e ".[test]"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Running Tests
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
pytest
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## API Reference
|
|
155
|
+
|
|
156
|
+
### Request Arguments
|
|
157
|
+
|
|
158
|
+
| Field | Type | Required | Description |
|
|
159
|
+
|----------|------------|----------|-----------------------------------------------|
|
|
160
|
+
| command | string[] | Yes | Command and its arguments as array elements |
|
|
161
|
+
| stdin | string | No | Input to be passed to the command |
|
|
162
|
+
|
|
163
|
+
### Response Fields
|
|
164
|
+
|
|
165
|
+
| Field | Type | Description |
|
|
166
|
+
|----------------|---------|---------------------------------------------|
|
|
167
|
+
| stdout | string | Standard output from the command |
|
|
168
|
+
| stderr | string | Standard error output from the command |
|
|
169
|
+
| status | integer | Exit status code |
|
|
170
|
+
| execution_time | float | Time taken to execute (in seconds) |
|
|
171
|
+
| error | string | Error message (only present if failed) |
|
|
172
|
+
|
|
173
|
+
## Requirements
|
|
174
|
+
|
|
175
|
+
* Python 3.11 or higher
|
|
176
|
+
* mcp>=1.1.0
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT License - See LICENSE file for details
|
|
@@ -0,0 +1,158 @@
|
|
|
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
|
+
* **Standard Input 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
|
+
## MCP client setting in your Claude.app
|
|
13
|
+
|
|
14
|
+
```shell
|
|
15
|
+
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"shell": {
|
|
22
|
+
"command": "uv",
|
|
23
|
+
"args": [
|
|
24
|
+
"--directory",
|
|
25
|
+
".",
|
|
26
|
+
"run",
|
|
27
|
+
"mcp-shell-server"
|
|
28
|
+
],
|
|
29
|
+
"env": {
|
|
30
|
+
"ALLOW_COMMANDS": "ls,cat,pwd,grep,wc,touch,find"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install mcp-shell-server
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Starting the Server
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
ALLOW_COMMANDS="ls,cat,echo" uvx mcp-shell-server
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The `ALLOW_COMMANDS` environment variable specifies which commands are allowed to be executed. Commands can be separated by commas with optional spaces around them.
|
|
52
|
+
|
|
53
|
+
Valid formats for ALLOW_COMMANDS:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
ALLOW_COMMANDS="ls,cat,echo" # Basic format
|
|
57
|
+
ALLOW_COMMANDS="ls ,echo, cat" # With spaces
|
|
58
|
+
ALLOW_COMMANDS="ls, cat , echo" # Multiple spaces
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Request Format
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
# Basic command execution
|
|
65
|
+
{
|
|
66
|
+
"command": ["ls", "-l", "/tmp"]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Command with stdin input
|
|
70
|
+
{
|
|
71
|
+
"command": ["cat"],
|
|
72
|
+
"stdin": "Hello, World!"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Response Format
|
|
77
|
+
|
|
78
|
+
Successful response:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"stdout": "command output",
|
|
83
|
+
"stderr": "",
|
|
84
|
+
"status": 0,
|
|
85
|
+
"execution_time": 0.123
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Error response:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"error": "Command not allowed: rm",
|
|
94
|
+
"status": 1,
|
|
95
|
+
"stdout": "",
|
|
96
|
+
"stderr": "Command not allowed: rm",
|
|
97
|
+
"execution_time": 0
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Security
|
|
102
|
+
|
|
103
|
+
The server implements several security measures:
|
|
104
|
+
|
|
105
|
+
1. **Command Whitelisting**: Only explicitly allowed commands can be executed
|
|
106
|
+
2. **Shell Operator Validation**: Commands after shell operators (;, &&, ||, |) are also validated against the whitelist
|
|
107
|
+
3. **No Shell Injection**: Commands are executed directly without shell interpretation
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
### Setting up Development Environment
|
|
112
|
+
|
|
113
|
+
1. Clone the repository
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
git clone https://github.com/yourusername/mcp-shell-server.git
|
|
117
|
+
cd mcp-shell-server
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
2. Install dependencies including test requirements
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pip install -e ".[test]"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Running Tests
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pytest
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## API Reference
|
|
133
|
+
|
|
134
|
+
### Request Arguments
|
|
135
|
+
|
|
136
|
+
| Field | Type | Required | Description |
|
|
137
|
+
|----------|------------|----------|-----------------------------------------------|
|
|
138
|
+
| command | string[] | Yes | Command and its arguments as array elements |
|
|
139
|
+
| stdin | string | No | Input to be passed to the command |
|
|
140
|
+
|
|
141
|
+
### Response Fields
|
|
142
|
+
|
|
143
|
+
| Field | Type | Description |
|
|
144
|
+
|----------------|---------|---------------------------------------------|
|
|
145
|
+
| stdout | string | Standard output from the command |
|
|
146
|
+
| stderr | string | Standard error output from the command |
|
|
147
|
+
| status | integer | Exit status code |
|
|
148
|
+
| execution_time | float | Time taken to execute (in seconds) |
|
|
149
|
+
| error | string | Error message (only present if failed) |
|
|
150
|
+
|
|
151
|
+
## Requirements
|
|
152
|
+
|
|
153
|
+
* Python 3.11 or higher
|
|
154
|
+
* mcp>=1.1.0
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT License - See LICENSE file for details
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""MCP Shell Server Package."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from . import server
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
"""Main entry point for the package."""
|
|
10
|
+
asyncio.run(server.main())
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Optionally expose other important items at package level
|
|
14
|
+
__all__ = ["main", "server"]
|
|
15
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import traceback
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from mcp.server import Server
|
|
7
|
+
from mcp.types import TextContent, Tool
|
|
8
|
+
|
|
9
|
+
from .shell_executor import ShellExecutor
|
|
10
|
+
|
|
11
|
+
# Configure logging
|
|
12
|
+
logging.basicConfig(level=logging.INFO)
|
|
13
|
+
logger = logging.getLogger("mcp-shell-server")
|
|
14
|
+
|
|
15
|
+
app = Server("mcp-shell-server")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ExecuteToolHandler:
|
|
19
|
+
"""Handler for shell command execution"""
|
|
20
|
+
|
|
21
|
+
name = "shell_execute"
|
|
22
|
+
description = "Execute a shell command"
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.executor = ShellExecutor()
|
|
26
|
+
|
|
27
|
+
def get_allowed_commands(self) -> list[str]:
|
|
28
|
+
"""Get the allowed commands"""
|
|
29
|
+
return self.executor.get_allowed_commands()
|
|
30
|
+
|
|
31
|
+
def get_tool_description(self) -> Tool:
|
|
32
|
+
"""Get the tool description for the execute command"""
|
|
33
|
+
return Tool(
|
|
34
|
+
name=self.name,
|
|
35
|
+
description=f"{self.description}\nAllowed commands: {', '.join(self.get_allowed_commands())}",
|
|
36
|
+
inputSchema={
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties": {
|
|
39
|
+
"command": {
|
|
40
|
+
"type": "array",
|
|
41
|
+
"items": {"type": "string"},
|
|
42
|
+
"description": "Command and its arguments as array",
|
|
43
|
+
},
|
|
44
|
+
"stdin": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Input to be passed to the command via stdin",
|
|
47
|
+
},
|
|
48
|
+
"directory": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "Working directory where the command will be executed",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
"required": ["command"],
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
async def run_tool(self, arguments: dict) -> Sequence[TextContent]:
|
|
58
|
+
"""Execute the shell command with the given arguments"""
|
|
59
|
+
command = arguments.get("command", [])
|
|
60
|
+
stdin = arguments.get("stdin")
|
|
61
|
+
directory = arguments.get("directory")
|
|
62
|
+
|
|
63
|
+
if not command:
|
|
64
|
+
raise ValueError("No command provided")
|
|
65
|
+
|
|
66
|
+
result = await self.executor.execute(command, stdin, directory)
|
|
67
|
+
|
|
68
|
+
# Raise error if command execution failed
|
|
69
|
+
if result.get("error"):
|
|
70
|
+
raise RuntimeError(result["error"])
|
|
71
|
+
|
|
72
|
+
# Convert executor result to TextContent sequence
|
|
73
|
+
content: list[TextContent] = []
|
|
74
|
+
|
|
75
|
+
if result.get("stdout"):
|
|
76
|
+
content.append(TextContent(type="text", text=result["stdout"]))
|
|
77
|
+
if result.get("stderr"):
|
|
78
|
+
content.append(TextContent(type="text", text=result["stderr"]))
|
|
79
|
+
|
|
80
|
+
return content
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# Initialize tool handlers
|
|
84
|
+
tool_handler = ExecuteToolHandler()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.list_tools()
|
|
88
|
+
async def list_tools() -> list[Tool]:
|
|
89
|
+
"""List available tools."""
|
|
90
|
+
return [tool_handler.get_tool_description()]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@app.call_tool()
|
|
94
|
+
async def call_tool(name: str, arguments: Any) -> Sequence[TextContent]:
|
|
95
|
+
"""Handle tool calls"""
|
|
96
|
+
try:
|
|
97
|
+
if name != tool_handler.name:
|
|
98
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
99
|
+
|
|
100
|
+
if not isinstance(arguments, dict):
|
|
101
|
+
raise ValueError("Arguments must be a dictionary")
|
|
102
|
+
|
|
103
|
+
return await tool_handler.run_tool(arguments)
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error(traceback.format_exc())
|
|
107
|
+
logger.error(f"Error during call_tool: {str(e)}")
|
|
108
|
+
raise RuntimeError(f"Error executing command: {str(e)}") from e
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def main() -> None:
|
|
112
|
+
"""Main entry point for the MCP shell server"""
|
|
113
|
+
logger.info("Starting MCP shell server")
|
|
114
|
+
try:
|
|
115
|
+
from mcp.server.stdio import stdio_server
|
|
116
|
+
|
|
117
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
118
|
+
await app.run(
|
|
119
|
+
read_stream, write_stream, app.create_initialization_options()
|
|
120
|
+
)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.error(f"Server error: {str(e)}")
|
|
123
|
+
raise
|