feat: ElevenLabs music, stem import, MCP lifespan hardening, and tests#6
feat: ElevenLabs music, stem import, MCP lifespan hardening, and tests#6phjoon1012 wants to merge 4 commits intomainfrom
Conversation
…nflict. Register optional ElevenLabs generate_music and separate_stems tools with intuitive stem track names and import order. Extract separate_stems_import_arrangement for reuse and load .env from the repo root. Replace sys.exit in the MCP lifespan with re-raise so clients surface the lock error. Declare elevenlabs optional extras and refresh the lockfile. Made-with: Cursor
Made-with: Cursor # Conflicts: # MCP_Server/tools/__init__.py
- Add tests/test_elevenlabs.py for stem ordering, display-name helpers, filename sanitization, client bootstrap error paths, separate_stems_import_arrangement, and generate_music / separate_stems (mocked Ableton and API). - Remove .env and MCP_Server/connections/__pycache__/*.pyc from version control; both are covered by .gitignore and should remain developer-local only. Made-with: Cursor
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 51 minutes and 18 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis pull request introduces ElevenLabs integration to the MCP Server, adding music generation and stem separation capabilities. It removes hardcoded API credentials from Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant MCP as MCP Server
participant ElevenLabs as ElevenLabs API
participant Ableton
participant FileSystem as File System
User->>MCP: generate_music(prompt, auto_import=true)
MCP->>ElevenLabs: POST /text-to-music
ElevenLabs-->>MCP: MP3 stream
MCP->>FileSystem: Save sanitized timestamped file
alt auto_import enabled
MCP->>Ableton: Connect to Ableton
MCP->>Ableton: Create audio track
MCP->>Ableton: Import MP3 at position 0.0
Ableton-->>MCP: Track index
end
MCP-->>User: JSON metadata (filepath, track_index)
sequenceDiagram
participant User
participant MCP as MCP Server
participant ElevenLabs as ElevenLabs API
participant FileSystem as File System
participant Ableton
User->>MCP: separate_stems(filepath, auto_import=true)
MCP->>ElevenLabs: POST stem separation request
ElevenLabs-->>MCP: ZIP stream
MCP->>FileSystem: Extract stems (vocals, drums, etc.)
alt auto_import enabled
MCP->>Ableton: Connect to Ableton
loop For each stem
MCP->>Ableton: Create track (display name)
MCP->>Ableton: Import stem at position 0.0
Ableton-->>MCP: Track index
end
end
MCP-->>User: JSON with stem files & track indices
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes The changes introduce substantial new logic spanning API integration, file operations, Ableton automation, and ZIP parsing. The new Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Review Summary by QodoAdd ElevenLabs integration with stem separation and MCP lifespan hardening
WalkthroughsDescription• Add ElevenLabs music generation and stem separation tools with intuitive track naming and import order • Harden MCP server lifespan by re-raising singleton lock errors instead of calling sys.exit • Register elevenlabs module in tool initialization and add comprehensive test coverage • Remove .env and __pycache__ artifacts from version control Diagramflowchart LR
A["User Prompt"] -->|generate_music| B["ElevenLabs API"]
B -->|compose| C["Audio File"]
C -->|separate_stems| D["6 Stems"]
D -->|import_arrangement| E["Ableton Tracks"]
F["Audio File"] -->|separate_stems| D
G["MCP Server"] -->|singleton lock| H["Re-raise Error"]
File Changes1. MCP_Server/tools/elevenlabs.py
|
Code Review by Qodo
1.
|
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (4)
tests/test_elevenlabs.py (3)
122-131: Ruff A002:globals/localsshadow builtins.The shadowing is unavoidable here since the parameter names must mirror
builtins.__import__'s signature. Add a# noqa: A002to acknowledge intent and silence the linter.♻️ Proposed change
- def fail_elevenlabs(name, globals=None, locals=None, fromlist=(), level=0): + def fail_elevenlabs(name, globals=None, locals=None, fromlist=(), level=0): # noqa: A002🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_elevenlabs.py` around lines 122 - 131, The test defines a stub function fail_elevenlabs that intentionally mirrors builtins.__import__ signature (parameters globals and locals) which triggers Ruff A002; silence the linter by appending a noqa comment to that function definition (e.g., add "# noqa: A002" to the def fail_elevenlabs(...) line in tests/test_elevenlabs.py) so the shadowing is explicit while keeping the behavior used to patch __import__ for _get_elevenlabs_client() verification.
133-178:test_raises_when_api_key_missingmutatesos.environwithoutmonkeypatch.Lines 134 and 161 manipulate
os.environmanually with try/finally. If the test (or the patchedel._get_elevenlabs_client) raises before reaching the restore,ELEVENLABS_API_KEYis leaked between tests in this same process. Prefermonkeypatch.delenv("ELEVENLABS_API_KEY", raising=False)for automatic teardown.Same comment for
sys.modulessave/restore at lines 136-140 and 164-168 —monkeypatch.setitem/monkeypatch.delitemwould be safer than the manual try/finally.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_elevenlabs.py` around lines 133 - 178, Replace manual try/finally mutations of os.environ and sys.modules in test_raises_when_api_key_missing and test_builds_client_when_key_present with pytest's monkeypatch utilities: use monkeypatch.delenv("ELEVENLABS_API_KEY", raising=False) to remove/restore the env var around calls to el._get_elevenlabs_client and use monkeypatch.setitem/delitem or monkeypatch.syspath_prepend/monkeypatch.setenv and monkeypatch.setitem on sys.modules (for entries created by _inject_minimal_elevenlabs_package) instead of popping/updating sys.modules directly so teardown is automatic and safe even if the test raises.
47-48: Use FastMCP's publicget_tool()method instead of accessing_toolsdict directly.The code reaches into the private
_toolsdict:mcp._tool_manager._tools[name].fn. FastMCP's ToolManager exposes a public methodget_tool(name: str) -> Tool | Nonethat returns a Tool object with thefnattribute. Prefer this to reduce brittleness:Suggested change
def _tool_fn(mcp, name: str): tool = mcp._tool_manager.get_tool(name) return tool.fn if tool else None🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_elevenlabs.py` around lines 47 - 48, Replace direct private-dict access in _tool_fn with the public ToolManager accessor: call mcp._tool_manager.get_tool(name) and return its .fn if a Tool is returned, otherwise return None; update references to avoid using _tools directly and keep _tool_fn, mcp, _tool_manager, get_tool, and fn as the locating symbols.MCP_Server/tools/elevenlabs.py (1)
62-67: Preserve exception chaining (or suppress withfrom None).Ruff B904: raising inside
except ImportErrorwithoutfromloses the original cause. Either chain the cause or explicitly suppress.♻️ Proposed change
try: from elevenlabs.client import ElevenLabs - except ImportError: + except ImportError as e: raise RuntimeError( "elevenlabs package not installed. Run: uv pip install -e \".[elevenlabs]\"" - ) + ) from e🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@MCP_Server/tools/elevenlabs.py` around lines 62 - 67, In the import block that tries "from elevenlabs.client import ElevenLabs", capture the ImportError as a variable (e.g., "except ImportError as exc:") and re-raise the RuntimeError with exception chaining (e.g., "raise RuntimeError(...) from exc") or explicitly suppress the cause with "from None"; update the try/except around ElevenLabs import accordingly to preserve or suppress the original ImportError.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@MCP_Server/tools/elevenlabs.py`:
- Around line 230-279: The auto_import branch currently trusts
ableton.send_command("create_audio_track") and uses track_result.get("index",
0), which can silently overwrite track 0; validate track_result (from
create_audio_track) for success and an explicit "index" key and raise or return
an error if missing instead of defaulting to 0, and only call
set_track_name/import_audio_to_arrangement when a valid track_index is
confirmed; additionally wrap get_ableton_connection() in a try/except so if it
fails you still return a successful generation response including the saved
filepath and duration_s (so users know the file exists and credits were used),
and include clear error context from the Ableton calls in the JSON error message
(reference variables: track_result, track_idx, name, filepath,
get_ableton_connection, import_audio_to_arrangement).
- Around line 23-25: SAMPLES_DIR is hardcoded to a macOS Ableton path causing
incorrect output locations on Windows or custom installs; update the module to
read an environment override (e.g., ABLETON_SAMPLES_DIR or
ELEVENLABS_OUTPUT_DIR) first and fall back to a platform-aware default based on
os.name/platform (use %USERPROFILE%/Documents/Ableton/User Library/Samples on
Windows and the existing ~/Music/... on macOS/Linux), and ensure the SAMPLES_DIR
constant is computed once (reference SAMPLES_DIR) so callers use the resolved
path.
- Around line 195-198: The prompt validation currently uses "if not prompt:"
which allows whitespace-only strings; update the validation to check the
stripped prompt instead (e.g., assign stripped = prompt.strip() or use prompt :=
prompt.strip()) and raise ValueError when the stripped prompt is empty, while
leaving the music_length_ms range check unchanged; look for the prompt
validation block around the existing "if not prompt:" and "if music_length_ms <
3000 ..." checks in elevenlabs.py to apply this change.
- Around line 287-404: The separate_stems tool duplicates logic from
separate_stems_import_arrangement: the ZIP stream extraction (zip -> save stems
-> collect metadata) and the Ableton import loop are re-implemented instead of
calling the parameterized helper; update separate_stems to call
separate_stems_import_arrangement (or the shared helper) passing the appropriate
stem_variation_id ("six_stems_v1" or "two_stems_v1") and source_name, remove the
duplicated block that reads the zip and builds stem_files, and remove the
duplicated Ableton import loop in favor of the helper’s import path so you only
have the small orchestration and JSON response left; also consolidate the sleep
duration by using the helper’s single sleep value (parameterize if needed) so
timing is consistent across both callers.
- Around line 95-162: The helper separate_stems_import_arrangement currently
hardcodes stem_variation_id="six_stems_v1" and default-falls
track_result.get("index", 0), which prevents reuse and can silently import onto
track 0; change separate_stems_import_arrangement to accept a stem_variation_id
(or stem_mode) parameter and use it in the client.music.separate_stems call
(update all callers to pass the desired variation), and replace the silent
default on track_result.get("index", 0") with explicit validation: check that
"index" exists in the track_result returned by ableton.send_command (in
separate_stems_import_arrangement and any other helpers that call
create_audio_track) and raise or return a clear error if missing so we never
assume index 0. Ensure references: separate_stems_import_arrangement,
client.music.separate_stems, and the create_audio_track handling where
track_result.get("index", 0) appears are updated accordingly.
- Around line 69-85: Update the ElevenLabs SDK requirement to v2.36.1+ in
pyproject.toml (so calls like client.music.compose and
client.music.separate_stems are guaranteed) and modify the dotenv import
handling in tools/elevenlabs.py: instead of silently passing on ImportError for
load_dotenv, log a warning (use an existing logger or create one) stating that
python-dotenv is missing and .env won’t be loaded, then continue; keep the
existing ELEVENLABS_API_KEY check and ElevenLabs/httpx client creation (symbols:
load_dotenv, ELEVENLABS_API_KEY, ElevenLabs, httpx.Client) unchanged otherwise.
In `@tests/test_elevenlabs.py`:
- Around line 254-333: The pytest-asyncio mode is strict by default and will
fail the async tests (e.g., tests
TestGenerateMusicTool.test_auto_import_false_skips_ableton,
test_separate_stems_imports_in_order, test_validation_empty_prompt); fix by
adding asyncio_mode = "auto" to pytest config: add a [tool.pytest.ini_options]
section with asyncio_mode = "auto" to your pyproject.toml, or alternatively
annotate each async test with `@pytest.mark.asyncio`(mode="auto") so the decorated
coroutines execute correctly.
---
Nitpick comments:
In `@MCP_Server/tools/elevenlabs.py`:
- Around line 62-67: In the import block that tries "from elevenlabs.client
import ElevenLabs", capture the ImportError as a variable (e.g., "except
ImportError as exc:") and re-raise the RuntimeError with exception chaining
(e.g., "raise RuntimeError(...) from exc") or explicitly suppress the cause with
"from None"; update the try/except around ElevenLabs import accordingly to
preserve or suppress the original ImportError.
In `@tests/test_elevenlabs.py`:
- Around line 122-131: The test defines a stub function fail_elevenlabs that
intentionally mirrors builtins.__import__ signature (parameters globals and
locals) which triggers Ruff A002; silence the linter by appending a noqa comment
to that function definition (e.g., add "# noqa: A002" to the def
fail_elevenlabs(...) line in tests/test_elevenlabs.py) so the shadowing is
explicit while keeping the behavior used to patch __import__ for
_get_elevenlabs_client() verification.
- Around line 133-178: Replace manual try/finally mutations of os.environ and
sys.modules in test_raises_when_api_key_missing and
test_builds_client_when_key_present with pytest's monkeypatch utilities: use
monkeypatch.delenv("ELEVENLABS_API_KEY", raising=False) to remove/restore the
env var around calls to el._get_elevenlabs_client and use
monkeypatch.setitem/delitem or monkeypatch.syspath_prepend/monkeypatch.setenv
and monkeypatch.setitem on sys.modules (for entries created by
_inject_minimal_elevenlabs_package) instead of popping/updating sys.modules
directly so teardown is automatic and safe even if the test raises.
- Around line 47-48: Replace direct private-dict access in _tool_fn with the
public ToolManager accessor: call mcp._tool_manager.get_tool(name) and return
its .fn if a Tool is returned, otherwise return None; update references to avoid
using _tools directly and keep _tool_fn, mcp, _tool_manager, get_tool, and fn as
the locating symbols.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bf0b8dcf-a58a-467e-b256-2cc6d8a3df6f
⛔ Files ignored due to path filters (3)
MCP_Server/connections/__pycache__/__init__.cpython-314.pycis excluded by!**/*.pycMCP_Server/connections/__pycache__/ableton.cpython-314.pycis excluded by!**/*.pycMCP_Server/connections/__pycache__/m4l.cpython-314.pycis excluded by!**/*.pyc
📒 Files selected for processing (6)
.envMCP_Server/server.pyMCP_Server/tools/__init__.pyMCP_Server/tools/elevenlabs.pypyproject.tomltests/test_elevenlabs.py
💤 Files with no reviewable changes (1)
- .env
| try: | ||
| from dotenv import load_dotenv | ||
| _repo_root = pathlib.Path(__file__).resolve().parents[2] | ||
| load_dotenv(_repo_root / ".env") | ||
| except ImportError: | ||
| pass | ||
|
|
||
| api_key = os.getenv("ELEVENLABS_API_KEY") | ||
| if not api_key: | ||
| raise RuntimeError( | ||
| "ELEVENLABS_API_KEY not set. Add it to your .env file." | ||
| ) | ||
|
|
||
| import httpx | ||
| http_client = httpx.Client(timeout=httpx.Timeout(120.0, connect=10.0)) | ||
| _el_client = ElevenLabs(api_key=api_key, httpx_client=http_client) | ||
| return _el_client |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What version of the elevenlabs Python SDK introduced client.music.compose and client.music.separate_stems methods?
💡 Result:
The client.music.compose and client.music.separate_stems methods were introduced in the ElevenLabs Python SDK during the initial rollout of music API support, around version v2.36.0 or earlier in February 2026. Music features, including compose, were present by v2.36.1 (released 2026-02-19), with subsequent releases like v2.37.0, v2.38.0, and v2.38.1 adding parameters and fixes. The separate_stems method appears in the current codebase (commit 8303d37d, part of recent releases up to v2.43.0), likely added in a Fern regeneration commit such as #740 (2026-03-05, around v2.38.0). Exact introduction versions are not explicitly stated in release notes, but both methods are documented in official API references and SDK code by early March 2026.
Citations:
- 1: https://github.com/elevenlabs/elevenlabs-python/blob/8303d37d/src/elevenlabs/music/client.py
- 2: https://elevenlabs.io/docs/api-reference/music/compose
- 3: https://github.com/elevenlabs/elevenlabs-python
- 4: https://elevenlabs.io/docs/api-reference/music/separate-stems?explorer=true
- 5: https://github.com/elevenlabs/elevenlabs-python/releases/tag/v2.38.1
- 6: https://github.com/elevenlabs/elevenlabs-python/releases/tag/v2.36.1
- 7: https://github.com/elevenlabs/elevenlabs-python/releases/tag/v2.37.0
- 8: Music client is overwritten by music_custom.py excluding some arguments elevenlabs/elevenlabs-python#732
- 9: elevenlabs/elevenlabs-python@9590446
- 10: elevenlabs/elevenlabs-python@v2.37.0...v2.38.0
- 11: https://elevenlabs.io/docs/api-reference/music/separate-stems
🏁 Script executed:
find . -name "pyproject.toml" -type f | head -5Repository: hidingwill/AbletonBridge
Length of output: 85
🏁 Script executed:
cat MCP_Server/tools/elevenlabs.py | head -100Repository: hidingwill/AbletonBridge
Length of output: 2950
🏁 Script executed:
cat pyproject.toml | grep -A 5 -B 5 "elevenlabs"Repository: hidingwill/AbletonBridge
Length of output: 676
🏁 Script executed:
wc -l MCP_Server/tools/elevenlabs.pyRepository: hidingwill/AbletonBridge
Length of output: 103
🏁 Script executed:
sed -n '100,110p' MCP_Server/tools/elevenlabs.pyRepository: hidingwill/AbletonBridge
Length of output: 400
🏁 Script executed:
sed -n '200,210p' MCP_Server/tools/elevenlabs.pyRepository: hidingwill/AbletonBridge
Length of output: 478
Update elevenlabs version constraint in pyproject.toml to require v2.36.1 or later.
The current constraint elevenlabs>=0.2.26 specifies the legacy SDK series. The code uses client.music.compose() (line 207) and client.music.separate_stems() (lines 104/326), which are only available in the v2.x series (introduced in v2.36.1, February 2026). If a user installs a 0.2.x release, these calls will fail with AttributeError at runtime instead of a clear error message.
Additionally, the dotenv import failure at lines 73–74 is silently swallowed. If python-dotenv isn't installed but the user expected .env loading, they'll receive the misleading "ELEVENLABS_API_KEY not set" error instead of a clear import failure. Add at least a logger.warning() to surface the diagnostic.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@MCP_Server/tools/elevenlabs.py` around lines 69 - 85, Update the ElevenLabs
SDK requirement to v2.36.1+ in pyproject.toml (so calls like
client.music.compose and client.music.separate_stems are guaranteed) and modify
the dotenv import handling in tools/elevenlabs.py: instead of silently passing
on ImportError for load_dotenv, log a warning (use an existing logger or create
one) stating that python-dotenv is missing and .env won’t be loaded, then
continue; keep the existing ELEVENLABS_API_KEY check and ElevenLabs/httpx client
creation (symbols: load_dotenv, ELEVENLABS_API_KEY, ElevenLabs, httpx.Client)
unchanged otherwise.
… samples dir override
Summary
Adds optional ElevenLabs integration to the AbletonBridge MCP server: AI music generation from a text prompt, six-stem separation, and arrangement import into Live. Hardens MCP startup when a second server instance hits the singleton lock. Adds tests and stops tracking
.env/__pycache__artifacts.Changes
ElevenLabs (
MCP_Server/tools/elevenlabs.py)generate_music— compose from prompt; optional stems + import.separate_stems— stem-separate a file; optional import.separate_stems_import_arrangement— shared zip → Samples → tracks → clips path.ELEVENLABS_API_KEYfrom repo-root.envvia explicit path.Registration
elevenlabsinMCP_Server/tools/__init__.pywith upstreamscenes(post-merge).MCP server (
MCP_Server/server.py)sys.exit(1)for clearer MCP client errors.Dependencies
pyproject.toml/uv.lock— ElevenLabs optional extra as needed.Tests (
tests/test_elevenlabs.py)Hygiene
.envandMCP_Server/connections/__pycache__/*.pycfrom version control (.gitignoreapplies).Test plan
Notes
.envwas ever in git history, rotate any exposed keys.Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores