What is actually true in this Ansible checkout from Claude Code, Cursor, or any MCP client - installed module option specs, task-argument checks, role interfaces, redacted inventory, play structure, and collection-upgrade impact.
@yawlabs/ansible-mcp answers from your checkout on your control node, not from upstream docs. The source of truth is ansible-doc --json run against your collections paths, with redirects such as ufw to community.general.ufw and tombstones resolved - so the option specs an agent checks against are the ones you actually have installed, not the ones the latest release documents. It is read-only by construction: it never runs a play, never connects to a managed host, and never supplies a vault secret. Built and maintained by Yaw Labs, MIT licensed, published on npm.
npx -y @yawlabs/ansible-mcp@latestGitHub · npm · Add to Yaw MCP
Anyone who has watched an agent write a task against the wrong collection version. Your checkout has community.general 9.5.2; the model's training data has whatever version it saw most. The agent then writes git_config: list_all: true (removed in 11.0.0), forgets name (required since 12.0.0), or sets state: present on rhsm_repository (no longer accepted). The VS Code language server knows the exact specs, but only a person in the editor sees them. This server lets the agent check before it writes:
ansible_project_info reports the ansible-core version, effective config file, collections and roles paths, installed ansible-* binaries, detected playbooks, inventories and roles files. Call it first.ufw take in my version?" - ansible_module_doc returns every option of one module or plugin as installed: type, required, default, choices, aliases, suboptions, deprecations, no_log, and the providing collection version.ansible_check_task_args checks inline task YAML or a tasks file against the installed docs: unknown options, missing required ones, bad choices and types, suboptions, deprecated options and duplicate keys. It never claims "valid": it names what ansible-doc cannot check.ansible_role_interface reads a role's argument-spec entry points and options, defaults (redacted), undocumented defaults, dependencies and galaxy_info.ansible_upgrade_impact diffs the arg specs of the modules your tasks actually use against a target version and ends with a verdict tied to file:line.1. Have ansible-core where the server can run it. On macOS and Linux, ansible-core (and optionally ansible-lint) on PATH, or in a venv you point ANSIBLE_MCP_BIN_DIR at. On Windows, inside WSL:
python3 -m venv ~/.venvs/ansible && ~/.venvs/ansible/bin/pip install ansible-core ansible-lintA venv under $HOME survives WSL restarts; /tmp does not.
2. Add the server. Claude Code, from the project. On macOS and Linux:
claude mcp add ansible -- npx -y @yawlabs/ansible-mcp@latestOn Windows, with Ansible inside WSL:
claude mcp add ansible \
-e ANSIBLE_MCP_COMMAND_PREFIX='["wsl.exe","-d","Ubuntu","--exec"]' \
-e ANSIBLE_MCP_BIN_DIR=/home/me/.venvs/ansible/bin \
-- cmd /c npx -y @yawlabs/ansible-mcp@latestOr add it to .mcp.json in the project. On macOS and Linux:
{
"mcpServers": {
"ansible": {
"command": "npx",
"args": ["-y", "@yawlabs/ansible-mcp@latest"]
}
}
}On Windows, with Ansible inside WSL:
{
"mcpServers": {
"ansible": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@yawlabs/ansible-mcp@latest"],
"env": {
"ANSIBLE_MCP_COMMAND_PREFIX": "[\"wsl.exe\",\"-d\",\"Ubuntu\",\"--exec\"]",
"ANSIBLE_MCP_BIN_DIR": "/home/me/.venvs/ansible/bin"
}
}
}
}On Windows the cmd /c wrapper is required for the same reason as every other server: since Node 20, child_process.spawn cannot directly execute .cmd files, and that is what npx is on Windows. The prefix must use --exec (or -e), never --: with -- WSL re-parses the arguments with bash and a value like it's breaks the command. The server refuses a prefix without --exec.
3. Point it at the project. It uses the working directory by default; set ANSIBLE_MCP_DIR for a fixed project, or pass dir per call. On a Windows drive every folder looks world-writable to WSL and Ansible then ignores ansible.cfg - when the project has one, the server sets ANSIBLE_CONFIG to it explicitly and says so.
Running Yaw MCP? One click adds it to your local config, and it is then available in every Yaw Terminal session.
| Tool | What it does |
|---|---|
ansible_project_info | ansible-core version, effective config file, collections and roles paths, installed ansible-* binaries, detected playbooks, inventories (with a whole-file vault count), roles and requirements files. Call this first. |
ansible_module_doc | Every option of one module or plugin as installed: type, required, default, choices, aliases, suboptions, deprecations, no_log, the providing collection version, [has action plugin], returns and optionally examples. |
ansible_module_search | Find installed modules and plugins by name or description, ranked exact, prefix, token, then description. |
ansible_check_task_args | Check inline task YAML or a tasks file against the installed docs: unknown options, missing required ones (aliases count), bad choices and types, suboptions, mode: 0755, deprecated options, duplicate keys. Never claims "valid". |
ansible_role_interface | A role's argument-spec entry points and options, defaults (redacted, no_log honoured), undocumented defaults, dependencies, vars names and galaxy_info. Local roles and ns.collection.role. |
ansible_inventory | Groups, hosts and inventory-level vars (names or values), always redacted. Whole-file vaults degrade to a per-file view instead of failing. |
ansible_playbook_structure | --syntax-check, then plays, matched hosts, tasks with module and file:line, tags, and the deprecation warnings Ansible printed while parsing. |
ansible_config | Effective config with origins (ansible.cfg, env, default); galaxy credentials always redacted; settings the server overrides for safety are flagged. |
ansible_collections | requirements.yml vs installed: ok / VIOLATES / MISSING with file:line, galaxy constraint semantics, and shadowed copies across collections paths. |
ansible_lint_summary | ansible-lint --offline grouped by rule with counts, severities and locations. Never fixes. |
ansible_upgrade_impact | What breaks in this project's tasks if a collection moves to an exact version: removed or moved modules (redirects and tombstones), removed options and aliases, newly required options, dropped choices, type and default changes, requirements constraints, requires_ansible, an optional syntax-check against the target, and a verdict. |
Installed, not latest, is the whole point. The source of truth is ansible-doc run against the collections paths your checkout actually uses, so a module that was renamed or removed between your version and the upstream default is described as you have it, not as the docs describe it.
--check, never connects to a managed host, and never supplies a vault secret.ANSIBLE_MCP_ALLOW_DYNAMIC_INVENTORY=1.name / hosts / vars_files on this machine, so a play carrying one is refused before it is parsed, unless you opt in with ANSIBLE_MCP_ALLOW_PLAY_LOOKUPS=1.ansible-lint's args rule skips modules with action plugins such as copy and template; ansible_check_task_args covers those and says plainly which cross-option rules ansible-doc cannot express.off is false and 0755 is the integer 493. The server reads YAML the same way and says when that is probably a bug.shell: false, on macOS, Linux, and Windows through WSL.It answers what is true in your Ansible checkout on your control node, not in the upstream collection docs. The source of truth is ansible-doc --json run against your collections paths, with redirects and tombstones resolved, so the module option specs it reports are the ones you actually have installed. On top of that it checks task arguments, reads role interfaces, inspects inventory (redacted), parses play structure, resolves effective config and requirements drift, and computes what a collection upgrade would break in your tasks.
It is read-only by construction. It never runs a play, never runs --check, never connects to a managed host, and never supplies a vault secret: configured vault password scripts are replaced with a server-owned dummy password file so they never execute. Script, constructed and plugin inventory sources are off by default because they run code and may call cloud APIs, and play-level lookups are refused before Ansible parses a playbook because Ansible runs those on this machine.
It complements lint rather than replacing it. ansible-lint's args rule skips modules with action plugins, such as copy and template, so it cannot check their options at all. ansible_check_task_args covers those modules, catches unknown options and bad values, and says plainly which cross-option rules ansible-doc cannot express rather than claiming a task is valid. It also does what lint does not: role argument specs, inventory structure, effective config origins, requirements drift, and collection-upgrade impact tied to file:line.
Ansible itself has no Windows control node, so on Windows the server reaches it through WSL with a command prefix such as wsl.exe -d Ubuntu --exec. The prefix must use --exec or -e, never --, because with -- WSL re-parses the arguments with a shell and a value like it's breaks the command; the server refuses a prefix without --exec. Windows environment variables do not cross into WSL, so variables the Ansible side needs are forwarded by name with ANSIBLE_MCP_PASS_ENV, and Windows paths are translated using the mount root wslpath reports.
Given a collection and an exact target version, it downloads only that version into its own cache and diffs the argument specs of the modules your tasks actually use. It reports removed or moved modules via runtime.yml redirects and tombstones, removed options and aliases, newly required options, dropped choices, type and default changes, requirements constraints and requires_ansible, optionally confirms with a syntax-check against the target, and ends with a verdict tied to file:line.
Published by Yaw Labs.