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/mock_servers.json ADDED
@@ -0,0 +1,186 @@
1
+ {
2
+ "octagon": {
3
+ "qualifiedName": "octagon",
4
+ "displayName": "Octagon MCP Server (Mock)",
5
+ "remote": false,
6
+ "connections": [
7
+ {
8
+ "type": "stdio",
9
+ "stdioFunction": [
10
+ "npx"
11
+ ],
12
+ "published": true,
13
+ "configSchema": {
14
+ "args": [
15
+ "-y",
16
+ "octagon-mcp@latest"
17
+ ],
18
+ "env": {
19
+ "OCTAGON_API_KEY": {
20
+ "type": "string",
21
+ "description": "OCTAGON_API_KEY"
22
+ }
23
+ },
24
+ "required": [
25
+ "OCTAGON_API_KEY"
26
+ ]
27
+ }
28
+ }
29
+ ]
30
+ },
31
+ "mcp-gitlab": {
32
+ "qualifiedName": "@zereight/mcp-gitlab",
33
+ "displayName": "@zereight/mcp-gitlab",
34
+ "remote": false,
35
+ "connections": [
36
+ {
37
+ "type": "stdio",
38
+ "stdioFunction": [
39
+ "npx"
40
+ ],
41
+ "published": true,
42
+ "configSchema": {
43
+ "args": [
44
+ "-y",
45
+ "@zereight/mcp-gitlab"
46
+ ],
47
+ "env": {
48
+ "GITLAB_PERSONAL_ACCESS_TOKEN": {
49
+ "type": "string",
50
+ "description": "GITLAB_PERSONAL_ACCESS_TOKEN"
51
+ },
52
+ "GITLAB_API_URL": {
53
+ "type": "string",
54
+ "description": "GITLAB_API_URL",
55
+ "default": "GITLAB_API_URL"
56
+ },
57
+ "GITLAB_READ_ONLY_MODE": {
58
+ "type": "string",
59
+ "description": "GITLAB_READ_ONLY_MODE"
60
+ }
61
+ },
62
+ "required": [
63
+ "GITLAB_PERSONAL_ACCESS_TOKEN",
64
+ "GITLAB_API_URL"
65
+ ]
66
+ }
67
+ },
68
+ {
69
+ "type": "ws",
70
+ "deploymentUrl": "wss://example.com",
71
+ "published": true
72
+ }
73
+ ]
74
+ },
75
+ "mcp-twikit": {
76
+ "qualifiedName": "mcp-twikit",
77
+ "displayName": "mcp-twikit (Mock)",
78
+ "remote": false,
79
+ "connections": [
80
+ {
81
+ "type": "stdio",
82
+ "stdioFunction": [
83
+ "uvx"
84
+ ],
85
+ "published": true,
86
+ "configSchema": {
87
+ "args": [
88
+ "--from",
89
+ "git+https://github.com/adhikasp/mcp-twikit",
90
+ "mcp-twikit"
91
+ ],
92
+ "env": {
93
+ "TWITTER_USERNAME": {
94
+ "type": "string",
95
+ "description": "TWITTER_USERNAME",
96
+ "default": "@example"
97
+ },
98
+ "TWITTER_EMAIL": {
99
+ "type": "string",
100
+ "description": "TWITTER_EMAIL",
101
+ "default": "me@example.com"
102
+ },
103
+ "TWITTER_PASSWORD": {
104
+ "type": "string",
105
+ "description": "TWITTER_PASSWORD",
106
+ "default": "secret"
107
+ }
108
+ },
109
+ "required": [
110
+ "TWITTER_USERNAME",
111
+ "TWITTER_EMAIL",
112
+ "TWITTER_PASSWORD"
113
+ ]
114
+ }
115
+ }
116
+ ]
117
+ },
118
+ "@suekou/mcp-notion-server": {
119
+ "qualifiedName": "@suekou/mcp-notion-server",
120
+ "displayName": "mcp-notion (Mock)",
121
+ "remote": false,
122
+ "connections": [
123
+ {
124
+ "type": "stdio",
125
+ "stdioFunction": [
126
+ "npx"
127
+ ],
128
+ "published": true,
129
+ "configSchema": {
130
+ "args": [
131
+ "-y",
132
+ "@suekou/mcp-notion-server"
133
+ ],
134
+ "env": {
135
+ "NOTION_API_TOKEN": {
136
+ "type": "string",
137
+ "description": "NOTION_API_TOKEN",
138
+ "default": "your-integration-token"
139
+ }
140
+ },
141
+ "required": [
142
+ "NOTION_API_TOKEN"
143
+ ]
144
+ }
145
+ }
146
+ ]
147
+ },
148
+ "@test/install-notion": {
149
+ "qualifiedName": "@suekou/mcp-notion-server",
150
+ "displayName": "mcp-notion (Mock)",
151
+ "remote": false,
152
+ "connections": [
153
+ {
154
+ "type": "stdio",
155
+ "stdioFunction": [
156
+ "npx"
157
+ ],
158
+ "published": true,
159
+ "configSchema": {
160
+ "args": [
161
+ "-y",
162
+ "@suekou/mcp-notion-server",
163
+ "--from",
164
+ "py-mcpdock-cli==1.0.12",
165
+ "--index-url",
166
+ "https://test.pypi.org/simple/",
167
+ "--extra-index-url",
168
+ "https://pypi.org/simple/",
169
+ "mcpy",
170
+ "run"
171
+ ],
172
+ "env": {
173
+ "NOTION_API_TOKEN": {
174
+ "type": "string",
175
+ "description": "NOTION_API_TOKEN",
176
+ "default": "your-integration-token"
177
+ }
178
+ },
179
+ "required": [
180
+ "NOTION_API_TOKEN"
181
+ ]
182
+ }
183
+ }
184
+ ]
185
+ }
186
+ }
cli/registry.py ADDED
@@ -0,0 +1,136 @@
1
+ """
2
+ Registry module for MCP servers.
3
+
4
+ This module handles resolving package names to server metadata and fetching configuration.
5
+ """
6
+ from typing import Dict, Any, Tuple, Optional
7
+ import json
8
+ import os
9
+
10
+ from .utils.logger import verbose
11
+ from .types.registry import (
12
+ RegistryServer, ConnectionDetails, ConfigSchema, ConfigSchemaProperty
13
+ )
14
+
15
+
16
+ MOCK_SERVERS_PATH = os.path.join(os.path.dirname(__file__), "mock_servers.json")
17
+
18
+
19
+ def _load_mock_servers():
20
+ with open(MOCK_SERVERS_PATH, "r", encoding="utf-8") as f:
21
+ return json.load(f)
22
+
23
+
24
+ def _dict_to_registry_server(data):
25
+ # Helper to recursively convert dict to dataclasses
26
+ def _to_config_schema_property(prop):
27
+ return ConfigSchemaProperty(**prop)
28
+
29
+ def _to_config_schema(schema):
30
+ env = {k: _to_config_schema_property(v) for k, v in schema.get("env", {}).items()}
31
+ return ConfigSchema(env=env, required=schema.get("required", []), args=schema.get("args"))
32
+
33
+ def _to_connection_details(conn):
34
+ config_schema = None
35
+ if "configSchema" in conn and conn["configSchema"]:
36
+ config_schema = _to_config_schema(conn["configSchema"])
37
+ return ConnectionDetails(
38
+ type=conn["type"],
39
+ stdioFunction=conn.get("stdioFunction"),
40
+ deploymentUrl=conn.get("deploymentUrl"),
41
+ published=conn.get("published"),
42
+ configSchema=config_schema
43
+ )
44
+ return RegistryServer(
45
+ qualifiedName=data["qualifiedName"],
46
+ displayName=data["displayName"],
47
+ remote=data["remote"],
48
+ connections=[_to_connection_details(c) for c in data["connections"]]
49
+ )
50
+
51
+
52
+ mock_servers_data = _load_mock_servers()
53
+
54
+
55
+ async def resolve_package(package_name: str) -> RegistryServer:
56
+ """
57
+ Resolves a package name to server metadata.
58
+ """
59
+ verbose(f"Mock resolving package {package_name}")
60
+ data = mock_servers_data.get(package_name) or mock_servers_data.get("default")
61
+ return _dict_to_registry_server(data)
62
+
63
+
64
+ async def fetch_connection(
65
+ package_name: str,
66
+ config: Dict[str, Any]
67
+ ) -> Dict[str, Any]:
68
+ """
69
+ Fetches server connection details for a package with given configuration.
70
+
71
+ This function uses the package name to resolve the server details and then
72
+ formats the connection configuration based on the schema and user provided config.
73
+
74
+ Args:
75
+ package_name: The name of the package to fetch connection details for
76
+ config: User-provided configuration values
77
+
78
+ Returns:
79
+ Dictionary containing command, args and env configuration for the connection
80
+ """
81
+ verbose(f"Fetching connection details for {package_name} with config: {config}")
82
+
83
+ # Get server metadata first
84
+ server_data = await resolve_package(package_name)
85
+
86
+ # Find stdio connection
87
+ stdio_connection = next((conn for conn in server_data.connections if conn.type == "stdio"), None)
88
+ if not stdio_connection:
89
+ verbose("No stdio connection found in server metadata")
90
+ return {}
91
+
92
+ # Get command type (npx, uv, etc.) from stdioFunction
93
+ command = "python" # Default as fallback
94
+ if stdio_connection.stdioFunction and len(stdio_connection.stdioFunction) > 0:
95
+ command = stdio_connection.stdioFunction[0]
96
+
97
+ # Get args from the connection schema
98
+ args = []
99
+ if stdio_connection.configSchema and stdio_connection.configSchema.args:
100
+ args = stdio_connection.configSchema.args
101
+ else:
102
+ # Fallback default
103
+ args = ["-m", package_name]
104
+
105
+ # Return formatted connection config
106
+ connection_config = {
107
+ "command": command,
108
+ "args": args,
109
+ "env": config # User-provided environment variables
110
+ }
111
+
112
+ verbose(f"Formatted connection config: {connection_config}")
113
+ return connection_config
114
+
115
+
116
+ async def fetch_config_with_api_key(
117
+ package_name: str,
118
+ api_key: str
119
+ ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
120
+ """
121
+ Fetches server and configuration information using an API key.
122
+
123
+ Args:
124
+ package_name: The name of the package to fetch configuration for
125
+ api_key: The API key to authenticate with
126
+
127
+ Returns:
128
+ A tuple containing (server_info, config_info)
129
+ """
130
+ verbose(f"Mock fetching config for {package_name} using API key")
131
+
132
+ # This is a placeholder implementation
133
+ # In a real implementation, this would make an API call
134
+ # to retrieve the server and configuration information
135
+
136
+ return {}, {} # Empty dicts for now as placeholder
@@ -0,0 +1,21 @@
1
+ """
2
+ Runners for different types of MCP servers.
3
+
4
+ This package contains implementations for running different types of MCP servers:
5
+ - stdio_runner: For running local servers via stdio communication
6
+ - ws_runner: For connecting to remote WebSocket servers
7
+ - command_runner: For running Python/Node packages using uv/npx
8
+ """
9
+ from .stdio_runner import create_stdio_runner
10
+ from .ws_runner import create_ws_runner
11
+ from .stream_http_runner import create_stream_http_runner
12
+ from .command_runner import create_uv_runner, create_npx_runner, run_package_with_command
13
+
14
+ __all__ = [
15
+ 'create_stdio_runner',
16
+ 'create_ws_runner',
17
+ 'create_uv_runner',
18
+ 'create_npx_runner',
19
+ 'create_stream_http_runner',
20
+ 'run_package_with_command'
21
+ ]
@@ -0,0 +1,172 @@
1
+ """
2
+ Command Runners implementation for running Python or Node.js packages
3
+ """
4
+ import json
5
+ import asyncio
6
+ import signal
7
+ import sys
8
+ import os
9
+ from typing import Dict, Any, Optional, List
10
+
11
+ from rich import print as rprint
12
+
13
+ from ..utils.logger import verbose
14
+
15
+
16
+ async def run_command_process(cmd: List[str], env: Optional[Dict[str, str]] = None) -> None:
17
+ """
18
+ Run a command as a subprocess and handle its output asynchronously
19
+
20
+ Args:
21
+ cmd: Command and arguments as a list
22
+ env: Optional environment variables
23
+ """
24
+ verbose(f"Running command: {' '.join(cmd)}")
25
+
26
+ process = await asyncio.create_subprocess_exec(
27
+ *cmd,
28
+ stdout=asyncio.subprocess.PIPE,
29
+ stderr=asyncio.subprocess.PIPE,
30
+ env=env
31
+ )
32
+
33
+ # Setup signal handling for graceful shutdown
34
+ def handle_sigint(sig, frame):
35
+ rprint("[yellow]Received stop signal, terminating command...[/yellow]")
36
+ process.terminate()
37
+ sys.exit(0)
38
+
39
+ signal.signal(signal.SIGINT, handle_sigint)
40
+
41
+ # Process output in real-time
42
+ async def read_stream(stream, prefix):
43
+ while True:
44
+ line = await stream.readline()
45
+ if not line:
46
+ break
47
+ try:
48
+ decoded_line = line.decode('utf-8').rstrip()
49
+ if prefix == "stdout":
50
+ rprint(f"[blue]{decoded_line}[/blue]")
51
+ else:
52
+ rprint(f"[yellow]{decoded_line}[/yellow]")
53
+ except UnicodeDecodeError:
54
+ rprint(f"[red]Error decoding output from {prefix}[/red]")
55
+
56
+ # Create tasks for stdout and stderr
57
+ stdout_task = asyncio.create_task(read_stream(process.stdout, "stdout"))
58
+ stderr_task = asyncio.create_task(read_stream(process.stderr, "stderr"))
59
+
60
+ # Wait for process to complete
61
+ exit_code = await process.wait()
62
+
63
+ # Wait for output to be fully processed
64
+ await stdout_task
65
+ await stderr_task
66
+
67
+ if exit_code != 0:
68
+ rprint(f"[red]Command exited with code {exit_code}[/red]")
69
+ else:
70
+ rprint(f"[green]Command completed successfully[/green]")
71
+
72
+
73
+ async def create_uv_runner(
74
+ package_name: str,
75
+ args: List[str],
76
+ config: Dict[str, Any],
77
+ api_key: Optional[str] = None
78
+ ) -> None:
79
+ """
80
+ Creates a runner for executing Python packages with uv package manager
81
+
82
+ Args:
83
+ package_name: Name of the Python package to run
84
+ args: Additional arguments to pass to the package
85
+ config: Configuration options
86
+ api_key: Optional API key for authentication
87
+ """
88
+ verbose(f"Starting uv runner for package: {package_name}")
89
+ rprint(f"[blue]Running Python package with uv: {package_name}[/blue]")
90
+
91
+ # Prepare environment variables from config
92
+ env = os.environ.copy()
93
+ for key, value in config.items():
94
+ if isinstance(value, str):
95
+ env[f"MCP_{key.upper()}"] = value
96
+ else:
97
+ env[f"MCP_{key.upper()}"] = json.dumps(value)
98
+
99
+ if api_key:
100
+ env["MCP_API_KEY"] = api_key
101
+
102
+ # Build the uv command
103
+ cmd = ["uv", "run", "-m", package_name]
104
+ if args:
105
+ cmd.extend(args)
106
+
107
+ await run_command_process(cmd, env)
108
+
109
+
110
+ async def create_npx_runner(
111
+ package_name: str,
112
+ args: List[str],
113
+ config: Dict[str, Any],
114
+ api_key: Optional[str] = None
115
+ ) -> None:
116
+ """
117
+ Creates a runner for executing Node.js packages with npx
118
+
119
+ Args:
120
+ package_name: Name of the Node.js package to run
121
+ args: Additional arguments to pass to the package
122
+ config: Configuration options
123
+ api_key: Optional API key for authentication
124
+ """
125
+ verbose(f"Starting npx runner for package: {package_name}")
126
+ rprint(f"[blue]Running Node.js package with npx: {package_name}[/blue]")
127
+
128
+ # Prepare environment variables from config
129
+ env = os.environ.copy()
130
+ for key, value in config.items():
131
+ if isinstance(value, str):
132
+ env[f"MCP_{key.upper()}"] = value
133
+ else:
134
+ env[f"MCP_{key.upper()}"] = json.dumps(value)
135
+
136
+ if api_key:
137
+ env["MCP_API_KEY"] = api_key
138
+
139
+ # Build the npx command
140
+ cmd = ["npx", package_name]
141
+ if args:
142
+ cmd.extend(args)
143
+
144
+ await run_command_process(cmd, env)
145
+
146
+
147
+ async def run_package_with_command(
148
+ command_type: str,
149
+ package_name: str,
150
+ args: List[str],
151
+ config: Dict[str, Any],
152
+ api_key: Optional[str] = None
153
+ ) -> None:
154
+ """
155
+ Run a package with the specified command runner (uv or npx)
156
+
157
+ Args:
158
+ command_type: Type of command runner to use ("uv" or "npx")
159
+ package_name: Name of the package to run
160
+ args: Additional arguments to pass to the package
161
+ config: Configuration options
162
+ api_key: Optional API key for authentication
163
+
164
+ Raises:
165
+ ValueError: If an unsupported command type is provided
166
+ """
167
+ if command_type.lower() == "uv":
168
+ await create_uv_runner(package_name, args, config, api_key)
169
+ elif command_type.lower() == "npx":
170
+ await create_npx_runner(package_name, args, config, api_key)
171
+ else:
172
+ raise ValueError(f"Unsupported command type: {command_type}")