rocannon 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.
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.4
2
+ Name: rocannon
3
+ Version: 0.1.0
4
+ Summary: Every Ansible module as a typed MCP tool
5
+ Keywords: ansible,mcp,model-context-protocol,automation,ibm-z,zos
6
+ Author: Adam Munawar Rahman
7
+ Author-email: Adam Munawar Rahman <msrahmanadam@gmail.com>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: System :: Systems Administration
17
+ Classifier: Topic :: Utilities
18
+ Classifier: Typing :: Typed
19
+ Requires-Dist: fastmcp>=3.4.2
20
+ Requires-Dist: prompt-toolkit>=3.0.52
21
+ Requires-Dist: typer>=0.26.7
22
+ Requires-Dist: pydantic>=2.13.4
23
+ Requires-Dist: pyyaml>=6.0.3
24
+ Requires-Dist: litellm>=1.89.0 ; extra == 'ai'
25
+ Requires-Dist: ansible-core>=2.21.0 ; extra == 'all'
26
+ Requires-Dist: ansible-runner>=2.4.3 ; extra == 'all'
27
+ Requires-Dist: litellm>=1.89.0 ; extra == 'all'
28
+ Requires-Dist: opentelemetry-sdk>=1.42.1 ; extra == 'all'
29
+ Requires-Dist: opentelemetry-exporter-otlp>=1.42.1 ; extra == 'all'
30
+ Requires-Dist: ansible-core>=2.21.0 ; extra == 'ansible'
31
+ Requires-Dist: ansible-runner>=2.4.3 ; extra == 'ansible'
32
+ Requires-Dist: opentelemetry-sdk>=1.42.1 ; extra == 'otel'
33
+ Requires-Dist: opentelemetry-exporter-otlp>=1.42.1 ; extra == 'otel'
34
+ Requires-Python: >=3.12
35
+ Project-URL: Homepage, https://github.com/msradam/rocannon
36
+ Project-URL: Repository, https://github.com/msradam/rocannon
37
+ Project-URL: Issues, https://github.com/msradam/rocannon/issues
38
+ Provides-Extra: ai
39
+ Provides-Extra: all
40
+ Provides-Extra: ansible
41
+ Provides-Extra: otel
42
+ Description-Content-Type: text/markdown
43
+
44
+ <h1 align="center">Rocannon</h1>
45
+
46
+ <p align="center">
47
+ <img src="https://raw.githubusercontent.com/msradam/rocannon/main/docs/assets/gryphon.svg" alt="" width="120">
48
+ </p>
49
+
50
+ Rocannon is an MCP server that registers every installed Ansible module as a
51
+ typed tool. It reads `ansible-doc -j` for each module at startup and builds a
52
+ Pydantic-validated function signature, then exposes the result over the MCP
53
+ protocol (stdio or HTTP). Any MCP client (Claude Code, Cursor, mcphost,
54
+ custom agents) calls the same tools an operator would call from a REPL.
55
+
56
+ Each registered tool carries the module's own `ansible-doc` metadata: a JSON
57
+ output schema for structured results, and MCP safety hints derived from the
58
+ module's attributes (read-only for fact modules, destructive and open-world for
59
+ `command`, `shell`, `script`, and `raw`).
60
+
61
+ Every module is also a top-level CLI subcommand:
62
+
63
+ ```
64
+ rocannon ansible.builtin.command --target h1 --cmd 'uptime'
65
+ rocannon ansible.builtin.copy --target h1 --src /etc/hosts --dest /tmp/h
66
+ ```
67
+
68
+ Append `--record path/to/runbook.yml` to any invocation and Rocannon writes
69
+ each call as a new play in a real Ansible playbook. The resulting file runs
70
+ directly under `ansible-playbook -i <inv> path/to/runbook.yml`.
71
+
72
+ Add `--check` to preview a change without applying it (Ansible check mode) and
73
+ `--diff` to see what would change. Each is offered per module according to its
74
+ declared check-mode support, both on the CLI and as a parameter on the matching
75
+ MCP tool. `rocannon playbook run <name> --check` previews an entire saved runbook.
76
+
77
+ Sessions driven via the MCP server save the same way: as Ansible playbooks
78
+ under `.rocannon/playbooks/`. Rocannon also loads them back on next startup
79
+ as MCP prompts.
80
+
81
+ ![demo](https://raw.githubusercontent.com/msradam/rocannon/main/docs/assets/demo.gif)
82
+
83
+ ## Install
84
+
85
+ ```bash
86
+ pip install 'rocannon[ansible]'
87
+ ```
88
+
89
+ `ansible-doc` and `ansible-runner` must be on PATH. `rocannon doctor` reports
90
+ anything missing.
91
+
92
+ ## Quickstart
93
+
94
+ The quickstart profile targets `localhost` with `ansible_connection=local`.
95
+
96
+ ```bash
97
+ cd examples/quickstart
98
+ rocannon mcp doctor --profile profile.yml # list registered tools
99
+ rocannon repl --profile profile.yml # operator shell
100
+ ```
101
+
102
+ Inside the REPL:
103
+
104
+ ```
105
+ rocannon> .target localhost
106
+ rocannon> ping
107
+ rocannon> command cmd="uptime"
108
+ rocannon> .save my_session
109
+ rocannon> .exit
110
+ ```
111
+
112
+ `.save` writes `.rocannon/playbooks/my_session.yml` as a standard Ansible
113
+ playbook. Run it directly with `ansible-playbook -i hosts my_session.yml`, or
114
+ let Rocannon load it back as an MCP prompt next time the server starts.
115
+
116
+ ## CLI
117
+
118
+ ```
119
+ rocannon <fqcn> invoke a module: rocannon ansible.builtin.copy ...
120
+ optional --record FILE appends each call to a playbook
121
+ rocannon mcp serve start the MCP server (stdio or http)
122
+ rocannon mcp doctor list registered tools, resources, prompts
123
+ rocannon repl interactive shell on the same MCP server
124
+ rocannon run legacy ad-hoc form (module FQCN + -a key=value)
125
+ rocannon doctor system health (binaries, env, inventory)
126
+ rocannon doc <module> print parsed schema for a module
127
+ rocannon search <q> find modules by name or description
128
+ rocannon ls list hosts/groups/modules from a profile
129
+ rocannon playbook list/show/run saved playbooks
130
+ ```
131
+
132
+ Per-module help (typed flags, defaults, descriptions from `ansible-doc`):
133
+ `rocannon ansible.builtin.copy --help`. Modules that support check mode also
134
+ accept `--check` and `--diff`.
135
+
136
+ ## Profiles
137
+
138
+ A profile is a YAML file declaring inventory + modules:
139
+
140
+ ```yaml
141
+ inventories:
142
+ - ./hosts
143
+ modules:
144
+ - ansible.builtin
145
+ - ibm.ibm_zos_core
146
+ ansible_cfg: ./ansible.cfg # optional
147
+ vault_password_file: ~/.vault_pass # optional
148
+ extra_envvars: # optional
149
+ ZOAU_HOME: /usr/lpp/IBM/zoautil
150
+ ```
151
+
152
+ `modules` accepts a specific module (`ansible.builtin.copy`), a collection
153
+ (`ansible.builtin`), or a namespace (`community`).
154
+
155
+ Multiple profiles can live under `.rocannon/profiles/`:
156
+
157
+ ```
158
+ .rocannon/profiles/
159
+ ├── box1.yml
160
+ ├── box2.yml
161
+ └── default.yml -> box1.yml
162
+ ```
163
+
164
+ ```bash
165
+ rocannon mcp serve # uses default.yml
166
+ rocannon mcp serve --profile box2
167
+ ```
168
+
169
+ The active profile can be switched at runtime via three MCP tools:
170
+ `rocannon_list_profiles`, `rocannon_current_profile`, `rocannon_use_profile`.
171
+ The tool surface is the union of every profile's modules; a call to a module
172
+ that isn't in the active profile returns a structured error pointing at
173
+ `rocannon_use_profile`.
174
+
175
+ ## MCP clients
176
+
177
+ A working `.mcp.json` ships at the repo root. Per-client snippets are in
178
+ [`examples/clients/`](examples/clients/).
179
+
180
+ | Client | Config location |
181
+ |---|---|
182
+ | Claude Code | `.mcp.json` at project root, or `claude mcp add` |
183
+ | Claude Desktop | macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` |
184
+ | Cursor | `.cursor/mcp.json` or `~/.cursor/mcp.json` |
185
+ | mcphost | `~/.mcphost.yml` or `--config <path>` |
186
+ | IBM Bob | `.bob/mcp.json` or `~/.bob/mcp_settings.json` |
187
+
188
+ All share the standard `mcpServers` envelope pointing at
189
+ `rocannon mcp serve --profile <your-profile.yml>`.
190
+
191
+ ## Development
192
+
193
+ ```bash
194
+ git clone https://github.com/msradam/rocannon.git
195
+ cd rocannon
196
+ uv sync
197
+ ./tests/check.sh # ruff format + lint + mypy + pytest
198
+ uv run pytest -m integration # opt-in: spins up a real UBI9 container
199
+ ```
200
+
201
+ See [`ARCHITECTURE.md`](ARCHITECTURE.md) for how the pieces fit together.
202
+
203
+ Rocannon is developed with AI assistance.
204
+
205
+ ## The name
206
+
207
+ Ursula K. Le Guin coined the word "ansible" in her 1966 novel *Rocannon's
208
+ World*. The gryphon is a nod to the Windsteeds that Rocannon and his
209
+ companions ride.
210
+
211
+ ## Credits
212
+
213
+ - Gryphon icon: [Gryphon by Aleksei Kovalenko from Noun Project](https://thenounproject.com/icon/gryphon-7096619/) (CC BY 3.0).
@@ -0,0 +1,170 @@
1
+ <h1 align="center">Rocannon</h1>
2
+
3
+ <p align="center">
4
+ <img src="https://raw.githubusercontent.com/msradam/rocannon/main/docs/assets/gryphon.svg" alt="" width="120">
5
+ </p>
6
+
7
+ Rocannon is an MCP server that registers every installed Ansible module as a
8
+ typed tool. It reads `ansible-doc -j` for each module at startup and builds a
9
+ Pydantic-validated function signature, then exposes the result over the MCP
10
+ protocol (stdio or HTTP). Any MCP client (Claude Code, Cursor, mcphost,
11
+ custom agents) calls the same tools an operator would call from a REPL.
12
+
13
+ Each registered tool carries the module's own `ansible-doc` metadata: a JSON
14
+ output schema for structured results, and MCP safety hints derived from the
15
+ module's attributes (read-only for fact modules, destructive and open-world for
16
+ `command`, `shell`, `script`, and `raw`).
17
+
18
+ Every module is also a top-level CLI subcommand:
19
+
20
+ ```
21
+ rocannon ansible.builtin.command --target h1 --cmd 'uptime'
22
+ rocannon ansible.builtin.copy --target h1 --src /etc/hosts --dest /tmp/h
23
+ ```
24
+
25
+ Append `--record path/to/runbook.yml` to any invocation and Rocannon writes
26
+ each call as a new play in a real Ansible playbook. The resulting file runs
27
+ directly under `ansible-playbook -i <inv> path/to/runbook.yml`.
28
+
29
+ Add `--check` to preview a change without applying it (Ansible check mode) and
30
+ `--diff` to see what would change. Each is offered per module according to its
31
+ declared check-mode support, both on the CLI and as a parameter on the matching
32
+ MCP tool. `rocannon playbook run <name> --check` previews an entire saved runbook.
33
+
34
+ Sessions driven via the MCP server save the same way: as Ansible playbooks
35
+ under `.rocannon/playbooks/`. Rocannon also loads them back on next startup
36
+ as MCP prompts.
37
+
38
+ ![demo](https://raw.githubusercontent.com/msradam/rocannon/main/docs/assets/demo.gif)
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install 'rocannon[ansible]'
44
+ ```
45
+
46
+ `ansible-doc` and `ansible-runner` must be on PATH. `rocannon doctor` reports
47
+ anything missing.
48
+
49
+ ## Quickstart
50
+
51
+ The quickstart profile targets `localhost` with `ansible_connection=local`.
52
+
53
+ ```bash
54
+ cd examples/quickstart
55
+ rocannon mcp doctor --profile profile.yml # list registered tools
56
+ rocannon repl --profile profile.yml # operator shell
57
+ ```
58
+
59
+ Inside the REPL:
60
+
61
+ ```
62
+ rocannon> .target localhost
63
+ rocannon> ping
64
+ rocannon> command cmd="uptime"
65
+ rocannon> .save my_session
66
+ rocannon> .exit
67
+ ```
68
+
69
+ `.save` writes `.rocannon/playbooks/my_session.yml` as a standard Ansible
70
+ playbook. Run it directly with `ansible-playbook -i hosts my_session.yml`, or
71
+ let Rocannon load it back as an MCP prompt next time the server starts.
72
+
73
+ ## CLI
74
+
75
+ ```
76
+ rocannon <fqcn> invoke a module: rocannon ansible.builtin.copy ...
77
+ optional --record FILE appends each call to a playbook
78
+ rocannon mcp serve start the MCP server (stdio or http)
79
+ rocannon mcp doctor list registered tools, resources, prompts
80
+ rocannon repl interactive shell on the same MCP server
81
+ rocannon run legacy ad-hoc form (module FQCN + -a key=value)
82
+ rocannon doctor system health (binaries, env, inventory)
83
+ rocannon doc <module> print parsed schema for a module
84
+ rocannon search <q> find modules by name or description
85
+ rocannon ls list hosts/groups/modules from a profile
86
+ rocannon playbook list/show/run saved playbooks
87
+ ```
88
+
89
+ Per-module help (typed flags, defaults, descriptions from `ansible-doc`):
90
+ `rocannon ansible.builtin.copy --help`. Modules that support check mode also
91
+ accept `--check` and `--diff`.
92
+
93
+ ## Profiles
94
+
95
+ A profile is a YAML file declaring inventory + modules:
96
+
97
+ ```yaml
98
+ inventories:
99
+ - ./hosts
100
+ modules:
101
+ - ansible.builtin
102
+ - ibm.ibm_zos_core
103
+ ansible_cfg: ./ansible.cfg # optional
104
+ vault_password_file: ~/.vault_pass # optional
105
+ extra_envvars: # optional
106
+ ZOAU_HOME: /usr/lpp/IBM/zoautil
107
+ ```
108
+
109
+ `modules` accepts a specific module (`ansible.builtin.copy`), a collection
110
+ (`ansible.builtin`), or a namespace (`community`).
111
+
112
+ Multiple profiles can live under `.rocannon/profiles/`:
113
+
114
+ ```
115
+ .rocannon/profiles/
116
+ ├── box1.yml
117
+ ├── box2.yml
118
+ └── default.yml -> box1.yml
119
+ ```
120
+
121
+ ```bash
122
+ rocannon mcp serve # uses default.yml
123
+ rocannon mcp serve --profile box2
124
+ ```
125
+
126
+ The active profile can be switched at runtime via three MCP tools:
127
+ `rocannon_list_profiles`, `rocannon_current_profile`, `rocannon_use_profile`.
128
+ The tool surface is the union of every profile's modules; a call to a module
129
+ that isn't in the active profile returns a structured error pointing at
130
+ `rocannon_use_profile`.
131
+
132
+ ## MCP clients
133
+
134
+ A working `.mcp.json` ships at the repo root. Per-client snippets are in
135
+ [`examples/clients/`](examples/clients/).
136
+
137
+ | Client | Config location |
138
+ |---|---|
139
+ | Claude Code | `.mcp.json` at project root, or `claude mcp add` |
140
+ | Claude Desktop | macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` |
141
+ | Cursor | `.cursor/mcp.json` or `~/.cursor/mcp.json` |
142
+ | mcphost | `~/.mcphost.yml` or `--config <path>` |
143
+ | IBM Bob | `.bob/mcp.json` or `~/.bob/mcp_settings.json` |
144
+
145
+ All share the standard `mcpServers` envelope pointing at
146
+ `rocannon mcp serve --profile <your-profile.yml>`.
147
+
148
+ ## Development
149
+
150
+ ```bash
151
+ git clone https://github.com/msradam/rocannon.git
152
+ cd rocannon
153
+ uv sync
154
+ ./tests/check.sh # ruff format + lint + mypy + pytest
155
+ uv run pytest -m integration # opt-in: spins up a real UBI9 container
156
+ ```
157
+
158
+ See [`ARCHITECTURE.md`](ARCHITECTURE.md) for how the pieces fit together.
159
+
160
+ Rocannon is developed with AI assistance.
161
+
162
+ ## The name
163
+
164
+ Ursula K. Le Guin coined the word "ansible" in her 1966 novel *Rocannon's
165
+ World*. The gryphon is a nod to the Windsteeds that Rocannon and his
166
+ companions ride.
167
+
168
+ ## Credits
169
+
170
+ - Gryphon icon: [Gryphon by Aleksei Kovalenko from Noun Project](https://thenounproject.com/icon/gryphon-7096619/) (CC BY 3.0).
@@ -0,0 +1,155 @@
1
+ [project]
2
+ name = "rocannon"
3
+ version = "0.1.0"
4
+ description = "Every Ansible module as a typed MCP tool"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ authors = [
8
+ { name = "Adam Munawar Rahman", email = "msrahmanadam@gmail.com" }
9
+ ]
10
+ requires-python = ">=3.12"
11
+ keywords = ["ansible", "mcp", "model-context-protocol", "automation", "ibm-z", "zos"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Environment :: Console",
15
+ "Intended Audience :: Developers",
16
+ "Intended Audience :: System Administrators",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Topic :: System :: Systems Administration",
21
+ "Topic :: Utilities",
22
+ "Typing :: Typed",
23
+ ]
24
+
25
+ # Core framework only; Ansible is opt-in via the `ansible` extra.
26
+ dependencies = [
27
+ "fastmcp>=3.4.2",
28
+ "prompt_toolkit>=3.0.52",
29
+ "typer>=0.26.7",
30
+ "pydantic>=2.13.4",
31
+ "pyyaml>=6.0.3",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ # `pip install 'rocannon[ansible]'` pulls in ansible-core + ansible-runner.
36
+ ansible = [
37
+ "ansible-core>=2.21.0",
38
+ "ansible-runner>=2.4.3",
39
+ ]
40
+
41
+ # --- AI ---
42
+ # `pip install 'rocannon[ai]'` enables `.ai` mode in the REPL. Backend is
43
+ # picked per-call via ROCANNON_AI_MODEL (e.g. "anthropic/claude-sonnet-4-5",
44
+ # "openai/gpt-4o", "ollama/llama3.1", "watsonx/<model>"). LiteLLM owns the
45
+ # provider matrix; rocannon stays unopinionated about it.
46
+ ai = [
47
+ "litellm>=1.89.0",
48
+ ]
49
+
50
+ # --- Observability ---
51
+ # `pip install 'rocannon[otel]'`; run under `opentelemetry-instrument rocannon mcp serve …`
52
+ otel = [
53
+ "opentelemetry-sdk>=1.42.1",
54
+ "opentelemetry-exporter-otlp>=1.42.1",
55
+ ]
56
+
57
+ # --- Meta ---
58
+ # `pip install 'rocannon[all]'`, everything.
59
+ all = [
60
+ "ansible-core>=2.21.0",
61
+ "ansible-runner>=2.4.3",
62
+ "litellm>=1.89.0",
63
+ "opentelemetry-sdk>=1.42.1",
64
+ "opentelemetry-exporter-otlp>=1.42.1",
65
+ ]
66
+
67
+ [project.urls]
68
+ Homepage = "https://github.com/msradam/rocannon"
69
+ Repository = "https://github.com/msradam/rocannon"
70
+ Issues = "https://github.com/msradam/rocannon/issues"
71
+
72
+ [project.scripts]
73
+ rocannon = "rocannon.cli:main"
74
+
75
+ [build-system]
76
+ requires = ["uv_build>=0.11.14,<0.12.0"]
77
+ build-backend = "uv_build"
78
+
79
+ [dependency-groups]
80
+ # `uv sync` installs these alongside the project. Includes the `ansible` extra's
81
+ # Python deps so the test suite + lint can run against the full codebase.
82
+ dev = [
83
+ "ansible-core>=2.21.0",
84
+ "ansible-runner>=2.4.3",
85
+ "litellm>=1.89.0",
86
+ "mypy>=2.1.0",
87
+ "opentelemetry-sdk>=1.42.1",
88
+ "ollama>=0.6.2",
89
+ "pre-commit>=4.6.0",
90
+ "pytest>=9.1.0",
91
+ "pytest-asyncio>=1.4.0",
92
+ "pytest-cov>=7.1.0",
93
+ "ruff>=0.15.17",
94
+ "types-pyyaml>=6.0.12.20260518",
95
+ ]
96
+
97
+ [tool.pytest.ini_options]
98
+ asyncio_mode = "auto"
99
+ # Collect only the tracked suite by default. dev/tests/ holds local LLM evals
100
+ # that hit a real model; running them is opt-in via an explicit path.
101
+ testpaths = ["tests"]
102
+ markers = [
103
+ "integration: end-to-end tests that touch real docker/ansible. Opt-in via `pytest -m integration`.",
104
+ ]
105
+ # Default: run unit tests, skip integration. Override with `pytest -m integration`.
106
+ addopts = "-m 'not integration'"
107
+
108
+ [tool.coverage.run]
109
+ branch = true
110
+ source = ["src/rocannon"]
111
+ omit = ["src/rocannon/__main__.py"]
112
+
113
+ [tool.coverage.report]
114
+ show_missing = true
115
+ skip_covered = false
116
+ exclude_lines = [
117
+ "pragma: no cover",
118
+ "if __name__ == .__main__.:",
119
+ "raise NotImplementedError",
120
+ ]
121
+
122
+ # --- Ruff ---
123
+ [tool.ruff]
124
+ line-length = 100
125
+ target-version = "py312"
126
+ src = ["src"]
127
+
128
+ [tool.ruff.lint]
129
+ select = ["E", "W", "F", "I", "N", "UP", "B", "C4", "SIM", "S", "FURB"]
130
+
131
+ [tool.ruff.lint.per-file-ignores]
132
+ "tests/**" = ["S101", "S105", "S108", "S110", "S310", "S603", "S607"]
133
+ "src/rocannon/schema.py" = ["S603", "S607"]
134
+ "src/rocannon/inventory.py" = ["S603"]
135
+ "src/rocannon/cli.py" = ["S603", "S607"]
136
+
137
+ [tool.ruff.format]
138
+ quote-style = "double"
139
+
140
+ # --- Mypy ---
141
+ [tool.mypy]
142
+ python_version = "3.12"
143
+ strict = true
144
+ disallow_untyped_defs = true
145
+ warn_return_any = true
146
+ warn_unused_ignores = true
147
+ warn_redundant_casts = true
148
+ no_implicit_reexport = true
149
+ mypy_path = "src"
150
+
151
+ # Third-party libs without bundled type stubs. ansible_runner ships
152
+ # partial stubs only.
153
+ [[tool.mypy.overrides]]
154
+ module = ["ansible_runner.*"]
155
+ ignore_missing_imports = true
File without changes