py-mcpdock-cli 1.0.13__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/__init__.py +0 -0
- cli/commands/__init__.py +0 -0
- cli/commands/install.py +182 -0
- cli/commands/run.py +148 -0
- cli/config/__init__.py +0 -0
- cli/config/app_config.py +11 -0
- cli/config/client_config.py +248 -0
- cli/main.py +12 -0
- cli/mock_servers.json +186 -0
- cli/registry.py +136 -0
- cli/runners/__init__.py +21 -0
- cli/runners/command_runner.py +172 -0
- cli/runners/stdio_runner.py +494 -0
- cli/runners/stream_http_runner.py +166 -0
- cli/runners/ws_runner.py +43 -0
- cli/types/__init__.py +0 -0
- cli/types/registry.py +69 -0
- cli/utils/__init__.py +0 -0
- cli/utils/client.py +0 -0
- cli/utils/config.py +441 -0
- cli/utils/logger.py +79 -0
- cli/utils/runtime.py +163 -0
- py_mcpdock_cli-1.0.13.dist-info/METADATA +28 -0
- py_mcpdock_cli-1.0.13.dist-info/RECORD +27 -0
- py_mcpdock_cli-1.0.13.dist-info/WHEEL +5 -0
- py_mcpdock_cli-1.0.13.dist-info/entry_points.txt +2 -0
- py_mcpdock_cli-1.0.13.dist-info/top_level.txt +1 -0
cli/utils/runtime.py
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
import subprocess
|
2
|
+
import sys
|
3
|
+
import shutil
|
4
|
+
import os
|
5
|
+
from typing import Dict, Optional, List
|
6
|
+
import questionary
|
7
|
+
from rich import print as rprint
|
8
|
+
|
9
|
+
from ..types.registry import ConnectionDetails, RegistryServer # Assuming RegistryServer includes connections
|
10
|
+
from .logger import verbose
|
11
|
+
|
12
|
+
|
13
|
+
def check_command_installed(command: str) -> bool:
|
14
|
+
"""Checks if a command is available in the system's PATH."""
|
15
|
+
if not shutil.which(command):
|
16
|
+
verbose(f"Command '{command}' not found in PATH.")
|
17
|
+
return False
|
18
|
+
# Optionally, run a version command to be more certain
|
19
|
+
try:
|
20
|
+
# Use shell=True if command might be an alias or shell function, but be cautious
|
21
|
+
# Using the direct path from shutil.which is safer if possible
|
22
|
+
cmd_path = shutil.which(command)
|
23
|
+
if not cmd_path:
|
24
|
+
return False # Should not happen if which found it, but safety check
|
25
|
+
result = subprocess.run([cmd_path, "--version"], capture_output=True, text=True, timeout=5, check=False)
|
26
|
+
verbose(f"'{command} --version' exited with code {result.returncode}")
|
27
|
+
# Check return code - 0 usually means success
|
28
|
+
return result.returncode == 0
|
29
|
+
except (subprocess.TimeoutExpired, OSError, FileNotFoundError) as e:
|
30
|
+
verbose(f"Error checking version for '{command}': {e}")
|
31
|
+
# If version check fails, assume it might still work or is problematic
|
32
|
+
return False # Treat version check failure as potentially not installed correctly
|
33
|
+
except Exception as e:
|
34
|
+
verbose(f"Unexpected error checking '{command}': {e}")
|
35
|
+
return False
|
36
|
+
|
37
|
+
async def prompt_for_install(command: str, install_url: str, command_name: str) -> bool:
|
38
|
+
"""Asks the user if they want to install a missing command."""
|
39
|
+
should_install = await questionary.confirm(
|
40
|
+
f"{command_name} is required for this operation. Would you like to attempt installation?",
|
41
|
+
default=True
|
42
|
+
).ask_async()
|
43
|
+
|
44
|
+
if not should_install:
|
45
|
+
rprint(f"[yellow]{command_name} installation declined. You can install it manually from {install_url}[/yellow]")
|
46
|
+
return False
|
47
|
+
|
48
|
+
return await install_command(command, install_url, command_name)
|
49
|
+
|
50
|
+
async def install_command(command: str, install_url: str, command_name: str) -> bool:
|
51
|
+
"""Attempts to install a command using provided curl/powershell scripts."""
|
52
|
+
rprint(f"Attempting to install {command_name}...")
|
53
|
+
try:
|
54
|
+
if sys.platform == "win32":
|
55
|
+
ps_command = f"powershell -ExecutionPolicy ByPass -NoProfile -Command \"irm '{install_url}/install.ps1' | iex\""
|
56
|
+
verbose(f"Running install command (Windows): {ps_command}")
|
57
|
+
# Using shell=True for powershell pipeline
|
58
|
+
result = subprocess.run(ps_command, shell=True, capture_output=True, text=True, check=True)
|
59
|
+
else:
|
60
|
+
sh_command = f"curl -LsSf '{install_url}/install.sh' | sh"
|
61
|
+
verbose(f"Running install command (Unix): {sh_command}")
|
62
|
+
# Using shell=True for curl pipeline
|
63
|
+
result = subprocess.run(sh_command, shell=True, capture_output=True, text=True, check=True)
|
64
|
+
|
65
|
+
verbose(f"Installation stdout:\n{result.stdout}")
|
66
|
+
if result.stderr:
|
67
|
+
verbose(f"Installation stderr:\n{result.stderr}")
|
68
|
+
rprint(f"[green]✓ {command_name} installed successfully.[/green]")
|
69
|
+
return True
|
70
|
+
except subprocess.CalledProcessError as e:
|
71
|
+
rprint(f"[red]Failed to install {command_name}.[/red]")
|
72
|
+
rprint(f"Stderr:\n{e.stderr}")
|
73
|
+
rprint(f"You can try installing it manually from {install_url}")
|
74
|
+
return False
|
75
|
+
except Exception as e:
|
76
|
+
rprint(f"[red]An unexpected error occurred during installation: {e}[/red]")
|
77
|
+
rprint(f"You can try installing it manually from {install_url}")
|
78
|
+
return False
|
79
|
+
|
80
|
+
def is_command_required(connection: ConnectionDetails, command: str) -> bool:
|
81
|
+
"""Checks if a command is listed in the stdioFunction for a stdio connection."""
|
82
|
+
return (
|
83
|
+
connection.type == "stdio" and
|
84
|
+
isinstance(connection.stdioFunction, list) and
|
85
|
+
command in connection.stdioFunction
|
86
|
+
)
|
87
|
+
|
88
|
+
async def ensure_command_installed(
|
89
|
+
connection: ConnectionDetails,
|
90
|
+
command: str,
|
91
|
+
command_name: str,
|
92
|
+
install_url: str,
|
93
|
+
) -> bool:
|
94
|
+
"""Checks if a required command is installed, prompts to install if not."""
|
95
|
+
if is_command_required(connection, command):
|
96
|
+
verbose(f"{command_name} installation check required for connection type {connection.type}.")
|
97
|
+
if not check_command_installed(command):
|
98
|
+
rprint(f"[yellow]Required command '{command_name}' ('{command}') not found or not working.[/yellow]")
|
99
|
+
installed = await prompt_for_install(command, install_url, command_name)
|
100
|
+
if not installed:
|
101
|
+
rprint(f"[yellow]Warning: {command_name} is not installed. The server might fail to launch.[/yellow]")
|
102
|
+
return False
|
103
|
+
# Re-check after installation attempt
|
104
|
+
if not check_command_installed(command):
|
105
|
+
rprint(f"[red]Error: {command_name} installation seemed to succeed, but '{command}' is still not working correctly.[/red]")
|
106
|
+
return False
|
107
|
+
return True # Installed successfully
|
108
|
+
else:
|
109
|
+
verbose(f"Command '{command_name}' ('{command}') is installed.")
|
110
|
+
return True # Already installed
|
111
|
+
else:
|
112
|
+
verbose(f"Command '{command_name}' not required for this connection.")
|
113
|
+
return True # Not required for this connection
|
114
|
+
|
115
|
+
async def ensure_uv_installed(connection: ConnectionDetails) -> bool:
|
116
|
+
"""Ensures UV (uvx command) is installed if required by the connection."""
|
117
|
+
# Assuming uvx is the command to check for uv
|
118
|
+
return await ensure_command_installed(connection, "uvx", "UV", "https://astral.sh/uv")
|
119
|
+
|
120
|
+
async def ensure_bun_installed(connection: ConnectionDetails) -> bool:
|
121
|
+
"""Ensures Bun (bunx command) is installed if required by the connection."""
|
122
|
+
# Assuming bunx is the command to check for bun
|
123
|
+
return await ensure_command_installed(connection, "bunx", "Bun", "https://bun.sh")
|
124
|
+
|
125
|
+
def get_runtime_environment(base_env: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
126
|
+
"""Gets runtime environment variables.
|
127
|
+
|
128
|
+
Placeholder: This function needs to replicate the behavior of
|
129
|
+
`@modelcontextprotocol/sdk/client/stdio.js#getDefaultEnvironment`
|
130
|
+
if specific environment variables are required by the MCP servers.
|
131
|
+
Currently, it merges provided base_env with the current process environment.
|
132
|
+
"""
|
133
|
+
default_env = os.environ.copy()
|
134
|
+
verbose("Using current process environment as default runtime environment.")
|
135
|
+
# --- Placeholder Start ---
|
136
|
+
# TODO: Determine if specific MCP environment variables are needed
|
137
|
+
# and set them here if possible.
|
138
|
+
# Example: default_env["MCP_PYTHON_CLIENT"] = "1"
|
139
|
+
# --- Placeholder End ---
|
140
|
+
|
141
|
+
if base_env:
|
142
|
+
return {**default_env, **base_env}
|
143
|
+
else:
|
144
|
+
return default_env
|
145
|
+
|
146
|
+
def check_and_notify_remote_server(server: RegistryServer) -> bool:
|
147
|
+
"""Checks if the server is remote and prints a security notice if it is."""
|
148
|
+
# Determine if remote based on connections or explicit flag
|
149
|
+
is_remote = (
|
150
|
+
any(conn.type == "ws" and conn.deploymentUrl for conn in server.connections)
|
151
|
+
and server.remote is not False # Explicitly false overrides ws check
|
152
|
+
)
|
153
|
+
|
154
|
+
if is_remote:
|
155
|
+
verbose("Remote server detected, showing security notice")
|
156
|
+
rprint(
|
157
|
+
"[blue]Installing remote server. Please ensure you trust the server author, "
|
158
|
+
"especially when sharing sensitive data.[/blue]"
|
159
|
+
)
|
160
|
+
# Add link if available
|
161
|
+
# rprint(f"[blue]For information on data policy, please visit: [underline]https://example.com/data-policy[/underline][/blue]")
|
162
|
+
|
163
|
+
return is_remote
|
@@ -0,0 +1,28 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: py-mcpdock-cli
|
3
|
+
Version: 1.0.13
|
4
|
+
Summary: Python CLI for managing MCP servers
|
5
|
+
Author-email: dw <qindongwoxin@gmail.com>
|
6
|
+
Project-URL: Homepage, https://github.com/yourusername/py-mcpdock-cli
|
7
|
+
Project-URL: Bug Tracker, https://github.com/yourusername/py-mcpdock-cli/issues
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
14
|
+
Classifier: Operating System :: OS Independent
|
15
|
+
Requires-Python: >=3.8
|
16
|
+
Description-Content-Type: text/markdown
|
17
|
+
Requires-Dist: click>=8.0.0
|
18
|
+
Requires-Dist: questionary>=1.10.0
|
19
|
+
Requires-Dist: rich>=12.0.0
|
20
|
+
Requires-Dist: asyncio>=3.4.3
|
21
|
+
Requires-Dist: python-dotenv>=1.0.0
|
22
|
+
Requires-Dist: mcp
|
23
|
+
Requires-Dist: aiohttp>=3.8.0
|
24
|
+
|
25
|
+
# MCP CLI Python Implementation
|
26
|
+
|
27
|
+
This is a Python implementation of the Model Context Protocol (MCP) command-line tool.
|
28
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
cli/main.py,sha256=EmP_Om2c0ltcu58B-beGaQXIwnDzme1hO-r4g-tij5w,317
|
3
|
+
cli/mock_servers.json,sha256=12t6b6xlVL25G5nc1CIUxJRCBGWBqhqq6wpWfRYkfq4,6154
|
4
|
+
cli/registry.py,sha256=4-kXmj_NJNgErGvnes0L9H1UcS0F36fW-odVBzQqlos,4479
|
5
|
+
cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
cli/commands/install.py,sha256=VHk3uTwqYfoW0h_NOAsyBOXMl0a-J3WXTfxm1qwuFEQ,7431
|
7
|
+
cli/commands/run.py,sha256=PMzyyiQKINY9ED0cB0aynTtWuMHyK53bvzvT_LEM3pQ,5444
|
8
|
+
cli/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
cli/config/app_config.py,sha256=nvAkTEX9qgNx1QIK8ITskAmYtBX32Ihdb-d5bDr8MQQ,253
|
10
|
+
cli/config/client_config.py,sha256=ct_BGjbKyBSvHKE5iTICKR7NmJDp4g44_o7h3koyngs,10473
|
11
|
+
cli/runners/__init__.py,sha256=wyvNbZA3TnWv10gM5dmVFPs8FZgAJhi7z9i1mNSOXAQ,734
|
12
|
+
cli/runners/command_runner.py,sha256=J5f7qaHtbOr1tOwo_QAQYMEp_nQUl6_ex6mCzpLFNpk,5119
|
13
|
+
cli/runners/stdio_runner.py,sha256=WDUf-loiJHUt44AEvzJeAtxgqrUofGUlFwGbfYhzAPc,20493
|
14
|
+
cli/runners/stream_http_runner.py,sha256=rf3rMcMqgqcIz0YaC91g6KnQKL1DqkvX-T3eRP_DCWQ,5998
|
15
|
+
cli/runners/ws_runner.py,sha256=2hePDQGFThkihYZR6_Zo0B1COL1Geymevy6GrdcQRpA,1393
|
16
|
+
cli/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
+
cli/types/registry.py,sha256=ivIpSTAKsT_vj3eQBn7rBeQvonUQcCMEZXB0-uBZ-No,2024
|
18
|
+
cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
|
+
cli/utils/client.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
+
cli/utils/config.py,sha256=vTWnr8brTyuuwTkGWn3C7GM32xVRagXHjn2P3LxDtK4,16317
|
21
|
+
cli/utils/logger.py,sha256=wfP2KJIORFTj8KqTHzhqRJDmDsbjKst8t4_y9yuD4p0,2280
|
22
|
+
cli/utils/runtime.py,sha256=m5_8leYccmSJeNb9XI__951HnrHZNo0Bn40rFAbHtgU,7829
|
23
|
+
py_mcpdock_cli-1.0.13.dist-info/METADATA,sha256=t6GpTTsDbIjAfs5GZ5F35kFVBRhWUeyC2cOrz-oqyfM,1035
|
24
|
+
py_mcpdock_cli-1.0.13.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
|
25
|
+
py_mcpdock_cli-1.0.13.dist-info/entry_points.txt,sha256=LJzmhLLM_PEyc3ALv8YV_dPsdXY7hj3Pd9vJ6fHQly8,38
|
26
|
+
py_mcpdock_cli-1.0.13.dist-info/top_level.txt,sha256=2ImG917oaVHlm0nP9oJE-Qrgs-fq_fGWgba2H1f8fpE,4
|
27
|
+
py_mcpdock_cli-1.0.13.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
cli
|