py-mcpdock-cli 1.0.13__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.
Files changed (33) hide show
  1. py_mcpdock_cli-1.0.13/MANIFEST.in +1 -0
  2. py_mcpdock_cli-1.0.13/PKG-INFO +28 -0
  3. py_mcpdock_cli-1.0.13/README.md +4 -0
  4. py_mcpdock_cli-1.0.13/pyproject.toml +41 -0
  5. py_mcpdock_cli-1.0.13/setup.cfg +11 -0
  6. py_mcpdock_cli-1.0.13/src/cli/__init__.py +0 -0
  7. py_mcpdock_cli-1.0.13/src/cli/commands/__init__.py +0 -0
  8. py_mcpdock_cli-1.0.13/src/cli/commands/install.py +182 -0
  9. py_mcpdock_cli-1.0.13/src/cli/commands/run.py +148 -0
  10. py_mcpdock_cli-1.0.13/src/cli/config/__init__.py +0 -0
  11. py_mcpdock_cli-1.0.13/src/cli/config/app_config.py +11 -0
  12. py_mcpdock_cli-1.0.13/src/cli/config/client_config.py +248 -0
  13. py_mcpdock_cli-1.0.13/src/cli/main.py +12 -0
  14. py_mcpdock_cli-1.0.13/src/cli/mock_servers.json +186 -0
  15. py_mcpdock_cli-1.0.13/src/cli/registry.py +136 -0
  16. py_mcpdock_cli-1.0.13/src/cli/runners/__init__.py +21 -0
  17. py_mcpdock_cli-1.0.13/src/cli/runners/command_runner.py +172 -0
  18. py_mcpdock_cli-1.0.13/src/cli/runners/stdio_runner.py +494 -0
  19. py_mcpdock_cli-1.0.13/src/cli/runners/stream_http_runner.py +166 -0
  20. py_mcpdock_cli-1.0.13/src/cli/runners/ws_runner.py +43 -0
  21. py_mcpdock_cli-1.0.13/src/cli/types/__init__.py +0 -0
  22. py_mcpdock_cli-1.0.13/src/cli/types/registry.py +69 -0
  23. py_mcpdock_cli-1.0.13/src/cli/utils/__init__.py +0 -0
  24. py_mcpdock_cli-1.0.13/src/cli/utils/client.py +0 -0
  25. py_mcpdock_cli-1.0.13/src/cli/utils/config.py +441 -0
  26. py_mcpdock_cli-1.0.13/src/cli/utils/logger.py +79 -0
  27. py_mcpdock_cli-1.0.13/src/cli/utils/runtime.py +163 -0
  28. py_mcpdock_cli-1.0.13/src/py_mcpdock_cli.egg-info/PKG-INFO +28 -0
  29. py_mcpdock_cli-1.0.13/src/py_mcpdock_cli.egg-info/SOURCES.txt +32 -0
  30. py_mcpdock_cli-1.0.13/src/py_mcpdock_cli.egg-info/dependency_links.txt +1 -0
  31. py_mcpdock_cli-1.0.13/src/py_mcpdock_cli.egg-info/entry_points.txt +2 -0
  32. py_mcpdock_cli-1.0.13/src/py_mcpdock_cli.egg-info/requires.txt +7 -0
  33. py_mcpdock_cli-1.0.13/src/py_mcpdock_cli.egg-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ recursive-include src/cli *.json
@@ -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,4 @@
1
+ # MCP CLI Python Implementation
2
+
3
+ This is a Python implementation of the Model Context Protocol (MCP) command-line tool.
4
+
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "py-mcpdock-cli"
7
+ version = "1.0.13"
8
+ authors = [
9
+ {name = "dw", email = "qindongwoxin@gmail.com"},
10
+ ]
11
+ description = "Python CLI for managing MCP servers"
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.8",
17
+ "Programming Language :: Python :: 3.9",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ ]
23
+ dependencies = [
24
+ "click>=8.0.0",
25
+ "questionary>=1.10.0",
26
+ "rich>=12.0.0",
27
+ "asyncio>=3.4.3",
28
+ "python-dotenv>=1.0.0",
29
+ "mcp",
30
+ "aiohttp>=3.8.0",
31
+ ]
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
35
+
36
+ [project.scripts]
37
+ mcpy = "cli.main:cli"
38
+
39
+ [project.urls]
40
+ "Homepage" = "https://github.com/yourusername/py-mcpdock-cli"
41
+ "Bug Tracker" = "https://github.com/yourusername/py-mcpdock-cli/issues"
@@ -0,0 +1,11 @@
1
+ [pep8]
2
+ max-line-length = 120
3
+
4
+ [autopep8]
5
+ aggressive = 1
6
+ max-line-length = 120
7
+
8
+ [egg_info]
9
+ tag_build =
10
+ tag_date = 0
11
+
File without changes
File without changes
@@ -0,0 +1,182 @@
1
+ import click
2
+ import json
3
+ import asyncio
4
+ from typing import Dict, Any, Optional
5
+
6
+ from rich import print as rprint
7
+ from rich.console import Console
8
+ from rich.spinner import Spinner
9
+
10
+ from ..utils.logger import verbose, info, debug, error, warning
11
+ from ..registry import resolve_package
12
+ from ..utils.runtime import ensure_uv_installed, ensure_bun_installed, check_and_notify_remote_server
13
+ from ..utils.config import choose_connection, collect_config_values, get_server_name, format_server_config, format_config_values
14
+ from ..config.client_config import read_config, write_config
15
+ from ..types.registry import ConfiguredServer, ClientConfig
16
+
17
+
18
+ async def validate_config_values(
19
+ connection: Any,
20
+ existing_values: Optional[Dict[str, Any]] = None,
21
+ ) -> Dict[str, Any]:
22
+ """
23
+ Validates config values without prompting for user input.
24
+ Only checks if required values are present.
25
+
26
+ Args:
27
+ connection: Connection details containing schema
28
+ existing_values: Existing config values to validate
29
+
30
+ Returns:
31
+ Dictionary with validated config values
32
+
33
+ Raises:
34
+ ValueError: If required configuration values are missing
35
+ """
36
+ schema = connection.configSchema or {}
37
+ env = schema.env if schema else {}
38
+ if not env:
39
+ return {}
40
+
41
+ # Use existing values or empty dict
42
+ config_values = existing_values or {}
43
+
44
+ # Get list of required env variables
45
+ required = set(schema.required if schema else [])
46
+
47
+ # Check if all required env variables are provided
48
+ missing_required = []
49
+ for key in required:
50
+ if key not in config_values or config_values[key] is None:
51
+ missing_required.append(key)
52
+
53
+ # Raise error if any required env variables are missing
54
+ if missing_required:
55
+ missing_fields = ", ".join(missing_required)
56
+ raise ValueError(f"Missing required configuration values: {missing_fields}")
57
+
58
+ # Format and return the validated config
59
+ return format_config_values(connection, config_values)
60
+
61
+
62
+ async def install_mcp_server(
63
+ package_identifier: str,
64
+ target_client: Optional[str] = None,
65
+ initial_config: Optional[Dict[str, Any]] = None,
66
+ api_key: Optional[str] = None
67
+ ) -> None:
68
+ """
69
+ Installs and configures an AI server package for a specified client.
70
+
71
+ Args:
72
+ package_identifier: The fully qualified name of the AI server package
73
+ target_client: The AI client to configure the server for
74
+ initial_config: Optional pre-defined configuration values
75
+ api_key: Optional API key for fetching saved configurations
76
+ """
77
+ target_client = target_client or "claude" # Default to Claude if no client specified
78
+ verbose(f"Initiating installation of {package_identifier} for {target_client}")
79
+
80
+ console = Console()
81
+
82
+ # Create and start spinner for package resolution
83
+ with console.status(f"Resolving {package_identifier}...", spinner="dots") as status:
84
+ try:
85
+ # Resolve the package (fetch server metadata)
86
+ verbose(f"Resolving package: {package_identifier}")
87
+ server_data = await resolve_package(package_identifier)
88
+ verbose(f"Package resolved successfully: {server_data.qualifiedName}")
89
+
90
+ # Choose the appropriate connection type
91
+ verbose("Choosing connection type...")
92
+ connection = choose_connection(server_data)
93
+ verbose(f"Selected connection type: {connection.type}")
94
+
95
+ # Check for required runtimes and install if needed
96
+ # Commented out as these are specific to JS environment
97
+ # await ensure_uv_installed(connection)
98
+ # await ensure_bun_installed(connection)
99
+
100
+ # Inform users of remote server installation if applicable
101
+ is_remote = check_and_notify_remote_server(server_data)
102
+ if is_remote:
103
+ verbose("Remote server detected, notification displayed")
104
+
105
+ # Get the validated config values, no prompting
106
+ status.update(f"Validating configuration for {package_identifier}...")
107
+ collected_config_values = await validate_config_values(connection, initial_config)
108
+
109
+ # Determine if we need to pass config flag
110
+ config_flag_needed = initial_config is not None
111
+
112
+ verbose(f"Config values validated: {json.dumps(collected_config_values, indent=2)}")
113
+ verbose(f"Using config flag: {config_flag_needed}")
114
+
115
+ # Format the server configuration
116
+ verbose("Formatting server configuration...")
117
+ server_config = format_server_config(
118
+ package_identifier,
119
+ collected_config_values,
120
+ api_key,
121
+ config_flag_needed,
122
+ )
123
+ verbose(f"Formatted server config: {json.dumps(server_config.__dict__, indent=2)}")
124
+
125
+ # Read existing config from client
126
+ status.update(f"Installing for {target_client}...")
127
+ verbose(f"Reading configuration for client: {target_client}")
128
+ client_config = read_config(target_client)
129
+
130
+ # Get normalized server name to use as key
131
+ server_name = get_server_name(package_identifier)
132
+ verbose(f"Normalized server ID: {server_name}")
133
+
134
+ # Update client configuration with new server
135
+ verbose("Updating client configuration...")
136
+ if not isinstance(client_config.mcpServers, dict):
137
+ # Initialize if needed
138
+ client_config.mcpServers = {}
139
+
140
+ # Add the new server config
141
+ client_config.mcpServers[server_name] = server_config
142
+
143
+ # Write updated configuration
144
+ verbose("Writing updated configuration...")
145
+ write_config(client_config, target_client)
146
+ verbose("Configuration successfully written")
147
+
148
+ rprint(f"[green]{package_identifier} successfully installed for {target_client}[/green]")
149
+
150
+ # No prompt for client restart
151
+ verbose("Installation completed successfully")
152
+
153
+ except Exception as e:
154
+ verbose(f"Installation error: {str(e)}")
155
+ rprint(f"[red]Failed to install {package_identifier}[/red]")
156
+ rprint(f"[red]Error: {str(e)}[/red]")
157
+ raise
158
+
159
+
160
+ @click.command("install")
161
+ @click.argument("mcp_server", type=click.STRING)
162
+ @click.option("--client", type=click.STRING, help="Specify the target AI client")
163
+ @click.option("--env", "env_json", type=click.STRING, help="Provide configuration as JSON (bypasses interactive prompts)")
164
+ @click.option("--api-key", type=click.STRING, help="Provide an API key for fetching saved configurations")
165
+ def install(mcp_server: str, client: Optional[str], env_json: Optional[str], api_key: Optional[str]):
166
+ """Install an AI mcp-server with optional configuration."""
167
+ click.echo(f"Attempting to install {mcp_server}...")
168
+ user_config = None
169
+ if env_json:
170
+ try:
171
+ user_config = json.loads(env_json)
172
+ click.echo(f"Using provided config: {user_config}")
173
+ except json.JSONDecodeError as e:
174
+ click.echo(f"Error: Invalid JSON provided for --config: {e}", err=True)
175
+ return
176
+
177
+ try:
178
+ # Run the async installation process
179
+ asyncio.run(install_mcp_server(mcp_server, client, user_config, api_key))
180
+ except Exception as e:
181
+ click.echo(f"Installation failed: {str(e)}", err=True)
182
+ return 1
@@ -0,0 +1,148 @@
1
+ import click
2
+ import json
3
+ import asyncio
4
+ from typing import Dict, Any, Optional
5
+
6
+ from rich import print as rprint
7
+ from rich.console import Console
8
+
9
+ from ..utils.logger import verbose
10
+ from ..registry import resolve_package
11
+ from ..utils.config import choose_connection, format_run_config_values
12
+ from ..utils.runtime import check_and_notify_remote_server
13
+ from ..types.registry import RegistryServer, ConnectionDetails
14
+ from ..runners import create_stdio_runner, create_ws_runner, run_package_with_command, create_stream_http_runner
15
+
16
+
17
+ # These runner functions have been moved to their respective modules in the runners package
18
+
19
+ async def pick_server_and_run(
20
+ server_details: RegistryServer,
21
+ config: Dict[str, Any],
22
+ api_key: Optional[str] = None,
23
+ analytics_enabled: bool = False
24
+ ) -> None:
25
+ """
26
+ Selects the appropriate runner and starts the server.
27
+ """
28
+ connection = choose_connection(server_details)
29
+
30
+ if connection.type == "ws":
31
+ if not connection.deploymentUrl:
32
+ raise ValueError("Missing deployment URL")
33
+ await create_ws_runner(connection.deploymentUrl, config, api_key)
34
+ elif connection.type == "stdio":
35
+ await create_stdio_runner(server_details, config, api_key, analytics_enabled)
36
+ elif connection.type == "stream-http":
37
+ await create_stream_http_runner(server_details, config, api_key, analytics_enabled)
38
+ else:
39
+ raise ValueError(f"Unsupported connection type: {connection.type}")
40
+
41
+
42
+ async def run_server(
43
+ qualified_name: str,
44
+ config: Dict[str, Any],
45
+ api_key: Optional[str] = None
46
+ ) -> None:
47
+ """
48
+ Runs a server with the specified configuration.
49
+
50
+ The qualified_name can be in these formats:
51
+ - Standard MCP server name: "company/package"
52
+ - uv command: "uv:package_name [args]"
53
+ - npx command: "npx:package_name [args]"
54
+ """
55
+ try:
56
+ # # Check if this is a special command format (uv: or npx:)
57
+ # if qualified_name.startswith("uv:") or qualified_name.startswith("npx:"):
58
+ # command_parts = qualified_name.split(":", 1)
59
+ # command_type = command_parts[0].lower()
60
+ # package_spec = command_parts[1]
61
+
62
+ # # Extract package name and args if provided
63
+ # package_parts = package_spec.split(" ", 1)
64
+ # package_name = package_parts[0]
65
+ # args = package_parts[1].split() if len(package_parts) > 1 else []
66
+
67
+ # verbose(f"Running {command_type} command for package: {package_name}")
68
+ # await run_package_with_command(
69
+ # command_type,
70
+ # package_name,
71
+ # args,
72
+ # config,
73
+ # api_key
74
+ # )
75
+ # return
76
+
77
+ # Initialize settings for regular MCP server
78
+ verbose("Initializing runtime environment settings")
79
+
80
+ # Resolve server package
81
+ verbose(f"Resolving server package: {qualified_name}")
82
+ resolved_server = await resolve_package(qualified_name)
83
+
84
+ if not resolved_server:
85
+ raise ValueError(f"Could not resolve server: {qualified_name}")
86
+
87
+ # Format the final configuration with validation
88
+ connection = choose_connection(resolved_server)
89
+ final_config = format_run_config_values(connection, config)
90
+
91
+ # Inform about remote server if applicable
92
+ # check_and_notify_remote_server(resolved_server)
93
+
94
+ rprint(f"[blue][Runner] Connecting to server:[/blue] {resolved_server.qualifiedName}")
95
+ rprint(f"Connection types: {[c.type for c in resolved_server.connections]}")
96
+
97
+ # Assume analytics is disabled for now
98
+ analytics_enabled = False
99
+
100
+ # Run the server with the appropriate runner
101
+ await pick_server_and_run(
102
+ resolved_server,
103
+ final_config,
104
+ api_key,
105
+ analytics_enabled
106
+ )
107
+ except Exception as e:
108
+ rprint(f"[red][Runner] Fatal error:[/red] {str(e)}")
109
+ raise
110
+
111
+
112
+ @click.command("run")
113
+ @click.argument("mcp_server", type=click.STRING)
114
+ @click.option("--config", "config_json", type=click.STRING, help="Provide JSON format configuration")
115
+ @click.option("--api-key", type=click.STRING, help="API key for retrieving saved configuration")
116
+ def run(mcp_server: str, config_json: Optional[str], api_key: Optional[str]):
117
+ """Run an AI MCP server."""
118
+ click.echo(f"Attempting to run {mcp_server}...")
119
+ server_config = None
120
+
121
+ # Parse command line provided configuration
122
+ if config_json:
123
+ try:
124
+ server_config = json.loads(config_json)
125
+ click.echo(f"Using provided config: {server_config}")
126
+ except json.JSONDecodeError as e:
127
+ click.echo(f"Error: Invalid JSON provided for --config: {e}", err=True)
128
+ return 1
129
+
130
+ # If no config provided, use empty dict
131
+ if server_config is None:
132
+ verbose("No config provided, running with empty config")
133
+ server_config = {}
134
+
135
+ if api_key:
136
+ verbose(f"API key provided: {'*' * len(api_key)}")
137
+
138
+ console = Console()
139
+
140
+ try:
141
+ # with console.status(f"Starting {mcp_server}...", spinner="dots") as status:
142
+ # Run the server asynchronously
143
+ verbose('running server....')
144
+ asyncio.run(run_server(mcp_server, server_config, api_key))
145
+ # status.update(f"Successfully started {mcp_server}")
146
+ except Exception as e:
147
+ click.echo(f"Run failed: {str(e)}", err=True)
148
+ return 1
File without changes
@@ -0,0 +1,11 @@
1
+ # config/app_config.py
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ # Load environment variables from a .env file
6
+ load_dotenv()
7
+
8
+
9
+ APP_NAME = os.getenv("APP_NAME", "My Application")
10
+ APP_VERSION = os.getenv("APP_VERSION", "1.0.0")
11
+ APP_ENV = os.getenv('ENV')