Music Assistant Provider System Architecture: How Loading, Unloading, and Dependencies Work

Music Assistant treats every integration as a provider managed through a lifecycle in music_assistant/mass.py, where load_provider() orchestrates manifest validation, dependency checks, and isolated module imports, while unload_provider() recursively cleans up dependents and resources.

The music-assistant/server repository implements a modular provider system that treats music services, player speakers, and metadata sources as pluggable components. Understanding this architecture is essential for developers extending the platform or troubleshooting integration failures.

Provider Discovery and Manifest Loading

Manifest Structure and Discovery

On server start, __load_provider_manifests() in music_assistant/mass.py traverses the music_assistant/providers/ directory, parsing each manifest.json to populate self._provider_manifests. These JSON files declare the provider's domain, type (music, player, metadata), pip requirements, and dependency constraints.

Built-in vs. Regular Providers

The system distinguishes between built-in providers—such as core controllers and the Home Assistant integration—and regular providers (third-party services). Built-in providers load unconditionally, even in safe mode, while regular providers initialize after core startup based on the DEFAULT_PROVIDERS list and user configuration.

The Provider Lifecycle

Loading a Provider

The entry point load_provider() cancels pending reload timers before delegating to load_provider_config(). The private _load_provider() method performs the heavy lifting:

  1. Unloads existing instances with the same ID via await self.unload_provider(conf.instance_id)
  2. Validates configuration against the manifest
  3. Checks singleton restrictions and multi-instance rules
  4. Imports the provider module via load_provider_module(domain, requirements) from music_assistant/helpers/util.py, which installs pip dependencies in an isolated environment
  5. Executes the provider's async setup() and handle_async_init() methods
  6. Registers the instance in self._providers, sets available=True, fires a ready event, and triggers discovery

Dependency Resolution

Before instantiation, _load_provider() inspects prov_manifest.depends_on. If a required provider is not loaded, the function returns early. The system automatically retries once the dependency becomes available, triggered by load_provider_config() after successful dependency loads.

Unloading and Cleanup

unload_provider() handles removal through several steps:

  • Unschedules music synchronization jobs
  • Notifies players and music controllers of the removal
  • Recursively unloads any providers that depend on the one being removed
  • Invokes the provider's unload(is_removed) method for custom cleanup
  • Purges internal dictionaries and discovery state

Error Handling and Retry Logic

When a provider raises MusicAssistantError (e.g., missing hardware), load_provider() catches the exception, writes the error to last_error in the config, and optionally schedules a retry using self.call_later(..., self.load_provider, ...). Fatal errors are logged without crashing the server.

Programmatic Provider Management

Developers can interact with the system directly.

Loading a provider:

await mass.load_provider("spotify_1", allow_retry=True)

Unloading a provider:

await mass.unload_provider("spotify_1")

Manifest example with dependencies:

{
  "domain": "spotify",
  "type": "music",
  "builtin": false,
  "multi_instance": false,
  "depends_on": "auth",
  "requirements": ["spotipy>=2.20"]
}

Dynamic module loading:

async def load_provider_module(domain: str, requirements: list[str]) -> ProviderModuleType:
    # install missing pip requirements if needed, then import the provider's __init__.py

    # returns a module that exposes a `setup(mass, manifest, config)` coroutine

    ...

Summary

  • Providers are discovered from music_assistant/providers/ via manifest.json files loaded by __load_provider_manifests()
  • The lifecycle is orchestrated in music_assistant/mass.py through load_provider(), _load_provider(), and unload_provider()
  • Dependency resolution ensures providers load only after their dependencies are satisfied, with automatic retry logic
  • Isolated module loading via load_provider_module() in music_assistant/helpers/util.py handles pip requirements safely
  • Recursive cleanup during unloading prevents orphaned dependent providers
  • Error handling uses MusicAssistantError catching with configurable retry timers via call_later()

Frequently Asked Questions

How does Music Assistant handle circular provider dependencies?

The system checks prov_manifest.depends_on before instantiation. If a dependency is missing, _load_provider() returns early without error, and load_provider_config() triggers a retry once the dependency loads. This lazy evaluation prevents circular deadlock, though the manifest structure should ideally declare acyclic dependencies.

What happens when a provider fails to load due to missing hardware?

The load_provider() method catches MusicAssistantError exceptions, stores the error message in the provider config's last_error field, and optionally schedules a retry using self.call_later(). The server continues running, and the provider remains in a failed state until manually reloaded or the issue resolves.

Can I run multiple instances of the same provider simultaneously?

This depends on the multi_instance flag in the provider's manifest.json. If set to false (as seen in the Spotify manifest), the system enforces singleton restrictions during _load_provider(). If true, you can create multiple configurations with unique instance IDs.

How does the system isolate provider code from the core server?

The load_provider_module() function in music_assistant/helpers/util.py imports provider code in an isolated environment and automatically installs required pip packages specified in the manifest's requirements array. This prevents dependency conflicts between providers while ensuring the core server remains stable.

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 →