livepilot 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.2 — 2026-03-17
4
+
5
+ ### Fixes
6
+ - Added missing `clip_index` validation to 4 arrangement MCP tools
7
+ - Standardized `json` import (removed `_json` alias)
8
+ - Added index range to arrangement clip error messages for consistency
9
+ - Made installer version-agnostic (`/^Live \d+/` regex instead of hardcoded "Live 12")
10
+ - Standardized project description across all config files
11
+
12
+ ## 1.1.1 — 2026-03-17
13
+
14
+ ### Fixes
15
+ - Fixed `create_arrangement_clip` ignoring `loop_length` parameter (was always using source clip length for tiling)
16
+ - Fixed double `end_undo_step()` calls in `create_arrangement_clip` and `set_arrangement_automation` (now uses `try/finally`)
17
+ - Fixed `back_to_arranger` setting wrong value (`False` → `True`)
18
+ - Added guard against `None` clip index after arrangement placement failure
19
+ - Fixed `--doctor` summary not reflecting connection test result in exit code
20
+
3
21
  ## 1.1.0 — 2026-03-17
4
22
 
5
23
  ### New Tools (+7)
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![CI](https://github.com/dreamrec/LivePilot/actions/workflows/ci.yml/badge.svg)](https://github.com/dreamrec/LivePilot/actions/workflows/ci.yml)
12
12
  [![GitHub stars](https://img.shields.io/github/stars/dreamrec/LivePilot)](https://github.com/dreamrec/LivePilot/stargazers)
13
13
 
14
- **AI copilot for Ableton Live 12** — 91 MCP tools for real-time music production, sound design, and mixing.
14
+ **AI copilot for Ableton Live 12** — 91 MCP tools for music production, sound design, and mixing.
15
15
 
16
16
  Control your entire Ableton session through natural language. Create tracks, program MIDI, load instruments, tweak parameters, arrange songs, and mix — all without leaving the keyboard.
17
17
 
package/bin/livepilot.js CHANGED
@@ -246,7 +246,10 @@ async function doctor() {
246
246
  // 8. TCP connection to Ableton
247
247
  console.log("");
248
248
  console.log("Connection test:");
249
- await checkStatus();
249
+ const connected = await checkStatus();
250
+ if (!connected) {
251
+ ok = false;
252
+ }
250
253
 
251
254
  // Summary
252
255
  console.log("");
@@ -26,7 +26,7 @@ function findAbletonPaths() {
26
26
  try {
27
27
  const entries = fs.readdirSync(prefsDir);
28
28
  for (const entry of entries) {
29
- if (entry.startsWith("Live 12")) {
29
+ if (/^Live \d+/.test(entry)) {
30
30
  candidates.push({
31
31
  path: path.join(prefsDir, entry, "User Remote Scripts"),
32
32
  description: `Ableton Preferences (${entry})`,
@@ -53,7 +53,7 @@ function findAbletonPaths() {
53
53
  try {
54
54
  const entries = fs.readdirSync(abletonAppData);
55
55
  for (const entry of entries) {
56
- if (entry.startsWith("Live 12")) {
56
+ if (/^Live \d+/.test(entry)) {
57
57
  candidates.push({
58
58
  path: path.join(abletonAppData, entry, "Preferences", "User Remote Scripts"),
59
59
  description: `Ableton AppData (${entry})`,
@@ -3,7 +3,7 @@
3
3
  19 tools matching the Remote Script arrangement domain.
4
4
  """
5
5
 
6
- import json as _json
6
+ import json
7
7
 
8
8
  from fastmcp import Context
9
9
 
@@ -147,8 +147,9 @@ def add_arrangement_notes(
147
147
  start_time in notes is relative to the clip start, not the song timeline.
148
148
  """
149
149
  _validate_track_index(track_index)
150
+ _validate_clip_index(clip_index)
150
151
  if isinstance(notes, str):
151
- notes = _json.loads(notes)
152
+ notes = json.loads(notes)
152
153
  return _get_ableton(ctx).send_command("add_arrangement_notes", {
153
154
  "track_index": track_index,
154
155
  "clip_index": clip_index,
@@ -181,6 +182,7 @@ def set_arrangement_automation(
181
182
  For parameter_type="send": send_index required (0=A, 1=B, ...).
182
183
  """
183
184
  _validate_track_index(track_index)
185
+ _validate_clip_index(clip_index)
184
186
  if parameter_type not in ("device", "volume", "panning", "send"):
185
187
  raise ValueError("parameter_type must be 'device', 'volume', 'panning', or 'send'")
186
188
  if parameter_type == "device":
@@ -189,7 +191,7 @@ def set_arrangement_automation(
189
191
  if parameter_type == "send" and send_index is None:
190
192
  raise ValueError("send_index required for parameter_type='send'")
191
193
  if isinstance(points, str):
192
- points = _json.loads(points)
194
+ points = json.loads(points)
193
195
  if not points:
194
196
  raise ValueError("points list cannot be empty")
195
197
  params: dict = {
@@ -224,6 +226,7 @@ def transpose_arrangement_notes(
224
226
  time_span: length of note range in beats (defaults to full clip)
225
227
  """
226
228
  _validate_track_index(track_index)
229
+ _validate_clip_index(clip_index)
227
230
  if not -127 <= semitones <= 127:
228
231
  raise ValueError("semitones must be between -127 and 127")
229
232
  params: dict = {
@@ -248,6 +251,7 @@ def set_arrangement_clip_name(
248
251
  ) -> dict:
249
252
  """Rename an arrangement clip by its index in the track's arrangement_clips list."""
250
253
  _validate_track_index(track_index)
254
+ _validate_clip_index(clip_index)
251
255
  if not name.strip():
252
256
  raise ValueError("name cannot be empty")
253
257
  return _get_ableton(ctx).send_command("set_arrangement_clip_name", {
@@ -333,7 +337,7 @@ def remove_arrangement_notes_by_id(
333
337
  _validate_track_index(track_index)
334
338
  _validate_clip_index(clip_index)
335
339
  if isinstance(note_ids, str):
336
- note_ids = _json.loads(note_ids)
340
+ note_ids = json.loads(note_ids)
337
341
  if not note_ids:
338
342
  raise ValueError("note_ids list cannot be empty")
339
343
  return _get_ableton(ctx).send_command("remove_arrangement_notes_by_id", {
@@ -355,7 +359,7 @@ def modify_arrangement_notes(
355
359
  _validate_track_index(track_index)
356
360
  _validate_clip_index(clip_index)
357
361
  if isinstance(modifications, str):
358
- modifications = _json.loads(modifications)
362
+ modifications = json.loads(modifications)
359
363
  if not modifications:
360
364
  raise ValueError("modifications list cannot be empty")
361
365
  for mod in modifications:
@@ -388,7 +392,7 @@ def duplicate_arrangement_notes(
388
392
  _validate_track_index(track_index)
389
393
  _validate_clip_index(clip_index)
390
394
  if isinstance(note_ids, str):
391
- note_ids = _json.loads(note_ids)
395
+ note_ids = json.loads(note_ids)
392
396
  if not note_ids:
393
397
  raise ValueError("note_ids list cannot be empty")
394
398
  return _get_ableton(ctx).send_command("duplicate_arrangement_notes", {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "mcpName": "io.github.dreamrec/livepilot",
5
- "description": "AI copilot for Ableton Live 12 — 91 MCP tools for production, sound design, and mixing",
5
+ "description": "AI copilot for Ableton Live 12 — 91 MCP tools for music production, sound design, and mixing",
6
6
  "author": "Pilot Studio",
7
7
  "license": "MIT",
8
8
  "type": "commonjs",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.1.0",
4
- "description": "AI copilot for Ableton Live 12 — 91 tools for production, sound design, and mixing",
3
+ "version": "1.1.2",
4
+ "description": "AI copilot for Ableton Live 12 — 91 MCP tools for music production, sound design, and mixing",
5
5
  "author": "Pilot Studio",
6
6
  "skills": [
7
7
  "skills/livepilot-core"
@@ -89,18 +89,15 @@ def create_arrangement_clip(song, params):
89
89
  break
90
90
 
91
91
  clip_count += 1
92
- pos += source_length
93
- except Exception as e:
92
+ pos += loop_length
93
+ finally:
94
94
  song.end_undo_step()
95
- raise ValueError(
96
- "duplicate_clip_to_arrangement failed: %s" % str(e)
97
- )
98
-
99
- song.end_undo_step()
100
95
 
101
96
  # Re-read to get accurate final state
102
97
  arr_clips = list(track.arrangement_clips)
103
- first_clip = arr_clips[first_clip_index] if first_clip_index is not None else None
98
+ if first_clip_index is None or first_clip_index >= len(arr_clips):
99
+ raise ValueError("Failed to place any clips in arrangement")
100
+ first_clip = arr_clips[first_clip_index]
104
101
 
105
102
  return {
106
103
  "track_index": track_index,
@@ -499,7 +496,6 @@ def set_arrangement_automation(song, params):
499
496
  if temp_envelope is None:
500
497
  # Neither direct nor session clip approach works
501
498
  slot.delete_clip()
502
- song.end_undo_step()
503
499
  raise ValueError(
504
500
  "Cannot create automation envelope for parameter '%s' "
505
501
  "(neither arrangement nor session clip supports it)"
@@ -519,13 +515,8 @@ def set_arrangement_automation(song, params):
519
515
 
520
516
  # Clean up the temporary session clip
521
517
  slot.delete_clip()
522
- except Exception as e:
518
+ finally:
523
519
  song.end_undo_step()
524
- raise ValueError(
525
- "Automation workaround failed: %s" % str(e)
526
- )
527
-
528
- song.end_undo_step()
529
520
 
530
521
  return {
531
522
  "track_index": track_index,
@@ -592,7 +583,10 @@ def set_arrangement_clip_name(song, params):
592
583
  track = get_track(song, track_index)
593
584
  arr_clips = list(track.arrangement_clips)
594
585
  if clip_index < 0 or clip_index >= len(arr_clips):
595
- raise IndexError("Arrangement clip index out of range")
586
+ raise IndexError(
587
+ "Arrangement clip index %d out of range (0..%d)"
588
+ % (clip_index, len(arr_clips) - 1)
589
+ )
596
590
  arr_clips[clip_index].name = name
597
591
  return {"track_index": track_index, "clip_index": clip_index, "name": name}
598
592
 
@@ -674,5 +668,5 @@ def toggle_cue_point(song, params):
674
668
  @register("back_to_arranger")
675
669
  def back_to_arranger(song, params):
676
670
  """Switch playback from session clips back to the arrangement timeline."""
677
- song.back_to_arranger = False
671
+ song.back_to_arranger = True
678
672
  return {"back_to_arranger": song.back_to_arranger}
@@ -1,10 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Read(//Users/visansilviugeorge/Music/Ableton/User Library/Remote Scripts/LivePilot/**)",
5
- "Read(//Applications/**)",
6
- "Bash(npm list:*)",
7
- "mcp__plugin_livepilot_LivePilot__get_session_info"
8
- ]
9
- }
10
- }
@@ -1 +0,0 @@
1
- ghu_HBMOaCx3m6AhzGz0LCfV04JgwolYFP2b7pv9
@@ -1 +0,0 @@
1
- {"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtY3AtcmVnaXN0cnkiLCJleHAiOjE3NzM3NjM1NTQsIm5iZiI6MTc3Mzc2MzI1NCwiaWF0IjoxNzczNzYzMjU0LCJhdXRoX21ldGhvZCI6ImdpdGh1Yi1hdCIsImF1dGhfbWV0aG9kX3N1YiI6ImRyZWFtcmVjIiwicGVybWlzc2lvbnMiOlt7ImFjdGlvbiI6InB1Ymxpc2giLCJyZXNvdXJjZSI6ImlvLmdpdGh1Yi5kcmVhbXJlYy8qIn1dfQ.f0qqH3xlVr08nLhOgS9JeFJQGVpMNWKhTXEXKZ4CL22zI6IlsELBFb1ReC5zRK-ViHFh1ZZzA03IsOn5qOaMDg","expires_at":1773763554}
@@ -1,10 +0,0 @@
1
- [ 944ms] ReferenceError: __name is not defined
2
- at https://mcp.so/:10:13
3
- at https://mcp.so/:17:13
4
- [ 5151ms] [ERROR] Not signed in with the identity provider. @ https://mcp.so/:0
5
- [ 8070ms] [ERROR] [GSI_LOGGER]: FedCM get() rejects with NetworkError: Error retrieving a token. @ https://mcp.so/_next/static/chunks/5774-3c92e2fd4a5f0665.js:0
6
- [ 51793ms] ReferenceError: __name is not defined
7
- at https://mcp.so/submit:10:13
8
- at https://mcp.so/submit:17:13
9
- [ 52509ms] [ERROR] Not signed in with the identity provider. @ https://mcp.so/submit:0
10
- [ 64373ms] [ERROR] [GSI_LOGGER]: FedCM get() rejects with NetworkError: Error retrieving a token. @ https://mcp.so/_next/static/chunks/5774-3c92e2fd4a5f0665.js:0
@@ -1,10 +0,0 @@
1
- [ 2220ms] Error: Minified React error #418; visit https://react.dev/errors/418?args[]=text&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
2
- at Li (https://static.glama.ai/client/react-BhmDw-jB.js:54:31190)
3
- at Lc (https://static.glama.ai/client/react-BhmDw-jB.js:54:87415)
4
- at Ru (https://static.glama.ai/client/react-BhmDw-jB.js:54:116726)
5
- at Fu (https://static.glama.ai/client/react-BhmDw-jB.js:54:115990)
6
- at Pu (https://static.glama.ai/client/react-BhmDw-jB.js:54:115901)
7
- at Nu (https://static.glama.ai/client/react-BhmDw-jB.js:54:115759)
8
- at yu (https://static.glama.ai/client/react-BhmDw-jB.js:54:111299)
9
- at fd (https://static.glama.ai/client/react-BhmDw-jB.js:54:123284)
10
- at MessagePort.D (https://static.glama.ai/client/react-BhmDw-jB.js:47:33962)
@@ -1,12 +0,0 @@
1
- [ 1229ms] [WARNING] You should call navigate() in a React.useEffect(), not when your component is first rendered. @ https://static.glama.ai/client/react-BhmDw-jB.js:54
2
- [ 1237ms] [WARNING] You should call navigate() in a React.useEffect(), not when your component is first rendered. @ https://static.glama.ai/client/react-BhmDw-jB.js:54
3
- [ 1256ms] Error: Minified React error #418; visit https://react.dev/errors/418?args[]=text&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
4
- at Li (https://static.glama.ai/client/react-BhmDw-jB.js:54:31190)
5
- at Lc (https://static.glama.ai/client/react-BhmDw-jB.js:54:87415)
6
- at Ru (https://static.glama.ai/client/react-BhmDw-jB.js:54:116726)
7
- at Fu (https://static.glama.ai/client/react-BhmDw-jB.js:54:115990)
8
- at Pu (https://static.glama.ai/client/react-BhmDw-jB.js:54:115901)
9
- at Nu (https://static.glama.ai/client/react-BhmDw-jB.js:54:115759)
10
- at yu (https://static.glama.ai/client/react-BhmDw-jB.js:54:111299)
11
- at fd (https://static.glama.ai/client/react-BhmDw-jB.js:54:123284)
12
- at MessagePort.D (https://static.glama.ai/client/react-BhmDw-jB.js:47:33962)
@@ -1,10 +0,0 @@
1
- [ 3510ms] Error: Minified React error #418; visit https://react.dev/errors/418?args[]=text&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
2
- at Li (https://static.glama.ai/client/react-BhmDw-jB.js:54:31190)
3
- at Lc (https://static.glama.ai/client/react-BhmDw-jB.js:54:87415)
4
- at Ru (https://static.glama.ai/client/react-BhmDw-jB.js:54:116726)
5
- at Fu (https://static.glama.ai/client/react-BhmDw-jB.js:54:115990)
6
- at Pu (https://static.glama.ai/client/react-BhmDw-jB.js:54:115901)
7
- at Nu (https://static.glama.ai/client/react-BhmDw-jB.js:54:115759)
8
- at yu (https://static.glama.ai/client/react-BhmDw-jB.js:54:111299)
9
- at fd (https://static.glama.ai/client/react-BhmDw-jB.js:54:123284)
10
- at MessagePort.D (https://static.glama.ai/client/react-BhmDw-jB.js:47:33962)
@@ -1 +0,0 @@
1
- [ 609ms] [ERROR] Failed to load resource: the server responded with a status of 404 () @ https://glama.ai/mcp/servers/dreamrec/LivePilot:0