Skip to content

Ableton Live Agent Skill

Producer Pal ships a portable Agent Skill that lets coding agents control Ableton Live through Producer Pal's REST API — no MCP client required.

Agent Skills are a small, open convention shared across the major coding-agent CLIs: a folder containing a SKILL.md (with frontmatter describing when to use it) plus optional scripts and resources. The folder is loaded lazily when the agent decides the skill is relevant. The same folder works across all three:

ToolSkills location
Claude Code~/.claude/skills/<name>/SKILL.md
Codex CLI~/.codex/skills/<name>/SKILL.md
Gemini CLI~/.gemini/skills/<name>/SKILL.md

When to use this vs MCP

Producer Pal's MCP server is the recommended path when your agent supports MCP — the tools come with rich descriptions and the LLM picks them up automatically.

The skill is for REST-API-driven workflows: agents not configured with the Producer Pal MCP server, scripts and pipelines that don't run an MCP client, or environments where you want a single drop-in folder rather than per-tool MCP setup.

Install

Copy the producer-pal/ folder from the Producer Pal repo into your agent's skills directory.

bash
# Clone (or download) the repo, then copy the skill folder
git clone --depth 1 https://github.com/adamjmurray/producer-pal.git
cp -r producer-pal/examples/skills/producer-pal ~/.claude/skills/
# or ~/.codex/skills/, or ~/.gemini/skills/

The skill folder contains a SKILL.md (frontmatter + instructions for the agent) and a ppal.mjs (the Node CLI it shells out to).

How it works

When the user asks the agent something Producer-Pal-shaped ("set tempo to 120", "what's in track 2", "make a 4-bar drum loop"), the agent loads SKILL.md and follows its bootstrap:

  1. List toolsnode ppal.mjs --list-tools returns the full tool catalog with input schemas, so the agent knows what's available without baking it into the skill.
  2. Call ppal-connect — the agent's first call. Its response includes the up-to-date Producer Pal Skills (bar|beat notation, MIDI syntax, code transforms, conventions) — the same instructions Producer Pal's MCP clients receive at session start. The skill stays small; the heavy guidance comes from Producer Pal itself.
  3. Use the other tools per those instructions, via node ppal.mjs <tool> [json-args].

Because the skill is just a thin pointer + bootstrap, it stays correct as Producer Pal evolves: new tools, schema changes, and skill updates land in ppal-connect's response automatically.

The bundled script

ppal.mjs is a zero-dependency Node 18+ script that wraps Producer Pal's REST API. It's both the CLI the skill shells out to and a small library you can import in your own code:

js
#!/usr/bin/env node

// Producer Pal REST API client (Node 18+, no dependencies).
//
// CLI:
//   node ppal.mjs --list-tools
//   node ppal.mjs <tool> [json-args] [options]
//
// Options:
//   --url <baseUrl>      override Producer Pal URL (default http://localhost:3350)
//   --timeout-ms <ms>    per-request timeout (1–60000)
//
// Examples:
//   node ppal.mjs --list-tools
//   node ppal.mjs ppal-read-live-set
//   node ppal.mjs ppal-read-track '{"trackIndex": 0}'
//   node ppal.mjs ppal-create-clip '{...}' --timeout-ms 10000
//
// Library:
//   import { listTools, callTool } from "./ppal.mjs";
//   const { result, warnings } = await callTool("ppal-read-live-set");

const DEFAULT_BASE_URL = "http://localhost:3350";

/**
 * GET /api/tools — returns the full envelope `{tools: [...]}` as a parsed
 * object. The tool list endpoint always returns JSON; it has no `?format`
 * toggle.
 */
export async function listTools(baseUrl = DEFAULT_BASE_URL) {
  const res = await fetch(`${baseUrl}/api/tools`);
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  return res.json();
}

/**
 * Call a Producer Pal tool by name. Always uses `?format=json` so `result` is
 * a parsed value (object/array/etc.) and warnings are surfaced as a separate
 * `warnings: string[]` field.
 */
export async function callTool(name, args = {}, options = {}) {
  const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
  const params = new URLSearchParams({ format: "json" });
  if (options.timeoutMs != null)
    params.set("timeoutMs", String(options.timeoutMs));

  const res = await fetch(`${baseUrl}/api/tools/${name}?${params}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(args),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  return res.json();
}

// --- CLI ---

function parseArgs(argv) {
  const opts = { baseUrl: DEFAULT_BASE_URL, listTools: false };
  const positional = [];
  for (let i = 0; i < argv.length; i++) {
    const arg = argv[i];
    if (arg === "--url") opts.baseUrl = argv[++i];
    else if (arg === "--timeout-ms") opts.timeoutMs = Number(argv[++i]);
    else if (arg === "--list-tools") opts.listTools = true;
    else if (arg === "--help" || arg === "-h") opts.help = true;
    else positional.push(arg);
  }
  return { opts, positional };
}

const HELP = `Producer Pal REST API client

Usage:
  node ppal.mjs --list-tools
  node ppal.mjs <tool> [json-args] [options]

Options:
  --url <baseUrl>      override Producer Pal URL (default ${DEFAULT_BASE_URL})
  --timeout-ms <ms>    per-request timeout (1–60000)
  --help, -h           show this help

Examples:
  node ppal.mjs --list-tools
  node ppal.mjs ppal-read-live-set
  node ppal.mjs ppal-read-track '{"trackIndex": 0}'
`;

async function main(argv) {
  const { opts, positional } = parseArgs(argv);
  if (opts.help) {
    console.log(HELP);
    return;
  }

  if (opts.listTools) {
    const result = await listTools(opts.baseUrl);
    console.log(JSON.stringify(result, null, 2));
    return;
  }

  const [toolName, argsJson = "{}"] = positional;
  if (!toolName) {
    console.error(
      "Missing tool name. Use --list-tools to discover tools, or pass a tool name as the first argument.",
    );
    process.exit(1);
  }

  let args;
  try {
    args = JSON.parse(argsJson);
  } catch (err) {
    console.error(`Invalid JSON for tool args: ${err.message}`);
    process.exit(1);
  }

  const response = await callTool(toolName, args, opts);
  if (response.isError) {
    console.error(`API error: ${response.result}`);
    process.exit(1);
  }
  console.log(JSON.stringify(response, null, 2));
}

// Run main() when invoked as CLI (not when imported as a library)
if (import.meta.url === `file://${process.argv[1]}`) {
  main(process.argv.slice(2)).catch((err) => {
    if (err.cause?.code === "ECONNREFUSED") {
      console.error(
        "Could not connect to Producer Pal. Is Ableton Live running with the Producer Pal device?",
      );
    } else {
      console.error(err.message ?? err);
    }
    process.exit(1);
  });
}

Prefer Python?

The skill ships with the Node script because nearly every agent runtime has Node available, but a zero-dependency Python equivalent is also maintained — see the Python sample script. To use it, swap the node ppal.mjs commands in SKILL.md for python ppal.py and drop ppal.py into the skill folder.

Source

Released under the GPL-3.0 License.