# Profiles

A profile is a YAML file that declares which inventory to use and which modules and roles to expose as MCP tools. Rocannon reads one or more profiles at startup and registers the union of their tools.

## Full profile reference

    inventories:
      - ./hosts                        # path to inventory file or directory
      - ./hosts.d/                     # multiple inventories are supported

    modules:
      - ansible.builtin.copy           # a single module FQCN
      - ansible.builtin                # a whole collection → all its modules
      - community                      # a namespace → all collections in it
      - community.docker               # mix module, collection, namespace specs freely

    roles:                             # optional
      - my_ns.my_coll.setup_web        # FQCN for a collection role
      - standalone_role                # bare name for a standalone role (needs roles_path)

    roles_path: ./roles                # optional; resolves against the profile file's directory

    ansible_cfg: ./ansible.cfg         # optional; sets ANSIBLE_CONFIG for module runs

    vault_password_file: ./vault.key   # optional; sets ANSIBLE_VAULT_PASSWORD_FILE

    extra_envvars:                     # optional; merged into the ansible-runner environment
      CUSTOM_VAR: value
      ANOTHER_VAR: other

    timeouts:                          # optional; per-module execution timeout in seconds
      ansible.builtin.command: 60      # default is 300s (5 minutes)
      ansible.builtin.shell: 60

All file paths in a profile resolve relative to the profile file's directory, not the process CWD. This is what makes absolute-path invocations from MCP clients work correctly.

## Configuration keys

Key | Required | Description
--- | --- | ---
`inventories` | required | List of paths to Ansible inventory files or directories. Rocannon delegates all parsing (group_vars, Jinja2, dynamic inventories) to `ansible-inventory`.
`modules` | required* | List of module, collection, or namespace specs. A namespace like `community` expands to every installed module in that namespace. Only modules become tools; filter and lookup plugins are skipped. *Required unless `roles` is set.
`roles` | optional | List of role names to expose. A collection role uses its FQCN (`my_ns.my_coll.role`). A standalone role uses its directory name. Roles without `meta/argument_specs.yml` are skipped: Rocannon cannot build a typed interface without a documented spec.
`roles_path` | optional | Directory containing standalone roles. Set this when roles are not in a collection and not on the default Ansible roles path.
`ansible_cfg` | optional | Path to an `ansible.cfg` file. Passed to `ansible-runner` as `ANSIBLE_CONFIG`.
`vault_password_file` | optional | Path to a Vault password file. Passed as `ANSIBLE_VAULT_PASSWORD_FILE`. Vault-encrypted variables in inventory or host_vars are decrypted automatically.
`extra_envvars` | optional | Key-value pairs merged into the subprocess environment for every module run. Useful for passing credentials that modules read from environment variables (e.g. `AWS_PROFILE`, `ZOAU_HOME`).
`timeouts` | optional | Per-module execution timeout in seconds. The global default is 300s (overridden by `ROCANNON_TIMEOUT`). Set shorter timeouts for modules that should fail fast, or longer ones for slow provisioning steps.

## Profile discovery

When you run `rocannon mcp serve` without `--profile`, Rocannon walks up from the current directory looking for `.rocannon/profiles/`. If it finds the directory, it loads every `*.yml` file in it as a named profile.

Default profile resolution order:

1. `default.yml` as a symlink: the symlink target's stem becomes the active name
2. `default.yml` as a regular file
3. The only profile in the directory, if exactly one exists
4. Falls back to `~/.rocannon/profiles/` if no project-level directory is found

## Runtime switching

Load multiple profiles in `.rocannon/profiles/` and switch between them at runtime without restarting the server:

    # In your MCP client or REPL:
    rocannon_list_profiles     → lists all profiles and which is active
    rocannon_current_profile   → returns the active profile's full config
    rocannon_use_profile       → switches to a named profile

Tool functions read the active profile's inventory, envvars, and timeouts on every call. A profile switch takes effect immediately for subsequent calls.

Union registration. Rocannon registers the union of every profile's modules at startup. Calling a module that isn't in the active profile returns a structured error pointing at `rocannon_use_profile`, rather than failing inside `ansible-runner`.

## Example: dev/staging/prod

    .rocannon/profiles/
      dev.yml         → dev inventory, full module set, permissive timeouts
      staging.yml     → staging inventory, same modules, tighter timeouts
      prod.yml        → production inventory, restricted module subset
      default.yml     → symlink → dev.yml

The agent sees all module tools on startup. `rocannon_use_profile("prod")` narrows execution to the production inventory and module subset without re-registering anything.

## Example: network automation

    inventories:
      - ./inventory/network.yml    # Arista and Cisco hosts

    modules:
      - arista.eos
      - cisco.ios
      - ansible.netcommon

    ansible_cfg: ./ansible-network.cfg   # network-optimised connection settings

    timeouts:
      arista.eos.eos_config: 120         # config pushes can be slow on large devices

## Dependencies: modules with third-party libraries

Some modules require Python packages beyond ansible-core:

Collection | Python dependency
--- | ---
`community.docker` | `pip install docker`
`community.crypto` | `pip install cryptography`
`community.mongodb` | `pip install pymongo`
`community.postgresql` | `pip install psycopg2-binary`
`amazon.aws` | `pip install boto3`
`kubernetes.core` | `pip install kubernetes`

Install these in the same environment as Rocannon. The quickstart profile pins `ansible_python_interpreter` so localhost modules use the right interpreter automatically.