tunnel-manager 1.0.9__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,110 @@
1
+ #!/usr/bin/python
2
+ # coding: utf-8
3
+
4
+ import os
5
+ import pickle
6
+ from pathlib import Path
7
+ from typing import Any, Union, List
8
+ from importlib.resources import files, as_file
9
+
10
+ import yaml
11
+ from fasta2a import Skill
12
+
13
+
14
+ def to_integer(string: Union[str, int] = None) -> int:
15
+ if isinstance(string, int):
16
+ return string
17
+ if not string:
18
+ return 0
19
+ try:
20
+ return int(string.strip())
21
+ except ValueError:
22
+ raise ValueError(f"Cannot convert '{string}' to integer")
23
+
24
+
25
+ def to_boolean(string: Union[str, bool] = None) -> bool:
26
+ if isinstance(string, bool):
27
+ return string
28
+ if not string:
29
+ return False
30
+ normalized = str(string).strip().lower()
31
+ true_values = {"t", "true", "y", "yes", "1"}
32
+ false_values = {"f", "false", "n", "no", "0"}
33
+ if normalized in true_values:
34
+ return True
35
+ elif normalized in false_values:
36
+ return False
37
+ else:
38
+ raise ValueError(f"Cannot convert '{string}' to boolean")
39
+
40
+
41
+ def save_model(model: Any, file_name: str = "model", file_path: str = ".") -> str:
42
+ pickle_file = os.path.join(file_path, f"{file_name}.pkl")
43
+ with open(pickle_file, "wb") as file:
44
+ pickle.dump(model, file)
45
+ return pickle_file
46
+
47
+
48
+ def load_model(file: str) -> Any:
49
+ with open(file, "rb") as model_file:
50
+ model = pickle.load(model_file)
51
+ return model
52
+
53
+
54
+ def get_skills_path() -> str:
55
+ skills_dir = files("tunnel_manager") / "skills"
56
+ with as_file(skills_dir) as path:
57
+ skills_path = str(path)
58
+ return skills_path
59
+
60
+
61
+ def get_mcp_config_path() -> str:
62
+ mcp_config_file = files("tunnel_manager") / "mcp_config.json"
63
+ with as_file(mcp_config_file) as path:
64
+ mcp_config_path = str(path)
65
+ return mcp_config_path
66
+
67
+
68
+ def load_skills_from_directory(directory: str) -> List[Skill]:
69
+ skills = []
70
+ base_path = Path(directory)
71
+
72
+ if not base_path.exists():
73
+ print(f"Skills directory not found: {directory}")
74
+ return skills
75
+
76
+ for item in base_path.iterdir():
77
+ if item.is_dir():
78
+ skill_file = item / "SKILL.md"
79
+ if skill_file.exists():
80
+ try:
81
+ with open(skill_file, "r") as f:
82
+ # Extract frontmatter
83
+ content = f.read()
84
+ if content.startswith("---"):
85
+ _, frontmatter, _ = content.split("---", 2)
86
+ data = yaml.safe_load(frontmatter)
87
+
88
+ skill_id = item.name
89
+ skill_name = data.get("name", skill_id)
90
+ skill_desc = data.get(
91
+ "description", f"Access to {skill_name} tools"
92
+ )
93
+
94
+ tag_name = skill_id.replace("tunnel-manager-", "")
95
+ tags = ["tunnel-manager", tag_name]
96
+
97
+ skills.append(
98
+ Skill(
99
+ id=skill_id,
100
+ name=skill_name,
101
+ description=skill_desc,
102
+ tags=tags,
103
+ input_modes=["text"],
104
+ output_modes=["text"],
105
+ )
106
+ )
107
+ except Exception as e:
108
+ print(f"Error loading skill from {skill_file}: {e}")
109
+
110
+ return skills
@@ -0,0 +1,565 @@
1
+ Metadata-Version: 2.4
2
+ Name: tunnel-manager
3
+ Version: 1.0.9
4
+ Summary: Create SSH Tunnels to your remote hosts and host as an MCP Server for Agentic AI!
5
+ Author-email: Audel Rouhi <knucklessg1@gmail.com>
6
+ License: MIT
7
+ Classifier: Development Status :: 5 - Production/Stable
8
+ Classifier: License :: Public Domain
9
+ Classifier: Environment :: Console
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: fastmcp>=2.13.0.2
16
+ Requires-Dist: paramiko>=4.0.0
17
+ Provides-Extra: mcp
18
+ Requires-Dist: fastmcp>=2.13.0.2; extra == "mcp"
19
+ Requires-Dist: eunomia-mcp>=0.3.10; extra == "mcp"
20
+ Provides-Extra: a2a
21
+ Requires-Dist: pydantic-ai-slim[a2a,ag-ui,anthropic,fastmcp,google,huggingface,openai,web]>=1.32.0; extra == "a2a"
22
+ Requires-Dist: pydantic-ai-skills; extra == "a2a"
23
+ Provides-Extra: all
24
+ Requires-Dist: fastmcp>=2.13.0.2; extra == "all"
25
+ Requires-Dist: eunomia-mcp>=0.3.10; extra == "all"
26
+ Requires-Dist: pydantic-ai-slim[a2a,ag-ui,anthropic,fastmcp,google,huggingface,openai,web]>=1.32.0; extra == "all"
27
+ Requires-Dist: pydantic-ai-skills; extra == "all"
28
+ Dynamic: license-file
29
+
30
+ # Tunnel Manager - A2A | AG-UI | MCP
31
+
32
+ ![PyPI - Version](https://img.shields.io/pypi/v/tunnel-manager)
33
+ ![MCP Server](https://badge.mcpx.dev?type=server 'MCP Server')
34
+ ![PyPI - Downloads](https://img.shields.io/pypi/dd/tunnel-manager)
35
+ ![GitHub Repo stars](https://img.shields.io/github/stars/Knuckles-Team/tunnel-manager)
36
+ ![GitHub forks](https://img.shields.io/github/forks/Knuckles-Team/tunnel-manager)
37
+ ![GitHub contributors](https://img.shields.io/github/contributors/Knuckles-Team/tunnel-manager)
38
+ ![PyPI - License](https://img.shields.io/pypi/l/tunnel-manager)
39
+ ![GitHub](https://img.shields.io/github/license/Knuckles-Team/tunnel-manager)
40
+
41
+ ![GitHub last commit (by committer)](https://img.shields.io/github/last-commit/Knuckles-Team/tunnel-manager)
42
+ ![GitHub pull requests](https://img.shields.io/github/issues-pr/Knuckles-Team/tunnel-manager)
43
+ ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/Knuckles-Team/tunnel-manager)
44
+ ![GitHub issues](https://img.shields.io/github/issues/Knuckles-Team/tunnel-manager)
45
+
46
+ ![GitHub top language](https://img.shields.io/github/languages/top/Knuckles-Team/tunnel-manager)
47
+ ![GitHub language count](https://img.shields.io/github/languages/count/Knuckles-Team/tunnel-manager)
48
+ ![GitHub repo size](https://img.shields.io/github/repo-size/Knuckles-Team/tunnel-manager)
49
+ ![GitHub repo file count (file type)](https://img.shields.io/github/directory-file-count/Knuckles-Team/tunnel-manager)
50
+ ![PyPI - Wheel](https://img.shields.io/pypi/wheel/tunnel-manager)
51
+ ![PyPI - Implementation](https://img.shields.io/pypi/implementation/tunnel-manager)
52
+
53
+ *Version: 1.0.9*
54
+
55
+ ## Overview
56
+
57
+ This project provides a Python-based `Tunnel` class for secure SSH connections and file transfers, integrated with a FastMCP server (`tunnel_manager_mcp.py`) to expose these capabilities as tools for AI-driven workflows. The implementation supports both standard SSH (e.g., for local networks) and Teleport's secure access platform, leveraging the `paramiko` library for SSH operations.
58
+
59
+ ### Features
60
+
61
+ ### Tunnel Class
62
+ - **Purpose**: Facilitates secure SSH connections, file transfers, and key management for single or multiple hosts.
63
+ - **Key Functionality**:
64
+ - **Run Remote Commands**: Execute shell commands on a remote host and retrieve output.
65
+ - **File Upload/Download**: Transfer files to/from a single host or all hosts in an inventory group using SFTP.
66
+ - **Passwordless SSH Setup**: Configure key-based authentication for secure, passwordless access, with support for RSA and Ed25519 key types.
67
+ - **SSH Config Management**: Copy local SSH config files to remote hosts.
68
+ - **Key Rotation**: Generate and deploy new SSH key pairs (RSA or Ed25519), updating `authorized_keys`.
69
+ - **Inventory Support**: Operate on multiple hosts defined in an Ansible-style YAML inventory, with group targeting (e.g., `all`, `homelab`, `poweredge`).
70
+ - **Teleport Support**: Seamlessly integrates with Teleport's certificate-based authentication and proxying.
71
+ - **Configuration Flexibility**: Loads SSH settings from `~/.ssh/config` by default, with optional overrides for username, password, identity files, certificates, and proxy commands.
72
+ - **Logging**: Optional file-based logging for debugging and auditing.
73
+ - **Parallel Execution**: Support for parallel operations across multiple hosts with configurable thread limits.
74
+ - **Key Type Support**: Explicit support for both RSA and Ed25519 keys in authentication, generation, and rotation for enhanced security and compatibility.
75
+
76
+ ### FastMCP Server
77
+ - **Purpose**: Exposes `Tunnel` class functionality as a FastMCP server, enabling AI tools to perform remote operations programmatically.
78
+ - **Tools Provided**:
79
+ - `run_command_on_remote_host`: Runs a shell command on a single remote host.
80
+ - `send_file_to_remote_host`: Uploads a file to a single remote host via SFTP.
81
+ - `receive_file_from_remote_host`: Downloads a file from a single remote host via SFTP.
82
+ - `check_ssh_server`: Checks if the SSH server is running and configured for key-based authentication.
83
+ - `test_key_auth`: Tests key-based authentication for a host.
84
+ - `setup_passwordless_ssh`: Sets up passwordless SSH for a single host.
85
+ - `copy_ssh_config`: Copies an SSH config file to a single remote host.
86
+ - `rotate_ssh_key`: Rotates SSH keys for a single host.
87
+ - `remove_host_key`: Removes a host’s key from the local `known_hosts` file.
88
+ - `configure_key_auth_on_inventory`: Sets up passwordless SSH for all hosts in an inventory group.
89
+ - `run_command_on_inventory`: Runs a command on all hosts in an inventory group.
90
+ - `copy_ssh_config_on_inventory`: Copies an SSH config file to all hosts in an inventory group.
91
+ - `rotate_ssh_key_on_inventory`: Rotates SSH keys for all hosts in an inventory group.
92
+ - `send_file_to_inventory`: Uploads a file to all hosts in an inventory group via SFTP.
93
+ - `receive_file_from_inventory`: Downloads a file from all hosts in an inventory group via SFTP.
94
+ - **Transport Options**: Supports `stdio` (for local scripting) and `http` (for networked access) transport modes.
95
+ - **Progress Reporting**: Integrates with FastMCP's `Context` for progress updates during operations.
96
+ - **Logging**: Comprehensive logging to a file (`tunnel_mcp.log` by default) or a user-specified file.
97
+
98
+ ## Usage
99
+
100
+ ### CLI
101
+ | Short Flag | Long Flag | Description | Required | Default Value |
102
+ |------------|----------------------|----------------------------------------------------------|----------|---------------|
103
+ | -h | --help | Show usage for the script | No | None |
104
+ | | --log-file | Log to specified file (default: console output) | No | Console |
105
+ | | setup-all | Setup passwordless SSH for all hosts in inventory | Yes* | None |
106
+ | | --inventory | YAML inventory path | Yes | None |
107
+ | | --shared-key-path | Path to shared private key | No | ~/.ssh/id_shared |
108
+ | | --key-type | Key type (rsa or ed25519) | No | ed25519 |
109
+ | | --group | Inventory group to target | No | all |
110
+ | | --parallel | Run operation in parallel | No | False |
111
+ | | --max-threads | Max threads for parallel execution | No | 5 |
112
+ | | run-command | Run a shell command on all hosts in inventory | Yes* | None |
113
+ | | --remote-command | Shell command to run | Yes | None |
114
+ | | copy-config | Copy SSH config to all hosts in inventory | Yes* | None |
115
+ | | --local-config-path | Local SSH config path | Yes | None |
116
+ | | --remote-config-path | Remote path for SSH config | No | ~/.ssh/config |
117
+ | | rotate-key | Rotate SSH keys for all hosts in inventory | Yes* | None |
118
+ | | --key-prefix | Prefix for new key paths (appends hostname) | No | ~/.ssh/id_ |
119
+ | | --key-type | Key type (rsa or ed25519) | No | ed25519 |
120
+ | | send-file | Upload a file to all hosts in inventory | Yes* | None |
121
+ | | --local-path | Local file path to upload | Yes | None |
122
+ | | --remote-path | Remote destination path | Yes | None |
123
+ | | receive-file | Download a file from all hosts in inventory | Yes* | None |
124
+ | | --remote-path | Remote file path to download | Yes | None |
125
+ | | --local-path-prefix | Local directory path prefix to save files | Yes | None |
126
+
127
+ ### Notes
128
+ One of the commands (`setup-all`, `run-command`, `copy-config`, `rotate-key`, `send-file`, `receive-file`) must be specified as the first argument to `tunnel_manager.py`. Each command has required arguments that must be specified with flags:
129
+ - `setup-all`: Requires `--inventory`.
130
+ - `run-command`: Requires `--inventory` and `--remote-command`.
131
+ - `copy-config`: Requires `--inventory` and `--local-config-path`.
132
+ - `rotate-key`: Requires `--inventory`.
133
+ - `send-file`: Requires `--inventory`, `--local-path`, and `--remote-path`.
134
+ - `receive-file`: Requires `--inventory`, `--remote-path`, and `--local-path-prefix`.
135
+
136
+ ### Additional Notes
137
+ - Ensure `ansible_host` values in `inventory.yml` are resolvable IPs or hostnames.
138
+ - Update `ansible_ssh_private_key_file` in the inventory after running `rotate-key`.
139
+ - Use `--log-file` for file-based logging or omit for console output.
140
+ - The `--parallel` option speeds up operations but may overload resources; adjust `--max-threads` as needed.
141
+ - The `receive-file` command saves files to `local_path_prefix/<hostname>/<filename>` to preserve original filenames and avoid conflicts.
142
+ - Ed25519 keys are recommended for better security and performance over RSA, but RSA is supported for compatibility with older systems.
143
+
144
+ #### 1. Setup Passwordless SSH
145
+ Set up passwordless SSH for hosts in the inventory, distributing a shared key. Use `--key-type` to specify RSA or Ed25519 (default: ed25519).
146
+ - **Target `all` group (sequential, Ed25519)**:
147
+ ```bash
148
+ tunnel-manager setup-all --inventory inventory.yml --shared-key-path ~/.ssh/id_shared --key-type ed25519
149
+ ```
150
+ - **Target `homelab` group (parallel, 3 threads, RSA)**:
151
+ ```bash
152
+ tunnel-manager setup-all --inventory inventory.yml --shared-key-path ~/.ssh/id_shared_rsa --key-type rsa --group homelab --parallel --max-threads 3
153
+ ```
154
+ - **Target `poweredge` group (sequential, Ed25519)**:
155
+ ```bash
156
+ tunnel-manager --log-file setup_poweredge.log setup-all --inventory inventory.yml --shared-key-path ~/.ssh/id_shared --key-type ed25519 --group poweredge
157
+ ```
158
+
159
+ #### 2. Run a Command
160
+ Execute a shell command on all hosts in the specified group.
161
+ - **Run `uptime` on `all` group (sequential)**:
162
+ ```bash
163
+ tunnel-manager run-command --inventory inventory.yml --remote-command "uptime"
164
+ ```
165
+ - **Run `df -h` on `homelab` group (parallel, 5 threads)**:
166
+ ```bash
167
+ tunnel-manager run-command --inventory inventory.yml --remote-command "df -h" --group homelab --parallel --max-threads 5
168
+ ```
169
+ - **Run `whoami` on `poweredge` group (sequential)**:
170
+ ```bash
171
+ tunnel-manager run-command --inventory inventory.yml --remote-command "whoami" --group poweredge
172
+ ```
173
+
174
+ #### 3. Copy SSH Config
175
+ Copy a local SSH config file to the remote hosts’ `~/.ssh/config`.
176
+ - **Copy to `all` group (sequential)**:
177
+ ```bash
178
+ tunnel-manager copy-config --inventory inventory.yml --local-config-path ~/.ssh/config
179
+ ```
180
+ - **Copy to `homelab` group (parallel, 4 threads)**:
181
+ ```bash
182
+ tunnel-manager copy-config --inventory inventory.yml --local-config-path ~/.ssh/config --group homelab --parallel --max-threads 4
183
+ ```
184
+ - **Copy to `poweredge` group with custom remote path**:
185
+ ```bash
186
+ tunnel-manager --log-file copy_config.log copy-config --inventory inventory.yml --local-config-path ~/.ssh/config --remote-config-path ~/.ssh/custom_config --group poweredge
187
+ ```
188
+
189
+ #### 4. Rotate SSH Keys
190
+ Rotate SSH keys for hosts, generating new keys with a prefix. Use `--key-type` to specify RSA or Ed25519 (default: ed25519).
191
+ - **Rotate keys for `all` group (sequential, Ed25519)**:
192
+ ```bash
193
+ tunnel-manager rotate-key --inventory inventory.yml --key-prefix ~/.ssh/id_ --key-type ed25519
194
+ ```
195
+ - **Rotate keys for `homelab` group (parallel, 3 threads, RSA)**:
196
+ ```bash
197
+ tunnel-manager rotate-key --inventory inventory.yml --key-prefix ~/.ssh/id_rsa_ --key-type rsa --group homelab --parallel --max-threads 3
198
+ ```
199
+ - **Rotate keys for `poweredge` group (sequential, Ed25519)**:
200
+ ```bash
201
+ tunnel-manager --log-file rotate.log rotate-key --inventory inventory.yml --key-prefix ~/.ssh/id_ --key-type ed25519 --group poweredge
202
+ ```
203
+
204
+ #### 5. Upload a File
205
+ Upload a local file to all hosts in the specified group.
206
+ - **Upload to `all` group (sequential)**:
207
+ ```bash
208
+ tunnel-manager send-file --inventory inventory.yml --local-path ./myfile.txt --remote-path /home/user/myfile.txt
209
+ ```
210
+ - **Upload to `homelab` group (parallel, 3 threads)**:
211
+ ```bash
212
+ tunnel-manager send-file --inventory inventory.yml --local-path ./myfile.txt --remote-path /home/user/myfile.txt --group homelab --parallel --max-threads 3
213
+ ```
214
+ - **Upload to `poweredge` group (sequential)**:
215
+ ```bash
216
+ tunnel-manager --log-file upload_poweredge.log send-file --inventory inventory.yml --local-path ./myfile.txt --remote-path /home/user/myfile.txt --group poweredge
217
+ ```
218
+
219
+ #### 6. Download a File
220
+ Download a file from all hosts in the specified group, saving to host-specific subdirectories (e.g., `downloads/R510/myfile.txt`).
221
+ - **Download from `all` group (sequential)**:
222
+ ```bash
223
+ tunnel-manager receive-file --inventory inventory.yml --remote-path /home/user/myfile.txt --local-path-prefix ./downloads
224
+ ```
225
+ - **Download from `homelab` group (parallel, 3 threads)**:
226
+ ```bash
227
+ tunnel-manager receive-file --inventory inventory.yml --remote-path /home/user/myfile.txt --local-path-prefix ./downloads --group homelab --parallel --max-threads 3
228
+ ```
229
+ - **Download from `poweredge` group (sequential)**:
230
+ ```bash
231
+ tunnel-manager --log-file download_poweredge.log receive-file --inventory inventory.yml --remote-path /home/user/myfile.txt --local-path-prefix ./downloads --group poweredge
232
+ ```
233
+
234
+ ### Tunnel Manager Inventory
235
+
236
+ **Inventory File Example (`inventory.yml`)**:
237
+
238
+ ```yaml
239
+ all:
240
+ hosts:
241
+ r510:
242
+ ansible_host: 192.168.1.10
243
+ ansible_user: admin
244
+ ansible_ssh_private_key_file: "~/.ssh/id_ed25519"
245
+ r710:
246
+ ansible_host: 192.168.1.11
247
+ ansible_user: admin
248
+ ansible_ssh_pass: mypassword
249
+ gr1080:
250
+ ansible_host: 192.168.1.14
251
+ ansible_user: admin
252
+ ansible_ssh_private_key_file: "~/.ssh/id_rsa"
253
+ homelab:
254
+ hosts:
255
+ r510:
256
+ ansible_host: 192.168.1.10
257
+ ansible_user: admin
258
+ ansible_ssh_private_key_file: "~/.ssh/id_ed25519"
259
+ r710:
260
+ ansible_host: 192.168.1.11
261
+ ansible_user: admin
262
+ ansible_ssh_pass: mypassword
263
+ gr1080:
264
+ ansible_host: 192.168.1.14
265
+ ansible_user: admin
266
+ ansible_ssh_private_key_file: "~/.ssh/id_rsa"
267
+ poweredge:
268
+ hosts:
269
+ r510:
270
+ ansible_host: 192.168.1.10
271
+ ansible_user: admin
272
+ ansible_ssh_private_key_file: "~/.ssh/id_ed25519"
273
+ r710:
274
+ ansible_host: 192.168.1.11
275
+ ansible_user: admin
276
+ ansible_ssh_pass: mypassword
277
+ ```
278
+
279
+ Replace IPs, usernames, and passwords with your actual values.
280
+
281
+
282
+ ### MCP CLI
283
+
284
+ | Short Flag | Long Flag | Description |
285
+ |------------|------------------------------------|-----------------------------------------------------------------------------|
286
+ | -h | --help | Display help information |
287
+ | -t | --transport | Transport method: 'stdio', 'http', or 'sse' [legacy] (default: stdio) |
288
+ | -s | --host | Host address for HTTP transport (default: 0.0.0.0) |
289
+ | -p | --port | Port number for HTTP transport (default: 8000) |
290
+ | | --auth-type | Authentication type: 'none', 'static', 'jwt', 'oauth-proxy', 'oidc-proxy', 'remote-oauth' (default: none) |
291
+ | | --token-jwks-uri | JWKS URI for JWT verification |
292
+ | | --token-issuer | Issuer for JWT verification |
293
+ | | --token-audience | Audience for JWT verification |
294
+ | | --oauth-upstream-auth-endpoint | Upstream authorization endpoint for OAuth Proxy |
295
+ | | --oauth-upstream-token-endpoint | Upstream token endpoint for OAuth Proxy |
296
+ | | --oauth-upstream-client-id | Upstream client ID for OAuth Proxy |
297
+ | | --oauth-upstream-client-secret | Upstream client secret for OAuth Proxy |
298
+ | | --oauth-base-url | Base URL for OAuth Proxy |
299
+ | | --oidc-config-url | OIDC configuration URL |
300
+ | | --oidc-client-id | OIDC client ID |
301
+ | | --oidc-client-secret | OIDC client secret |
302
+ | | --oidc-base-url | Base URL for OIDC Proxy |
303
+ | | --remote-auth-servers | Comma-separated list of authorization servers for Remote OAuth |
304
+ | | --remote-base-url | Base URL for Remote OAuth |
305
+ | | --allowed-client-redirect-uris | Comma-separated list of allowed client redirect URIs |
306
+ | | --eunomia-type | Eunomia authorization type: 'none', 'embedded', 'remote' (default: none) |
307
+ | | --eunomia-policy-file | Policy file for embedded Eunomia (default: mcp_policies.json) |
308
+ | | --eunomia-remote-url | URL for remote Eunomia server |
309
+
310
+ ### Using as an MCP Server
311
+
312
+ The MCP Server can be run in two modes: `stdio` (for local testing) or `http` (for networked access). To start the server, use the following commands:
313
+
314
+ ### A2A CLI
315
+ #### Endpoints
316
+ - **Web UI**: `http://localhost:8000/` (if enabled)
317
+ - **A2A**: `http://localhost:8000/a2a` (Discovery: `/a2a/.well-known/agent.json`)
318
+ - **AG-UI**: `http://localhost:8000/ag-ui` (POST)
319
+
320
+ | Short Flag | Long Flag | Description |
321
+ |------------|-------------------|------------------------------------------------------------------------|
322
+ | -h | --help | Display help information |
323
+ | | --host | Host to bind the server to (default: 0.0.0.0) |
324
+ | | --port | Port to bind the server to (default: 9000) |
325
+ | | --reload | Enable auto-reload |
326
+ | | --provider | LLM Provider: 'openai', 'anthropic', 'google', 'huggingface' |
327
+ | | --model-id | LLM Model ID (default: qwen3:4b) |
328
+ | | --base-url | LLM Base URL (for OpenAI compatible providers) |
329
+ | | --api-key | LLM API Key |
330
+ | | --mcp-url | MCP Server URL (default: http://localhost:8000/mcp) |
331
+ | | --web | Enable Pydantic AI Web UI | False (Env: ENABLE_WEB_UI) |
332
+
333
+
334
+
335
+
336
+ #### Run in stdio mode (default):
337
+ ```bash
338
+ tunnel-manager-mcp --transport "stdio"
339
+ ```
340
+
341
+ #### Run in HTTP mode:
342
+ ```bash
343
+ tunnel-manager-mcp --transport "http" --host "0.0.0.0" --port "8000"
344
+ ```
345
+
346
+ ### Tunnel Class
347
+ The `Tunnel` class can be used standalone for SSH operations. Examples:
348
+
349
+ #### Using RSA Keys
350
+ ```python
351
+ from tunnel_manager.tunnel_manager import Tunnel
352
+
353
+ # Initialize with a remote host (assumes ~/.ssh/config or explicit params)
354
+ tunnel = Tunnel(
355
+ remote_host="192.168.1.10",
356
+ username="admin",
357
+ password="mypassword",
358
+ identity_file="/path/to/id_rsa",
359
+ certificate_file="/path/to/cert", # Optional for Teleport
360
+ proxy_command="tsh proxy ssh %h", # Optional for Teleport
361
+ ssh_config_file="~/.ssh/config",
362
+ )
363
+
364
+ # Connect and run a command
365
+ tunnel.connect()
366
+ out, err = tunnel.run_command("ls -la /tmp")
367
+ print(f"Output: {out}\nError: {err}")
368
+
369
+ # Upload a file
370
+ tunnel.send_file("/local/file.txt", "/remote/file.txt")
371
+
372
+ # Download a file
373
+ tunnel.receive_file("/remote/file.txt", "/local/downloaded.txt")
374
+
375
+ # Setup passwordless SSH with RSA
376
+ tunnel.setup_passwordless_ssh(local_key_path="~/.ssh/id_rsa", key_type="rsa")
377
+
378
+ # Copy SSH config
379
+ tunnel.copy_ssh_config("/local/ssh_config", "~/.ssh/config")
380
+
381
+ # Rotate SSH key with RSA
382
+ tunnel.rotate_ssh_key("/path/to/new_rsa_key", key_type="rsa")
383
+
384
+ # Close the connection
385
+ tunnel.close()
386
+ ```
387
+
388
+ #### Using Ed25519 Keys
389
+ ```python
390
+ from tunnel_manager.tunnel_manager import Tunnel
391
+
392
+ # Initialize with a remote host (assumes ~/.ssh/config or explicit params)
393
+ tunnel = Tunnel(
394
+ remote_host="192.168.1.10",
395
+ username="admin",
396
+ password="mypassword",
397
+ identity_file="/path/to/id_ed25519",
398
+ certificate_file="/path/to/cert", # Optional for Teleport
399
+ proxy_command="tsh proxy ssh %h", # Optional for Teleport
400
+ ssh_config_file="~/.ssh/config",
401
+ )
402
+
403
+ # Connect and run a command
404
+ tunnel.connect()
405
+ out, err = tunnel.run_command("ls -la /tmp")
406
+ print(f"Output: {out}\nError: {err}")
407
+
408
+ # Upload a file
409
+ tunnel.send_file("/local/file.txt", "/remote/file.txt")
410
+
411
+ # Download a file
412
+ tunnel.receive_file("/remote/file.txt", "/local/downloaded.txt")
413
+
414
+ # Setup passwordless SSH with Ed25519
415
+ tunnel.setup_passwordless_ssh(local_key_path="~/.ssh/id_ed25519", key_type="ed25519")
416
+
417
+ # Copy SSH config
418
+ tunnel.copy_ssh_config("/local/ssh_config", "~/.ssh/config")
419
+
420
+ # Rotate SSH key with Ed25519
421
+ tunnel.rotate_ssh_key("/path/to/new_ed25519_key", key_type="ed25519")
422
+
423
+ # Close the connection
424
+ tunnel.close()
425
+ ```
426
+
427
+ ### Deploy MCP Server as a Service
428
+
429
+ The MCP server can be deployed using Docker, with configurable authentication, middleware, and Eunomia authorization.
430
+
431
+ #### Using Docker Run
432
+
433
+ ```bash
434
+ docker pull knucklessg1/tunnel-manager:latest
435
+
436
+ docker run -d \
437
+ --name tunnel-manager-mcp \
438
+ -p 8004:8004 \
439
+ -e HOST=0.0.0.0 \
440
+ -e PORT=8004 \
441
+ -e TRANSPORT=http \
442
+ -e AUTH_TYPE=none \
443
+ -e EUNOMIA_TYPE=none \
444
+ knucklessg1/tunnel-manager:latest
445
+ ```
446
+
447
+ For advanced authentication (e.g., JWT, OAuth Proxy, OIDC Proxy, Remote OAuth) or Eunomia, add the relevant environment variables:
448
+
449
+ ```bash
450
+ docker run -d \
451
+ --name tunnel-manager-mcp \
452
+ -p 8004:8004 \
453
+ -e HOST=0.0.0.0 \
454
+ -e PORT=8004 \
455
+ -e TRANSPORT=http \
456
+ -e AUTH_TYPE=oidc-proxy \
457
+ -e OIDC_CONFIG_URL=https://provider.com/.well-known/openid-configuration \
458
+ -e OIDC_CLIENT_ID=your-client-id \
459
+ -e OIDC_CLIENT_SECRET=your-client-secret \
460
+ -e OIDC_BASE_URL=https://your-server.com \
461
+ -e ALLOWED_CLIENT_REDIRECT_URIS=http://localhost:*,https://*.example.com/* \
462
+ -e EUNOMIA_TYPE=embedded \
463
+ -e EUNOMIA_POLICY_FILE=/app/mcp_policies.json \
464
+ knucklessg1/tunnel-manager:latest
465
+ ```
466
+
467
+ #### Using Docker Compose
468
+
469
+ Create a `docker-compose.yml` file:
470
+
471
+ ```yaml
472
+ services:
473
+ tunnel-manager-mcp:
474
+ image: knucklessg1/tunnel-manager:latest
475
+ environment:
476
+ - HOST=0.0.0.0
477
+ - PORT=8004
478
+ - TRANSPORT=http
479
+ - AUTH_TYPE=none
480
+ - EUNOMIA_TYPE=none
481
+ ports:
482
+ - 8004:8004
483
+ ```
484
+
485
+ For advanced setups with authentication and Eunomia:
486
+
487
+ ```yaml
488
+ services:
489
+ tunnel-manager-mcp:
490
+ image: knucklessg1/tunnel-manager:latest
491
+ environment:
492
+ - HOST=0.0.0.0
493
+ - PORT=8004
494
+ - TRANSPORT=http
495
+ - AUTH_TYPE=oidc-proxy
496
+ - OIDC_CONFIG_URL=https://provider.com/.well-known/openid-configuration
497
+ - OIDC_CLIENT_ID=your-client-id
498
+ - OIDC_CLIENT_SECRET=your-client-secret
499
+ - OIDC_BASE_URL=https://your-server.com
500
+ - ALLOWED_CLIENT_REDIRECT_URIS=http://localhost:*,https://*.example.com/*
501
+ - EUNOMIA_TYPE=embedded
502
+ - EUNOMIA_POLICY_FILE=/app/mcp_policies.json
503
+ ports:
504
+ - 8004:8004
505
+ volumes:
506
+ - ./mcp_policies.json:/app/mcp_policies.json
507
+ ```
508
+
509
+ Run the service:
510
+
511
+ ```bash
512
+ docker-compose up -d
513
+ ```
514
+
515
+ #### Configure `mcp.json` for AI Integration
516
+
517
+ ```json
518
+ {
519
+ "mcpServers": {
520
+ "tunnel_manager": {
521
+ "command": "uv",
522
+ "args": [
523
+ "run",
524
+ "--with",
525
+ "tunnel-manager",
526
+ "tunnel_manager_mcp"
527
+ ],
528
+ "env": {
529
+ "TUNNEL_REMOTE_HOST": "192.168.1.12", // Optional
530
+ "TUNNEL_USERNAME": "admin", // Optional
531
+ "TUNNEL_PASSWORD": "", // Optional
532
+ "TUNNEL_REMOTE_PORT": "22", // Optional
533
+ "TUNNEL_IDENTITY_FILE": "", // Optional
534
+ "TUNNEL_INVENTORY": "~/inventory.yaml", // Optional
535
+ "TUNNEL_INVENTORY_GROUP": "all", // Optional
536
+ "TUNNEL_PARALLEL": "true", // Optional
537
+ "TUNNEL_CERTIFICATE": "", // Optional
538
+ "TUNNEL_PROXY_COMMAND": "", // Optional
539
+ "TUNNEL_LOG_FILE": "~/tunnel_log.txt", // Optional
540
+ "TUNNEL_MAX_THREADS": "6" // Optional
541
+ },
542
+ "timeout": 200000
543
+ }
544
+ }
545
+ }
546
+ ```
547
+
548
+ ## Install Python Package
549
+ ```bash
550
+ python -m pip install tunnel-manager
551
+ ```
552
+
553
+ or
554
+
555
+ ```bash
556
+ uv pip install --upgrade tunnel-manager
557
+ ```
558
+
559
+ ## Repository Owners
560
+
561
+
562
+ <img width="100%" height="180em" src="https://github-readme-stats.vercel.app/api?username=Knucklessg1&show_icons=true&hide_border=true&&count_private=true&include_all_commits=true" />
563
+
564
+ ![GitHub followers](https://img.shields.io/github/followers/Knucklessg1)
565
+ ![GitHub User's stars](https://img.shields.io/github/stars/Knucklessg1)