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

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 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) 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. This utility function handles two critical tasks:


# 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) 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:


# 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 Central orchestrator containing manifest loading, dependency graph construction, and provider lifecycle management music_assistant/mass.py
music_assistant/helpers/util.py Dynamic module import and pip requirement installation music_assistant/helpers/util.py
music_assistant/models/provider.py Base Provider class defining load() and unload() signatures music_assistant/models/provider.py
scripts/parse_manifest_deps.py CLI utility for parsing and debugging dependency trees scripts/parse_manifest_deps.py
providers/*/manifest.json Provider metadata including requires and requirements fields Example: music_assistant/providers/spotify/manifest.json

The 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 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 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 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. 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →