# Music Assistant Provider Dependency Resolution Startup: How MA Loads Providers in Order

> Learn how Music Assistant resolves provider dependencies at startup. Discover its efficient strategy for loading providers in topological order, ensuring all requirements are met.

- Repository: [Music Assistant/server](https://github.com/music-assistant/server)
- Tags: internals
- Published: 2026-06-21

---

**Music Assistant resolves provider dependencies at startup by scanning manifests, building a dependency graph, and recursively loading providers in topological order to ensure requirements are satisfied before dependents initialize.**

The `music-assistant/server` repository implements a modular provider architecture where each integration declares its dependencies in a manifest file. At startup, the core `Mass` class orchestrates the loading sequence to guarantee that base providers (like Spotify, MPD, or Home Assistant) are fully initialized before dependent plugins attempt to connect to them.

## Manifest Discovery and Dependency Graph Construction

The resolution process begins in [`music_assistant/mass.py`](https://github.com/music-assistant/server/blob/main/music_assistant/mass.py) where the `Mass.__load_provider_manifests()` method scans the `providers/*/manifest.json` files across the codebase. Each manifest specifies a provider's `domain`, `instance_id`, and a `requires` array listing other providers it depends on.

The system constructs a directed dependency graph where an edge *A → B* indicates that provider *A* requires provider *B* to be active first. The resolver performs cycle detection during this phase; if a circular dependency is detected (for example, Provider A requiring Provider B while Provider B requires Provider A), the system raises a `DependencyError` and aborts the loading process to prevent deadlock.

## Recursive Loading and Topological Ordering

The `Mass._load_provider(conf)` method (located at approximately line 1036 in [`mass.py`](https://github.com/music-assistant/server/blob/main/mass.py)) implements the recursive loading strategy:

1. **Unload existing instances**: First, it removes any previous instance using `await self.unload_provider(conf.instance_id)` to prevent conflicts.
2. **Resolve dependencies**: For each entry in `conf.requires`, it fetches the dependency's configuration and recursively calls `await self._load_provider(dep_prov_conf)`.
3. **Load the target**: Only after all dependencies resolve successfully does it proceed to import the provider module.

This depth-first traversal ensures topological ordering—providers are instantiated only after their entire dependency subtree is fully loaded and initialized.

## Dynamic Module Import and Requirement Installation

Once the dependency chain is verified, the system dynamically imports the provider module via `load_provider_module()` in [`music_assistant/helpers/util.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/util.py). This utility function handles two critical tasks:

```python

# helpers/util.py

async def load_provider_module(domain: str, requirements: list[str]) -> ProviderModuleType:
    # Install external pip requirements, if any

    if requirements:
        await run_subprocess("pip", "install", *requirements)
    # Import the package (e.g., music_assistant.providers.spotify)

    module_path = f"music_assistant.providers.{domain}"
    return importlib.import_module(module_path)

```

The function installs any Python packages listed in the manifest's `requirements` field before importing the module, ensuring the provider's runtime dependencies are satisfied before any provider code executes.

After import, the provider class (extending `Provider` from [`music_assistant/models/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/provider.py)) is instantiated and its `load()` coroutine is awaited. Providers can declare optional dependencies through `depends_on` entries in their action schemas, allowing the UI to hide actions until required configurations are present.

## Error Handling and Retry Logic

If a provider fails to load, `Mass.unload_provider_with_error()` logs the failure and gracefully removes the provider from the active set. To maintain graph consistency, all providers that depend on the failed node are also unloaded.

For providers that depend on external services (such as **snapcast** or **hass**) that may be unavailable at startup, the system implements retry logic:

```python

# Example from provider implementations

if not await self._check_server():
    # Retry after 5 seconds if the server is still starting

    self.mass.call_later(5, self.mass.load_provider, self.instance_id, allow_retry=True)

```

This scheduling mechanism uses `call_later()` to defer loading attempts without blocking the entire startup sequence, allowing transient services time to become reachable.

## Key Files and Implementation Details

| File | Role | GitHub Reference |
|------|------|------------------|
| [`music_assistant/mass.py`](https://github.com/music-assistant/server/blob/main/music_assistant/mass.py) | Central orchestrator containing manifest loading, dependency graph construction, and provider lifecycle management | [`music_assistant/mass.py`](https://github.com/music-assistant/server/blob/main/music_assistant/mass.py) |
| [`music_assistant/helpers/util.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/util.py) | Dynamic module import and pip requirement installation | [`music_assistant/helpers/util.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/util.py) |
| [`music_assistant/models/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/provider.py) | Base `Provider` class defining `load()` and `unload()` signatures | [`music_assistant/models/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/provider.py) |
| [`scripts/parse_manifest_deps.py`](https://github.com/music-assistant/server/blob/main/scripts/parse_manifest_deps.py) | CLI utility for parsing and debugging dependency trees | [`scripts/parse_manifest_deps.py`](https://github.com/music-assistant/server/blob/main/scripts/parse_manifest_deps.py) |
| `providers/*/manifest.json` | Provider metadata including `requires` and `requirements` fields | Example: [`music_assistant/providers/spotify/manifest.json`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/spotify/manifest.json) |

The [`parse_manifest_deps.py`](https://github.com/music-assistant/server/blob/main/parse_manifest_deps.py) script is particularly useful for developers debugging complex dependency chains, as it visualizes the provider hierarchy before runtime.

## Summary

- **Manifest scanning**: `Mass.__load_provider_manifests()` discovers all providers and their `requires` declarations from JSON manifest files.
- **Topological ordering**: `Mass._load_provider()` recursively loads dependencies before instantiating the target provider, guaranteeing prerequisite availability.
- **Dynamic imports**: `load_provider_module()` in [`helpers/util.py`](https://github.com/music-assistant/server/blob/main/helpers/util.py) handles runtime pip installation and module importing via `importlib`.
- **Resilience**: Failed providers trigger cascading unloads of dependents, while `call_later()` enables non-blocking retries for transient service dependencies.
- **Cycle prevention**: The dependency graph walker detects circular references and raises `DependencyError` before any provider code executes.

## Frequently Asked Questions

### How does Music Assistant prevent circular dependency deadlocks at startup?

The dependency resolver in [`mass.py`](https://github.com/music-assistant/server/blob/main/mass.py) constructs a directed graph from the `requires` fields in provider manifests and walks it before loading begins. If it detects a cycle (Provider A depending on Provider B which ultimately depends on Provider A), it raises a `DependencyError` immediately and aborts the loading process, preventing the system from entering a deadlock state.

### What happens if a provider's external Python dependencies are not installed?

The `load_provider_module()` function in [`helpers/util.py`](https://github.com/music-assistant/server/blob/main/helpers/util.py) checks the `requirements` list in the provider's manifest before importing. If external packages are specified, it runs `pip install` in an isolated subprocess to satisfy them. Only after successful installation does it proceed with `importlib.import_module()`, ensuring the provider's code never executes with missing dependencies.

### Can providers depend on services that are not yet running when Music Assistant starts?

Yes. Providers like the Snapcast or Home Assistant integrations can schedule retries using `self.mass.call_later(5, self.mass.load_provider, ...)`. This defers the loading attempt by a specified number of seconds (typically 5) without blocking other providers, allowing transient network services or local daemons time to become available without preventing the rest of the system from initializing.

### Where is the provider base class defined, and what methods must implementations override?

The base `Provider` class is defined in [`music_assistant/models/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/provider.py). All provider implementations must override the `load()` coroutine to initialize their connections and resources, and the `unload()` coroutine to clean up connections and release resources when the provider is stopped or when dependencies fail.