ansible-flow-mcp
Give agents Ansible. Not the keys.
MCP server that exposes real Ansible modules and playbooks to AI agents — and a SSH hub/spoke fabric so multi-host automation is enrolled, bastion-scoped, and check-first by default.
| Track | What you get |
|---|---|
| Agent loop | search → schema → check → execute on allowlisted collections |
| Fleet fabric | One hub · join tokens · SSH ForceCommand spokes · fixed inventory |
OpenFlow dual-track · Campaign storyboard · Hub/spoke ops · Apache-2.0
Not affiliated with Red Hat or the Ansible project beyond using the public Ansible CLI and docs.
New here? Full step-by-step: docs/QUICKSTART.md — local MCP · compose lab · bare-metal hub/spoke · editor wiring.
Visual tour
| Why it exists | Agent loop |
| Hub / spoke fabric | Operators |
Screenshots live in docs/images/campaign-*.png. Re-shoot from docs/campaign/ with ./capture.sh.
Why this exists
Agents on a god-mode control node invent inventory, reach for shell, and treat every worker as an entrypoint. That is not a security model.
You need this when:
- You want Cursor / Claude / OpenCode to run Ansible like an operator, not freestyle root across the fleet
- Multi-host must mean bastion ops you already understand (SSH, inventory, enrollment) — not a mesh hop plane
- Prompt injection will still ask for bad ops — policy and topology must refuse
Two tracks
1. Agent loop — search → schema → check → execute
Curated module gallery. Slim argSpec before any run. Check mode default. Free-form modules denied. Playbooks path-jailed.
| Tool | Purpose |
|---|---|
search_modules |
Gallery search |
get_module_schema |
Slim argSpec for FQCN |
run_module |
Ad-hoc Ansible (check_mode default true) |
run_playbook |
ansible-playbook on a path-jailed .yml |
list_collections |
Collections in gallery |
Ritual (modules): search_modules → get_module_schema → run_module(..., check_mode=true) → apply only if appropriate.
Ritual (playbooks): confirm path under allowlisted roots → check → apply.
2. Hub/spoke — nothing is a target until enrolled
Secure multi-host mode: agent attaches to the hub only. Hub reaches spokes over SSH only. Spokes execute localhost and cannot lateral-move via this fabric.
| Full mesh (withdrawn) | Hub/spoke (shipped) | |
|---|---|---|
| Worker compromise | Could MCP-hop fleet-wide | No lateral MCP |
| Inventory | Gossip / replicas | Hub is source of truth |
| Agent attach | Any node | Hub only |
| Ops model | Mesh OS | Classic Ansible bastion |
Enrollment: hub init → issue-token (TTL, one-time jti) → spoke join (token + SSH identity) → hub inventory. Runtime: ForceCommand MCP session — no shell on the hub→spoke path.
Hub tools: list_nodes / hub_status, issue_token, revoke_node, groups (create_group, set_group_members, …), spoke_call, plus catalog run_* against enrolled hosts or groups only. Client-supplied -i is rejected in hub mode.
Deep ops: docs/HUB.md.
Operators
Day-2 surface matches the agent: enroll, group, hand the hub to OpenCode.
ansible-flow-mcp hub init --name ctrl-01
ansible-flow-mcp hub issue-token --name web-03 --ttl 15m
ansible-flow-mcp spoke join --token "$TOKEN" --hub user@hub:22 --public-addr web-03.example.com
ansible-flow-mcp hub session # MCP stdio for the agent
ansible-flow-mcp tui # servers · groups · invite · OpenCode
ansible-flow-mcp hub spoke-call --node web-03 --tool list_collections
Lab one-shot
cd lab && ./scripts/demo.sh
# then: ./scripts/tui.sh | ./scripts/opencode-host.sh
See lab/README.md.
Quick start
Full guide (all paths, verify, troubleshooting): docs/QUICKSTART.md
| Path | Guide section |
|---|---|
| Local MCP + Cursor/Claude | Path A |
| Compose lab (hub + 3 spokes) | Path B |
| Bare-metal hub/spoke | Path C |
Single-node / dev (short form)
Requirements: Python ≥ 3.11 · ansible-core on PATH · collection ansible.posix · collections you will run.
python3 -m pip install --user 'ansible-core>=2.16,<2.19'
export PATH="$HOME/.local/bin:$PATH"
ansible-galaxy collection install ansible.posix
cd ansible-flow-mcp
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest -q
ansible-flow-mcp
Editor snippets: examples/cursor-mcp.json, examples/claude-desktop.json.
Hub + OpenCode: examples/opencode-hub.jsonc · ansible-flow-mcp hub write-opencode-config.
{
"mcpServers": {
"ansible-flow": {
"command": "/path/to/ansible-flow-mcp/.venv/bin/ansible-flow-mcp"
}
}
}
Security (honest)
| Control | Behavior |
|---|---|
| Collection allowlist | Only configured collections |
| Module deny list | command / shell / raw / script denied by default |
| Check mode | Default true on run_module |
| Playbook jail | Allowlisted roots · size limit · .yml/.yaml only |
| No shell interpolation | argv-only subprocess |
| Hub inventory | Enrolled hosts only · no client -i · host key checking on |
| Spoke path | SSH ForceCommand · localhost exec · no peer fabric |
Residual: hub compromise = fleet (same class as any Ansible control node). Harden the bastion — see docs/SECURITY.md and docs/HUB.md.
Catalog & OpenFlow
catalog/collections-allowlist.yml— allowlist + deny free-form modulescatalog/gallery.json+catalog/schemas/— searchable gallery- Regenerate:
python scripts/generate_catalog.py - Galaxy factory TUI: scripts/factory/README.md
Dual-tracked with OpenFlow Ansible canvas gallery
(plan · umbrella).
| OpenFlow | This MCP server |
|---|---|
| Palette Ansible gallery | search_modules |
| Form | JSON module options | get_module_schema + run_module |
| Playbook resource | run_playbook |
| Control-node SSH / become | Inventory + Ansible config · hub→spoke SSH in hub mode |
Env (common)
| Variable | Meaning |
|---|---|
ANSIBLE_FLOW_CATALOG_DIR |
Override catalog path |
ANSIBLE_FLOW_COLLECTIONS |
Comma-separated allowlist override |
ANSIBLE_FLOW_INVENTORY |
Default -i (non-hub / dev) |
ANSIBLE_FLOW_TIMEOUT |
Seconds (default 120 module / 300 playbook) |
ANSIBLE_FLOW_PLAYBOOK_ROOTS |
Extra playbook roots (:-separated) |
ANSIBLE_FLOW_HUB_DIR |
Hub state (default: /var/lib/… if writable, else ~/.local/share/ansible-flow/hub) |
ANSIBLE_FLOW_SPOKE_DIR |
Spoke state (same pattern under …/spoke) |
License & publish
Apache-2.0
pip install build twine && python -m build
# twine upload dist/*
uvx --from ansible-flow-mcp ansible-flow-mcp
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ansible_flow_mcp-0.1.1.tar.gz.
File metadata
- Download URL: ansible_flow_mcp-0.1.1.tar.gz
- Upload date:
- Size: 7.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
167a3a4f1fefcd4b18071bb57cd831c98d22f32fc6067482690573438c25fe2f
|
|
| MD5 |
9ef44154d4f10297324e66f88b0ca900
|
|
| BLAKE2b-256 |
cee1f0dd3e41cbc0a671a2ab216c691d5d8bc4d45aa07cb82fe87eac8761b68b
|
Provenance
The following attestation bundles were made for ansible_flow_mcp-0.1.1.tar.gz:
Publisher:
publish.yml on real-limitless/ansible-flow-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ansible_flow_mcp-0.1.1.tar.gz -
Subject digest:
167a3a4f1fefcd4b18071bb57cd831c98d22f32fc6067482690573438c25fe2f - Sigstore transparency entry: 2399658734
- Sigstore integration time:
-
Permalink:
real-limitless/ansible-flow-mcp@71c86b24b0d95941c36bbe2cfd825df9d31a1a9a -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/real-limitless
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@71c86b24b0d95941c36bbe2cfd825df9d31a1a9a -
Trigger Event:
release
-
Statement type:
File details
Details for the file ansible_flow_mcp-0.1.1-py3-none-any.whl.
File metadata
- Download URL: ansible_flow_mcp-0.1.1-py3-none-any.whl
- Upload date:
- Size: 13.6 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1fef727404cca589286e11f335d5dadb79113b2fa0b4fce24625b3e25daf443c
|
|
| MD5 |
944af69a6719526ad91494a0c78c091c
|
|
| BLAKE2b-256 |
df18af227166bf688615acc06792802f03a52a4dd220df40844de85f2119b473
|
Provenance
The following attestation bundles were made for ansible_flow_mcp-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on real-limitless/ansible-flow-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ansible_flow_mcp-0.1.1-py3-none-any.whl -
Subject digest:
1fef727404cca589286e11f335d5dadb79113b2fa0b4fce24625b3e25daf443c - Sigstore transparency entry: 2399658782
- Sigstore integration time:
-
Permalink:
real-limitless/ansible-flow-mcp@71c86b24b0d95941c36bbe2cfd825df9d31a1a9a -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/real-limitless
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@71c86b24b0d95941c36bbe2cfd825df9d31a1a9a -
Trigger Event:
release
-
Statement type: