py-mcpdock-cli 1.0.13__py3-none-any.whl → 1.0.18__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/commands/install.py +78 -81
- cli/commands/query_pid.py +36 -0
- cli/commands/run.py +9 -11
- cli/main.py +6 -2
- cli/runners/stdio_runner.py +112 -320
- cli/utils/config.py +120 -21
- cli/utils/logger.py +4 -4
- cli/utils/mcp_utils.py +314 -0
- cli/utils/process_utils.py +656 -0
- cli/utils/run_query_pid_by_packagename.py +62 -0
- {py_mcpdock_cli-1.0.13.dist-info → py_mcpdock_cli-1.0.18.dist-info}/METADATA +3 -1
- {py_mcpdock_cli-1.0.13.dist-info → py_mcpdock_cli-1.0.18.dist-info}/RECORD +15 -11
- {py_mcpdock_cli-1.0.13.dist-info → py_mcpdock_cli-1.0.18.dist-info}/WHEEL +1 -1
- py_mcpdock_cli-1.0.18.dist-info/entry_points.txt +2 -0
- py_mcpdock_cli-1.0.13.dist-info/entry_points.txt +0 -2
- {py_mcpdock_cli-1.0.13.dist-info → py_mcpdock_cli-1.0.18.dist-info}/top_level.txt +0 -0
cli/commands/install.py
CHANGED
@@ -77,84 +77,81 @@ async def install_mcp_server(
|
|
77
77
|
target_client = target_client or "claude" # Default to Claude if no client specified
|
78
78
|
verbose(f"Initiating installation of {package_identifier} for {target_client}")
|
79
79
|
|
80
|
-
console = Console()
|
81
|
-
|
82
80
|
# Create and start spinner for package resolution
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
raise
|
81
|
+
try:
|
82
|
+
# Resolve the package (fetch server metadata)
|
83
|
+
verbose(f"Resolving package: {package_identifier}")
|
84
|
+
server_data = await resolve_package(package_identifier)
|
85
|
+
verbose(f"Package resolved successfully: {server_data.qualifiedName}")
|
86
|
+
|
87
|
+
# Choose the appropriate connection type
|
88
|
+
verbose("Choosing connection type...")
|
89
|
+
connection = choose_connection(server_data)
|
90
|
+
verbose(f"Selected connection type: {connection.type}")
|
91
|
+
|
92
|
+
# Check for required runtimes and install if needed
|
93
|
+
# Commented out as these are specific to JS environment
|
94
|
+
# await ensure_uv_installed(connection)
|
95
|
+
# await ensure_bun_installed(connection)
|
96
|
+
|
97
|
+
# Inform users of remote server installation if applicable
|
98
|
+
is_remote = check_and_notify_remote_server(server_data)
|
99
|
+
if is_remote:
|
100
|
+
verbose("Remote server detected, notification displayed")
|
101
|
+
|
102
|
+
# Get the validated config values, no prompting
|
103
|
+
verbose(f"Validating configuration for {package_identifier}...")
|
104
|
+
collected_config_values = await validate_config_values(connection, initial_config)
|
105
|
+
|
106
|
+
# Determine if we need to pass config flag
|
107
|
+
config_flag_needed = initial_config is not None
|
108
|
+
|
109
|
+
verbose(f"Config values validated: {json.dumps(collected_config_values, indent=2)}")
|
110
|
+
verbose(f"Using config flag: {config_flag_needed}")
|
111
|
+
|
112
|
+
# Format the server configuration
|
113
|
+
verbose("Formatting server configuration...")
|
114
|
+
server_config = format_server_config(
|
115
|
+
package_identifier,
|
116
|
+
collected_config_values,
|
117
|
+
api_key,
|
118
|
+
config_flag_needed,
|
119
|
+
)
|
120
|
+
verbose(f"Formatted server config: {json.dumps(server_config.__dict__, indent=2)}")
|
121
|
+
|
122
|
+
# Read existing config from client
|
123
|
+
verbose(f"Installing for {target_client}...")
|
124
|
+
verbose(f"Reading configuration for client: {target_client}")
|
125
|
+
client_config = read_config(target_client)
|
126
|
+
|
127
|
+
# Get normalized server name to use as key
|
128
|
+
server_name = get_server_name(package_identifier)
|
129
|
+
verbose(f"Normalized server ID: {server_name}")
|
130
|
+
|
131
|
+
# Update client configuration with new server
|
132
|
+
verbose("Updating client configuration...")
|
133
|
+
if not isinstance(client_config.mcpServers, dict):
|
134
|
+
# Initialize if needed
|
135
|
+
client_config.mcpServers = {}
|
136
|
+
|
137
|
+
# Add the new server config
|
138
|
+
client_config.mcpServers[server_name] = server_config
|
139
|
+
|
140
|
+
# Write updated configuration
|
141
|
+
verbose("Writing updated configuration...")
|
142
|
+
write_config(client_config, target_client)
|
143
|
+
verbose("Configuration successfully written")
|
144
|
+
|
145
|
+
rprint(f"[green]{package_identifier} successfully installed for {target_client}[/green]")
|
146
|
+
|
147
|
+
# No prompt for client restart
|
148
|
+
verbose("Installation completed successfully")
|
149
|
+
|
150
|
+
except Exception as e:
|
151
|
+
verbose(f"Installation error: {str(e)}")
|
152
|
+
rprint(f"[red]Failed to install {package_identifier}[/red]")
|
153
|
+
rprint(f"[red]Error: {str(e)}[/red]")
|
154
|
+
raise
|
158
155
|
|
159
156
|
|
160
157
|
@click.command("install")
|
@@ -164,19 +161,19 @@ async def install_mcp_server(
|
|
164
161
|
@click.option("--api-key", type=click.STRING, help="Provide an API key for fetching saved configurations")
|
165
162
|
def install(mcp_server: str, client: Optional[str], env_json: Optional[str], api_key: Optional[str]):
|
166
163
|
"""Install an AI mcp-server with optional configuration."""
|
167
|
-
|
164
|
+
verbose(f"Attempting to install {mcp_server}...")
|
168
165
|
user_config = None
|
169
166
|
if env_json:
|
170
167
|
try:
|
171
168
|
user_config = json.loads(env_json)
|
172
|
-
|
169
|
+
verbose(f"Using provided config: {user_config}")
|
173
170
|
except json.JSONDecodeError as e:
|
174
|
-
|
171
|
+
verbose(f"Error: Invalid JSON provided for --config: {e}", err=True)
|
175
172
|
return
|
176
173
|
|
177
174
|
try:
|
178
175
|
# Run the async installation process
|
179
176
|
asyncio.run(install_mcp_server(mcp_server, client, user_config, api_key))
|
180
177
|
except Exception as e:
|
181
|
-
|
178
|
+
verbose(f"Installation failed: {str(e)}", err=True)
|
182
179
|
return 1
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from cli.utils.logger import verbose
|
2
|
+
import click
|
3
|
+
import json
|
4
|
+
import sys
|
5
|
+
from ..utils.run_query_pid_by_packagename import query_pid_by_packagename
|
6
|
+
|
7
|
+
|
8
|
+
@click.command("query-pid")
|
9
|
+
@click.argument("package_names", type=click.STRING, nargs=-1, required=True)
|
10
|
+
@click.option("--json-output", is_flag=True, help="Output results in JSON format")
|
11
|
+
def query_pid(package_names, json_output):
|
12
|
+
"""
|
13
|
+
Check if the processes for the specified package names are running.
|
14
|
+
|
15
|
+
PACKAGE_NAMES: One or more package names to check.
|
16
|
+
Returns the running status for each package name. If the --json-output option is used, outputs the result in JSON format.
|
17
|
+
"""
|
18
|
+
try:
|
19
|
+
# Get the running status for each package name
|
20
|
+
status_dict = query_pid_by_packagename(list(package_names))
|
21
|
+
|
22
|
+
if json_output:
|
23
|
+
# Output results in JSON format
|
24
|
+
print(json.dumps(status_dict))
|
25
|
+
else:
|
26
|
+
# Output results in plain text format
|
27
|
+
for package_name, is_running in status_dict.items():
|
28
|
+
status = "Running" if is_running else "Not running"
|
29
|
+
verbose(f"Package name '{package_name}': {status}")
|
30
|
+
|
31
|
+
# Check if any process is running
|
32
|
+
any_running = any(status_dict.values())
|
33
|
+
exit(0 if any_running else 1)
|
34
|
+
except Exception as e:
|
35
|
+
print(f"Error occurred while executing query-pid command: {e}", file=sys.stderr)
|
36
|
+
exit(1) # Return 1 to indicate an error
|
cli/commands/run.py
CHANGED
@@ -3,7 +3,7 @@ import json
|
|
3
3
|
import asyncio
|
4
4
|
from typing import Dict, Any, Optional
|
5
5
|
|
6
|
-
from rich import print as
|
6
|
+
from rich import print as verbose
|
7
7
|
from rich.console import Console
|
8
8
|
|
9
9
|
from ..utils.logger import verbose
|
@@ -91,8 +91,8 @@ async def run_server(
|
|
91
91
|
# Inform about remote server if applicable
|
92
92
|
# check_and_notify_remote_server(resolved_server)
|
93
93
|
|
94
|
-
|
95
|
-
|
94
|
+
verbose(f"[blue][Runner] Connecting to server:[/blue] {resolved_server.qualifiedName}")
|
95
|
+
verbose(f"Connection types: {[c.type for c in resolved_server.connections]}")
|
96
96
|
|
97
97
|
# Assume analytics is disabled for now
|
98
98
|
analytics_enabled = False
|
@@ -105,7 +105,7 @@ async def run_server(
|
|
105
105
|
analytics_enabled
|
106
106
|
)
|
107
107
|
except Exception as e:
|
108
|
-
|
108
|
+
verbose(f"[red][Runner] Fatal error:[/red] {str(e)}")
|
109
109
|
raise
|
110
110
|
|
111
111
|
|
@@ -115,18 +115,18 @@ async def run_server(
|
|
115
115
|
@click.option("--api-key", type=click.STRING, help="API key for retrieving saved configuration")
|
116
116
|
def run(mcp_server: str, config_json: Optional[str], api_key: Optional[str]):
|
117
117
|
"""Run an AI MCP server."""
|
118
|
-
|
118
|
+
verbose(f"Attempting to run {mcp_server}...")
|
119
119
|
server_config = None
|
120
120
|
|
121
121
|
# Parse command line provided configuration
|
122
122
|
if config_json:
|
123
123
|
try:
|
124
|
+
verbose(config_json)
|
124
125
|
server_config = json.loads(config_json)
|
125
|
-
|
126
|
+
verbose(f"Using provided config: {server_config}")
|
126
127
|
except json.JSONDecodeError as e:
|
127
|
-
|
128
|
+
verbose(f"Error: Invalid JSON provided for --config: {e}")
|
128
129
|
return 1
|
129
|
-
|
130
130
|
# If no config provided, use empty dict
|
131
131
|
if server_config is None:
|
132
132
|
verbose("No config provided, running with empty config")
|
@@ -135,8 +135,6 @@ def run(mcp_server: str, config_json: Optional[str], api_key: Optional[str]):
|
|
135
135
|
if api_key:
|
136
136
|
verbose(f"API key provided: {'*' * len(api_key)}")
|
137
137
|
|
138
|
-
console = Console()
|
139
|
-
|
140
138
|
try:
|
141
139
|
# with console.status(f"Starting {mcp_server}...", spinner="dots") as status:
|
142
140
|
# Run the server asynchronously
|
@@ -144,5 +142,5 @@ def run(mcp_server: str, config_json: Optional[str], api_key: Optional[str]):
|
|
144
142
|
asyncio.run(run_server(mcp_server, server_config, api_key))
|
145
143
|
# status.update(f"Successfully started {mcp_server}")
|
146
144
|
except Exception as e:
|
147
|
-
|
145
|
+
verbose(f"Run failed: {str(e)}")
|
148
146
|
return 1
|
cli/main.py
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
import click
|
2
2
|
from .commands.install import install
|
3
3
|
from .commands.run import run
|
4
|
+
from .commands.query_pid import query_pid
|
5
|
+
|
4
6
|
|
5
7
|
@click.group()
|
6
|
-
@click.version_option(version="1.0.0", package_name="py-cli")
|
8
|
+
@click.version_option(version="1.0.0", package_name="py-cli") # Corresponds to program.version("1.0.0")
|
7
9
|
def cli():
|
8
10
|
"""A custom CLI tool translated to Python."""
|
9
11
|
pass
|
10
12
|
|
13
|
+
|
11
14
|
cli.add_command(install)
|
12
|
-
cli.add_command(run)
|
15
|
+
cli.add_command(run)
|
16
|
+
cli.add_command(query_pid)
|