Skip to content

feat: ElevenLabs music, stem import, MCP lifespan hardening, and tests#6

Open
phjoon1012 wants to merge 4 commits intomainfrom
feature/elevenlabs-integration
Open

feat: ElevenLabs music, stem import, MCP lifespan hardening, and tests#6
phjoon1012 wants to merge 4 commits intomainfrom
feature/elevenlabs-integration

Conversation

@phjoon1012
Copy link
Copy Markdown
Collaborator

@phjoon1012 phjoon1012 commented Apr 24, 2026

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.
  • Stem tracks use short names (Vocals, Drums, …) and consistent import order.
  • Loads ELEVENLABS_API_KEY from repo-root .env via explicit path.

Registration

  • Registers elevenlabs in MCP_Server/tools/__init__.py with upstream scenes (post-merge).

MCP server (MCP_Server/server.py)

  • Singleton lock failure re-raises instead of sys.exit(1) for clearer MCP client errors.

Dependencies

  • pyproject.toml / uv.lock — ElevenLabs optional extra as needed.

Tests (tests/test_elevenlabs.py)

  • Stem helpers, client bootstrap edge cases, mocked stem import and tools.

Hygiene

  • Remove .env and MCP_Server/connections/__pycache__/*.pyc from version control (.gitignore applies).

Test plan

uv run --extra dev python -m pytest tests/test_elevenlabs.py -v
uv run --extra dev python -m pytest tests/ -q

Notes

  • ElevenLabs API calls use account credits.
  • Only one AbletonBridge MCP server should run at a time (singleton lock).
  • If .env was ever in git history, rotate any exposed keys.

Summary by CodeRabbit

  • New Features

    • Added ElevenLabs music generation tool with automatic Ableton import support
    • Added stem separation tool for audio processing with optional Ableton track creation
  • Bug Fixes

    • Improved server startup error handling with proper error propagation
  • Tests

    • Added comprehensive test suite for ElevenLabs integration
  • Chores

    • Removed unused configuration variables

…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
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

Warning

Rate limit exceeded

@phjoon1012 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 18 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d94a34a-d70d-43a0-b99f-a5a83cf5df78

📥 Commits

Reviewing files that changed from the base of the PR and between c2c3550 and b15ec2f.

📒 Files selected for processing (3)
  • MCP_Server/tools/elevenlabs.py
  • pyproject.toml
  • tests/test_elevenlabs.py
📝 Walkthrough

Walkthrough

This pull request introduces ElevenLabs integration to the MCP Server, adding music generation and stem separation capabilities. It removes hardcoded API credentials from .env, registers new ElevenLabs tools, adjusts server startup error handling, and includes comprehensive test coverage with mocked dependencies.

Changes

Cohort / File(s) Summary
Configuration & Startup
.env, MCP_Server/server.py
Removes ELEVENLABS_API_KEY and ELEVENLABS_OUTPUT_DIR defaults from .env. Changes server startup singleton failure from sys.exit(1) to re-raising RuntimeError, allowing exception propagation through lifecycle handling.
ElevenLabs Integration
MCP_Server/tools/__init__.py, MCP_Server/tools/elevenlabs.py
Registers ElevenLabs tools module during server initialization. New module implements generate_music and separate_stems MCP tools with ElevenLabs client initialization, Ableton auto-import functionality, stem ZIP parsing, and file operations with sanitized timestamped filenames.
Build Configuration
pyproject.toml
Formatting and serialization normalization; no functional changes to dependencies, entry points, or metadata.
Test Coverage
tests/test_elevenlabs.py
New test suite validating stem sorting, filename sanitization, client bootstrap error handling, Ableton track import workflows, ZIP parsing, and MCP tool behavior under various flags with mocked Ableton and HTTP dependencies.

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)
Loading
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
Loading

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 elevenlabs.py module (404 lines) contains multi-step workflows with client initialization, error handling, and conditional Ableton track creation. Supporting test suite (389 lines) is comprehensive but requires verification of mocking and expected behaviors. Server startup error handling change warrants careful consideration of exception propagation implications.

Poem

🎵 A bunny hops through Eleven's domain,
Spinning stems and melodies from API rain,
Ableton tracks now dance in digital delight,
With auto-import magic, the harmonies take flight!
Sanitized filenames, in sorted array neat—
The MCP server's latest treat! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes all four major changes: ElevenLabs music/stem import feature, MCP lifespan hardening, and comprehensive test coverage.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/elevenlabs-integration

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add ElevenLabs integration with stem separation and MCP lifespan hardening

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• 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
Diagram
flowchart 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"]
Loading

Grey Divider

File Changes

1. MCP_Server/tools/elevenlabs.py ✨ Enhancement +404/-0

ElevenLabs music generation and stem separation tools

• Implements generate_music tool to compose audio from text prompts via ElevenLabs API with
 optional stem separation and Ableton import
• Implements separate_stems tool to decompose audio files into 6 or 2 stems with configurable
 import behavior
• Provides separate_stems_import_arrangement helper function for shared stem extraction, ordering,
 and track creation workflow
• Includes stem sorting by mixer order (drums → bass → guitar → piano → other → vocals →
 instrumental) and human-readable track display names
• Handles ElevenLabs client initialization with API key loading from repo-root .env file and
 graceful error handling for missing dependencies

MCP_Server/tools/elevenlabs.py


2. MCP_Server/tools/__init__.py ✨ Enhancement +2/-1

Register elevenlabs tools in MCP server

• Import elevenlabs module alongside existing tool modules
• Register elevenlabs tools via elevenlabs.register_tools(mcp) call in register_all_tools
 function

MCP_Server/tools/init.py


3. MCP_Server/server.py 🐞 Bug fix +1/-2

Harden MCP lifespan singleton lock error handling

• Remove unused sys import
• Replace sys.exit(1) with raise in singleton lock failure handler to propagate error to MCP
 client instead of terminating process

MCP_Server/server.py


View more (3)
4. tests/test_elevenlabs.py 🧪 Tests +389/-0

Comprehensive test coverage for ElevenLabs tools

• Test stem sorting order and unknown stem handling via _stem_sort_key function
• Test stem display name generation for known and unknown stem types via _stem_track_display_name
• Test filename sanitization and timestamp generation via _make_filename
• Test ElevenLabs client bootstrap with missing package, missing API key, and successful
 initialization scenarios
• Test separate_stems_import_arrangement function for track ordering, display names, and error
 handling when no audio stems found
• Test generate_music tool with auto_import disabled, stem separation with proper ordering, and
 validation of empty prompts
• Test separate_stems tool with missing/invalid file paths, auto_import disabled, and stem
 extraction workflows

tests/test_elevenlabs.py


5. .env ⚙️ Configuration changes +0/-2

Remove tracked environment variables

• Remove ELEVENLABS_API_KEY and ELEVENLABS_OUTPUT_DIR environment variables from version control
• File is now covered by .gitignore and should remain developer-local only

.env


6. pyproject.toml Dependencies +49/-49

Add elevenlabs optional dependencies and dev tools

• Add elevenlabs optional dependency group with elevenlabs>=0.2.26, python-dotenv>=1.0.0,
 httpx>=0.24.0, pydantic>=2.0, and rapidfuzz
• Add dev optional dependency group with pytest>=8.0 and pytest-asyncio>=0.24
• Normalize line endings from CRLF to LF (formatting only)

pyproject.toml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Apr 24, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Filename collision overwrites audio🐞 Bug ☼ Reliability
Description
_make_filename() uses only second-resolution timestamps, so multiple calls within the same second
with the same name can generate the same path and overwrite existing exports. This causes silent
data loss of generated mixes/stems.
Code

MCP_Server/tools/elevenlabs.py[R88-92]

+def _make_filename(name: str, ext: str = "mp3") -> str:
+    sanitized = re.sub(r'[<>:"/\\|?*]', '', name).strip()
+    sanitized = re.sub(r'\s+', '_', sanitized)[:60] or "untitled"
+    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
+    return f"{sanitized}_{ts}.{ext}"
Evidence
The filename uniqueness relies on %Y%m%d_%H%M%S (seconds). generate_music() writes with
open(filepath, "wb"), which overwrites an existing file if a collision occurs (same
prompt/track_name within the same second).

MCP_Server/tools/elevenlabs.py[88-92]
MCP_Server/tools/elevenlabs.py[203-217]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Generated/stem output filenames can collide (second-resolution timestamps), and current writes use `"wb"` which overwrites existing files.
### Issue Context
This can happen in interactive usage (rapid repeated calls) and results in silent loss of previous exports.
### Fix Focus Areas
- MCP_Server/tools/elevenlabs.py[88-92]
- MCP_Server/tools/elevenlabs.py[203-217]
- MCP_Server/tools/elevenlabs.py[118-124]
- MCP_Server/tools/elevenlabs.py[340-346]
### Suggested change
Implement one of:
1) Add higher entropy to filenames (microseconds and/or random suffix):
 - use `%f` (microseconds) or append a short `uuid4().hex[:8]`
2) Avoid overwrites by using exclusive create (`open(..., "xb")`) and retry on `FileExistsError`.
Prefer (2) + (1) for robustness.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Stem mode silently downgrades 🐞 Bug ≡ Correctness
Description
separate_stems() treats any stem_mode other than the exact string "six" as "two", so values like
"Six"/"SIX"/typos will unexpectedly run 2-stem separation. This can produce the wrong outputs and
incur unintended ElevenLabs credit usage.
Code

MCP_Server/tools/elevenlabs.py[R319-320]

+        variation = "six_stems_v1" if stem_mode == "six" else "two_stems_v1"
+        source_name = group_name or filepath.stem
Evidence
The tool docstring claims only "six" or "two" are valid, but the implementation uses a binary
conditional that defaults to two-stem for all other inputs, with no validation or case
normalization.

MCP_Server/tools/elevenlabs.py[296-321]
MCP_Server/tools/elevenlabs.py[319-320]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`separate_stems()` accepts a free-form `stem_mode` string but maps anything other than literal `'six'` to the two-stem variation, which silently changes behavior.
### Issue Context
The docstring documents only `"six"` and `"two"`. Typos/case differences should surface as a user-facing validation error (caught by `_tool_handler` and returned as JSON).
### Fix Focus Areas
- MCP_Server/tools/elevenlabs.py[289-322]
### Suggested change
- Normalize: `mode = stem_mode.strip().lower()`
- Validate: `if mode not in ("six", "two"): raise ValueError(...)`
- Use `mode` for selecting `variation`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. ElevenLabs blocks all tools 🐞 Bug ➹ Performance
Description
generate_music() and separate_stems() run under the global _ableton_semaphore, so long ElevenLabs
HTTP operations can block unrelated AbletonBridge tools for up to the tool timeout. This makes the
server appear unresponsive while music generation/stem separation is running.
Code

MCP_Server/tools/elevenlabs.py[R168-177]

+    @mcp.tool()
+    @_tool_handler("generating music with ElevenLabs")
+    def generate_music(
+        ctx: Context,
+        prompt: str,
+        music_length_ms: int = 30000,
+        force_instrumental: bool = False,
+        auto_import: bool = True,
+        separate_stems: bool = True,
+        track_name: str = "",
Evidence
_tool_handler acquires a single global semaphore intended to serialize access to the shared
Ableton TCP connection. The ElevenLabs tools are decorated with _tool_handler, so the semaphore is
held across the entire tool execution, including long-running ElevenLabs network and file I/O before
any Ableton interaction.

MCP_Server/tools/_base.py[9-13]
MCP_Server/tools/_base.py[19-44]
MCP_Server/tools/elevenlabs.py[168-177]
MCP_Server/tools/elevenlabs.py[200-240]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The global tool semaphore is held for the entire ElevenLabs tool call, including long-running HTTP/file work, blocking all other tools.
### Issue Context
`_tool_handler` is designed to serialize access to the Ableton TCP socket, but ElevenLabs calls don’t need that serialization until the import-to-Ableton phase.
### Fix Focus Areas
- MCP_Server/tools/_base.py[9-13]
- MCP_Server/tools/_base.py[19-44]
- MCP_Server/tools/elevenlabs.py[168-177]
- MCP_Server/tools/elevenlabs.py[200-280]
- MCP_Server/tools/elevenlabs.py[289-399]
### Suggested change
Refactor to only acquire the Ableton semaphore around Ableton commands:
- Option A (preferred): enhance `_tool_handler` to accept a flag (e.g., `requires_ableton=True`) and/or to provide a helper context manager `with_ableton_lock()`; in ElevenLabs tools, do HTTP work outside the lock, then acquire lock only for `create_audio_track` / `set_track_name` / `import_audio_to_arrangement`.
- Option B: create a second decorator for non-Ableton tools that skips `_ableton_semaphore` but keeps consistent error JSON, and manually lock only the import section.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

4. httpx client not closed 🐞 Bug ☼ Reliability
Description
_get_elevenlabs_client() creates and retains an httpx.Client in a global singleton, but server
shutdown never closes it. This can leave open connections/file descriptors in reload/test scenarios
and is generally poor resource hygiene.
Code

MCP_Server/tools/elevenlabs.py[R82-85]

+    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
Evidence
The ElevenLabs client is cached globally and constructed with a custom httpx.Client, but the
server lifespan shutdown sequence closes only Ableton/M4L connections and the singleton lock socket;
there is no corresponding close for the HTTP client.

MCP_Server/tools/elevenlabs.py[57-85]
MCP_Server/server.py[240-256]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A long-lived `httpx.Client` is created for ElevenLabs and never explicitly closed.
### Issue Context
While process exit will typically clean up, explicit close is important for clean shutdown and especially for dev reloads/tests.
### Fix Focus Areas
- MCP_Server/tools/elevenlabs.py[57-85]
- MCP_Server/server.py[240-256]
### Suggested change
- Store the created `httpx.Client` in a module-level variable (e.g., `_el_http_client`).
- Add a small `close_elevenlabs_client()` helper in `elevenlabs.py` that closes the http client and clears `_el_client`.
- Call that helper from `server_lifespan` shutdown (`finally` block) before releasing the singleton lock.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread MCP_Server/tools/elevenlabs.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (4)
tests/test_elevenlabs.py (3)

122-131: Ruff A002: globals/locals shadow builtins.

The shadowing is unavoidable here since the parameter names must mirror builtins.__import__'s signature. Add a # noqa: A002 to 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_missing mutates os.environ without monkeypatch.

Lines 134 and 161 manipulate os.environ manually with try/finally. If the test (or the patched el._get_elevenlabs_client) raises before reaching the restore, ELEVENLABS_API_KEY is leaked between tests in this same process. Prefer monkeypatch.delenv("ELEVENLABS_API_KEY", raising=False) for automatic teardown.

Same comment for sys.modules save/restore at lines 136-140 and 164-168 — monkeypatch.setitem/monkeypatch.delitem would 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 public get_tool() method instead of accessing _tools dict directly.

The code reaches into the private _tools dict: mcp._tool_manager._tools[name].fn. FastMCP's ToolManager exposes a public method get_tool(name: str) -> Tool | None that returns a Tool object with the fn attribute. 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 with from None).

Ruff B904: raising inside except ImportError without from loses 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0b55783 and c2c3550.

⛔ Files ignored due to path filters (3)
  • MCP_Server/connections/__pycache__/__init__.cpython-314.pyc is excluded by !**/*.pyc
  • MCP_Server/connections/__pycache__/ableton.cpython-314.pyc is excluded by !**/*.pyc
  • MCP_Server/connections/__pycache__/m4l.cpython-314.pyc is excluded by !**/*.pyc
📒 Files selected for processing (6)
  • .env
  • MCP_Server/server.py
  • MCP_Server/tools/__init__.py
  • MCP_Server/tools/elevenlabs.py
  • pyproject.toml
  • tests/test_elevenlabs.py
💤 Files with no reviewable changes (1)
  • .env

Comment thread MCP_Server/tools/elevenlabs.py Outdated
Comment on lines +69 to +85
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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:


🏁 Script executed:

find . -name "pyproject.toml" -type f | head -5

Repository: hidingwill/AbletonBridge

Length of output: 85


🏁 Script executed:

cat MCP_Server/tools/elevenlabs.py | head -100

Repository: 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.py

Repository: hidingwill/AbletonBridge

Length of output: 103


🏁 Script executed:

sed -n '100,110p' MCP_Server/tools/elevenlabs.py

Repository: hidingwill/AbletonBridge

Length of output: 400


🏁 Script executed:

sed -n '200,210p' MCP_Server/tools/elevenlabs.py

Repository: 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.

Comment thread MCP_Server/tools/elevenlabs.py Outdated
Comment thread MCP_Server/tools/elevenlabs.py Outdated
Comment thread MCP_Server/tools/elevenlabs.py
Comment thread MCP_Server/tools/elevenlabs.py
Comment thread tests/test_elevenlabs.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant