castrel-proxy 0.1.0__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.
@@ -0,0 +1,229 @@
1
+ # Castrel Default Command Whitelist
2
+ # One command name per line, lines starting with # are comments
3
+ # Only commands in this whitelist are allowed to execute
4
+
5
+ # ========== File and Directory Operations ==========
6
+ ls
7
+ ll
8
+ cat
9
+ head
10
+ tail
11
+ find
12
+ pwd
13
+ cd
14
+ mkdir
15
+ touch
16
+ cp
17
+ ln
18
+ tree
19
+ stat
20
+ file
21
+ readlink
22
+ realpath
23
+ basename
24
+ dirname
25
+
26
+ # ========== Text Processing ==========
27
+ grep
28
+ egrep
29
+ fgrep
30
+ awk
31
+ sed
32
+ sort
33
+ uniq
34
+ wc
35
+ diff
36
+ cut
37
+ tr
38
+ xargs
39
+ tee
40
+ less
41
+ more
42
+
43
+ # ========== Version Control ==========
44
+ git
45
+ svn
46
+
47
+ # ========== Development Tools - Python ==========
48
+ python
49
+ python3
50
+ pip
51
+ pip3
52
+ poetry
53
+ uv
54
+ pipenv
55
+ conda
56
+ virtualenv
57
+
58
+ # ========== Development Tools - Node.js ==========
59
+ node
60
+ npm
61
+ npx
62
+ yarn
63
+ pnpm
64
+ bun
65
+
66
+ # ========== Development Tools - Other Languages ==========
67
+ make
68
+ cmake
69
+ cargo
70
+ rustc
71
+ go
72
+ java
73
+ javac
74
+ mvn
75
+ gradle
76
+ ruby
77
+ gem
78
+ php
79
+ composer
80
+
81
+ # ========== Network Tools (Read-only) ==========
82
+ curl
83
+ wget
84
+ ping
85
+ traceroute
86
+ tracepath
87
+ nslookup
88
+ dig
89
+ host
90
+
91
+ # ========== Compression and Archiving ==========
92
+ tar
93
+ zip
94
+ unzip
95
+ gzip
96
+ gunzip
97
+ bzip2
98
+ bunzip2
99
+ xz
100
+ unxz
101
+ 7z
102
+ rar
103
+ unrar
104
+
105
+ # ========== System Information (Read-only) ==========
106
+ echo
107
+ which
108
+ whereis
109
+ env
110
+ printenv
111
+ date
112
+ whoami
113
+ id
114
+ hostname
115
+ uname
116
+ uptime
117
+ w
118
+ who
119
+ last
120
+ history
121
+ ps
122
+ top
123
+ htop
124
+ free
125
+ df
126
+ du
127
+ lsof
128
+ vmstat
129
+ iostat
130
+ sar
131
+ mpstat
132
+ pidstat
133
+ nproc
134
+ lsblk
135
+ blkid
136
+
137
+ # ========== Process Management (Safe) ==========
138
+ nohup
139
+ bg
140
+ fg
141
+ jobs
142
+ screen
143
+ tmux
144
+ journalctl
145
+
146
+ # ========== Network Information (Read-only) ==========
147
+ ip
148
+ ifconfig
149
+ netstat
150
+ ss
151
+
152
+ # ========== Docker (Client Commands) ==========
153
+ docker
154
+ docker-compose
155
+ buildx
156
+ docker-buildx
157
+
158
+ # ========== Kubernetes (Client Commands) ==========
159
+ kubectl
160
+ helm
161
+ helmfile
162
+ kustomize
163
+ skaffold
164
+ tilt
165
+ k9s
166
+ stern
167
+ kubectx
168
+ kubens
169
+ kubie
170
+ minikube
171
+ kind
172
+ k3s
173
+ k3d
174
+ microk8s
175
+ argocd
176
+ flux
177
+ fluxctl
178
+ velero
179
+ restic
180
+ istioctl
181
+ linkerd
182
+ oc
183
+ eksctl
184
+ gcloud
185
+ az
186
+ aws
187
+
188
+ # ========== Container Image Tools ==========
189
+ podman
190
+ buildah
191
+ skopeo
192
+ nerdctl
193
+ kaniko
194
+ dive
195
+ trivy
196
+ grype
197
+ syft
198
+
199
+ # ========== Logging ==========
200
+ logger
201
+
202
+ # ========== Binary Analysis (Read-only) ==========
203
+ objdump
204
+ nm
205
+ ldd
206
+
207
+ # ========== Editors ==========
208
+ vim
209
+ vi
210
+ nano
211
+ emacs
212
+
213
+ # ========== Other Common Tools ==========
214
+ watch
215
+ timeout
216
+ time
217
+ sleep
218
+ test
219
+ true
220
+ false
221
+ yes
222
+ seq
223
+ shuf
224
+ base64
225
+ md5sum
226
+ sha256sum
227
+ openssl
228
+ jq
229
+ yq
@@ -0,0 +1,8 @@
1
+ """MCP (Model Context Protocol) integration modules"""
2
+
3
+ from .manager import MCPManager, get_mcp_manager
4
+
5
+ __all__ = [
6
+ "MCPManager",
7
+ "get_mcp_manager",
8
+ ]
@@ -0,0 +1,278 @@
1
+ """
2
+ MCP Manager Module
3
+
4
+ Responsible for managing MCP client connections and fetching tools information
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Dict, List, Optional
12
+ from langchain_mcp_adapters.client import MultiServerMCPClient
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def convert_config_to_langchain_format(config_data: dict) -> dict:
18
+ """
19
+ Convert configuration to langchain-mcp-adapters format
20
+
21
+ Args:
22
+ config_data: Original configuration data
23
+
24
+ Returns:
25
+ dict: Configuration in langchain format
26
+
27
+ Raises:
28
+ ValueError: When configuration is invalid
29
+ """
30
+ langchain_config = {}
31
+
32
+ for name, server_config in config_data.items():
33
+ # Validate required transport key
34
+ if "transport" not in server_config:
35
+ error_msg = (
36
+ f"Configuration error for server '{name}': Missing 'transport' key. "
37
+ f"Each server must include 'transport' with one of: 'stdio', 'sse', 'http'. "
38
+ )
39
+ logger.error(error_msg)
40
+ raise ValueError(error_msg)
41
+
42
+ transport = server_config.get("transport")
43
+
44
+ if transport == "stdio":
45
+ # stdio type: use command and args
46
+ if not server_config.get("command"):
47
+ error_msg = f"Configuration error for server '{name}': Missing 'command' for stdio transport"
48
+ logger.error(error_msg)
49
+ raise ValueError(error_msg)
50
+
51
+ langchain_config[name] = {
52
+ "transport": "stdio",
53
+ "command": server_config.get("command"),
54
+ "args": server_config.get("args", []),
55
+ }
56
+ # Add environment variables if present
57
+ if server_config.get("env"):
58
+ langchain_config[name]["env"] = server_config.get("env")
59
+
60
+ elif transport == "http":
61
+ # http type: use url
62
+ if not server_config.get("url"):
63
+ error_msg = f"Configuration error for server '{name}': Missing 'url' for http transport"
64
+ logger.error(error_msg)
65
+ raise ValueError(error_msg)
66
+
67
+ langchain_config[name] = {
68
+ "transport": "http",
69
+ "url": server_config.get("url"),
70
+ }
71
+
72
+ elif transport in ["sse", "websocket"]:
73
+ error_msg = (
74
+ f"Configuration error for server '{name}': Transport type '{transport}' is not yet supported. "
75
+ f"Currently supported transports: 'stdio', 'http'"
76
+ )
77
+ logger.error(error_msg)
78
+ raise ValueError(error_msg)
79
+
80
+ else:
81
+ error_msg = (
82
+ f"Configuration error for server '{name}': Unknown transport type '{transport}'. "
83
+ f"Supported transports: 'stdio', 'http'"
84
+ )
85
+ logger.error(error_msg)
86
+ raise ValueError(error_msg)
87
+
88
+ return langchain_config
89
+
90
+
91
+ class MCPManager:
92
+ """MCP manager"""
93
+
94
+ def __init__(self, config_file: Optional[Path] = None):
95
+ """
96
+ Initialize MCP manager
97
+
98
+ Args:
99
+ config_file: Configuration file path, defaults to ~/.castrel/mcp.json
100
+ """
101
+ if config_file is None:
102
+ self.config_file = Path.home() / ".castrel" / "mcp.json"
103
+ else:
104
+ self.config_file = Path(config_file)
105
+
106
+ self.client: Optional[MultiServerMCPClient] = None
107
+ self.server_configs: Dict = {}
108
+
109
+ def load_config(self) -> Dict:
110
+ """
111
+ Load MCP configuration
112
+
113
+ Returns:
114
+ Dict: Configuration dictionary in langchain format
115
+ """
116
+ if not self.config_file.exists():
117
+ logger.warning(f"MCP configuration file does not exist: {self.config_file}")
118
+ return {}
119
+
120
+ try:
121
+ with open(self.config_file, "r", encoding="utf-8") as f:
122
+ data = json.load(f)
123
+
124
+ mcpServers = data.get("mcpServers", {})
125
+
126
+ logger.info(f"Loaded {len(mcpServers)} MCP configurations")
127
+ return mcpServers
128
+
129
+ except Exception as e:
130
+ logger.error(f"Failed to load MCP configuration: {e}")
131
+ return {}
132
+
133
+ def get_server_list(self) -> List[Dict]:
134
+ """
135
+ Get server configuration list (for display)
136
+
137
+ Returns:
138
+ List[Dict]: Server configuration list
139
+ """
140
+ if not self.config_file.exists():
141
+ return []
142
+
143
+ try:
144
+ with open(self.config_file, "r", encoding="utf-8") as f:
145
+ data = json.load(f)
146
+
147
+ servers = []
148
+ mcpServers = data.get("mcpServers", {})
149
+
150
+ for name, config in mcpServers.items():
151
+ servers.append(
152
+ {
153
+ "name": name,
154
+ "transport": config.get("transport", "stdio"),
155
+ "command": config.get("command", ""),
156
+ "args": config.get("args", []),
157
+ "url": config.get("url", ""),
158
+ "env": config.get("env", {}),
159
+ }
160
+ )
161
+
162
+ return servers
163
+
164
+ except Exception as e:
165
+ logger.error(f"Failed to get server list: {e}")
166
+ return []
167
+
168
+ async def connect_all(self) -> int:
169
+ """
170
+ Connect to all configured MCP services
171
+
172
+ Returns:
173
+ int: Number of successfully connected services
174
+
175
+ Raises:
176
+ SystemExit: When configuration is invalid
177
+ """
178
+ raw_config = self.load_config()
179
+ if not raw_config:
180
+ logger.info("No MCP services configured")
181
+ return 0
182
+
183
+ try:
184
+ # Convert configuration to langchain format
185
+ logger.info(f"Converting configuration for {len(raw_config)} MCP services...")
186
+ self.server_configs = convert_config_to_langchain_format(raw_config)
187
+
188
+ logger.info(f"Connecting to {len(self.server_configs)} MCP services...")
189
+
190
+ # Create MultiServerMCPClient
191
+ self.client = MultiServerMCPClient(self.server_configs)
192
+
193
+ logger.info(f"Successfully connected to {len(self.server_configs)} MCP services")
194
+ return len(self.server_configs)
195
+
196
+ except ValueError as e:
197
+ # Configuration error - exit immediately
198
+ logger.error(f"Configuration error: {e}")
199
+ logger.error("Exiting due to invalid MCP configuration")
200
+ sys.exit(1)
201
+
202
+ except Exception as e:
203
+ logger.error(f"Failed to connect to MCP services: {e}")
204
+ logger.error("Exiting due to MCP connection failure")
205
+ sys.exit(1)
206
+
207
+ async def get_all_tools(self) -> Dict[str, List[Dict]]:
208
+ """
209
+ Get tools from all MCP services
210
+
211
+ Returns:
212
+ Dict[str, List[Dict]]: All tools list
213
+
214
+ Raises:
215
+ SystemExit: When MCP client is not initialized or tools retrieval fails
216
+ """
217
+ if not self.client:
218
+ logger.error("MCP client not initialized")
219
+ logger.error("Exiting due to uninitialized MCP client")
220
+ sys.exit(1)
221
+
222
+ try:
223
+ # Use MultiServerMCPClient to get all tools
224
+ result = {}
225
+ count = 0
226
+ for server_name in self.server_configs:
227
+ tools = await self.client.get_tools(server_name=server_name)
228
+ # Convert to required format
229
+ formatted_tools = []
230
+ for tool in tools:
231
+ # Tool format returned by langchain-mcp-adapters
232
+ tool_info = {
233
+ "name": tool.name,
234
+ "description": tool.description or "",
235
+ "inputSchema": tool.args_schema if hasattr(tool, "args_schema") else {},
236
+ "mcp_server": getattr(tool, "server_name", "unknown"),
237
+ }
238
+ formatted_tools.append(tool_info)
239
+ count = count + len(formatted_tools)
240
+ result[server_name] = formatted_tools
241
+
242
+ if count == 0:
243
+ logger.warning("No tools retrieved from any MCP server")
244
+ logger.warning("Exiting due to zero tools retrieved")
245
+ sys.exit(1)
246
+
247
+ logger.info(f"Retrieved {count} tool(s)")
248
+ return result
249
+
250
+ except Exception as e:
251
+ # Check if it's a configuration error
252
+ if "Configuration error" in str(e) or "Missing 'transport' key" in str(e):
253
+ logger.error(f"Configuration error while getting MCP tools: {e}")
254
+ logger.error("Exiting due to invalid MCP configuration")
255
+ sys.exit(1)
256
+ else:
257
+ logger.error(f"Failed to get MCP tools: {e}")
258
+ logger.error("Exiting due to MCP tools retrieval failure")
259
+ sys.exit(1)
260
+
261
+ async def disconnect_all(self):
262
+ """Disconnect all MCP connections"""
263
+ if self.client:
264
+ try:
265
+ # MultiServerMCPClient manages connections automatically
266
+ self.client = None
267
+ logger.info("All MCP services disconnected")
268
+ except Exception as e:
269
+ logger.error(f"Failed to disconnect MCP services: {e}")
270
+
271
+
272
+ # Global MCP manager instance
273
+ _mcp_manager = MCPManager()
274
+
275
+
276
+ def get_mcp_manager() -> MCPManager:
277
+ """Get global MCP manager instance"""
278
+ return _mcp_manager
@@ -0,0 +1,13 @@
1
+ """Network communication modules for Castrel Bridge Proxy"""
2
+
3
+ from .api_client import APIClient, APIError, NetworkError, PairingError, get_api_client
4
+ from .websocket_client import WebSocketClient
5
+
6
+ __all__ = [
7
+ "APIClient",
8
+ "APIError",
9
+ "NetworkError",
10
+ "PairingError",
11
+ "get_api_client",
12
+ "WebSocketClient",
13
+ ]