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.
- castrel_proxy/__init__.py +22 -0
- castrel_proxy/cli/__init__.py +5 -0
- castrel_proxy/cli/commands.py +608 -0
- castrel_proxy/core/__init__.py +18 -0
- castrel_proxy/core/client_id.py +94 -0
- castrel_proxy/core/config.py +158 -0
- castrel_proxy/core/daemon.py +206 -0
- castrel_proxy/core/executor.py +166 -0
- castrel_proxy/data/__init__.py +1 -0
- castrel_proxy/data/default_whitelist.txt +229 -0
- castrel_proxy/mcp/__init__.py +8 -0
- castrel_proxy/mcp/manager.py +278 -0
- castrel_proxy/network/__init__.py +13 -0
- castrel_proxy/network/api_client.py +284 -0
- castrel_proxy/network/websocket_client.py +1148 -0
- castrel_proxy/operations/__init__.py +17 -0
- castrel_proxy/operations/document.py +343 -0
- castrel_proxy/security/__init__.py +17 -0
- castrel_proxy/security/whitelist.py +403 -0
- castrel_proxy-0.1.0.dist-info/METADATA +302 -0
- castrel_proxy-0.1.0.dist-info/RECORD +24 -0
- castrel_proxy-0.1.0.dist-info/WHEEL +4 -0
- castrel_proxy-0.1.0.dist-info/entry_points.txt +2 -0
- castrel_proxy-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -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,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
|
+
]
|