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

> Explore the Music Assistant provider system architecture. Understand how providers load, unload, and manage dependencies for seamless integration. Learn about manifest validation and resource cleanup.

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

---

**Music Assistant treats every integration as a provider managed through a lifecycle in [`music_assistant/mass.py`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/music_assistant/mass.py) traverses the `music_assistant/providers/` directory, parsing each [`manifest.json`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/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:**

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

```

**Unloading a provider:**

```python
await mass.unload_provider("spotify_1")

```

**Manifest example with dependencies:**

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

```

**Dynamic module loading:**

```python
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`](https://github.com/music-assistant/server/blob/main/manifest.json) files loaded by `__load_provider_manifests()`
- The lifecycle is orchestrated in [`music_assistant/mass.py`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/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.