Skip to main content

ansible-mcp

CI

A minimal Ansible controller with an MCP interface, for AI agents — and a REST API for the humans who work alongside them.

Give it a playbook and an inventory; it runs them and keeps the history, the logs and the artifacts of every run. One process, one SQLite file, one container. No database server, no message broker, no worker fleet.

Русская версия: README.ru.md

Why

Running Ansible against a handful of hosts is easy. Running it programmatically is not, and the moment you want that the options get heavy: AWX and Ansible Automation Platform need PostgreSQL, Redis, RabbitMQ, Receptor and in practice Kubernetes. For fifty hosts that control plane costs more than the problem it solves.

And an AI agent driving infrastructure needs a machine-facing interface. A REST API designed for a human clicking through a UI means deep object graphs and a dozen calls to launch one job; an agent wants a handful of well-named verbs.

That is what this is: a dumb executor with an agent-shaped interface. It does not write playbooks and does not decide what to run — the calling agent does both. See docs/concept.md.

Quick start

The three-line version is below; the user guide is the same path with every step explained, plus what to do when it does not work.

The fastest path is stdio, where the client launches the process. The package is ansible-mcp-controller on PyPI, and ansible-core arrives with it:

uv tool install ansible-mcp-controller
ansible-mcp                   # speaks MCP on stdin/stdout

In Claude Code, or any MCP client:

{
  "mcpServers": {
    "ansible-mcp": {
      "command": "uvx",
      "args": ["--from", "ansible-mcp-controller", "ansible-mcp"],
      "env": { "ANSIBLE_MCP_DATA_DIR": "/tmp/ansible-mcp" }
    }
  }
}

As a container, serving over HTTP:

docker build -f Containerfile -t ansible-mcp .
mkdir -p data && sudo chown 1000:1000 data     # the image runs as uid 1000
docker run -p 8080:8080 -v ./data:/data \
  -e ANSIBLE_MCP_API_KEY="$(openssl rand -hex 32)" ansible-mcp

The key is not optional there: the container listens beyond loopback, and the server refuses to serve an unauthenticated endpoint that executes playbooks. For a host installation, see packaging.

The tools

Tool What it does
run_playbook Starts a run and returns a task id; the run continues in the background. check=true is a dry run: it contacts the hosts and changes nothing
syntax_check_playbook Parses a playbook and says whether it is valid, contacting nothing
get_task_status pending, running, success, failed or cancelled, with timestamps and exit code
get_task_logs What Ansible printed, the tail by default, secrets removed; after_line pages forward without re-reading
cancel_task Stops a queued or running playbook; needs confirm=true
list_tasks Recent runs, newest first, filterable by status
save_playbook / list_playbooks / get_playbook / delete_playbook Playbooks by name, so a run need not carry its text
add_provider / list_providers / get_inventory / delete_provider Where an inventory comes from

A provider is a small plugin that produces an inventory: static ships built in, others register through the ansible_mcp.providers entry point group. Cloud providers are the next ones planned.

The same thing with curl

When it serves HTTP, every one of those operations is also at /api/v1, on the same port as /mcp and behind the same token:

curl -si -X POST http://127.0.0.1:8080/api/v1/runs \
  -H "Authorization: Bearer $ANSIBLE_MCP_API_KEY" -H 'Content-Type: application/json' \
  -d '{"playbook_name":"site","provider":"lab"}'
HTTP/1.1 202 Accepted
location: /api/v1/runs/8aeac1aeb682464c81b55b0a338e45ec

Then GET /api/v1/runs/{id} follows it and .../logs reads what it printed. MCP stays the primary interface and REST is the second door onto the same operations (ADR-0002, ADR-0015); the schema is at /api/v1/openapi.json, and the user guide has the whole loop.

What it deliberately does not do

  • No RBAC, no multi-tenancy. One key gates the instance.
  • No horizontal scaling. One node, concurrency bounded by a semaphore.
  • No web UI.
  • No playbook generation. That is the calling agent's job, and the LLM layer the prototype had was removed rather than ported.

Each of these is a decision with its reasoning and its rejected alternatives in the decision log. Outgrowing them is the signal to move to a full platform, and that boundary is what keeps this project small.

Security: the API key is shell access

Read this before exposing the endpoint to anything.

Whoever holds the API key can run arbitrary code on the controller host, as the user the service runs as. Not through a bug — through the feature. A playbook with hosts: localhost, or an inventory line carrying ansible_connection=local, executes on the controller itself, and the controller is built to run whatever playbook it is handed: it does not inspect, judge or restrict them (ADR-0004).

So treat the key exactly as you would an SSH login to that host. One key covers the whole instance, there is no per-caller identity, and the audit log records what was done rather than by whom.

What reduces the blast radius today:

  • Run it as an unprivileged user. The container image and the Debian package both do; a manual install should too.
  • Give that user only the SSH credentials it needs, and no sudo on the controller.
  • Keep the endpoint off the network unless it has to be on it. The stdio transport has no port at all, and a loopback bind needs no key because reaching it already means being on the machine.

What actually fixes it is running each playbook inside a container rather than on the host, and that now exists. It is off by default, because it needs a container runtime and this project promises none:

ANSIBLE_MCP_ISOLATION=true
ANSIBLE_MCP_CONTAINER_RUNTIME=podman
ANSIBLE_MCP_EXECUTION_IMAGE=my-execution-environment:latest

With it on, a playbook that says hosts: localhost runs inside a container and the controller's filesystem is not there to touch. The operator turns it on for the whole installation and a run may pick its image but cannot decline the sandbox, which is what stops a caller stepping out of it (ADR-0008, ADR-0016). The packaging notes have the full setup; it is for the Debian package, not the container image, which already isolates the whole service from its host.

What it does not change is the fleet: whoever holds the key can still run any playbook against every host the controller's credentials reach. Isolation bounds the damage on this machine, not on the machines it manages.

Working on it

poetry install
poetry run task tests        # 250 tests
poetry run task lint         # ruff + ruff format + mypy
make integration             # playbooks against real hosts over SSH
poetry run task eval         # can a local model actually drive this?

The eval harness is not decoration: it reads the tool schemas from the running server and measures whether a model picks the right tool. Several tool descriptions and the whole argument-coercion layer exist because it failed first. See evals/README.md.

Contributors, human or otherwise, start at AGENTS.md.

Documentation

User guide Install, connect, and everything it can do
Concept The problem, the consumers, the trade-offs
Architecture What the code does now
Configuration Every setting
Reaching hosts Keys, passwords and host keys, per install method
Decision log Why the boundaries are where they are
Packaging Container and Debian package
Evals Measuring whether an agent can drive it
Integration Connecting a client, and a ready-made skill
Roadmap What is done and what is next (in Russian)

Status

The core works: runs, cancellation, timeouts, recovery after a crash, playbook storage, providers, redaction, an audit trail, a checked token and both surfaces — MCP and REST. Not yet: cloud providers — see the roadmap.

License

Apache-2.0 — see LICENSE.

Metadata

Release files for ansible-mcp-controller 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ansible-mcp-controller 0.1.0
File Size Uploaded Provenance Actions
ansible_mcp_controller-0.1.0.tar.gz 64.1 kB With provenance Details for ansible_mcp_controller-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ansible-mcp-controller 0.1.0
File Interpreter ABI Platform Actions
ansible_mcp_controller-0.1.0-py3-none-any.whl Python 3 none any Details for ansible_mcp_controller-0.1.0-py3-none-any.whl

Total release size: 147.1 kB

Release files / ansible_mcp_controller-0.1.0.tar.gz

Download URL ansible_mcp_controller-0.1.0.tar.gz
Size 64.1 kB
Tags Source
SHA-256 checksum
How to use checksums
7c611f69f6ce1a5fb237bafc95e4fde885d1b0f96ca3b9f117fc3ed19d37afe4
BLAKE2b-256 checksum
How to use checksums
1f30d7ee923cf313092ad6be575403c29d38f70957d30be7585eae881baa214a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

Release files / ansible_mcp_controller-0.1.0-py3-none-any.whl

Download URL ansible_mcp_controller-0.1.0-py3-none-any.whl
Size 83.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
46efa2c77608e9632bd55ceebc252810e58b65e23e752ae1ca69b5edd027f7bb
BLAKE2b-256 checksum
How to use checksums
98a34c31932f68c3a46a4a37316bc7de248a5ac907372886bfd72fc7634c4f10
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

Security for ansible-mcp-controller 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ansible_mcp_controller-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page