cli-mcp-server 0.1.2__py3-none-any.whl
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/__init__.py +12 -0
- cli_mcp_server/server.py +337 -0
- cli_mcp_server-0.1.2.dist-info/METADATA +222 -0
- cli_mcp_server-0.1.2.dist-info/RECORD +7 -0
- cli_mcp_server-0.1.2.dist-info/WHEEL +4 -0
- cli_mcp_server-0.1.2.dist-info/entry_points.txt +2 -0
- cli_mcp_server-0.1.2.dist-info/licenses/LICENSE +21 -0
cli_mcp_server/server.py
ADDED
@@ -0,0 +1,337 @@
|
|
1
|
+
import os
|
2
|
+
import re
|
3
|
+
import shlex
|
4
|
+
import subprocess
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from typing import List, Dict, Any, Optional
|
7
|
+
|
8
|
+
import mcp.server.stdio
|
9
|
+
import mcp.types as types
|
10
|
+
from mcp.server import NotificationOptions, Server
|
11
|
+
from mcp.server.models import InitializationOptions
|
12
|
+
|
13
|
+
server = Server("cli-mcp-server")
|
14
|
+
|
15
|
+
|
16
|
+
class CommandSecurityError(Exception):
|
17
|
+
"""
|
18
|
+
Custom exception for command security violations
|
19
|
+
"""
|
20
|
+
|
21
|
+
pass
|
22
|
+
|
23
|
+
|
24
|
+
@dataclass
|
25
|
+
class SecurityConfig:
|
26
|
+
"""
|
27
|
+
Security configuration for command execution
|
28
|
+
"""
|
29
|
+
|
30
|
+
allowed_commands: set[str]
|
31
|
+
allowed_flags: set[str]
|
32
|
+
allowed_patterns: List[str]
|
33
|
+
max_command_length: int
|
34
|
+
command_timeout: int
|
35
|
+
|
36
|
+
|
37
|
+
class CommandExecutor:
|
38
|
+
def __init__(self, allowed_dir: str, security_config: SecurityConfig):
|
39
|
+
if not allowed_dir or not os.path.exists(allowed_dir):
|
40
|
+
raise ValueError("Valid ALLOWED_DIR is required")
|
41
|
+
self.allowed_dir = os.path.abspath(os.path.realpath(allowed_dir))
|
42
|
+
self.security_config = security_config
|
43
|
+
|
44
|
+
def validate_command(self, command_string: str) -> tuple[str, List[str]]:
|
45
|
+
"""
|
46
|
+
Validates and parses a command string for security and formatting.
|
47
|
+
|
48
|
+
Checks the command string for unsupported shell operators and splits it into
|
49
|
+
command and arguments. Only single commands without shell operators are allowed.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
command_string (str): The command string to validate and parse.
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
tuple[str, List[str]]: A tuple containing:
|
56
|
+
- The command name (str)
|
57
|
+
- List of command arguments (List[str])
|
58
|
+
|
59
|
+
Raises:
|
60
|
+
CommandSecurityError: If the command contains unsupported shell operators.
|
61
|
+
"""
|
62
|
+
|
63
|
+
# Check for shell operators that we don't support
|
64
|
+
shell_operators = ["&&", "||", "|", ">", ">>", "<", "<<", ";"]
|
65
|
+
for operator in shell_operators:
|
66
|
+
if operator in command_string:
|
67
|
+
raise CommandSecurityError(
|
68
|
+
f"Shell operator '{operator}' is not supported. "
|
69
|
+
"Only single commands are allowed."
|
70
|
+
)
|
71
|
+
|
72
|
+
try:
|
73
|
+
parts = shlex.split(command_string)
|
74
|
+
if not parts:
|
75
|
+
raise CommandSecurityError("Empty command")
|
76
|
+
|
77
|
+
command, args = parts[0], parts[1:]
|
78
|
+
|
79
|
+
# Validate command
|
80
|
+
if command not in self.security_config.allowed_commands:
|
81
|
+
raise CommandSecurityError(f"Command '{command}' is not allowed")
|
82
|
+
|
83
|
+
# Validate arguments
|
84
|
+
for arg in args:
|
85
|
+
if arg.startswith("-"):
|
86
|
+
if arg not in self.security_config.allowed_flags:
|
87
|
+
raise CommandSecurityError(f"Flag '{arg}' is not allowed")
|
88
|
+
continue
|
89
|
+
|
90
|
+
# Validate path if argument looks like a path
|
91
|
+
if "/" in arg or "\\" in arg or os.path.isabs(arg):
|
92
|
+
full_path = os.path.abspath(os.path.join(self.allowed_dir, arg))
|
93
|
+
if not self._is_path_safe(full_path):
|
94
|
+
raise CommandSecurityError(f"Path '{arg}' is not allowed")
|
95
|
+
|
96
|
+
# Check patterns
|
97
|
+
if not any(
|
98
|
+
re.match(pattern, arg)
|
99
|
+
for pattern in self.security_config.allowed_patterns
|
100
|
+
):
|
101
|
+
raise CommandSecurityError(
|
102
|
+
f"Argument '{arg}' doesn't match allowed patterns"
|
103
|
+
)
|
104
|
+
|
105
|
+
return command, args
|
106
|
+
except ValueError as e:
|
107
|
+
raise CommandSecurityError(f"Invalid command format: {str(e)}")
|
108
|
+
|
109
|
+
def _is_path_safe(self, path: str) -> bool:
|
110
|
+
"""
|
111
|
+
Checks if a given path is safe to access within allowed directory boundaries.
|
112
|
+
|
113
|
+
Validates that the absolute resolved path is within the allowed directory
|
114
|
+
to prevent directory traversal attacks.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
path (str): The path to validate.
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
bool: True if path is within allowed directory, False otherwise.
|
121
|
+
Returns False if path resolution fails for any reason.
|
122
|
+
|
123
|
+
Private method intended for internal use only.
|
124
|
+
"""
|
125
|
+
try:
|
126
|
+
abs_path = os.path.abspath(os.path.realpath(path))
|
127
|
+
return abs_path.startswith(self.allowed_dir)
|
128
|
+
except Exception:
|
129
|
+
return False
|
130
|
+
|
131
|
+
def execute(self, command_string: str) -> subprocess.CompletedProcess:
|
132
|
+
"""
|
133
|
+
Executes a command string in a secure, controlled environment.
|
134
|
+
|
135
|
+
Runs the command after validating it against security constraints including length limits
|
136
|
+
and shell operator restrictions. Executes with controlled parameters for safety.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
command_string (str): The command string to execute.
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
subprocess.CompletedProcess: The result of the command execution containing
|
143
|
+
stdout, stderr, and return code.
|
144
|
+
|
145
|
+
Raises:
|
146
|
+
CommandSecurityError: If the command:
|
147
|
+
- Exceeds maximum length
|
148
|
+
- Contains invalid shell operators
|
149
|
+
- Fails security validation
|
150
|
+
- Fails during execution
|
151
|
+
|
152
|
+
Notes:
|
153
|
+
- Executes with shell=False for security
|
154
|
+
- Uses timeout and working directory constraints
|
155
|
+
- Captures both stdout and stderr
|
156
|
+
"""
|
157
|
+
if len(command_string) > self.security_config.max_command_length:
|
158
|
+
raise CommandSecurityError("Command string too long")
|
159
|
+
|
160
|
+
try:
|
161
|
+
|
162
|
+
command, args = self.validate_command(command_string)
|
163
|
+
return subprocess.run(
|
164
|
+
[command] + args,
|
165
|
+
shell=False,
|
166
|
+
text=True,
|
167
|
+
capture_output=True,
|
168
|
+
timeout=self.security_config.command_timeout,
|
169
|
+
cwd=self.allowed_dir,
|
170
|
+
)
|
171
|
+
except Exception as e:
|
172
|
+
if isinstance(e, CommandSecurityError):
|
173
|
+
raise
|
174
|
+
raise CommandSecurityError(f"Command execution failed: {str(e)}")
|
175
|
+
|
176
|
+
|
177
|
+
# Load security configuration from environment
|
178
|
+
def load_security_config() -> SecurityConfig:
|
179
|
+
"""
|
180
|
+
Loads security configuration from environment variables with default fallbacks.
|
181
|
+
|
182
|
+
Creates a SecurityConfig instance using environment variables to configure allowed
|
183
|
+
commands, flags, patterns, and execution constraints. Uses predefined defaults if
|
184
|
+
environment variables are not set.
|
185
|
+
|
186
|
+
Returns:
|
187
|
+
SecurityConfig: Configuration object containing:
|
188
|
+
- allowed_commands: Set of permitted command names
|
189
|
+
- allowed_flags: Set of permitted command flags/options
|
190
|
+
- allowed_patterns: List of regex patterns for valid inputs
|
191
|
+
- max_command_length: Maximum length of command string
|
192
|
+
- command_timeout: Maximum execution time in seconds
|
193
|
+
|
194
|
+
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")
|
198
|
+
MAX_COMMAND_LENGTH: Maximum command string length (default: 1024)
|
199
|
+
COMMAND_TIMEOUT: Command timeout in seconds (default: 30)
|
200
|
+
"""
|
201
|
+
return SecurityConfig(
|
202
|
+
allowed_commands=set(os.getenv("ALLOWED_COMMANDS", "ls,cat,pwd").split(",")),
|
203
|
+
allowed_flags=set(os.getenv("ALLOWED_FLAGS", "-l,-a,--help").split(",")),
|
204
|
+
allowed_patterns=[
|
205
|
+
r"^[\w\-. ]+$", # Basic filename pattern
|
206
|
+
*os.getenv("ALLOWED_PATTERNS", "*.txt,*.log,*.md").split(","),
|
207
|
+
],
|
208
|
+
max_command_length=int(os.getenv("MAX_COMMAND_LENGTH", "1024")),
|
209
|
+
command_timeout=int(os.getenv("COMMAND_TIMEOUT", "30")),
|
210
|
+
)
|
211
|
+
|
212
|
+
|
213
|
+
executor = CommandExecutor(
|
214
|
+
allowed_dir=os.getenv("ALLOWED_DIR", ""), security_config=load_security_config()
|
215
|
+
)
|
216
|
+
|
217
|
+
|
218
|
+
@server.list_tools()
|
219
|
+
async def handle_list_tools() -> list[types.Tool]:
|
220
|
+
return [
|
221
|
+
types.Tool(
|
222
|
+
name="run_command",
|
223
|
+
description=(
|
224
|
+
f"Allows command (CLI) execution in the directory: {executor.allowed_dir}\n\n"
|
225
|
+
f"Available commands: {', '.join(executor.security_config.allowed_commands)}\n"
|
226
|
+
f"Available flags: {', '.join(executor.security_config.allowed_flags)}\n\n"
|
227
|
+
"Note: Shell operators (&&, |, >, >>) are not supported."
|
228
|
+
),
|
229
|
+
inputSchema={
|
230
|
+
"type": "object",
|
231
|
+
"properties": {
|
232
|
+
"command": {
|
233
|
+
"type": "string",
|
234
|
+
"description": "Single command to execute (example: 'ls -l' or 'cat file.txt')"
|
235
|
+
}
|
236
|
+
},
|
237
|
+
"required": ["command"],
|
238
|
+
},
|
239
|
+
),
|
240
|
+
types.Tool(
|
241
|
+
name="show_security_rules",
|
242
|
+
description=(
|
243
|
+
"Show what commands and operations are allowed in this environment.\n"
|
244
|
+
),
|
245
|
+
inputSchema={
|
246
|
+
"type": "object",
|
247
|
+
"properties": {},
|
248
|
+
},
|
249
|
+
)
|
250
|
+
]
|
251
|
+
|
252
|
+
|
253
|
+
@server.call_tool()
|
254
|
+
async def handle_call_tool(
|
255
|
+
name: str, arguments: Optional[Dict[str, Any]]
|
256
|
+
) -> List[types.TextContent]:
|
257
|
+
if name == "run_command":
|
258
|
+
if not arguments or "command" not in arguments:
|
259
|
+
return [
|
260
|
+
types.TextContent(type="text", text="No command provided", error=True)
|
261
|
+
]
|
262
|
+
|
263
|
+
try:
|
264
|
+
result = executor.execute(arguments["command"])
|
265
|
+
|
266
|
+
response = []
|
267
|
+
if result.stdout:
|
268
|
+
response.append(types.TextContent(type="text", text=result.stdout))
|
269
|
+
if result.stderr:
|
270
|
+
response.append(
|
271
|
+
types.TextContent(type="text", text=result.stderr, error=True)
|
272
|
+
)
|
273
|
+
|
274
|
+
response.append(
|
275
|
+
types.TextContent(
|
276
|
+
type="text",
|
277
|
+
text=f"\nCommand completed with return code: {result.returncode}",
|
278
|
+
)
|
279
|
+
)
|
280
|
+
|
281
|
+
return response
|
282
|
+
|
283
|
+
except CommandSecurityError as e:
|
284
|
+
return [
|
285
|
+
types.TextContent(
|
286
|
+
type="text", text=f"Security violation: {str(e)}", error=True
|
287
|
+
)
|
288
|
+
]
|
289
|
+
except subprocess.TimeoutExpired:
|
290
|
+
return [
|
291
|
+
types.TextContent(
|
292
|
+
type="text",
|
293
|
+
text=f"Command timed out after {executor.security_config.command_timeout} seconds",
|
294
|
+
error=True,
|
295
|
+
)
|
296
|
+
]
|
297
|
+
except Exception as e:
|
298
|
+
return [types.TextContent(type="text", text=f"Error: {str(e)}", error=True)]
|
299
|
+
|
300
|
+
elif name == "show_security_rules":
|
301
|
+
security_info = (
|
302
|
+
"Security Configuration:\n"
|
303
|
+
f"==================\n"
|
304
|
+
f"Working Directory: {executor.allowed_dir}\n"
|
305
|
+
f"\nAllowed Commands:\n"
|
306
|
+
f"----------------\n"
|
307
|
+
f"{', '.join(sorted(executor.security_config.allowed_commands))}\n"
|
308
|
+
f"\nAllowed Flags:\n"
|
309
|
+
f"-------------\n"
|
310
|
+
f"{', '.join(sorted(executor.security_config.allowed_flags))}\n"
|
311
|
+
f"\nAllowed Patterns:\n"
|
312
|
+
f"----------------\n"
|
313
|
+
f"{', '.join(executor.security_config.allowed_patterns)}\n"
|
314
|
+
f"\nSecurity Limits:\n"
|
315
|
+
f"---------------\n"
|
316
|
+
f"Max Command Length: {executor.security_config.max_command_length} characters\n"
|
317
|
+
f"Command Timeout: {executor.security_config.command_timeout} seconds\n"
|
318
|
+
)
|
319
|
+
return [types.TextContent(type="text", text=security_info)]
|
320
|
+
|
321
|
+
raise ValueError(f"Unknown tool: {name}")
|
322
|
+
|
323
|
+
|
324
|
+
async def main():
|
325
|
+
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
326
|
+
await server.run(
|
327
|
+
read_stream,
|
328
|
+
write_stream,
|
329
|
+
InitializationOptions(
|
330
|
+
server_name="cli-mcp-server",
|
331
|
+
server_version="0.1.1",
|
332
|
+
capabilities=server.get_capabilities(
|
333
|
+
notification_options=NotificationOptions(),
|
334
|
+
experimental_capabilities={},
|
335
|
+
),
|
336
|
+
),
|
337
|
+
)
|
@@ -0,0 +1,222 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: cli-mcp-server
|
3
|
+
Version: 0.1.2
|
4
|
+
Summary: Command line interface for MCP clients with secure execution and customizable security policies
|
5
|
+
Project-URL: Homepage, https://github.com/MladenSU/cli-mcp-server
|
6
|
+
Project-URL: Documentation, https://github.com/MladenSU/cli-mcp-server#readme
|
7
|
+
Project-URL: Repository, https://github.com/MladenSU/cli-mcp-server.git
|
8
|
+
Project-URL: Bug Tracker, https://github.com/MladenSU/cli-mcp-server/issues
|
9
|
+
Author-email: Mladen <fangs-lever6n@icloud.com>
|
10
|
+
Requires-Python: >=3.10
|
11
|
+
Requires-Dist: mcp>=1.0.0
|
12
|
+
Description-Content-Type: text/markdown
|
13
|
+
|
14
|
+
# CLI MCP Server
|
15
|
+
|
16
|
+
---
|
17
|
+
|
18
|
+
A secure Model Context Protocol (MCP) server implementation for executing controlled command-line operations with
|
19
|
+
comprehensive security
|
20
|
+
features.
|
21
|
+
|
22
|
+

|
23
|
+

|
24
|
+

|
25
|
+
|
26
|
+
---
|
27
|
+
|
28
|
+
# Table of Contents
|
29
|
+
|
30
|
+
1. [Overview](#overview)
|
31
|
+
2. [Features](#features)
|
32
|
+
3. [Configuration](#configuration)
|
33
|
+
4. [Available Tools](#available-tools)
|
34
|
+
- [run_command](#run_command)
|
35
|
+
- [show_security_rules](#show_security_rules)
|
36
|
+
5. [Usage with Claude Desktop](#usage-with-claude-desktop)
|
37
|
+
- [Development/Unpublished Servers Configuration](#developmentunpublished-servers-configuration)
|
38
|
+
- [Published Servers Configuration](#published-servers-configuration)
|
39
|
+
6. [Security Features](#security-features)
|
40
|
+
7. [Error Handling](#error-handling)
|
41
|
+
8. [Development](#development)
|
42
|
+
- [Prerequisites](#prerequisites)
|
43
|
+
- [Building and Publishing](#building-and-publishing)
|
44
|
+
- [Debugging](#debugging)
|
45
|
+
9. [License](#license)
|
46
|
+
|
47
|
+
---
|
48
|
+
|
49
|
+
## Overview
|
50
|
+
|
51
|
+
This MCP server enables secure command-line execution with robust security measures including command whitelisting, path
|
52
|
+
validation, and
|
53
|
+
execution controls. Perfect for providing controlled CLI access to LLM applications while maintaining security.
|
54
|
+
|
55
|
+
## Features
|
56
|
+
|
57
|
+
- 🔒 Secure command execution with strict validation
|
58
|
+
- ⚙️ Configurable command and flag whitelisting
|
59
|
+
- 🛡️ Path traversal prevention
|
60
|
+
- 🚫 Shell operator injection protection
|
61
|
+
- ⏱️ Execution timeouts and length limits
|
62
|
+
- 📝 Detailed error reporting
|
63
|
+
- 🔄 Async operation support
|
64
|
+
|
65
|
+
## Configuration
|
66
|
+
|
67
|
+
Configure the server using environment variables:
|
68
|
+
|
69
|
+
| Variable | Description | Default |
|
70
|
+
|----------------------|------------------------------------------|--------------------|
|
71
|
+
| `ALLOWED_DIR` | Base directory for command execution | Required |
|
72
|
+
| `ALLOWED_COMMANDS` | Comma-separated list of allowed commands | `ls,cat,pwd` |
|
73
|
+
| `ALLOWED_FLAGS` | Comma-separated list of allowed flags | `-l,-a,--help` |
|
74
|
+
| `ALLOWED_PATTERNS` | Comma-separated file patterns | `*.txt,*.log,*.md` |
|
75
|
+
| `MAX_COMMAND_LENGTH` | Maximum command string length | `1024` |
|
76
|
+
| `COMMAND_TIMEOUT` | Command execution timeout (seconds) | `30` |
|
77
|
+
|
78
|
+
## Available Tools
|
79
|
+
|
80
|
+
### run_command
|
81
|
+
|
82
|
+
Executes whitelisted CLI commands within allowed directories.
|
83
|
+
|
84
|
+
**Input Schema:**
|
85
|
+
|
86
|
+
```json
|
87
|
+
{
|
88
|
+
"command": {
|
89
|
+
"type": "string",
|
90
|
+
"description": "Command to execute (e.g., 'ls -l' or 'cat file.txt')"
|
91
|
+
}
|
92
|
+
}
|
93
|
+
```
|
94
|
+
|
95
|
+
### show_security_rules
|
96
|
+
|
97
|
+
Displays current security configuration and restrictions.
|
98
|
+
|
99
|
+
## Usage with Claude Desktop
|
100
|
+
|
101
|
+
Add to your `~/Library/Application\ Support/Claude/claude_desktop_config.json`:
|
102
|
+
|
103
|
+
> Development/Unpublished Servers Configuration
|
104
|
+
|
105
|
+
```json
|
106
|
+
{
|
107
|
+
"mcpServers": {
|
108
|
+
"cli-mcp-server": {
|
109
|
+
"command": "uv",
|
110
|
+
"args": [
|
111
|
+
"--directory",
|
112
|
+
"<path/to/the/repo>/cli-mcp-server",
|
113
|
+
"run",
|
114
|
+
"cli-mcp-server"
|
115
|
+
],
|
116
|
+
"env": {
|
117
|
+
"ALLOWED_DIR": "</your/desired/dir>",
|
118
|
+
"ALLOWED_COMMANDS": "ls,cat,pwd,echo",
|
119
|
+
"ALLOWED_FLAGS": "-l,-a,--help,--version",
|
120
|
+
"ALLOWED_PATTERNS": "*.txt,*.log,*.md",
|
121
|
+
"MAX_COMMAND_LENGTH": "1024",
|
122
|
+
"COMMAND_TIMEOUT": "30"
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
```
|
128
|
+
|
129
|
+
> Published Servers Configuration
|
130
|
+
|
131
|
+
```json
|
132
|
+
{
|
133
|
+
"mcpServers": {
|
134
|
+
"cli-mcp-server": {
|
135
|
+
"command": "uvx",
|
136
|
+
"args": [
|
137
|
+
"cli-mcp-server"
|
138
|
+
],
|
139
|
+
"env": {
|
140
|
+
"ALLOWED_DIR": "</your/desired/dir>",
|
141
|
+
"ALLOWED_COMMANDS": "ls,cat,pwd,echo",
|
142
|
+
"ALLOWED_FLAGS": "-l,-a,--help,--version",
|
143
|
+
"ALLOWED_PATTERNS": "*.txt,*.log,*.md",
|
144
|
+
"MAX_COMMAND_LENGTH": "1024",
|
145
|
+
"COMMAND_TIMEOUT": "30"
|
146
|
+
}
|
147
|
+
}
|
148
|
+
}
|
149
|
+
}
|
150
|
+
```
|
151
|
+
|
152
|
+
## Security Features
|
153
|
+
|
154
|
+
- ✅ Command whitelist enforcement
|
155
|
+
- ✅ Flag validation
|
156
|
+
- ✅ Path traversal prevention
|
157
|
+
- ✅ Shell operator blocking
|
158
|
+
- ✅ Command length limits
|
159
|
+
- ✅ Execution timeouts
|
160
|
+
- ✅ Working directory restrictions
|
161
|
+
|
162
|
+
## Error Handling
|
163
|
+
|
164
|
+
The server provides detailed error messages for:
|
165
|
+
|
166
|
+
- Security violations
|
167
|
+
- Command timeouts
|
168
|
+
- Invalid command formats
|
169
|
+
- Path security violations
|
170
|
+
- Execution failures
|
171
|
+
|
172
|
+
## Development
|
173
|
+
|
174
|
+
### Prerequisites
|
175
|
+
|
176
|
+
- Python 3.10+
|
177
|
+
- MCP protocol library
|
178
|
+
|
179
|
+
## Development
|
180
|
+
|
181
|
+
### Building and Publishing
|
182
|
+
|
183
|
+
To prepare the package for distribution:
|
184
|
+
|
185
|
+
1. Sync dependencies and update lockfile:
|
186
|
+
```bash
|
187
|
+
uv sync
|
188
|
+
```
|
189
|
+
|
190
|
+
2. Build package distributions:
|
191
|
+
```bash
|
192
|
+
uv build
|
193
|
+
```
|
194
|
+
|
195
|
+
> This will create source and wheel distributions in the `dist/` directory.
|
196
|
+
|
197
|
+
3. Publish to PyPI:
|
198
|
+
```bash
|
199
|
+
uv publish --token {{YOUR_PYPI_API_TOKEN}}
|
200
|
+
```
|
201
|
+
|
202
|
+
### Debugging
|
203
|
+
|
204
|
+
Since MCP servers run over stdio, debugging can be challenging. For the best debugging
|
205
|
+
experience, we strongly recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).
|
206
|
+
|
207
|
+
You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with
|
208
|
+
this command:
|
209
|
+
|
210
|
+
```bash
|
211
|
+
npx @modelcontextprotocol/inspector uv --directory {{your source code local directory}}/unichat-mcp-server run unichat-mcp-server
|
212
|
+
```
|
213
|
+
|
214
|
+
Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.
|
215
|
+
|
216
|
+
## License
|
217
|
+
|
218
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
219
|
+
|
220
|
+
---
|
221
|
+
|
222
|
+
For more information or support, please open an issue on the project repository.
|
@@ -0,0 +1,7 @@
|
|
1
|
+
cli_mcp_server/__init__.py,sha256=F95EXGXtBnIVQqu-bXcasy5OfORA-gzLnvXyqWFtjcY,216
|
2
|
+
cli_mcp_server/server.py,sha256=vXfPFCIbPjZ8J6cyRinCMZgFYBev5muwmPkq4ZFAh3s,12336
|
3
|
+
cli_mcp_server-0.1.2.dist-info/METADATA,sha256=daSaUn_7-fGogH5fHN9VOPWETW1x2hO7pauuZ59x5eI,6199
|
4
|
+
cli_mcp_server-0.1.2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
5
|
+
cli_mcp_server-0.1.2.dist-info/entry_points.txt,sha256=07bDmJJSXg-6VCFEFTOlsGoxI-0faJafT1yEEjUdN-U,55
|
6
|
+
cli_mcp_server-0.1.2.dist-info/licenses/LICENSE,sha256=85rOR_IMpb2VzXBA4VCRZh_KWlaO1Rly8HDYDwGgMWk,1062
|
7
|
+
cli_mcp_server-0.1.2.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Mladen
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|