runspec-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 (32) hide show
  1. runspec_mcp-0.1.0/.gitignore +61 -0
  2. runspec_mcp-0.1.0/CHANGELOG.md +29 -0
  3. runspec_mcp-0.1.0/PKG-INFO +121 -0
  4. runspec_mcp-0.1.0/README.md +101 -0
  5. runspec_mcp-0.1.0/pyproject.toml +77 -0
  6. runspec_mcp-0.1.0/runspec_mcp/__init__.py +16 -0
  7. runspec_mcp-0.1.0/runspec_mcp/catalog.py +324 -0
  8. runspec_mcp-0.1.0/runspec_mcp/cli.py +237 -0
  9. runspec_mcp-0.1.0/runspec_mcp/config.py +184 -0
  10. runspec_mcp-0.1.0/runspec_mcp/configmerge.py +327 -0
  11. runspec_mcp-0.1.0/runspec_mcp/configseed.py +101 -0
  12. runspec_mcp-0.1.0/runspec_mcp/credentials.py +316 -0
  13. runspec_mcp-0.1.0/runspec_mcp/executor.py +1084 -0
  14. runspec_mcp-0.1.0/runspec_mcp/hosts.py +288 -0
  15. runspec_mcp-0.1.0/runspec_mcp/manifest.py +51 -0
  16. runspec_mcp-0.1.0/runspec_mcp/plugin/.claude-plugin/marketplace.json +13 -0
  17. runspec_mcp-0.1.0/runspec_mcp/plugin/.claude-plugin/plugin.json +18 -0
  18. runspec_mcp-0.1.0/runspec_mcp/plugin/commands/runspec-hosts.md +9 -0
  19. runspec_mcp-0.1.0/runspec_mcp/plugin/commands/runspec-refresh.md +11 -0
  20. runspec_mcp-0.1.0/runspec_mcp/plugin/hooks/autonomy_gate.py +155 -0
  21. runspec_mcp-0.1.0/runspec_mcp/plugin/hooks/hooks.json +15 -0
  22. runspec_mcp-0.1.0/runspec_mcp/runner.py +113 -0
  23. runspec_mcp-0.1.0/runspec_mcp/server.py +227 -0
  24. runspec_mcp-0.1.0/runspec_mcp/ssh_pool.py +675 -0
  25. runspec_mcp-0.1.0/runspec_mcp/sshkeys.py +136 -0
  26. runspec_mcp-0.1.0/tests/test_catalog.py +97 -0
  27. runspec_mcp-0.1.0/tests/test_config.py +71 -0
  28. runspec_mcp-0.1.0/tests/test_credentials.py +80 -0
  29. runspec_mcp-0.1.0/tests/test_manifest.py +31 -0
  30. runspec_mcp-0.1.0/tests/test_parity.py +57 -0
  31. runspec_mcp-0.1.0/tests/test_server.py +67 -0
  32. runspec_mcp-0.1.0/tests/test_sshkeys.py +67 -0
@@ -0,0 +1,61 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ .Python
7
+ *.egg
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ .eggs/
12
+ .venv/
13
+ venv/
14
+ env/
15
+ .env
16
+ pip-wheel-metadata/
17
+ .pytest_cache/
18
+ .mypy_cache/
19
+ .ruff_cache/
20
+ htmlcov/
21
+ .coverage
22
+ coverage.xml
23
+ *.cover
24
+
25
+ # Node
26
+ node_modules/
27
+ dist/
28
+ *.js.map
29
+ .npm
30
+
31
+ # Go
32
+ *.exe
33
+ *.test
34
+ *.out
35
+ vendor/
36
+
37
+ # IDE
38
+ .idea/
39
+ .vscode/
40
+ *.iml
41
+ *.iws
42
+ *.ipr
43
+ .DS_Store
44
+ Thumbs.db
45
+
46
+ # Docs
47
+ site/
48
+
49
+ # Misc
50
+ *.log
51
+ *.tmp
52
+
53
+ # External reference repos (cloned locally, not committed)
54
+ chainlit-docs/
55
+ .chainlit/
56
+
57
+ # Claude Code local config (machine-specific)
58
+ .claude/launch.json
59
+
60
+ # Stray committed test venv (removed from tracking)
61
+ .venv-test/
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ All notable changes to `runspec-mcp` are documented here.
4
+
5
+ ## [0.1.0]
6
+
7
+ Initial release. A local MCP **gateway** that connects Claude Code to local and
8
+ remote runspec runnables from one config file.
9
+
10
+ - Aggregating MCP stdio server (`runspec-mcp serve`) over local venvs and remote
11
+ SSH venvs, discovering runnables via `runspec local --format json` and expanding
12
+ them into leaf MCP tools identical to `runspec serve`.
13
+ - **Live discovery**: declares `tools.listChanged` and pushes
14
+ `notifications/tools/list_changed` from a background discovery thread — the tool
15
+ list stays current with no restart or manual refresh.
16
+ - **Credential injection** as name-derived environment variables (reusing the
17
+ runspec-console convention; secrets in the OS keychain, shared service name),
18
+ auto-matched to runnables by their declared arg `env` or by scope globs.
19
+ - One config file (`runspec_mcp.toml`), or reuse of an existing runspec-console
20
+ config directory (`--config-dir`), or a bundled **config seed** package.
21
+ - **Managed SSH keypair**: the gateway generates and uses its own ed25519 key as
22
+ the default SSH identity (no `~/.ssh` setup needed); `runspec-mcp copy-key`
23
+ installs the public key on hosts over a one-time password login, and
24
+ `runspec-mcp key` shows / rotates it. A per-host `identity_file` and the
25
+ ssh-agent / default keys still work.
26
+ - Ships a Claude Code plugin (MCP server entry + autonomy PreToolUse hook, whose
27
+ manifest the gateway keeps fresh + slash commands).
28
+ - Transport/credentials/config layer vendored from runspec-console, kept
29
+ behaviour-identical by parity tests.
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.5
2
+ Name: runspec-mcp
3
+ Version: 0.1.0
4
+ Summary: Connect Claude Code to local and remote runspec runnables as MCP tools, from one config file
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: cryptography>=41
8
+ Requires-Dist: paramiko>=3.0
9
+ Requires-Dist: runspec>=0.48.0
10
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
11
+ Provides-Extra: credentials
12
+ Requires-Dist: keyring>=24; extra == 'credentials'
13
+ Provides-Extra: dev
14
+ Requires-Dist: keyring>=24; extra == 'dev'
15
+ Requires-Dist: mypy; extra == 'dev'
16
+ Requires-Dist: pytest>=8.0; extra == 'dev'
17
+ Requires-Dist: ruff==0.15.20; extra == 'dev'
18
+ Requires-Dist: runspec-console; extra == 'dev'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # runspec-mcp
22
+
23
+ Connect **Claude Code** to the runspec runnables installed across your machines —
24
+ local venvs and remote SSH venvs — from **one config file**, with credentials
25
+ injected as environment variables and each runnable's declared `autonomy` carried
26
+ into Claude Code's permission layer.
27
+
28
+ `runspec-mcp` is a small local **MCP gateway**: Claude Code spawns it as a
29
+ single MCP server; it reads your hosts + credential metadata, discovers the
30
+ runnables in every configured venv (`runspec local --format json`, locally and
31
+ over SSH), and presents them all as MCP tools. Discovery is **live** — as hosts
32
+ connect/disconnect or their runnables change, the tool list updates automatically
33
+ (no restart, no manual refresh).
34
+
35
+ ```
36
+ pip install "runspec-mcp[credentials]"
37
+ # write runspec_mcp.toml (or point at an existing runspec-console config dir)
38
+ claude --plugin-dir "$(runspec-mcp plugin-path)"
39
+ ```
40
+
41
+ **Any MCP client** (Claude Desktop, Cursor, Cline, or Claude Code without the
42
+ plugin) works too — `runspec-mcp serve` is a standard MCP stdio server. Add a
43
+ `mcpServers` entry:
44
+
45
+ ```json
46
+ { "mcpServers": { "runspec-mcp": {
47
+ "command": "runspec-mcp", "args": ["serve"],
48
+ "env": { "RUNSPEC_MCP_CONFIG": "/absolute/path/to/runspec_mcp.toml" } } } }
49
+ ```
50
+
51
+ The Claude Code plugin adds one thing over a bare client: the PreToolUse hook that
52
+ auto-approves `autonomous` runnables. Either way, `manual` runnables are refused
53
+ by the gateway and `password` args never reach a tool schema. See
54
+ [`docs/mcp.md`](../../../docs/mcp.md) for per-client details and the config-seed
55
+ ("fleet adapter") how-to.
56
+
57
+ ## Config (`runspec_mcp.toml`)
58
+
59
+ ```toml
60
+ [local]
61
+ venv_globs = ["~/venvs/*"] # local multi-venv discovery
62
+
63
+ [gateway]
64
+ refresh_interval = 30 # background rediscovery cadence (s); 0 = off
65
+
66
+ [[host]]
67
+ name = "prod"
68
+ ssh = "deploy@prod.example.com" # omit ssh → this machine
69
+ runspec_paths = ["/opt/venvs/fleet/bin/runspec"]
70
+ jump = "bastion" # optional ProxyJump (system ssh)
71
+
72
+ [[credential]]
73
+ id = "Windows" # label → WINDOWS_* env vars
74
+ kind = "userpass" # userpass | password | token | ssh-key
75
+ username = "svc-bot"
76
+ # secret stored in the OS keychain: runspec-mcp cred set Windows
77
+ # scope_hosts / scope_runnables optional — else auto-matched by a runnable's arg `env`
78
+ ```
79
+
80
+ A runnable that declares it reads `WINDOWS_USERNAME` / `WINDOWS_PASSWORD` (via an
81
+ arg `env`) auto-pulls the `Windows` credential — no scope config needed.
82
+
83
+ Secrets live in the OS keychain (`keyring`), keyed by credential `id` under the
84
+ `runspec-console` service, so they are **shared with the desktop console**. Point
85
+ the gateway at an existing console setup with `--config-dir <dir>`.
86
+
87
+ ## SSH keys
88
+
89
+ **No SSH key ever goes in the MCP config** — only path references. The gateway
90
+ manages its **own ed25519 keypair** and uses it as the default SSH identity, so you
91
+ never touch `~/.ssh`; authorise it on your hosts once:
92
+
93
+ ```bash
94
+ runspec-mcp key # print the managed public key
95
+ runspec-mcp copy-key --all # install it on every configured host (one password prompt)
96
+ ```
97
+
98
+ A per-host `identity_file` path still overrides it, and ssh-agent / `~/.ssh/id_*`
99
+ remain a fallback.
100
+
101
+ ## Commands
102
+
103
+ | command | what it does |
104
+ |---|---|
105
+ | `runspec-mcp serve` | the MCP gateway (what the plugin runs) |
106
+ | `runspec-mcp hosts` | list configured hosts + probe connectivity |
107
+ | `runspec-mcp discover [--host H]` | print discovered tools |
108
+ | `runspec-mcp key [--regenerate]` | show / rotate the managed SSH key |
109
+ | `runspec-mcp copy-key [--all] [HOST…]` | install the managed key on host(s) |
110
+ | `runspec-mcp cred set\|list\|rm ID` | manage credential secrets in the keychain |
111
+ | `runspec-mcp seed-status` | show bundled config-seed contributors |
112
+ | `runspec-mcp plugin-path` | print the bundled Claude Code plugin dir |
113
+ | `runspec-mcp check` | validate config + connectivity + manifest |
114
+
115
+ ## Autonomy
116
+
117
+ Each runnable's `autonomy` maps onto a Claude Code permission decision via a
118
+ PreToolUse hook the plugin ships (`autonomous → allow`, `confirm`/`supervised →
119
+ ask`, `manual → deny`, unknown → ask). The gateway rewrites the hook's manifest on
120
+ every discovery change, so it is always fresh. The remote side stays authoritative
121
+ (venv denylist, `enforce_run_as`, `password` args omitted from schemas).
@@ -0,0 +1,101 @@
1
+ # runspec-mcp
2
+
3
+ Connect **Claude Code** to the runspec runnables installed across your machines —
4
+ local venvs and remote SSH venvs — from **one config file**, with credentials
5
+ injected as environment variables and each runnable's declared `autonomy` carried
6
+ into Claude Code's permission layer.
7
+
8
+ `runspec-mcp` is a small local **MCP gateway**: Claude Code spawns it as a
9
+ single MCP server; it reads your hosts + credential metadata, discovers the
10
+ runnables in every configured venv (`runspec local --format json`, locally and
11
+ over SSH), and presents them all as MCP tools. Discovery is **live** — as hosts
12
+ connect/disconnect or their runnables change, the tool list updates automatically
13
+ (no restart, no manual refresh).
14
+
15
+ ```
16
+ pip install "runspec-mcp[credentials]"
17
+ # write runspec_mcp.toml (or point at an existing runspec-console config dir)
18
+ claude --plugin-dir "$(runspec-mcp plugin-path)"
19
+ ```
20
+
21
+ **Any MCP client** (Claude Desktop, Cursor, Cline, or Claude Code without the
22
+ plugin) works too — `runspec-mcp serve` is a standard MCP stdio server. Add a
23
+ `mcpServers` entry:
24
+
25
+ ```json
26
+ { "mcpServers": { "runspec-mcp": {
27
+ "command": "runspec-mcp", "args": ["serve"],
28
+ "env": { "RUNSPEC_MCP_CONFIG": "/absolute/path/to/runspec_mcp.toml" } } } }
29
+ ```
30
+
31
+ The Claude Code plugin adds one thing over a bare client: the PreToolUse hook that
32
+ auto-approves `autonomous` runnables. Either way, `manual` runnables are refused
33
+ by the gateway and `password` args never reach a tool schema. See
34
+ [`docs/mcp.md`](../../../docs/mcp.md) for per-client details and the config-seed
35
+ ("fleet adapter") how-to.
36
+
37
+ ## Config (`runspec_mcp.toml`)
38
+
39
+ ```toml
40
+ [local]
41
+ venv_globs = ["~/venvs/*"] # local multi-venv discovery
42
+
43
+ [gateway]
44
+ refresh_interval = 30 # background rediscovery cadence (s); 0 = off
45
+
46
+ [[host]]
47
+ name = "prod"
48
+ ssh = "deploy@prod.example.com" # omit ssh → this machine
49
+ runspec_paths = ["/opt/venvs/fleet/bin/runspec"]
50
+ jump = "bastion" # optional ProxyJump (system ssh)
51
+
52
+ [[credential]]
53
+ id = "Windows" # label → WINDOWS_* env vars
54
+ kind = "userpass" # userpass | password | token | ssh-key
55
+ username = "svc-bot"
56
+ # secret stored in the OS keychain: runspec-mcp cred set Windows
57
+ # scope_hosts / scope_runnables optional — else auto-matched by a runnable's arg `env`
58
+ ```
59
+
60
+ A runnable that declares it reads `WINDOWS_USERNAME` / `WINDOWS_PASSWORD` (via an
61
+ arg `env`) auto-pulls the `Windows` credential — no scope config needed.
62
+
63
+ Secrets live in the OS keychain (`keyring`), keyed by credential `id` under the
64
+ `runspec-console` service, so they are **shared with the desktop console**. Point
65
+ the gateway at an existing console setup with `--config-dir <dir>`.
66
+
67
+ ## SSH keys
68
+
69
+ **No SSH key ever goes in the MCP config** — only path references. The gateway
70
+ manages its **own ed25519 keypair** and uses it as the default SSH identity, so you
71
+ never touch `~/.ssh`; authorise it on your hosts once:
72
+
73
+ ```bash
74
+ runspec-mcp key # print the managed public key
75
+ runspec-mcp copy-key --all # install it on every configured host (one password prompt)
76
+ ```
77
+
78
+ A per-host `identity_file` path still overrides it, and ssh-agent / `~/.ssh/id_*`
79
+ remain a fallback.
80
+
81
+ ## Commands
82
+
83
+ | command | what it does |
84
+ |---|---|
85
+ | `runspec-mcp serve` | the MCP gateway (what the plugin runs) |
86
+ | `runspec-mcp hosts` | list configured hosts + probe connectivity |
87
+ | `runspec-mcp discover [--host H]` | print discovered tools |
88
+ | `runspec-mcp key [--regenerate]` | show / rotate the managed SSH key |
89
+ | `runspec-mcp copy-key [--all] [HOST…]` | install the managed key on host(s) |
90
+ | `runspec-mcp cred set\|list\|rm ID` | manage credential secrets in the keychain |
91
+ | `runspec-mcp seed-status` | show bundled config-seed contributors |
92
+ | `runspec-mcp plugin-path` | print the bundled Claude Code plugin dir |
93
+ | `runspec-mcp check` | validate config + connectivity + manifest |
94
+
95
+ ## Autonomy
96
+
97
+ Each runnable's `autonomy` maps onto a Claude Code permission decision via a
98
+ PreToolUse hook the plugin ships (`autonomous → allow`, `confirm`/`supervised →
99
+ ask`, `manual → deny`, unknown → ask). The gateway rewrites the hook's manifest on
100
+ every discovery change, so it is always fresh. The remote side stays authoritative
101
+ (venv denylist, `enforce_run_as`, `password` args omitted from schemas).
@@ -0,0 +1,77 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "runspec-mcp"
7
+ version = "0.1.0"
8
+ requires-python = ">=3.10"
9
+ description = "Connect Claude Code to local and remote runspec runnables as MCP tools, from one config file"
10
+ readme = "README.md"
11
+ license = "MIT"
12
+ dependencies = [
13
+ # 0.48.0 for the `discoverable` boolean + serve leaf globals; the gateway
14
+ # reuses runspec.cli._build_schema and runspec.become.build_become_argv.
15
+ "runspec>=0.48.0",
16
+ # Warm SSH connection pool for remote venvs (mirrors runspec-console).
17
+ "paramiko>=3.0",
18
+ # Managed ed25519 keypair generation (also a transitive paramiko dep).
19
+ "cryptography>=41",
20
+ "tomli>=2.0; python_version < '3.11'",
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ # Secret storage in the OS keychain. Without it the gateway still runs; only
25
+ # credential injection is disabled (same opt-in split as runspec-console).
26
+ credentials = ["keyring>=24"]
27
+ dev = [
28
+ "ruff==0.15.20",
29
+ "mypy",
30
+ "pytest>=8.0",
31
+ "keyring>=24",
32
+ # Dev-only: the parity tests import the console originals to assert the
33
+ # vendored transport/credential/merge helpers behave identically.
34
+ "runspec-console",
35
+ ]
36
+
37
+ [project.scripts]
38
+ runspec-mcp = "runspec_mcp.cli:main"
39
+
40
+ # A corporate seed package can ship a config_seed/ dir under either group; the
41
+ # fleet reads both so one seed configures the console and the plugin alike.
42
+ # [project.entry-points."runspec_mcp.config_seed"]
43
+ # mycorp = "mycorp_fleet"
44
+
45
+ [tool.hatch.build.targets.wheel]
46
+ # The Claude Code plugin under runspec_mcp/plugin/ ships as package data so
47
+ # `runspec-mcp plugin-path` can point Claude Code at it.
48
+ packages = ["runspec_mcp"]
49
+ artifacts = ["runspec_mcp/plugin/**"]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+
54
+ [tool.mypy]
55
+ python_version = "3.10"
56
+ # Vendored-verbatim modules follow the console's own typing upstream; kept
57
+ # honest by tests/test_parity.py rather than re-checked here.
58
+ exclude = "runspec_mcp/(executor|ssh_pool|configmerge|hosts)\\.py$"
59
+
60
+ [[tool.mypy.overrides]]
61
+ module = ["paramiko.*", "keyring.*", "tomli", "runspec.*", "runspec_console.*"]
62
+ ignore_missing_imports = true
63
+
64
+ [tool.ruff]
65
+ line-length = 200
66
+ target-version = "py310"
67
+ # These modules are vendored verbatim from runspec-console and kept
68
+ # behaviour-identical by tests/test_parity.py — they follow the console's own
69
+ # lint config upstream; restyling them here would create drift, so we don't lint
70
+ # them locally.
71
+ extend-exclude = ["runspec_mcp/executor.py", "runspec_mcp/ssh_pool.py", "runspec_mcp/configmerge.py", "runspec_mcp/hosts.py", "runspec_mcp/plugin"]
72
+
73
+ [tool.ruff.lint]
74
+ select = ["E", "F", "I", "UP", "B", "SIM"]
75
+ # try/except/pass with an explanatory comment (best-effort swallow) reads more
76
+ # clearly than contextlib.suppress here, and matches the vendored modules.
77
+ ignore = ["SIM105"]
@@ -0,0 +1,16 @@
1
+ """
2
+ runspec-mcp — connect Claude Code to local and remote runspec runnables.
3
+
4
+ A single local MCP *gateway* server that reads one config file (hosts + credential
5
+ metadata), discovers the runnables installed in every configured venv (local and
6
+ over SSH), and presents them all to Claude Code as MCP tools — with each
7
+ runnable's declared ``autonomy`` carried into Claude Code's permission layer and
8
+ credentials injected as environment variables the way runspec-console does.
9
+
10
+ The public surface is the ``runspec-mcp`` CLI (see ``cli.py``); the MCP server
11
+ is ``runspec-mcp serve``.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ __version__ = "0.1.0"