cli-mcp-server 0.1.2__tar.gz → 0.2.1__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.
- {cli_mcp_server-0.1.2 → cli_mcp_server-0.2.1}/PKG-INFO +18 -8
- {cli_mcp_server-0.1.2 → cli_mcp_server-0.2.1}/README.md +14 -5
- {cli_mcp_server-0.1.2 → cli_mcp_server-0.2.1}/pyproject.toml +2 -2
- {cli_mcp_server-0.1.2 → cli_mcp_server-0.2.1}/src/cli_mcp_server/__init__.py +1 -1
- {cli_mcp_server-0.1.2 → cli_mcp_server-0.2.1}/src/cli_mcp_server/server.py +107 -77
- {cli_mcp_server-0.1.2 → cli_mcp_server-0.2.1}/uv.lock +5 -5
- {cli_mcp_server-0.1.2 → cli_mcp_server-0.2.1}/.gitignore +0 -0
- {cli_mcp_server-0.1.2 → cli_mcp_server-0.2.1}/.python-version +0 -0
- {cli_mcp_server-0.1.2 → cli_mcp_server-0.2.1}/LICENSE +0 -0
@@ -1,14 +1,15 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: cli-mcp-server
|
3
|
-
Version: 0.1
|
3
|
+
Version: 0.2.1
|
4
4
|
Summary: Command line interface for MCP clients with secure execution and customizable security policies
|
5
5
|
Project-URL: Homepage, https://github.com/MladenSU/cli-mcp-server
|
6
6
|
Project-URL: Documentation, https://github.com/MladenSU/cli-mcp-server#readme
|
7
7
|
Project-URL: Repository, https://github.com/MladenSU/cli-mcp-server.git
|
8
8
|
Project-URL: Bug Tracker, https://github.com/MladenSU/cli-mcp-server/issues
|
9
9
|
Author-email: Mladen <fangs-lever6n@icloud.com>
|
10
|
+
License-File: LICENSE
|
10
11
|
Requires-Python: >=3.10
|
11
|
-
Requires-Dist: mcp>=1.
|
12
|
+
Requires-Dist: mcp>=1.1.0
|
12
13
|
Description-Content-Type: text/markdown
|
13
14
|
|
14
15
|
# CLI MCP Server
|
@@ -22,6 +23,9 @@ features.
|
|
22
23
|

|
23
24
|

|
24
25
|

|
26
|
+
[](https://smithery.ai/protocol/cli-mcp-server)
|
27
|
+
|
28
|
+
<a href="https://glama.ai/mcp/servers/q89277vzl1"><img width="380" height="200" src="https://glama.ai/mcp/servers/q89277vzl1/badge" /></a>
|
25
29
|
|
26
30
|
---
|
27
31
|
|
@@ -71,10 +75,17 @@ Configure the server using environment variables:
|
|
71
75
|
| `ALLOWED_DIR` | Base directory for command execution | Required |
|
72
76
|
| `ALLOWED_COMMANDS` | Comma-separated list of allowed commands | `ls,cat,pwd` |
|
73
77
|
| `ALLOWED_FLAGS` | Comma-separated list of allowed flags | `-l,-a,--help` |
|
74
|
-
| `ALLOWED_PATTERNS` | Comma-separated file patterns | `*.txt,*.log,*.md` |
|
75
78
|
| `MAX_COMMAND_LENGTH` | Maximum command string length | `1024` |
|
76
79
|
| `COMMAND_TIMEOUT` | Command execution timeout (seconds) | `30` |
|
77
80
|
|
81
|
+
## Installation
|
82
|
+
|
83
|
+
To install CLI MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/protocol/cli-mcp-server):
|
84
|
+
|
85
|
+
```bash
|
86
|
+
npx @smithery/cli install cli-mcp-server --client claude
|
87
|
+
```
|
88
|
+
|
78
89
|
## Available Tools
|
79
90
|
|
80
91
|
### run_command
|
@@ -117,7 +128,6 @@ Add to your `~/Library/Application\ Support/Claude/claude_desktop_config.json`:
|
|
117
128
|
"ALLOWED_DIR": "</your/desired/dir>",
|
118
129
|
"ALLOWED_COMMANDS": "ls,cat,pwd,echo",
|
119
130
|
"ALLOWED_FLAGS": "-l,-a,--help,--version",
|
120
|
-
"ALLOWED_PATTERNS": "*.txt,*.log,*.md",
|
121
131
|
"MAX_COMMAND_LENGTH": "1024",
|
122
132
|
"COMMAND_TIMEOUT": "30"
|
123
133
|
}
|
@@ -140,7 +150,6 @@ Add to your `~/Library/Application\ Support/Claude/claude_desktop_config.json`:
|
|
140
150
|
"ALLOWED_DIR": "</your/desired/dir>",
|
141
151
|
"ALLOWED_COMMANDS": "ls,cat,pwd,echo",
|
142
152
|
"ALLOWED_FLAGS": "-l,-a,--help,--version",
|
143
|
-
"ALLOWED_PATTERNS": "*.txt,*.log,*.md",
|
144
153
|
"MAX_COMMAND_LENGTH": "1024",
|
145
154
|
"COMMAND_TIMEOUT": "30"
|
146
155
|
}
|
@@ -148,6 +157,7 @@ Add to your `~/Library/Application\ Support/Claude/claude_desktop_config.json`:
|
|
148
157
|
}
|
149
158
|
}
|
150
159
|
```
|
160
|
+
> In case it's not working or showing in the UI, clear your cache via `uv clean`.
|
151
161
|
|
152
162
|
## Security Features
|
153
163
|
|
@@ -208,7 +218,7 @@ You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-
|
|
208
218
|
this command:
|
209
219
|
|
210
220
|
```bash
|
211
|
-
npx @modelcontextprotocol/inspector uv --directory {{your source code local directory}}/
|
221
|
+
npx @modelcontextprotocol/inspector uv --directory {{your source code local directory}}/cli-mcp-server run cli-mcp-server
|
212
222
|
```
|
213
223
|
|
214
224
|
Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.
|
@@ -219,4 +229,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
219
229
|
|
220
230
|
---
|
221
231
|
|
222
|
-
For more information or support, please open an issue on the project repository.
|
232
|
+
For more information or support, please open an issue on the project repository.
|
@@ -9,6 +9,9 @@ features.
|
|
9
9
|

|
10
10
|

|
11
11
|

|
12
|
+
[](https://smithery.ai/protocol/cli-mcp-server)
|
13
|
+
|
14
|
+
<a href="https://glama.ai/mcp/servers/q89277vzl1"><img width="380" height="200" src="https://glama.ai/mcp/servers/q89277vzl1/badge" /></a>
|
12
15
|
|
13
16
|
---
|
14
17
|
|
@@ -58,10 +61,17 @@ Configure the server using environment variables:
|
|
58
61
|
| `ALLOWED_DIR` | Base directory for command execution | Required |
|
59
62
|
| `ALLOWED_COMMANDS` | Comma-separated list of allowed commands | `ls,cat,pwd` |
|
60
63
|
| `ALLOWED_FLAGS` | Comma-separated list of allowed flags | `-l,-a,--help` |
|
61
|
-
| `ALLOWED_PATTERNS` | Comma-separated file patterns | `*.txt,*.log,*.md` |
|
62
64
|
| `MAX_COMMAND_LENGTH` | Maximum command string length | `1024` |
|
63
65
|
| `COMMAND_TIMEOUT` | Command execution timeout (seconds) | `30` |
|
64
66
|
|
67
|
+
## Installation
|
68
|
+
|
69
|
+
To install CLI MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/protocol/cli-mcp-server):
|
70
|
+
|
71
|
+
```bash
|
72
|
+
npx @smithery/cli install cli-mcp-server --client claude
|
73
|
+
```
|
74
|
+
|
65
75
|
## Available Tools
|
66
76
|
|
67
77
|
### run_command
|
@@ -104,7 +114,6 @@ Add to your `~/Library/Application\ Support/Claude/claude_desktop_config.json`:
|
|
104
114
|
"ALLOWED_DIR": "</your/desired/dir>",
|
105
115
|
"ALLOWED_COMMANDS": "ls,cat,pwd,echo",
|
106
116
|
"ALLOWED_FLAGS": "-l,-a,--help,--version",
|
107
|
-
"ALLOWED_PATTERNS": "*.txt,*.log,*.md",
|
108
117
|
"MAX_COMMAND_LENGTH": "1024",
|
109
118
|
"COMMAND_TIMEOUT": "30"
|
110
119
|
}
|
@@ -127,7 +136,6 @@ Add to your `~/Library/Application\ Support/Claude/claude_desktop_config.json`:
|
|
127
136
|
"ALLOWED_DIR": "</your/desired/dir>",
|
128
137
|
"ALLOWED_COMMANDS": "ls,cat,pwd,echo",
|
129
138
|
"ALLOWED_FLAGS": "-l,-a,--help,--version",
|
130
|
-
"ALLOWED_PATTERNS": "*.txt,*.log,*.md",
|
131
139
|
"MAX_COMMAND_LENGTH": "1024",
|
132
140
|
"COMMAND_TIMEOUT": "30"
|
133
141
|
}
|
@@ -135,6 +143,7 @@ Add to your `~/Library/Application\ Support/Claude/claude_desktop_config.json`:
|
|
135
143
|
}
|
136
144
|
}
|
137
145
|
```
|
146
|
+
> In case it's not working or showing in the UI, clear your cache via `uv clean`.
|
138
147
|
|
139
148
|
## Security Features
|
140
149
|
|
@@ -195,7 +204,7 @@ You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-
|
|
195
204
|
this command:
|
196
205
|
|
197
206
|
```bash
|
198
|
-
npx @modelcontextprotocol/inspector uv --directory {{your source code local directory}}/
|
207
|
+
npx @modelcontextprotocol/inspector uv --directory {{your source code local directory}}/cli-mcp-server run cli-mcp-server
|
199
208
|
```
|
200
209
|
|
201
210
|
Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.
|
@@ -206,4 +215,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
206
215
|
|
207
216
|
---
|
208
217
|
|
209
|
-
For more information or support, please open an issue on the project repository.
|
218
|
+
For more information or support, please open an issue on the project repository.
|
@@ -1,10 +1,10 @@
|
|
1
1
|
[project]
|
2
2
|
name = "cli-mcp-server"
|
3
|
-
version = "0.1
|
3
|
+
version = "0.2.1"
|
4
4
|
description = "Command line interface for MCP clients with secure execution and customizable security policies"
|
5
5
|
readme = "README.md"
|
6
6
|
requires-python = ">=3.10"
|
7
|
-
dependencies = ["mcp>=1.
|
7
|
+
dependencies = ["mcp>=1.1.0"]
|
8
8
|
authors = [
|
9
9
|
{ name = "Mladen", email = "fangs-lever6n@icloud.com" },
|
10
10
|
]
|
@@ -13,10 +13,26 @@ from mcp.server.models import InitializationOptions
|
|
13
13
|
server = Server("cli-mcp-server")
|
14
14
|
|
15
15
|
|
16
|
-
class
|
17
|
-
"""
|
18
|
-
|
19
|
-
|
16
|
+
class CommandError(Exception):
|
17
|
+
"""Base exception for command-related errors"""
|
18
|
+
|
19
|
+
pass
|
20
|
+
|
21
|
+
|
22
|
+
class CommandSecurityError(CommandError):
|
23
|
+
"""Security violation errors"""
|
24
|
+
|
25
|
+
pass
|
26
|
+
|
27
|
+
|
28
|
+
class CommandExecutionError(CommandError):
|
29
|
+
"""Command execution errors"""
|
30
|
+
|
31
|
+
pass
|
32
|
+
|
33
|
+
|
34
|
+
class CommandTimeoutError(CommandError):
|
35
|
+
"""Command timeout errors"""
|
20
36
|
|
21
37
|
pass
|
22
38
|
|
@@ -29,9 +45,10 @@ class SecurityConfig:
|
|
29
45
|
|
30
46
|
allowed_commands: set[str]
|
31
47
|
allowed_flags: set[str]
|
32
|
-
allowed_patterns: List[str]
|
33
48
|
max_command_length: int
|
34
49
|
command_timeout: int
|
50
|
+
allow_all_commands: bool = False
|
51
|
+
allow_all_flags: bool = False
|
35
52
|
|
36
53
|
|
37
54
|
class CommandExecutor:
|
@@ -41,6 +58,27 @@ class CommandExecutor:
|
|
41
58
|
self.allowed_dir = os.path.abspath(os.path.realpath(allowed_dir))
|
42
59
|
self.security_config = security_config
|
43
60
|
|
61
|
+
def _normalize_path(self, path: str) -> str:
|
62
|
+
"""
|
63
|
+
Normalizes a path and ensures it's within allowed directory.
|
64
|
+
"""
|
65
|
+
try:
|
66
|
+
if os.path.isabs(path):
|
67
|
+
# If absolute path, check directly
|
68
|
+
real_path = os.path.abspath(os.path.realpath(path))
|
69
|
+
else:
|
70
|
+
# If relative path, combine with allowed_dir first
|
71
|
+
real_path = os.path.abspath(os.path.realpath(os.path.join(self.allowed_dir, path)))
|
72
|
+
|
73
|
+
if not self._is_path_safe(real_path):
|
74
|
+
raise CommandSecurityError(f"Path '{path}' is outside of allowed directory: {self.allowed_dir}")
|
75
|
+
|
76
|
+
return real_path
|
77
|
+
except CommandSecurityError:
|
78
|
+
raise
|
79
|
+
except Exception as e:
|
80
|
+
raise CommandSecurityError(f"Invalid path '{path}': {str(e)}")
|
81
|
+
|
44
82
|
def validate_command(self, command_string: str) -> tuple[str, List[str]]:
|
45
83
|
"""
|
46
84
|
Validates and parses a command string for security and formatting.
|
@@ -64,10 +102,7 @@ class CommandExecutor:
|
|
64
102
|
shell_operators = ["&&", "||", "|", ">", ">>", "<", "<<", ";"]
|
65
103
|
for operator in shell_operators:
|
66
104
|
if operator in command_string:
|
67
|
-
raise CommandSecurityError(
|
68
|
-
f"Shell operator '{operator}' is not supported. "
|
69
|
-
"Only single commands are allowed."
|
70
|
-
)
|
105
|
+
raise CommandSecurityError(f"Shell operator '{operator}' is not supported")
|
71
106
|
|
72
107
|
try:
|
73
108
|
parts = shlex.split(command_string)
|
@@ -76,33 +111,29 @@ class CommandExecutor:
|
|
76
111
|
|
77
112
|
command, args = parts[0], parts[1:]
|
78
113
|
|
79
|
-
# Validate command
|
80
|
-
if command not in self.security_config.allowed_commands:
|
114
|
+
# Validate command if not in allow-all mode
|
115
|
+
if not self.security_config.allow_all_commands and command not in self.security_config.allowed_commands:
|
81
116
|
raise CommandSecurityError(f"Command '{command}' is not allowed")
|
82
117
|
|
83
|
-
#
|
118
|
+
# Process and validate arguments
|
119
|
+
validated_args = []
|
84
120
|
for arg in args:
|
85
121
|
if arg.startswith("-"):
|
86
|
-
if arg not in self.security_config.allowed_flags:
|
122
|
+
if not self.security_config.allow_all_flags and arg not in self.security_config.allowed_flags:
|
87
123
|
raise CommandSecurityError(f"Flag '{arg}' is not allowed")
|
124
|
+
validated_args.append(arg)
|
88
125
|
continue
|
89
126
|
|
90
|
-
#
|
91
|
-
if "/" in arg or "\\" in arg or os.path.isabs(arg):
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
):
|
101
|
-
raise CommandSecurityError(
|
102
|
-
f"Argument '{arg}' doesn't match allowed patterns"
|
103
|
-
)
|
104
|
-
|
105
|
-
return command, args
|
127
|
+
# For any path-like argument, validate it
|
128
|
+
if "/" in arg or "\\" in arg or os.path.isabs(arg) or arg == ".":
|
129
|
+
normalized_path = self._normalize_path(arg)
|
130
|
+
validated_args.append(normalized_path)
|
131
|
+
else:
|
132
|
+
# For non-path arguments, add them as-is
|
133
|
+
validated_args.append(arg)
|
134
|
+
|
135
|
+
return command, validated_args
|
136
|
+
|
106
137
|
except ValueError as e:
|
107
138
|
raise CommandSecurityError(f"Invalid command format: {str(e)}")
|
108
139
|
|
@@ -123,8 +154,12 @@ class CommandExecutor:
|
|
123
154
|
Private method intended for internal use only.
|
124
155
|
"""
|
125
156
|
try:
|
126
|
-
|
127
|
-
|
157
|
+
# Resolve any symlinks and get absolute path
|
158
|
+
real_path = os.path.abspath(os.path.realpath(path))
|
159
|
+
allowed_dir_real = os.path.abspath(os.path.realpath(self.allowed_dir))
|
160
|
+
|
161
|
+
# Check if the path starts with allowed_dir
|
162
|
+
return real_path.startswith(allowed_dir_real)
|
128
163
|
except Exception:
|
129
164
|
return False
|
130
165
|
|
@@ -155,11 +190,11 @@ class CommandExecutor:
|
|
155
190
|
- Captures both stdout and stderr
|
156
191
|
"""
|
157
192
|
if len(command_string) > self.security_config.max_command_length:
|
158
|
-
raise CommandSecurityError("Command
|
193
|
+
raise CommandSecurityError(f"Command exceeds maximum length of {self.security_config.max_command_length}")
|
159
194
|
|
160
195
|
try:
|
161
|
-
|
162
196
|
command, args = self.validate_command(command_string)
|
197
|
+
|
163
198
|
return subprocess.run(
|
164
199
|
[command] + args,
|
165
200
|
shell=False,
|
@@ -168,10 +203,12 @@ class CommandExecutor:
|
|
168
203
|
timeout=self.security_config.command_timeout,
|
169
204
|
cwd=self.allowed_dir,
|
170
205
|
)
|
206
|
+
except subprocess.TimeoutExpired:
|
207
|
+
raise CommandTimeoutError(f"Command timed out after {self.security_config.command_timeout} seconds")
|
208
|
+
except CommandError:
|
209
|
+
raise
|
171
210
|
except Exception as e:
|
172
|
-
|
173
|
-
raise
|
174
|
-
raise CommandSecurityError(f"Command execution failed: {str(e)}")
|
211
|
+
raise CommandExecutionError(f"Command execution failed: {str(e)}")
|
175
212
|
|
176
213
|
|
177
214
|
# Load security configuration from environment
|
@@ -187,43 +224,48 @@ def load_security_config() -> SecurityConfig:
|
|
187
224
|
SecurityConfig: Configuration object containing:
|
188
225
|
- allowed_commands: Set of permitted command names
|
189
226
|
- allowed_flags: Set of permitted command flags/options
|
190
|
-
- allowed_patterns: List of regex patterns for valid inputs
|
191
227
|
- max_command_length: Maximum length of command string
|
192
228
|
- command_timeout: Maximum execution time in seconds
|
229
|
+
- allow_all_commands: Whether all commands are allowed
|
230
|
+
- allow_all_flags: Whether all flags are allowed
|
193
231
|
|
194
232
|
Environment Variables:
|
195
|
-
ALLOWED_COMMANDS: Comma-separated list of allowed commands (default: "ls,cat,pwd")
|
196
|
-
ALLOWED_FLAGS: Comma-separated list of allowed flags (default: "-l,-a,--help")
|
197
|
-
ALLOWED_PATTERNS: Comma-separated list of patterns (default: "*.txt,*.log,*.md")
|
233
|
+
ALLOWED_COMMANDS: Comma-separated list of allowed commands or 'all' (default: "ls,cat,pwd")
|
234
|
+
ALLOWED_FLAGS: Comma-separated list of allowed flags or 'all' (default: "-l,-a,--help")
|
198
235
|
MAX_COMMAND_LENGTH: Maximum command string length (default: 1024)
|
199
236
|
COMMAND_TIMEOUT: Command timeout in seconds (default: 30)
|
200
237
|
"""
|
238
|
+
allowed_commands = os.getenv("ALLOWED_COMMANDS", "ls,cat,pwd")
|
239
|
+
allowed_flags = os.getenv("ALLOWED_FLAGS", "-l,-a,--help")
|
240
|
+
|
241
|
+
allow_all_commands = allowed_commands.lower() == 'all'
|
242
|
+
allow_all_flags = allowed_flags.lower() == 'all'
|
243
|
+
|
201
244
|
return SecurityConfig(
|
202
|
-
allowed_commands=set(
|
203
|
-
allowed_flags=set(
|
204
|
-
allowed_patterns=[
|
205
|
-
r"^[\w\-. ]+$", # Basic filename pattern
|
206
|
-
*os.getenv("ALLOWED_PATTERNS", "*.txt,*.log,*.md").split(","),
|
207
|
-
],
|
245
|
+
allowed_commands=set() if allow_all_commands else set(allowed_commands.split(",")),
|
246
|
+
allowed_flags=set() if allow_all_flags else set(allowed_flags.split(",")),
|
208
247
|
max_command_length=int(os.getenv("MAX_COMMAND_LENGTH", "1024")),
|
209
248
|
command_timeout=int(os.getenv("COMMAND_TIMEOUT", "30")),
|
249
|
+
allow_all_commands=allow_all_commands,
|
250
|
+
allow_all_flags=allow_all_flags,
|
210
251
|
)
|
211
252
|
|
212
253
|
|
213
|
-
executor = CommandExecutor(
|
214
|
-
allowed_dir=os.getenv("ALLOWED_DIR", ""), security_config=load_security_config()
|
215
|
-
)
|
254
|
+
executor = CommandExecutor(allowed_dir=os.getenv("ALLOWED_DIR", ""), security_config=load_security_config())
|
216
255
|
|
217
256
|
|
218
257
|
@server.list_tools()
|
219
258
|
async def handle_list_tools() -> list[types.Tool]:
|
259
|
+
commands_desc = "all commands" if executor.security_config.allow_all_commands else ", ".join(executor.security_config.allowed_commands)
|
260
|
+
flags_desc = "all flags" if executor.security_config.allow_all_flags else ", ".join(executor.security_config.allowed_flags)
|
261
|
+
|
220
262
|
return [
|
221
263
|
types.Tool(
|
222
264
|
name="run_command",
|
223
265
|
description=(
|
224
266
|
f"Allows command (CLI) execution in the directory: {executor.allowed_dir}\n\n"
|
225
|
-
f"Available commands: {
|
226
|
-
f"Available flags: {
|
267
|
+
f"Available commands: {commands_desc}\n"
|
268
|
+
f"Available flags: {flags_desc}\n\n"
|
227
269
|
"Note: Shell operators (&&, |, >, >>) are not supported."
|
228
270
|
),
|
229
271
|
inputSchema={
|
@@ -231,7 +273,7 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
231
273
|
"properties": {
|
232
274
|
"command": {
|
233
275
|
"type": "string",
|
234
|
-
"description": "Single command to execute (example: 'ls -l' or 'cat file.txt')"
|
276
|
+
"description": "Single command to execute (example: 'ls -l' or 'cat file.txt')",
|
235
277
|
}
|
236
278
|
},
|
237
279
|
"required": ["command"],
|
@@ -239,26 +281,20 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
239
281
|
),
|
240
282
|
types.Tool(
|
241
283
|
name="show_security_rules",
|
242
|
-
description=(
|
243
|
-
"Show what commands and operations are allowed in this environment.\n"
|
244
|
-
),
|
284
|
+
description=("Show what commands and operations are allowed in this environment.\n"),
|
245
285
|
inputSchema={
|
246
286
|
"type": "object",
|
247
287
|
"properties": {},
|
248
288
|
},
|
249
|
-
)
|
289
|
+
),
|
250
290
|
]
|
251
291
|
|
252
292
|
|
253
293
|
@server.call_tool()
|
254
|
-
async def handle_call_tool(
|
255
|
-
name: str, arguments: Optional[Dict[str, Any]]
|
256
|
-
) -> List[types.TextContent]:
|
294
|
+
async def handle_call_tool(name: str, arguments: Optional[Dict[str, Any]]) -> List[types.TextContent]:
|
257
295
|
if name == "run_command":
|
258
296
|
if not arguments or "command" not in arguments:
|
259
|
-
return [
|
260
|
-
types.TextContent(type="text", text="No command provided", error=True)
|
261
|
-
]
|
297
|
+
return [types.TextContent(type="text", text="No command provided", error=True)]
|
262
298
|
|
263
299
|
try:
|
264
300
|
result = executor.execute(arguments["command"])
|
@@ -267,9 +303,7 @@ async def handle_call_tool(
|
|
267
303
|
if result.stdout:
|
268
304
|
response.append(types.TextContent(type="text", text=result.stdout))
|
269
305
|
if result.stderr:
|
270
|
-
response.append(
|
271
|
-
types.TextContent(type="text", text=result.stderr, error=True)
|
272
|
-
)
|
306
|
+
response.append(types.TextContent(type="text", text=result.stderr, error=True))
|
273
307
|
|
274
308
|
response.append(
|
275
309
|
types.TextContent(
|
@@ -281,11 +315,7 @@ async def handle_call_tool(
|
|
281
315
|
return response
|
282
316
|
|
283
317
|
except CommandSecurityError as e:
|
284
|
-
return [
|
285
|
-
types.TextContent(
|
286
|
-
type="text", text=f"Security violation: {str(e)}", error=True
|
287
|
-
)
|
288
|
-
]
|
318
|
+
return [types.TextContent(type="text", text=f"Security violation: {str(e)}", error=True)]
|
289
319
|
except subprocess.TimeoutExpired:
|
290
320
|
return [
|
291
321
|
types.TextContent(
|
@@ -298,19 +328,19 @@ async def handle_call_tool(
|
|
298
328
|
return [types.TextContent(type="text", text=f"Error: {str(e)}", error=True)]
|
299
329
|
|
300
330
|
elif name == "show_security_rules":
|
331
|
+
commands_desc = "All commands allowed" if executor.security_config.allow_all_commands else ", ".join(sorted(executor.security_config.allowed_commands))
|
332
|
+
flags_desc = "All flags allowed" if executor.security_config.allow_all_flags else ", ".join(sorted(executor.security_config.allowed_flags))
|
333
|
+
|
301
334
|
security_info = (
|
302
335
|
"Security Configuration:\n"
|
303
336
|
f"==================\n"
|
304
337
|
f"Working Directory: {executor.allowed_dir}\n"
|
305
338
|
f"\nAllowed Commands:\n"
|
306
339
|
f"----------------\n"
|
307
|
-
f"{
|
340
|
+
f"{commands_desc}\n"
|
308
341
|
f"\nAllowed Flags:\n"
|
309
342
|
f"-------------\n"
|
310
|
-
f"{
|
311
|
-
f"\nAllowed Patterns:\n"
|
312
|
-
f"----------------\n"
|
313
|
-
f"{', '.join(executor.security_config.allowed_patterns)}\n"
|
343
|
+
f"{flags_desc}\n"
|
314
344
|
f"\nSecurity Limits:\n"
|
315
345
|
f"---------------\n"
|
316
346
|
f"Max Command Length: {executor.security_config.max_command_length} characters\n"
|
@@ -328,10 +358,10 @@ async def main():
|
|
328
358
|
write_stream,
|
329
359
|
InitializationOptions(
|
330
360
|
server_name="cli-mcp-server",
|
331
|
-
server_version="0.
|
361
|
+
server_version="0.2.1",
|
332
362
|
capabilities=server.get_capabilities(
|
333
363
|
notification_options=NotificationOptions(),
|
334
364
|
experimental_capabilities={},
|
335
365
|
),
|
336
366
|
),
|
337
|
-
)
|
367
|
+
)
|
@@ -36,14 +36,14 @@ wheels = [
|
|
36
36
|
|
37
37
|
[[package]]
|
38
38
|
name = "cli-mcp-server"
|
39
|
-
version = "0.1
|
39
|
+
version = "0.2.1"
|
40
40
|
source = { editable = "." }
|
41
41
|
dependencies = [
|
42
42
|
{ name = "mcp" },
|
43
43
|
]
|
44
44
|
|
45
45
|
[package.metadata]
|
46
|
-
requires-dist = [{ name = "mcp", specifier = ">=1.
|
46
|
+
requires-dist = [{ name = "mcp", specifier = ">=1.1.0" }]
|
47
47
|
|
48
48
|
[[package]]
|
49
49
|
name = "click"
|
@@ -132,7 +132,7 @@ wheels = [
|
|
132
132
|
|
133
133
|
[[package]]
|
134
134
|
name = "mcp"
|
135
|
-
version = "1.
|
135
|
+
version = "1.1.2"
|
136
136
|
source = { registry = "https://pypi.org/simple" }
|
137
137
|
dependencies = [
|
138
138
|
{ name = "anyio" },
|
@@ -142,9 +142,9 @@ dependencies = [
|
|
142
142
|
{ name = "sse-starlette" },
|
143
143
|
{ name = "starlette" },
|
144
144
|
]
|
145
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
145
|
+
sdist = { url = "https://files.pythonhosted.org/packages/9b/f3/5cf212e60681ea6da0dbb6e0d1bc0ab2dbf5eebc749b69663d46f114fea1/mcp-1.1.2.tar.gz", hash = "sha256:694aa9df7a8641b24953c935eb72c63136dc948981021525a0add199bdfee402", size = 57628 }
|
146
146
|
wheels = [
|
147
|
-
{ url = "https://files.pythonhosted.org/packages/
|
147
|
+
{ url = "https://files.pythonhosted.org/packages/df/40/9883eac3718b860d4006eba1920bfcb628f0a1fe37fac46a4f4e391edca6/mcp-1.1.2-py3-none-any.whl", hash = "sha256:a4d32d60fd80a1702440ba4751b847a8a88957a1f7b059880953143e9759965a", size = 36652 },
|
148
148
|
]
|
149
149
|
|
150
150
|
[[package]]
|
File without changes
|
File without changes
|
File without changes
|