Skip to content

Feat/update connection protocol #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

12Matt3r
Copy link

@12Matt3r 12Matt3r commented Aug 16, 2025

PR Type

Enhancement


Description

  • Implement newline-delimited JSON protocol for robust communication

  • Add get_device_parameters tool for device parameter inspection

  • Enhance error handling and message buffering

  • Fix connection reliability issues


Diagram Walkthrough

flowchart LR
  A["MCP Server"] -- "JSON + newline" --> B["Socket Connection"]
  B -- "Buffered Messages" --> C["Remote Script"]
  C -- "JSON Response + newline" --> B
  B -- "Complete Response" --> A
  D["get_device_parameters"] --> E["Device Parameter Data"]
Loading

File Walkthrough

Relevant files
Enhancement
__init__.py
Implement robust communication protocol and device parameters

AbletonMCP_Remote_Script/init.py

  • Replace fragile JSON parsing with newline-delimited protocol
  • Add comprehensive error handling and message buffering
  • Implement _get_device_parameters method for device inspection
  • Append newlines to all outgoing responses
+95/-49 
server.py
Add device parameters tool and protocol updates                   

MCP_Server/server.py

  • Simplify response handling with newline-delimited JSON
  • Add get_device_parameters MCP tool function
  • Append newlines to outgoing commands
  • Remove complex chunked response logic
+40/-43 

Summary by CodeRabbit

  • New Features
    • Added command/endpoint to retrieve device parameters (name, value, min, max) for a specific track/device.
  • Improvements
    • Switched to newline-delimited JSON protocol, enabling multiple commands per connection and reliable message framing.
    • Consistent JSON responses per command.
  • Bug Fixes
    • More robust error handling with clear error messages; graceful handling of socket errors and timeouts.
  • Documentation
    • Updated docstrings to clarify protocol usage and expanded parameter descriptions for track/device queries.

… and the Ableton Remote Script to make it more robust.

The previous implementation was fragile and could fail if multiple JSON messages were received in a single TCP packet, leading to parsing errors and connection hangs.

This has been addressed by implementing a newline-delimited JSON protocol:

- The `AbletonMCP_Remote_Script` now buffers incoming data and processes one command per line, separated by a newline character (`\n`). It also appends a newline to all its responses.
- The `MCP_Server` is updated to append a newline to all outgoing commands and reads incoming responses until a newline is received.

This ensures that message boundaries are clearly defined, making the connection significantly more reliable.
…s the socket communication protocol.

New Feature:
A `get_device_parameters` tool has been added, allowing the AI to read the parameters (name, value, min, max) of any device on any track in the Ableton Live session. This is the foundational step for enabling more advanced device analysis and manipulation.

Protocol Enhancement:
The underlying socket communication protocol has been updated to use newline-delimited JSON. Both the server and the remote script now append a newline character to every message, ensuring clear and reliable message boundaries. This replaces a more fragile implementation and makes the entire connection more robust and less prone to hanging.
Copy link

coderabbitai bot commented Aug 16, 2025

Walkthrough

Switched client/server protocol to newline-delimited JSON over a persistent socket, updated per-command buffering and error handling, added a device-parameters retrieval command and helper, adjusted server send/receive framing and timeouts, and expanded the public API with get_device_parameters. Minor docstrings updated; receive_full_response default buffer size changed.

Changes

Cohort / File(s) Summary of changes
Protocol: newline-delimited JSON framing
AbletonMCP_Remote_Script/__init__.py, MCP_Server/server.py
Adopt newline-delimited JSON for both send and receive; accumulate buffers, process multiple commands per connection; adjust timeouts and buffer_size (4096).
Per-command error handling
AbletonMCP_Remote_Script/__init__.py, MCP_Server/server.py
Add granular JSON decode/processing error responses; simplify socket timeout handling; exit loop on socket errors.
Device parameters feature
AbletonMCP_Remote_Script/__init__.py, MCP_Server/server.py
Add _get_device_parameters helper and new get_device_parameters command/endpoint; return structured device and parameter data.
Docs and signatures
MCP_Server/server.py
Update docstrings (e.g., get_track_info); change receive_full_response default buffer_size to 4096; duplicate definition of get_device_parameters noted.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant MCP_Server as MCP Server
  participant Ableton as AbletonMCP Script

  Client->>MCP_Server: send_command(JSON + "\n")
  MCP_Server->>Ableton: TCP send(JSON + "\n")
  Ableton-->>Ableton: Buffer, split by newline, parse JSON
  Ableton->>Ableton: _process_command / get_device_parameters
  Ableton-->>MCP_Server: JSON response + "\n"
  MCP_Server-->>Client: receive_full_response (line-based)
Loading
sequenceDiagram
  participant Client
  participant MCP_Server as MCP Server
  participant Ableton as AbletonMCP Script

  Client->>MCP_Server: get_device_parameters(track_idx, device_idx)
  MCP_Server->>Ableton: {"type":"get_device_parameters", ...}\n
  Ableton->>Ableton: _get_device_parameters(track_idx, device_idx)
  Ableton-->>MCP_Server: {status, data:{device_name, parameters[]}}\n
  MCP_Server-->>Client: JSON string result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I tap my paws to the newline beat,
Packets hop neatly, complete and sweet.
Devices whisper their knobs and range,
I nibble bytes in a tidy exchange.
From track to script, the signals flow—
A rabbit approves: ready, set, go! 🐇💡

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Robustness

The newline-delimited framing assumes responses never contain embedded newlines. Validate that server never emits internal newlines within JSON and that client handling of multiple messages in one recv or partial lines is correct, especially with UTF-8 multibyte boundaries.

    response_str = json.dumps(response) + '\n'

    try:
        # Python 3: encode string to bytes
        client.sendall(response_str.encode('utf-8'))
    except AttributeError:
        # Python 2: string is already bytes
        client.sendall(response_str)

except ValueError as e:
Attribute Access

_get_device_parameters accesses param.min and param.max without guarding existence; some parameters or Max devices may not expose these attributes. Consider using getattr with defaults to avoid exceptions that would abort the response.

parameters.append({
    "name": param.name,
    "value": param.value,
    "min": param.min,
    "max": param.max
})
Buffer Handling

receive_full_response discards any bytes after the first newline; if server ever batches multiple responses or includes additional data, the extra bytes are dropped. Consider preserving remainder for subsequent reads or a per-connection buffer.

if b'\n' in buffer:
    response_data, _, _ = buffer.partition(b'\n')
    logger.info(f"Received complete response ({len(response_data)} bytes)")
    return response_data

Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Preserve backward compatibility

Switching to newline-delimited JSON on both client and server risks breaking
existing clients or remote scripts that expect raw JSON without delimiters or
chunked parsing. If any integrations rely on the previous protocol, provide a
compatibility layer or opt-in flag to support both modes during a transition,
and document the protocol change clearly with versioning.

Examples:

AbletonMCP_Remote_Script/__init__.py [155-174]
# Process all complete commands in the buffer
while '\n' in buffer:
    command_str, buffer = buffer.split('\n', 1)
    if not command_str:
        continue

    try:
        command = json.loads(command_str)
        self.log_message("Received command: " + str(command.get("type", "unknown")))


 ... (clipped 11 lines)
MCP_Server/server.py [46-59]
def receive_full_response(self, sock, buffer_size=4096):
    """Receive a newline-delimited JSON response from the socket."""
    buffer = b''
    sock.settimeout(15.0)
    try:
        while True:
            chunk = sock.recv(buffer_size)
            if not chunk:
                raise ConnectionError("Connection closed while waiting for response.")
            buffer += chunk

 ... (clipped 4 lines)

Solution Walkthrough:

Before:

# In MCP_Server/server.py
def receive_full_response(sock):
    buffer = b''
    while True:
        chunk = sock.recv(4096)
        buffer += chunk
        if b'\n' in buffer:
            response_data, _, _ = buffer.partition(b'\n')
            return response_data

After:

# In MCP_Server/server.py
def receive_full_response(sock, use_newline_protocol=True):
    if use_newline_protocol:
        # Newline-delimited logic
        buffer = b''
        while True:
            chunk = sock.recv(4096)
            buffer += chunk
            if b'\n' in buffer:
                response_data, _, _ = buffer.partition(b'\n')
                return response_data
    else:
        # Old raw JSON logic (simplified)
        return sock.recv(8192)
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical breaking change in the communication protocol and proposes a valid architectural solution to maintain backward compatibility, which is crucial for existing integrations.

High
Possible issue
Safely access parameter bounds

Guard access to param.min and param.max to avoid AttributeError on special or
automation-only parameters. Some Live parameters may not expose these attributes
reliably.

AbletonMCP_Remote_Script/init.py [442-451]

 if device.parameters:
     for param in device.parameters:
-        # Check for valid parameters that have a name and can be read
         if hasattr(param, 'name') and param.name and hasattr(param, 'value'):
+            param_min = getattr(param, 'min', None)
+            param_max = getattr(param, 'max', None)
             parameters.append({
                 "name": param.name,
                 "value": param.value,
-                "min": param.min,
-                "max": param.max
+                "min": param_min,
+                "max": param_max
             })
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This is a good defensive coding suggestion, as it prevents potential AttributeError exceptions if some parameters in Ableton Live do not have min or max attributes, making the script more robust.

Medium
Normalize line endings before parsing

Strip trailing carriage returns/spaces on each line before JSON parsing to avoid
decode failures on Windows or mixed line endings. Without this, '\r' or
whitespace can cause intermittent "Invalid JSON" errors.

AbletonMCP_Remote_Script/init.py [155-167]

 while '\n' in buffer:
     command_str, buffer = buffer.split('\n', 1)
+    command_str = command_str.strip('\r \t')
     if not command_str:
         continue
 
     try:
         command = json.loads(command_str)
         self.log_message("Received command: " + str(command.get("type", "unknown")))
 
         response = self._process_command(command)
 
         # Append newline to the response
         response_str = json.dumps(response) + '\n'
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: This suggestion correctly identifies a potential issue with different line endings (e.g., \r\n from Windows clients) which could cause JSON parsing to fail, and the proposed strip() call robustly handles this.

Low
  • More

Copy link

@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: 1

🔭 Outside diff range comments (2)
MCP_Server/server.py (2)

104-106: Pass the chosen timeout to receive_full_response

Propagate the per-command timeout so non-modifying commands use 10s and modifying use 15s as intended.

-            response_data = self.receive_full_response(self.sock)
+            response_data = self.receive_full_response(self.sock, buffer_size=4096, timeout=timeout)

77-85: Treat 'load_browser_item' as a modifying command

You send the 'load_browser_item' command (e.g., from load_instrument_or_effect), but it’s not in the modifying list. This skips the deliberate delays and uses a shorter timeout, increasing the chance of timeouts and flakiness when loading devices.

         is_modifying_command = command_type in [
             "create_midi_track", "create_audio_track", "set_track_name",
             "create_clip", "add_notes_to_clip", "set_clip_name",
             "set_tempo", "fire_clip", "stop_clip", "set_device_parameter",
-            "start_playback", "stop_playback", "load_instrument_or_effect"
+            "start_playback", "stop_playback", "load_instrument_or_effect",
+            "load_browser_item"
         ]
🧹 Nitpick comments (5)
AbletonMCP_Remote_Script/__init__.py (4)

136-146: Avoid indefinite blocking on client sockets to enable clean shutdowns

client.settimeout(None) can leave client threads blocked on recv() during shutdown, since you don't close client sockets in disconnect(). Use a finite timeout and handle socket.timeout to periodically check self.running.

Apply this diff:

-        client.settimeout(None)
+        # Use a finite timeout so we can periodically check self.running and exit cleanly
+        client.settimeout(1.0)

194-197: Handle socket timeout explicitly in client loop

With a finite timeout, add an except socket.timeout to continue the loop instead of treating it as an error.

-                except (socket.error, IOError) as e:
+                except socket.timeout:
+                    # Periodic wake-up to re-check self.running
+                    continue
+                except (socket.error, IOError) as e:
                     self.log_message("Socket error in client handler: " + str(e))
                     break # Exit the loop on socket errors

154-159: Trim CR from lines to be robust against CRLF line endings

When splitting on newline, Windows clients may send CRLF. Safely strip a trailing '\r' before JSON parsing.

-                        command_str, buffer = buffer.split('\n', 1)
+                        command_str, buffer = buffer.split('\n', 1)
+                        # Normalize CRLF endings
+                        if command_str.endswith('\r'):
+                            command_str = command_str[:-1]

200-206: Replace bare except with explicit exception handling

Bare except: hides non-Exception control-flow (e.g., KeyboardInterrupt) and trips linters. Catch Exception explicitly and log.

-                    except:
-                        pass
+                    except Exception as send_err:
+                        self.log_message("Failed to send error response to client: " + str(send_err))
MCP_Server/server.py (1)

60-66: Preserve original exception context using exception chaining

Use raise ... from e to retain the original traceback and satisfy Ruff B904.

-        except socket.timeout:
-            logger.error("Socket timeout while waiting for response from Ableton")
-            raise TimeoutError("Timeout waiting for Ableton response")
-        except Exception as e:
-            logger.error(f"Error receiving data: {str(e)}")
-            raise
+        except socket.timeout as e:
+            logger.error("Socket timeout while waiting for response from Ableton")
+            raise TimeoutError("Timeout waiting for Ableton response") from e
+        except Exception as e:
+            logger.error(f"Error receiving data: {str(e)}")
+            raise
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bea865e and 4b49d6c.

📒 Files selected for processing (2)
  • AbletonMCP_Remote_Script/__init__.py (3 hunks)
  • MCP_Server/server.py (3 hunks)
🧰 Additional context used
🪛 Ruff (0.12.2)
AbletonMCP_Remote_Script/__init__.py

204-204: Do not use bare except

(E722)

MCP_Server/server.py

62-62: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🔇 Additional comments (4)
AbletonMCP_Remote_Script/__init__.py (2)

229-239: New command route looks good

Command routing for get_device_parameters is consistent with existing patterns and returns a structured payload as expected.


428-462: Device parameter retrieval is correct and defensive

Index validation and parameter extraction look solid. Returning name/value/min/max is a useful, stable surface for clients.

MCP_Server/server.py (2)

88-93: LGTM: newline-delimited framing on send

Appending a newline and sending UTF-8 encoded JSON matches the new protocol and aligns with the remote script.


265-284: LGTM: device parameters tool endpoint

The public MCP tool delegates to the new remote command and returns a formatted JSON string with errors handled appropriately.

Comment on lines +46 to +49
def receive_full_response(self, sock, buffer_size=4096):
"""Receive a newline-delimited JSON response from the socket."""
buffer = b''
sock.settimeout(15.0)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Don’t override caller-selected timeouts inside receive_full_response

receive_full_response sets a hard 15s timeout, which overrides the per-command timeout set in send_command. Accept a timeout parameter and only set it if provided.

-    def receive_full_response(self, sock, buffer_size=4096):
+    def receive_full_response(self, sock, buffer_size=4096, timeout=None):
         """Receive a newline-delimited JSON response from the socket."""
-        buffer = b''
-        sock.settimeout(15.0)
+        buffer = b''
+        if timeout is not None:
+            sock.settimeout(timeout)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def receive_full_response(self, sock, buffer_size=4096):
"""Receive a newline-delimited JSON response from the socket."""
buffer = b''
sock.settimeout(15.0)
def receive_full_response(self, sock, buffer_size=4096, timeout=None):
"""Receive a newline-delimited JSON response from the socket."""
buffer = b''
if timeout is not None:
sock.settimeout(timeout)
# …rest of method…
🤖 Prompt for AI Agents
In MCP_Server/server.py around lines 46 to 49, receive_full_response currently
unconditionally sets sock.settimeout(15.0); change the function signature to
accept a timeout parameter (e.g. timeout=None) and only call
sock.settimeout(timeout) if timeout is not None (remove the hardcoded 15s);
update callers (notably send_command) to pass their per-command timeout through
to receive_full_response so caller-selected timeouts are honored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant