Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions AbletonMCP_Remote_Script/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ def _process_command(self, command):
elif command_type == "get_track_info":
track_index = params.get("track_index", 0)
response["result"] = self._get_track_info(track_index)
elif command_type == "get_rack_device_info":
track_index = params.get("track_index", 0)
device_index = params.get("device_index", 0)
response["result"] = self._get_rack_device_info(track_index, device_index)
# Commands that modify Live's state should be scheduled on the main thread
elif command_type in ["create_midi_track", "set_track_name",
"create_clip", "add_notes_to_clip", "set_clip_name",
Expand Down Expand Up @@ -389,12 +393,19 @@ def _get_track_info(self, track_index):
# Get devices
devices = []
for device_index, device in enumerate(track.devices):
devices.append({
device_info = {
"index": device_index,
"name": device.name,
"class_name": device.class_name,
"type": self._get_device_type(device)
})
}
if device.can_have_chains:
device_info["chains"] = [{
"index": i,
"name": chain.name,
"device_count": len(chain.devices)
} for i, chain in enumerate(device.chains)]
devices.append(device_info)

result = {
"index": track_index,
Expand All @@ -414,6 +425,49 @@ def _get_track_info(self, track_index):
self.log_message("Error getting track info: " + str(e))
raise

def _serialize_device(self, device):
"""Serialize a device, recursively including chains for rack devices"""
device_info = {
"name": device.name,
"class_name": device.class_name,
"type": self._get_device_type(device)
}
if device.can_have_chains:
chains = []
for chain_index, chain in enumerate(device.chains):
chain_devices = [self._serialize_device(dev) for dev in chain.devices]
chains.append({
"index": chain_index,
"name": chain.name,
"devices": chain_devices
})
device_info["chains"] = chains
return device_info
Comment on lines +428 to +445
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Unbounded rack serialization 🐞 Bug ⛯ Reliability

New rack serialization recursively walks all nested racks/chains with no depth/size limits, so
deep/large racks can cause very slow responses, huge payloads, or a Python RecursionError. Large
payloads also increase the chance the Remote Script’s blocking sendall (no timeout) will stall a
client handler thread if the client is slow/disconnected.
Agent Prompt
### Issue description
`get_rack_device_info` recursively serializes nested racks without any depth/size limits. Deep or large racks can produce huge responses, hit Python recursion limits, and/or cause long blocking socket sends.

### Issue Context
This endpoint is designed to “drill in” and may be called on complex Ableton projects where nested racks/chains can be large.

### Fix Focus Areas
- AbletonMCP_Remote_Script/__init__.py[428-469]
- AbletonMCP_Remote_Script/__init__.py[133-177]

### Suggested changes
- Update `_serialize_device(device, depth=0, max_depth=..., budget=...)`:
  - Stop recursing past `max_depth` and/or when a `max_total_devices` budget is exceeded.
  - Include a field like `"truncated": true` or `"truncation_reason": "max_depth"` when limits are reached.
  - Optionally include `index` values for devices within each chain to make truncated results still navigable.
- Update `_get_rack_device_info` to accept optional params (e.g., `max_depth`, `max_total_devices`) from `params` and pass them through.
- Consider making the client handler safer for large responses:
  - Set a reasonable socket timeout for sends, or
  - Send in chunks and abort cleanly if the client disconnects / blocks too long.

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


def _get_rack_device_info(self, track_index, device_index):
"""Get detailed information about a rack device's chains and nested devices"""
try:
if track_index < 0 or track_index >= len(self._song.tracks):
raise IndexError("Track index out of range")

track = self._song.tracks[track_index]

if device_index < 0 or device_index >= len(track.devices):
raise IndexError("Device index out of range")

Comment on lines +450 to +457
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. indexerror breaks client loop 📘 Rule violation ⛯ Reliability

The new rack introspection command raises IndexError for invalid indices, and the client handler
treats non-ValueError exceptions as serious and breaks the connection. This makes simple bad input
an availability/reliability issue instead of a handled edge case.
Agent Prompt
## Issue description
Out-of-range indices currently raise `IndexError`, which leads the client handler to break the loop and disconnect.

## Issue Context
The client handler only treats `ValueError` as non-fatal; other exceptions trigger a disconnect.

## Fix Focus Areas
- AbletonMCP_Remote_Script/__init__.py[450-457]
- AbletonMCP_Remote_Script/__init__.py[198-200]

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

device = track.devices[device_index]

if not device.can_have_chains:
raise ValueError("Device '{}' is not a rack and does not have chains".format(device.name))

Comment on lines +460 to +462
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Exception exposes device.name 📘 Rule violation ⛨ Security

The new rack endpoint includes device.name in an exception message, and error handling sends
str(e) back to the client. This can leak internal session/device details to the caller.
Agent Prompt
## Issue description
The error message includes `device.name` and is propagated to clients via `str(e)`.

## Issue Context
User-facing error responses should be generic and not reveal internal session/device details.

## Fix Focus Areas
- AbletonMCP_Remote_Script/__init__.py[460-462]
- AbletonMCP_Remote_Script/__init__.py[184-187]

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

result = self._serialize_device(device)
result["track_index"] = track_index
result["device_index"] = device_index
return result
except Exception as e:
self.log_message("Error getting rack device info: " + str(e))
raise

def _create_midi_track(self, index):
"""Create a new MIDI track at the specified index"""
try:
Expand Down
17 changes: 17 additions & 0 deletions MCP_Server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,23 @@ def get_track_info(ctx: Context, track_index: int) -> str:
logger.error(f"Error getting track info from Ableton: {str(e)}")
return f"Error getting track info: {str(e)}"

@mcp.tool()
def get_rack_device_info(ctx: Context, track_index: int, device_index: int) -> str:
"""
Get detailed information about a rack device's chains and nested devices.

Parameters:
- track_index: The index of the track containing the rack device
- device_index: The index of the device on the track (must be a rack)
"""
try:
ableton = get_ableton_connection()
result = ableton.send_command("get_rack_device_info", {"track_index": track_index, "device_index": device_index})
return json.dumps(result, indent=2)
except Exception as e:
logger.error(f"Error getting rack device info from Ableton: {str(e)}")
return f"Error getting rack device info: {str(e)}"

@mcp.tool()
def create_midi_track(ctx: Context, index: int = -1) -> str:
"""
Expand Down