nomad-mcp 0.1.0__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 (38) hide show
  1. nomad_mcp-0.1.0/LICENSE +21 -0
  2. nomad_mcp-0.1.0/PKG-INFO +205 -0
  3. nomad_mcp-0.1.0/README.md +188 -0
  4. nomad_mcp-0.1.0/pyproject.toml +38 -0
  5. nomad_mcp-0.1.0/setup.cfg +4 -0
  6. nomad_mcp-0.1.0/src/nomad/__init__.py +2 -0
  7. nomad_mcp-0.1.0/src/nomad/cli.py +124 -0
  8. nomad_mcp-0.1.0/src/nomad/config.py +366 -0
  9. nomad_mcp-0.1.0/src/nomad/result.py +97 -0
  10. nomad_mcp-0.1.0/src/nomad/schema.py +103 -0
  11. nomad_mcp-0.1.0/src/nomad/security.py +274 -0
  12. nomad_mcp-0.1.0/src/nomad/server.py +115 -0
  13. nomad_mcp-0.1.0/src/nomad/ssh.py +265 -0
  14. nomad_mcp-0.1.0/src/nomad/tools/__init__.py +1 -0
  15. nomad_mcp-0.1.0/src/nomad/tools/commands.py +255 -0
  16. nomad_mcp-0.1.0/src/nomad/tools/init.py +694 -0
  17. nomad_mcp-0.1.0/src/nomad/tools/network.py +871 -0
  18. nomad_mcp-0.1.0/src/nomad/tools/sync.py +627 -0
  19. nomad_mcp-0.1.0/src/nomad/tools/tasks.py +920 -0
  20. nomad_mcp-0.1.0/src/nomad/truncate.py +61 -0
  21. nomad_mcp-0.1.0/src/nomad_mcp.egg-info/PKG-INFO +205 -0
  22. nomad_mcp-0.1.0/src/nomad_mcp.egg-info/SOURCES.txt +36 -0
  23. nomad_mcp-0.1.0/src/nomad_mcp.egg-info/dependency_links.txt +1 -0
  24. nomad_mcp-0.1.0/src/nomad_mcp.egg-info/entry_points.txt +2 -0
  25. nomad_mcp-0.1.0/src/nomad_mcp.egg-info/requires.txt +5 -0
  26. nomad_mcp-0.1.0/src/nomad_mcp.egg-info/top_level.txt +1 -0
  27. nomad_mcp-0.1.0/tests/test_cli.py +46 -0
  28. nomad_mcp-0.1.0/tests/test_commands.py +435 -0
  29. nomad_mcp-0.1.0/tests/test_config.py +462 -0
  30. nomad_mcp-0.1.0/tests/test_init.py +589 -0
  31. nomad_mcp-0.1.0/tests/test_network.py +723 -0
  32. nomad_mcp-0.1.0/tests/test_result.py +104 -0
  33. nomad_mcp-0.1.0/tests/test_security.py +255 -0
  34. nomad_mcp-0.1.0/tests/test_server.py +106 -0
  35. nomad_mcp-0.1.0/tests/test_ssh.py +249 -0
  36. nomad_mcp-0.1.0/tests/test_sync.py +693 -0
  37. nomad_mcp-0.1.0/tests/test_tasks.py +1021 -0
  38. nomad_mcp-0.1.0/tests/test_truncate.py +81 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tsunam1
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.4
2
+ Name: nomad-mcp
3
+ Version: 0.1.0
4
+ Summary: Local MCP Server to manage remote development execution, sync, and tasks.
5
+ Author: Tsunam1
6
+ License-Expression: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: mcp>=1.0.0
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
15
+ Requires-Dist: pytest-mock>=3.0.0; extra == "dev"
16
+ Dynamic: license-file
17
+
18
+ # nomad
19
+
20
+ [中文说明](README.zh-CN.md)
21
+
22
+ nomad is a local MCP server for agentic remote development.
23
+
24
+ It helps an AI coding agent work with a remote machine while keeping the source
25
+ of truth on your local workstation: sync code with `rsync`, run short commands
26
+ over SSH, manage long-running jobs in remote `tmux` sessions, diagnose network
27
+ issues, and pull generated artifacts back into the local project.
28
+
29
+ nomad is not tied to a specific MCP client. Any agent environment that can start
30
+ a stdio MCP server with a command and arguments can use it.
31
+
32
+ ## Features
33
+
34
+ - Multi-target remote workspaces per local project.
35
+ - Project-local `.nomad.json` configuration with schema hints exposed through MCP.
36
+ - SSH preflight checks and read-only network diagnostics.
37
+ - Incremental `rsync` push with `.gitignore` conversion and `--delete` dry-run protection.
38
+ - Remote artifact pull into project-owned local directories.
39
+ - Short remote command execution with output truncation.
40
+ - Long-running remote task management through `tmux`.
41
+ - Optional persistent reverse SSH tunnel for sharing a local proxy with remote jobs.
42
+ - Path guards, dangerous-command checks, and secret redaction for safer agent workflows.
43
+
44
+ ## Requirements
45
+
46
+ - Python 3.11+
47
+ - `ssh`
48
+ - `rsync`
49
+ - `tmux` on remote machines when using long-running tasks
50
+ - Key-based SSH access to your remote targets
51
+
52
+ ## Installation
53
+
54
+ Run directly with `uvx`:
55
+
56
+ ```bash
57
+ uvx nomad-mcp
58
+ ```
59
+
60
+ Or install it as an isolated global command with `pipx`:
61
+
62
+ ```bash
63
+ pipx install nomad-mcp
64
+ ```
65
+
66
+ ## MCP Client Configuration
67
+
68
+ Use nomad as a stdio MCP server. The exact config file depends on your client.
69
+
70
+ Recommended no-install configuration:
71
+
72
+ ```json
73
+ {
74
+ "mcpServers": {
75
+ "nomad": {
76
+ "command": "uvx",
77
+ "args": ["nomad-mcp"]
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ For TOML-based clients:
84
+
85
+ ```toml
86
+ [mcp_servers.nomad]
87
+ command = "uvx"
88
+ args = ["nomad-mcp"]
89
+ startup_timeout_sec = 120
90
+ ```
91
+
92
+ If you installed with `pipx`, use the installed command instead:
93
+
94
+ ```json
95
+ {
96
+ "mcpServers": {
97
+ "nomad": {
98
+ "command": "nomad",
99
+ "args": []
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ You can also print config snippets with:
106
+
107
+ ```bash
108
+ nomad client-config
109
+ nomad client-config --runner nomad --format toml
110
+ ```
111
+
112
+ ## Quick Start
113
+
114
+ 1. Open an MCP-enabled agent in your local project directory.
115
+ 2. Ask it to call `init_discover`.
116
+ 3. Choose an SSH target and remote workspace path.
117
+ 4. Ask it to save a `.nomad.json` config with `init_save_config`.
118
+ 5. Push code with `sync_push`.
119
+ 6. Run short commands with `run_remote`.
120
+ 7. Run long jobs with `task_start`, then monitor them with `task_status` or `task_list`.
121
+ 8. Pull remote artifacts with `sync_pull`.
122
+
123
+ ## Example `.nomad.json`
124
+
125
+ ```json
126
+ {
127
+ "project_name": "my_project",
128
+ "mode": "remote",
129
+ "default_target": "devbox",
130
+ "targets": {
131
+ "devbox": {
132
+ "description": "Primary remote development machine",
133
+ "ssh_host": "devbox",
134
+ "remote_path": "/data/my_project",
135
+ "local_subpath": null,
136
+ "auto_create_remote_path": true,
137
+ "network": {
138
+ "use_proxy_for_ssh": false,
139
+ "jump_host": null,
140
+ "reverse_tunnel": {
141
+ "enabled": false,
142
+ "proxy_scheme": "socks5"
143
+ }
144
+ },
145
+ "sync": {
146
+ "respect_gitignore": true,
147
+ "extra_excludes": []
148
+ },
149
+ "runtime": {
150
+ "interpreter": null,
151
+ "extra_env": {}
152
+ },
153
+ "limits": {
154
+ "command_timeout_seconds": 60,
155
+ "max_output_lines": 200,
156
+ "max_output_bytes": 10240
157
+ }
158
+ }
159
+ }
160
+ }
161
+ ```
162
+
163
+ `run_remote` uses `limits.command_timeout_seconds`. For downloads, builds,
164
+ training, fuzzing, and other slow work, prefer `task_start` so the job runs in a
165
+ remote tmux session and can be checked later.
166
+
167
+ ## Tools
168
+
169
+ - `init_discover`: inspect the local workspace, SSH aliases, and proxy settings.
170
+ - `init_verify_and_probe`: verify SSH reachability and probe remote hardware/runtimes.
171
+ - `init_save_config`: validate and save `.nomad.json`.
172
+ - `init_probe_target`: refresh hardware/runtime information for a target.
173
+ - `sync_push`: push local code to the remote workspace.
174
+ - `sync_pull`: pull a remote file or directory into local `remote_artifacts/`.
175
+ - `run_remote`: run a short command in the remote workspace.
176
+ - `task_start`: start a long-running tmux task.
177
+ - `task_status`: inspect one task and return a log tail.
178
+ - `task_list`: list project-owned tasks across targets.
179
+ - `task_kill`: stop a task without deleting its logs.
180
+ - `net_diagnose`: run read-only SSH/network diagnostics.
181
+ - `tunnel_start`, `tunnel_status`, `tunnel_stop`: manage persistent reverse tunnels.
182
+
183
+ ## Safety Notes
184
+
185
+ nomad can execute commands over SSH and synchronize files with `rsync`. Use it
186
+ only with trusted local projects and trusted remote machines.
187
+
188
+ The server includes guardrails such as local/remote path checks, dangerous-command
189
+ blocking, `.nomad.json` sync exclusion, secret redaction, output truncation, and
190
+ `rsync --delete` dry-run protection. These guardrails reduce risk, but they do not
191
+ turn an untrusted agent or remote machine into a trusted one.
192
+
193
+ ## Development
194
+
195
+ ```bash
196
+ python -m pip install -e .[dev]
197
+ nomad --version
198
+ nomad doctor
199
+ python -m pytest
200
+ python -m compileall -q src tests
201
+ ```
202
+
203
+ ## License
204
+
205
+ MIT
@@ -0,0 +1,188 @@
1
+ # nomad
2
+
3
+ [中文说明](README.zh-CN.md)
4
+
5
+ nomad is a local MCP server for agentic remote development.
6
+
7
+ It helps an AI coding agent work with a remote machine while keeping the source
8
+ of truth on your local workstation: sync code with `rsync`, run short commands
9
+ over SSH, manage long-running jobs in remote `tmux` sessions, diagnose network
10
+ issues, and pull generated artifacts back into the local project.
11
+
12
+ nomad is not tied to a specific MCP client. Any agent environment that can start
13
+ a stdio MCP server with a command and arguments can use it.
14
+
15
+ ## Features
16
+
17
+ - Multi-target remote workspaces per local project.
18
+ - Project-local `.nomad.json` configuration with schema hints exposed through MCP.
19
+ - SSH preflight checks and read-only network diagnostics.
20
+ - Incremental `rsync` push with `.gitignore` conversion and `--delete` dry-run protection.
21
+ - Remote artifact pull into project-owned local directories.
22
+ - Short remote command execution with output truncation.
23
+ - Long-running remote task management through `tmux`.
24
+ - Optional persistent reverse SSH tunnel for sharing a local proxy with remote jobs.
25
+ - Path guards, dangerous-command checks, and secret redaction for safer agent workflows.
26
+
27
+ ## Requirements
28
+
29
+ - Python 3.11+
30
+ - `ssh`
31
+ - `rsync`
32
+ - `tmux` on remote machines when using long-running tasks
33
+ - Key-based SSH access to your remote targets
34
+
35
+ ## Installation
36
+
37
+ Run directly with `uvx`:
38
+
39
+ ```bash
40
+ uvx nomad-mcp
41
+ ```
42
+
43
+ Or install it as an isolated global command with `pipx`:
44
+
45
+ ```bash
46
+ pipx install nomad-mcp
47
+ ```
48
+
49
+ ## MCP Client Configuration
50
+
51
+ Use nomad as a stdio MCP server. The exact config file depends on your client.
52
+
53
+ Recommended no-install configuration:
54
+
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "nomad": {
59
+ "command": "uvx",
60
+ "args": ["nomad-mcp"]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ For TOML-based clients:
67
+
68
+ ```toml
69
+ [mcp_servers.nomad]
70
+ command = "uvx"
71
+ args = ["nomad-mcp"]
72
+ startup_timeout_sec = 120
73
+ ```
74
+
75
+ If you installed with `pipx`, use the installed command instead:
76
+
77
+ ```json
78
+ {
79
+ "mcpServers": {
80
+ "nomad": {
81
+ "command": "nomad",
82
+ "args": []
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ You can also print config snippets with:
89
+
90
+ ```bash
91
+ nomad client-config
92
+ nomad client-config --runner nomad --format toml
93
+ ```
94
+
95
+ ## Quick Start
96
+
97
+ 1. Open an MCP-enabled agent in your local project directory.
98
+ 2. Ask it to call `init_discover`.
99
+ 3. Choose an SSH target and remote workspace path.
100
+ 4. Ask it to save a `.nomad.json` config with `init_save_config`.
101
+ 5. Push code with `sync_push`.
102
+ 6. Run short commands with `run_remote`.
103
+ 7. Run long jobs with `task_start`, then monitor them with `task_status` or `task_list`.
104
+ 8. Pull remote artifacts with `sync_pull`.
105
+
106
+ ## Example `.nomad.json`
107
+
108
+ ```json
109
+ {
110
+ "project_name": "my_project",
111
+ "mode": "remote",
112
+ "default_target": "devbox",
113
+ "targets": {
114
+ "devbox": {
115
+ "description": "Primary remote development machine",
116
+ "ssh_host": "devbox",
117
+ "remote_path": "/data/my_project",
118
+ "local_subpath": null,
119
+ "auto_create_remote_path": true,
120
+ "network": {
121
+ "use_proxy_for_ssh": false,
122
+ "jump_host": null,
123
+ "reverse_tunnel": {
124
+ "enabled": false,
125
+ "proxy_scheme": "socks5"
126
+ }
127
+ },
128
+ "sync": {
129
+ "respect_gitignore": true,
130
+ "extra_excludes": []
131
+ },
132
+ "runtime": {
133
+ "interpreter": null,
134
+ "extra_env": {}
135
+ },
136
+ "limits": {
137
+ "command_timeout_seconds": 60,
138
+ "max_output_lines": 200,
139
+ "max_output_bytes": 10240
140
+ }
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ `run_remote` uses `limits.command_timeout_seconds`. For downloads, builds,
147
+ training, fuzzing, and other slow work, prefer `task_start` so the job runs in a
148
+ remote tmux session and can be checked later.
149
+
150
+ ## Tools
151
+
152
+ - `init_discover`: inspect the local workspace, SSH aliases, and proxy settings.
153
+ - `init_verify_and_probe`: verify SSH reachability and probe remote hardware/runtimes.
154
+ - `init_save_config`: validate and save `.nomad.json`.
155
+ - `init_probe_target`: refresh hardware/runtime information for a target.
156
+ - `sync_push`: push local code to the remote workspace.
157
+ - `sync_pull`: pull a remote file or directory into local `remote_artifacts/`.
158
+ - `run_remote`: run a short command in the remote workspace.
159
+ - `task_start`: start a long-running tmux task.
160
+ - `task_status`: inspect one task and return a log tail.
161
+ - `task_list`: list project-owned tasks across targets.
162
+ - `task_kill`: stop a task without deleting its logs.
163
+ - `net_diagnose`: run read-only SSH/network diagnostics.
164
+ - `tunnel_start`, `tunnel_status`, `tunnel_stop`: manage persistent reverse tunnels.
165
+
166
+ ## Safety Notes
167
+
168
+ nomad can execute commands over SSH and synchronize files with `rsync`. Use it
169
+ only with trusted local projects and trusted remote machines.
170
+
171
+ The server includes guardrails such as local/remote path checks, dangerous-command
172
+ blocking, `.nomad.json` sync exclusion, secret redaction, output truncation, and
173
+ `rsync --delete` dry-run protection. These guardrails reduce risk, but they do not
174
+ turn an untrusted agent or remote machine into a trusted one.
175
+
176
+ ## Development
177
+
178
+ ```bash
179
+ python -m pip install -e .[dev]
180
+ nomad --version
181
+ nomad doctor
182
+ python -m pytest
183
+ python -m compileall -q src tests
184
+ ```
185
+
186
+ ## License
187
+
188
+ MIT
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nomad-mcp"
7
+ version = "0.1.0"
8
+ description = "Local MCP Server to manage remote development execution, sync, and tasks."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ { name = "Tsunam1" }
15
+ ]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "Operating System :: OS Independent",
19
+ ]
20
+ dependencies = [
21
+ "mcp>=1.0.0",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ dev = [
26
+ "pytest>=7.0.0",
27
+ "pytest-mock>=3.0.0",
28
+ ]
29
+
30
+ [project.scripts]
31
+ nomad = "nomad.cli:main"
32
+
33
+ [tool.pytest.ini_options]
34
+ testpaths = ["tests"]
35
+ addopts = "-ra"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ # nomad package
2
+ __version__ = "0.1.0"
@@ -0,0 +1,124 @@
1
+ """
2
+ Command line entry point for nomad.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import shutil
9
+ import sys
10
+ from importlib.util import find_spec
11
+
12
+ from nomad import __version__
13
+ from nomad.schema import get_config_schema_hints
14
+
15
+
16
+ PACKAGE_NAME = "nomad-mcp"
17
+
18
+
19
+ def main(argv: list[str] | None = None) -> int | None:
20
+ """Runs helper CLI commands, or starts the MCP server when no args are given."""
21
+ args_list = list(sys.argv[1:] if argv is None else argv)
22
+ if not args_list:
23
+ from nomad.server import main as server_main
24
+
25
+ server_main()
26
+ return None
27
+
28
+ parser = _build_parser()
29
+ args = parser.parse_args(args_list)
30
+
31
+ if args.version:
32
+ print(__version__)
33
+ return 0
34
+
35
+ if args.command == "doctor":
36
+ return _doctor()
37
+ if args.command == "schema":
38
+ print(json.dumps(get_config_schema_hints(), indent=2))
39
+ return 0
40
+ if args.command == "client-config":
41
+ print(_client_config(args.runner, args.format))
42
+ return 0
43
+
44
+ parser.print_help()
45
+ return 0
46
+
47
+
48
+ def _build_parser() -> argparse.ArgumentParser:
49
+ parser = argparse.ArgumentParser(
50
+ prog="nomad",
51
+ description="Local MCP server for agentic remote development.",
52
+ )
53
+ parser.add_argument("--version", action="store_true", help="Print nomad version.")
54
+
55
+ subparsers = parser.add_subparsers(dest="command")
56
+ subparsers.add_parser("doctor", help="Check local runtime dependencies.")
57
+ subparsers.add_parser("schema", help="Print .nomad.json schema hints as JSON.")
58
+
59
+ config_parser = subparsers.add_parser(
60
+ "client-config", help="Print an MCP client configuration snippet."
61
+ )
62
+ config_parser.add_argument(
63
+ "--runner",
64
+ choices=("uvx", "nomad"),
65
+ default="uvx",
66
+ help="Use uvx package execution or an already-installed nomad command.",
67
+ )
68
+ config_parser.add_argument(
69
+ "--format",
70
+ choices=("json", "toml"),
71
+ default="json",
72
+ help="Output config format.",
73
+ )
74
+ return parser
75
+
76
+
77
+ def _doctor() -> int:
78
+ checks = [
79
+ ("python>=3.11", sys.version_info >= (3, 11), sys.version.split()[0]),
80
+ ("mcp package", find_spec("mcp") is not None, "import mcp"),
81
+ ("ssh", shutil.which("ssh") is not None, shutil.which("ssh") or "missing"),
82
+ ("rsync", shutil.which("rsync") is not None, shutil.which("rsync") or "missing"),
83
+ ]
84
+ ok = True
85
+ for name, passed, detail in checks:
86
+ ok = ok and passed
87
+ mark = "ok" if passed else "missing"
88
+ print(f"{mark:7} {name}: {detail}")
89
+ print("note remote tmux is required only when using task_start/task_status.")
90
+ return 0 if ok else 1
91
+
92
+
93
+ def _client_config(runner: str, output_format: str) -> str:
94
+ if runner == "uvx":
95
+ command = "uvx"
96
+ args = [PACKAGE_NAME]
97
+ else:
98
+ command = "nomad"
99
+ args = []
100
+
101
+ if output_format == "toml":
102
+ rendered_args = ", ".join(json.dumps(arg) for arg in args)
103
+ return (
104
+ "[mcp_servers.nomad]\n"
105
+ f"command = {json.dumps(command)}\n"
106
+ f"args = [{rendered_args}]\n"
107
+ "startup_timeout_sec = 120"
108
+ )
109
+
110
+ return json.dumps(
111
+ {
112
+ "mcpServers": {
113
+ "nomad": {
114
+ "command": command,
115
+ "args": args,
116
+ }
117
+ }
118
+ },
119
+ indent=2,
120
+ )
121
+
122
+
123
+ if __name__ == "__main__":
124
+ raise SystemExit(main())