@mthines/reaper-mcp 0.14.1 → 0.14.2-beta.17.1
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/main.js +219 -33
- package/package.json +2 -1
- package/reaper/mcp_bridge.lua +114 -19
package/main.js
CHANGED
|
@@ -2202,10 +2202,10 @@ function createServer() {
|
|
|
2202
2202
|
}
|
|
2203
2203
|
|
|
2204
2204
|
// apps/reaper-mcp-server/src/main.ts
|
|
2205
|
-
import { existsSync as
|
|
2206
|
-
import { join as
|
|
2205
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
|
|
2206
|
+
import { join as join5, dirname as dirname2 } from "node:path";
|
|
2207
2207
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2208
|
-
import { homedir as
|
|
2208
|
+
import { homedir as homedir3 } from "node:os";
|
|
2209
2209
|
|
|
2210
2210
|
// apps/reaper-mcp-server/src/cli.ts
|
|
2211
2211
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -2391,6 +2391,174 @@ function resolveAssetDirWithFallback(baseDir, buildName, sourceName) {
|
|
|
2391
2391
|
return resolveAssetDir(baseDir, sourceName);
|
|
2392
2392
|
}
|
|
2393
2393
|
|
|
2394
|
+
// apps/reaper-mcp-server/src/init.ts
|
|
2395
|
+
import { checkbox, select } from "@inquirer/prompts";
|
|
2396
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
2397
|
+
import { join as join4 } from "node:path";
|
|
2398
|
+
import { homedir as homedir2 } from "node:os";
|
|
2399
|
+
async function runInit(opts, dirResolver) {
|
|
2400
|
+
const isTTY = Boolean(process.stdin.isTTY);
|
|
2401
|
+
const headless = opts.yes || !isTTY;
|
|
2402
|
+
console.log("REAPER MCP \u2014 Interactive Setup\n");
|
|
2403
|
+
const bridgeDir = await ensureBridgeDir();
|
|
2404
|
+
console.log(`REAPER resource path: ${join4(bridgeDir, "..", "..")}
|
|
2405
|
+
`);
|
|
2406
|
+
let selections;
|
|
2407
|
+
if (headless) {
|
|
2408
|
+
selections = {
|
|
2409
|
+
bridge: true,
|
|
2410
|
+
skills: true,
|
|
2411
|
+
settings: true,
|
|
2412
|
+
projectConfig: opts.project,
|
|
2413
|
+
skillsScope: "global"
|
|
2414
|
+
};
|
|
2415
|
+
if (opts.yes) {
|
|
2416
|
+
console.log("Running in non-interactive mode (--yes flag).\n");
|
|
2417
|
+
} else {
|
|
2418
|
+
console.log("Non-interactive terminal detected. Running with defaults.\n");
|
|
2419
|
+
}
|
|
2420
|
+
} else {
|
|
2421
|
+
const components = await checkbox({
|
|
2422
|
+
message: "Which components would you like to install?",
|
|
2423
|
+
choices: [
|
|
2424
|
+
{ name: "REAPER Bridge (Lua bridge + JSFX analyzers)", value: "bridge", checked: true },
|
|
2425
|
+
{ name: `AI Skills & Agents (knowledge base, Claude agents, rules, skills)`, value: "skills", checked: true },
|
|
2426
|
+
{ name: `Claude Code Settings (auto-allow ${MCP_TOOL_NAMES.length} REAPER tools)`, value: "settings", checked: true },
|
|
2427
|
+
{ name: "Project Config (.mcp.json in current directory)", value: "projectConfig", checked: false }
|
|
2428
|
+
]
|
|
2429
|
+
});
|
|
2430
|
+
let skillsScope = "global";
|
|
2431
|
+
if (components.includes("skills")) {
|
|
2432
|
+
const scopeAnswer = await select({
|
|
2433
|
+
message: "Install scope for AI Skills & Agents:",
|
|
2434
|
+
choices: [
|
|
2435
|
+
{ name: "Global (~/.claude/) \u2014 available in all projects", value: "global" },
|
|
2436
|
+
{ name: "Project (.claude/) \u2014 current directory only", value: "project" }
|
|
2437
|
+
]
|
|
2438
|
+
});
|
|
2439
|
+
skillsScope = scopeAnswer;
|
|
2440
|
+
}
|
|
2441
|
+
selections = {
|
|
2442
|
+
bridge: components.includes("bridge"),
|
|
2443
|
+
skills: components.includes("skills"),
|
|
2444
|
+
settings: components.includes("settings"),
|
|
2445
|
+
projectConfig: components.includes("projectConfig"),
|
|
2446
|
+
skillsScope
|
|
2447
|
+
};
|
|
2448
|
+
}
|
|
2449
|
+
const __dirname2 = dirResolver();
|
|
2450
|
+
if (selections.bridge) {
|
|
2451
|
+
console.log("Installing REAPER Bridge...");
|
|
2452
|
+
const scriptsDir = getReaperScriptsPath();
|
|
2453
|
+
mkdirSync2(scriptsDir, { recursive: true });
|
|
2454
|
+
const reaperDir = resolveAssetDir(__dirname2, "reaper");
|
|
2455
|
+
const luaSrc = join4(reaperDir, "mcp_bridge.lua");
|
|
2456
|
+
const luaDest = join4(scriptsDir, "mcp_bridge.lua");
|
|
2457
|
+
if (installFile(luaSrc, luaDest)) {
|
|
2458
|
+
console.log(" Installed: mcp_bridge.lua");
|
|
2459
|
+
} else {
|
|
2460
|
+
console.log(` Not found: ${luaSrc}`);
|
|
2461
|
+
}
|
|
2462
|
+
const effectsDir = join4(getReaperEffectsPath(), "reaper-mcp");
|
|
2463
|
+
mkdirSync2(effectsDir, { recursive: true });
|
|
2464
|
+
for (const jsfx of REAPER_ASSETS) {
|
|
2465
|
+
if (jsfx === "mcp_bridge.lua") continue;
|
|
2466
|
+
const src = join4(reaperDir, jsfx);
|
|
2467
|
+
const dest = join4(effectsDir, jsfx);
|
|
2468
|
+
if (installFile(src, dest)) {
|
|
2469
|
+
console.log(` Installed: reaper-mcp/${jsfx}`);
|
|
2470
|
+
} else {
|
|
2471
|
+
console.log(` Not found: ${src}`);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
console.log("");
|
|
2475
|
+
}
|
|
2476
|
+
if (selections.skills) {
|
|
2477
|
+
const isGlobal = selections.skillsScope === "global";
|
|
2478
|
+
const baseDir = isGlobal ? join4(homedir2(), ".claude") : process.cwd();
|
|
2479
|
+
const claudeDir = isGlobal ? baseDir : join4(baseDir, ".claude");
|
|
2480
|
+
console.log(`Installing AI Skills & Agents (${selections.skillsScope})...`);
|
|
2481
|
+
const knowledgeSrc = resolveAssetDir(__dirname2, "knowledge");
|
|
2482
|
+
if (existsSync2(knowledgeSrc)) {
|
|
2483
|
+
const dest = join4(isGlobal ? join4(homedir2(), ".claude") : process.cwd(), "knowledge");
|
|
2484
|
+
const count = copyDirSync(knowledgeSrc, dest);
|
|
2485
|
+
console.log(` Installed knowledge base: ${count} files`);
|
|
2486
|
+
} else {
|
|
2487
|
+
console.log(" Knowledge base not found in package. Skipping.");
|
|
2488
|
+
}
|
|
2489
|
+
const rulesSrc = resolveAssetDirWithFallback(__dirname2, "claude-rules", join4(".claude", "rules"));
|
|
2490
|
+
if (existsSync2(rulesSrc)) {
|
|
2491
|
+
const dest = join4(claudeDir, "rules");
|
|
2492
|
+
const count = copyDirSync(rulesSrc, dest);
|
|
2493
|
+
console.log(` Installed Claude rules: ${count} files`);
|
|
2494
|
+
}
|
|
2495
|
+
const skillsSrc = resolveAssetDirWithFallback(__dirname2, "claude-skills", join4(".claude", "skills"));
|
|
2496
|
+
if (existsSync2(skillsSrc)) {
|
|
2497
|
+
const dest = join4(claudeDir, "skills");
|
|
2498
|
+
const count = copyDirSync(skillsSrc, dest);
|
|
2499
|
+
console.log(` Installed Claude skills: ${count} files`);
|
|
2500
|
+
}
|
|
2501
|
+
const agentsSrc = resolveAssetDirWithFallback(__dirname2, "claude-agents", join4(".claude", "agents"));
|
|
2502
|
+
if (existsSync2(agentsSrc)) {
|
|
2503
|
+
const dest = join4(claudeDir, "agents");
|
|
2504
|
+
const count = copyDirSync(agentsSrc, dest);
|
|
2505
|
+
console.log(` Installed Claude agents: ${count} files`);
|
|
2506
|
+
}
|
|
2507
|
+
console.log("");
|
|
2508
|
+
}
|
|
2509
|
+
if (selections.settings) {
|
|
2510
|
+
console.log("Configuring Claude Code Settings...");
|
|
2511
|
+
const settingsDir = join4(homedir2(), ".claude");
|
|
2512
|
+
const settingsPath = join4(settingsDir, "settings.json");
|
|
2513
|
+
const result = ensureClaudeSettings(settingsPath);
|
|
2514
|
+
if (result === "created") {
|
|
2515
|
+
console.log(` Created: ${settingsPath}`);
|
|
2516
|
+
} else if (result === "updated") {
|
|
2517
|
+
console.log(` Updated with new REAPER tools: ${settingsPath}`);
|
|
2518
|
+
} else {
|
|
2519
|
+
console.log(` Already configured: ${settingsPath}`);
|
|
2520
|
+
}
|
|
2521
|
+
console.log("");
|
|
2522
|
+
}
|
|
2523
|
+
if (selections.projectConfig) {
|
|
2524
|
+
console.log("Creating Project Config...");
|
|
2525
|
+
const mcpJsonPath = join4(process.cwd(), ".mcp.json");
|
|
2526
|
+
if (createMcpJson(mcpJsonPath)) {
|
|
2527
|
+
console.log(` Created: ${mcpJsonPath}`);
|
|
2528
|
+
} else {
|
|
2529
|
+
console.log(` Already exists: ${mcpJsonPath}`);
|
|
2530
|
+
}
|
|
2531
|
+
console.log("");
|
|
2532
|
+
}
|
|
2533
|
+
console.log("Running system check...");
|
|
2534
|
+
const bridgeRunning = await isBridgeRunning();
|
|
2535
|
+
console.log(` Lua bridge: ${bridgeRunning ? "Connected" : "Not detected (start after REAPER is open)"}`);
|
|
2536
|
+
const globalClaudeDir = join4(homedir2(), ".claude");
|
|
2537
|
+
const localAgents = existsSync2(join4(process.cwd(), ".claude", "agents"));
|
|
2538
|
+
const globalAgents = existsSync2(join4(globalClaudeDir, "agents"));
|
|
2539
|
+
const agentsExist = localAgents || globalAgents;
|
|
2540
|
+
console.log(` Mix agents: ${agentsExist ? "Installed" : "Not installed"}`);
|
|
2541
|
+
const mcpJsonExists = existsSync2(join4(process.cwd(), ".mcp.json"));
|
|
2542
|
+
console.log(` MCP config: ${mcpJsonExists ? ".mcp.json found" : ".mcp.json not present"}`);
|
|
2543
|
+
console.log("");
|
|
2544
|
+
console.log("Setup complete! Next steps:");
|
|
2545
|
+
if (selections.bridge) {
|
|
2546
|
+
const scriptsDir = getReaperScriptsPath();
|
|
2547
|
+
const luaDest = join4(scriptsDir, "mcp_bridge.lua");
|
|
2548
|
+
console.log(" 1. Open REAPER");
|
|
2549
|
+
console.log(" 2. Actions > Show action list > Load ReaScript");
|
|
2550
|
+
console.log(` 3. Select: ${luaDest}`);
|
|
2551
|
+
console.log(" 4. Run the script (it keeps running via defer loop)");
|
|
2552
|
+
console.log(" 5. Open Claude Code \u2014 REAPER tools + mix engineer agents are ready");
|
|
2553
|
+
} else {
|
|
2554
|
+
console.log(" 1. Open Claude Code \u2014 REAPER tools + mix engineer agents are ready");
|
|
2555
|
+
}
|
|
2556
|
+
if (agentsExist) {
|
|
2557
|
+
console.log('\nTry: @mix-engineer "Please gain stage my tracks"');
|
|
2558
|
+
console.log('Or: @mix-analyzer "Roast my mix"');
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2394
2562
|
// apps/reaper-mcp-server/src/main.ts
|
|
2395
2563
|
var __dirname = dirname2(fileURLToPath2(import.meta.url));
|
|
2396
2564
|
async function setup() {
|
|
@@ -2399,23 +2567,23 @@ async function setup() {
|
|
|
2399
2567
|
console.log(`Bridge directory: ${bridgeDir}
|
|
2400
2568
|
`);
|
|
2401
2569
|
const scriptsDir = getReaperScriptsPath();
|
|
2402
|
-
|
|
2570
|
+
mkdirSync3(scriptsDir, { recursive: true });
|
|
2403
2571
|
const reaperDir = resolveAssetDir(__dirname, "reaper");
|
|
2404
|
-
const luaSrc =
|
|
2405
|
-
const luaDest =
|
|
2572
|
+
const luaSrc = join5(reaperDir, "mcp_bridge.lua");
|
|
2573
|
+
const luaDest = join5(scriptsDir, "mcp_bridge.lua");
|
|
2406
2574
|
console.log("Installing Lua bridge...");
|
|
2407
2575
|
if (installFile(luaSrc, luaDest)) {
|
|
2408
2576
|
console.log(` Installed: mcp_bridge.lua`);
|
|
2409
2577
|
} else {
|
|
2410
2578
|
console.log(` Not found: ${luaSrc}`);
|
|
2411
2579
|
}
|
|
2412
|
-
const effectsDir =
|
|
2413
|
-
|
|
2580
|
+
const effectsDir = join5(getReaperEffectsPath(), "reaper-mcp");
|
|
2581
|
+
mkdirSync3(effectsDir, { recursive: true });
|
|
2414
2582
|
console.log("\nInstalling JSFX analyzers...");
|
|
2415
2583
|
for (const jsfx of REAPER_ASSETS) {
|
|
2416
2584
|
if (jsfx === "mcp_bridge.lua") continue;
|
|
2417
|
-
const src =
|
|
2418
|
-
const dest =
|
|
2585
|
+
const src = join5(reaperDir, jsfx);
|
|
2586
|
+
const dest = join5(effectsDir, jsfx);
|
|
2419
2587
|
if (installFile(src, dest)) {
|
|
2420
2588
|
console.log(` Installed: reaper-mcp/${jsfx}`);
|
|
2421
2589
|
} else {
|
|
@@ -2438,41 +2606,41 @@ async function installSkills(scope) {
|
|
|
2438
2606
|
console.log(`REAPER MCP \u2014 Install AI Mix Engineer Skills (scope: ${scope})
|
|
2439
2607
|
`);
|
|
2440
2608
|
const isGlobal = scope === "global";
|
|
2441
|
-
const baseDir = isGlobal ?
|
|
2442
|
-
const claudeDir = isGlobal ? baseDir :
|
|
2609
|
+
const baseDir = isGlobal ? join5(homedir3(), ".claude") : process.cwd();
|
|
2610
|
+
const claudeDir = isGlobal ? baseDir : join5(baseDir, ".claude");
|
|
2443
2611
|
const knowledgeSrc = resolveAssetDir(__dirname, "knowledge");
|
|
2444
|
-
if (
|
|
2445
|
-
const dest =
|
|
2612
|
+
if (existsSync3(knowledgeSrc)) {
|
|
2613
|
+
const dest = join5(baseDir, "knowledge");
|
|
2446
2614
|
const count = copyDirSync(knowledgeSrc, dest);
|
|
2447
2615
|
console.log(`Installed knowledge base: ${count} files \u2192 ${dest}`);
|
|
2448
2616
|
} else {
|
|
2449
2617
|
console.log("Knowledge base not found in package. Skipping.");
|
|
2450
2618
|
}
|
|
2451
|
-
const rulesSrc = resolveAssetDirWithFallback(__dirname, "claude-rules",
|
|
2452
|
-
if (
|
|
2453
|
-
const dest =
|
|
2619
|
+
const rulesSrc = resolveAssetDirWithFallback(__dirname, "claude-rules", join5(".claude", "rules"));
|
|
2620
|
+
if (existsSync3(rulesSrc)) {
|
|
2621
|
+
const dest = join5(claudeDir, "rules");
|
|
2454
2622
|
const count = copyDirSync(rulesSrc, dest);
|
|
2455
2623
|
console.log(`Installed Claude rules: ${count} files \u2192 ${dest}`);
|
|
2456
2624
|
} else {
|
|
2457
2625
|
console.log("Claude rules not found in package. Skipping.");
|
|
2458
2626
|
}
|
|
2459
|
-
const skillsSrc = resolveAssetDirWithFallback(__dirname, "claude-skills",
|
|
2460
|
-
if (
|
|
2461
|
-
const dest =
|
|
2627
|
+
const skillsSrc = resolveAssetDirWithFallback(__dirname, "claude-skills", join5(".claude", "skills"));
|
|
2628
|
+
if (existsSync3(skillsSrc)) {
|
|
2629
|
+
const dest = join5(claudeDir, "skills");
|
|
2462
2630
|
const count = copyDirSync(skillsSrc, dest);
|
|
2463
2631
|
console.log(`Installed Claude skills: ${count} files \u2192 ${dest}`);
|
|
2464
2632
|
} else {
|
|
2465
2633
|
console.log("Claude skills not found in package. Skipping.");
|
|
2466
2634
|
}
|
|
2467
|
-
const agentsSrc = resolveAssetDirWithFallback(__dirname, "claude-agents",
|
|
2468
|
-
if (
|
|
2469
|
-
const dest =
|
|
2635
|
+
const agentsSrc = resolveAssetDirWithFallback(__dirname, "claude-agents", join5(".claude", "agents"));
|
|
2636
|
+
if (existsSync3(agentsSrc)) {
|
|
2637
|
+
const dest = join5(claudeDir, "agents");
|
|
2470
2638
|
const count = copyDirSync(agentsSrc, dest);
|
|
2471
2639
|
console.log(`Installed Claude agents: ${count} files \u2192 ${dest}`);
|
|
2472
2640
|
} else {
|
|
2473
2641
|
console.log("Claude agents not found in package. Skipping.");
|
|
2474
2642
|
}
|
|
2475
|
-
const settingsPath =
|
|
2643
|
+
const settingsPath = join5(claudeDir, "settings.json");
|
|
2476
2644
|
const result = ensureClaudeSettings(settingsPath);
|
|
2477
2645
|
if (result === "created") {
|
|
2478
2646
|
console.log(`Created Claude settings: ${settingsPath}`);
|
|
@@ -2482,7 +2650,7 @@ async function installSkills(scope) {
|
|
|
2482
2650
|
console.log(`Claude settings already has all REAPER tools: ${settingsPath}`);
|
|
2483
2651
|
}
|
|
2484
2652
|
if (!isGlobal) {
|
|
2485
|
-
const mcpJsonPath =
|
|
2653
|
+
const mcpJsonPath = join5(baseDir, ".mcp.json");
|
|
2486
2654
|
if (createMcpJson(mcpJsonPath)) {
|
|
2487
2655
|
console.log(`
|
|
2488
2656
|
Created: ${mcpJsonPath}`);
|
|
@@ -2503,24 +2671,24 @@ async function doctor() {
|
|
|
2503
2671
|
if (!bridgeRunning) {
|
|
2504
2672
|
console.log(' \u2192 Run "npx @mthines/reaper-mcp setup" then load mcp_bridge.lua in REAPER');
|
|
2505
2673
|
}
|
|
2506
|
-
const globalClaudeDir =
|
|
2507
|
-
const localAgents =
|
|
2508
|
-
const globalAgents =
|
|
2674
|
+
const globalClaudeDir = join5(homedir3(), ".claude");
|
|
2675
|
+
const localAgents = existsSync3(join5(process.cwd(), ".claude", "agents"));
|
|
2676
|
+
const globalAgents = existsSync3(join5(globalClaudeDir, "agents"));
|
|
2509
2677
|
const agentsExist = localAgents || globalAgents;
|
|
2510
2678
|
const agentsLocation = localAgents ? ".claude/agents/" : globalAgents ? "~/.claude/agents/" : "";
|
|
2511
2679
|
console.log(`Mix agents: ${agentsExist ? `\u2713 Found (${agentsLocation})` : "\u2717 Not installed"}`);
|
|
2512
2680
|
if (!agentsExist) {
|
|
2513
2681
|
console.log(' \u2192 Run "npx @mthines/reaper-mcp install-skills"');
|
|
2514
2682
|
}
|
|
2515
|
-
const localKnowledge =
|
|
2516
|
-
const globalKnowledge =
|
|
2683
|
+
const localKnowledge = existsSync3(join5(process.cwd(), "knowledge"));
|
|
2684
|
+
const globalKnowledge = existsSync3(join5(globalClaudeDir, "knowledge"));
|
|
2517
2685
|
const knowledgeExists = localKnowledge || globalKnowledge;
|
|
2518
2686
|
const knowledgeLocation = localKnowledge ? "project" : globalKnowledge ? "~/.claude/" : "";
|
|
2519
2687
|
console.log(`Knowledge base: ${knowledgeExists ? `\u2713 Found (${knowledgeLocation})` : "\u2717 Not installed"}`);
|
|
2520
2688
|
if (!knowledgeExists) {
|
|
2521
2689
|
console.log(' \u2192 Run "npx @mthines/reaper-mcp install-skills"');
|
|
2522
2690
|
}
|
|
2523
|
-
const mcpJsonExists =
|
|
2691
|
+
const mcpJsonExists = existsSync3(join5(process.cwd(), ".mcp.json"));
|
|
2524
2692
|
console.log(`MCP config: ${mcpJsonExists ? "\u2713 .mcp.json found" : "\u2717 .mcp.json missing"}`);
|
|
2525
2693
|
if (!mcpJsonExists) {
|
|
2526
2694
|
console.log(' \u2192 Run "npx @mthines/reaper-mcp install-skills --project" to create .mcp.json');
|
|
@@ -2583,7 +2751,19 @@ async function serve() {
|
|
|
2583
2751
|
});
|
|
2584
2752
|
}
|
|
2585
2753
|
var command = process.argv[2];
|
|
2754
|
+
var cliArgs = process.argv.slice(3);
|
|
2755
|
+
var hasYesFlag = cliArgs.includes("--yes") || cliArgs.includes("-y");
|
|
2756
|
+
var hasProjectFlag = cliArgs.includes("--project");
|
|
2586
2757
|
switch (command) {
|
|
2758
|
+
case "init":
|
|
2759
|
+
runInit(
|
|
2760
|
+
{ yes: hasYesFlag, project: hasProjectFlag },
|
|
2761
|
+
() => __dirname
|
|
2762
|
+
).catch((err) => {
|
|
2763
|
+
console.error("Init failed:", err);
|
|
2764
|
+
process.exit(1);
|
|
2765
|
+
});
|
|
2766
|
+
break;
|
|
2587
2767
|
case "setup":
|
|
2588
2768
|
setup().catch((err) => {
|
|
2589
2769
|
console.error("Setup failed:", err);
|
|
@@ -2623,6 +2803,9 @@ switch (command) {
|
|
|
2623
2803
|
Usage:
|
|
2624
2804
|
npx @mthines/reaper-mcp Start MCP server (stdio mode)
|
|
2625
2805
|
npx @mthines/reaper-mcp serve Start MCP server (stdio mode)
|
|
2806
|
+
npx @mthines/reaper-mcp init Guided interactive setup (recommended for new users)
|
|
2807
|
+
npx @mthines/reaper-mcp init --yes Non-interactive setup (install everything with defaults)
|
|
2808
|
+
npx @mthines/reaper-mcp init --project Include .mcp.json in current directory
|
|
2626
2809
|
npx @mthines/reaper-mcp setup Install Lua bridge + JSFX analyzers into REAPER
|
|
2627
2810
|
npx @mthines/reaper-mcp install-skills Install AI knowledge + agents globally (default)
|
|
2628
2811
|
npx @mthines/reaper-mcp install-skills --project Install into current project directory
|
|
@@ -2630,7 +2813,10 @@ Usage:
|
|
|
2630
2813
|
npx @mthines/reaper-mcp doctor Check that everything is configured correctly
|
|
2631
2814
|
npx @mthines/reaper-mcp status Check if Lua bridge is running in REAPER
|
|
2632
2815
|
|
|
2633
|
-
Quick Start:
|
|
2816
|
+
Quick Start (interactive):
|
|
2817
|
+
npx @mthines/reaper-mcp init # guided setup \u2014 select components interactively
|
|
2818
|
+
|
|
2819
|
+
Quick Start (manual steps):
|
|
2634
2820
|
1. npx @mthines/reaper-mcp setup # install REAPER components
|
|
2635
2821
|
2. Load mcp_bridge.lua in REAPER (Actions > Load ReaScript > Run)
|
|
2636
2822
|
3. npx @mthines/reaper-mcp install-skills # install AI knowledge + agents (globally)
|
|
@@ -2638,7 +2824,7 @@ Quick Start:
|
|
|
2638
2824
|
|
|
2639
2825
|
Tip: install globally for shorter commands:
|
|
2640
2826
|
npm install -g @mthines/reaper-mcp
|
|
2641
|
-
reaper-mcp
|
|
2827
|
+
reaper-mcp init
|
|
2642
2828
|
`);
|
|
2643
2829
|
break;
|
|
2644
2830
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mthines/reaper-mcp",
|
|
3
|
-
"version": "0.14.1",
|
|
3
|
+
"version": "0.14.2-beta.17.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server for controlling REAPER DAW — real-time mixing, FX control, and frequency analysis for AI agents",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"reaper-mcp": "./main.js"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@inquirer/prompts": "^8.3.2",
|
|
30
31
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
31
32
|
"@opentelemetry/api": "^1.9.0",
|
|
32
33
|
"@opentelemetry/auto-instrumentations-node": "^0.71.0",
|
package/reaper/mcp_bridge.lua
CHANGED
|
@@ -584,23 +584,10 @@ function handlers.read_track_spectrum(params)
|
|
|
584
584
|
local track = reaper.GetTrack(0, idx)
|
|
585
585
|
if not track then return nil, "Track " .. idx .. " not found" end
|
|
586
586
|
|
|
587
|
-
--
|
|
588
|
-
local analyzer_idx =
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
local _, name = reaper.TrackFX_GetFXName(track, i)
|
|
592
|
-
if name and name:find(MCP_ANALYZER_FX_NAME) then
|
|
593
|
-
analyzer_idx = i
|
|
594
|
-
break
|
|
595
|
-
end
|
|
596
|
-
end
|
|
597
|
-
|
|
598
|
-
-- Auto-insert if not present
|
|
599
|
-
if analyzer_idx < 0 then
|
|
600
|
-
analyzer_idx = reaper.TrackFX_AddByName(track, MCP_ANALYZER_FX_NAME, false, -1)
|
|
601
|
-
if analyzer_idx < 0 then
|
|
602
|
-
return nil, "MCP Spectrum Analyzer JSFX not found. Run 'reaper-mcp setup' to install it."
|
|
603
|
-
end
|
|
587
|
+
-- Find or auto-insert analyzer (uses MCP Meters container if available)
|
|
588
|
+
local analyzer_idx, err = ensure_jsfx_on_track(track, MCP_ANALYZER_FX_NAME)
|
|
589
|
+
if not analyzer_idx then
|
|
590
|
+
return nil, err or "MCP Spectrum Analyzer JSFX not found. Run 'reaper-mcp setup' to install it."
|
|
604
591
|
end
|
|
605
592
|
|
|
606
593
|
-- Read spectrum data from gmem
|
|
@@ -1228,10 +1215,119 @@ end
|
|
|
1228
1215
|
local MCP_LUFS_METER_FX_NAME = "reaper-mcp/mcp_lufs_meter"
|
|
1229
1216
|
local MCP_CORRELATION_METER_FX_NAME = "reaper-mcp/mcp_correlation_meter"
|
|
1230
1217
|
local MCP_CREST_FACTOR_FX_NAME = "reaper-mcp/mcp_crest_factor"
|
|
1218
|
+
local MCP_FX_PREFIX = "reaper%-mcp/" -- Lua pattern for matching MCP JSFX names
|
|
1219
|
+
|
|
1220
|
+
-- Per-track cache of MCP container FX index to avoid rescanning on every read.
|
|
1221
|
+
-- Keyed by track pointer (userdata), value is container FX index or false (no container).
|
|
1222
|
+
-- Invalidated when the FX chain changes (checked via TrackFX_GetCount).
|
|
1223
|
+
local container_cache = {}
|
|
1224
|
+
|
|
1225
|
+
-- Find or create the MCP Meters container on a track.
|
|
1226
|
+
-- Uses REAPER 7.06+ container_item.X API. Returns container FX index, or nil
|
|
1227
|
+
-- if containers are not supported (REAPER < 7.06).
|
|
1228
|
+
local function find_or_create_mcp_container(track)
|
|
1229
|
+
-- Check cache first
|
|
1230
|
+
local cache_entry = container_cache[tostring(track)]
|
|
1231
|
+
if cache_entry then
|
|
1232
|
+
local cached_idx, cached_fx_count = cache_entry[1], cache_entry[2]
|
|
1233
|
+
local current_fx_count = reaper.TrackFX_GetCount(track)
|
|
1234
|
+
if current_fx_count == cached_fx_count then
|
|
1235
|
+
if cached_idx == false then return nil end -- cached negative result
|
|
1236
|
+
return cached_idx
|
|
1237
|
+
end
|
|
1238
|
+
-- FX count changed — invalidate cache
|
|
1239
|
+
container_cache[tostring(track)] = nil
|
|
1240
|
+
end
|
|
1241
|
+
|
|
1242
|
+
local fx_count = reaper.TrackFX_GetCount(track)
|
|
1243
|
+
|
|
1244
|
+
-- Search for existing container that holds MCP JSFX
|
|
1245
|
+
for i = 0, fx_count - 1 do
|
|
1246
|
+
local _, name = reaper.TrackFX_GetFXName(track, i)
|
|
1247
|
+
if name and (name:find("Container") or name:find("MCP Meters")) then
|
|
1248
|
+
local ok, count_str = reaper.TrackFX_GetNamedConfigParm(track, i, "container_count")
|
|
1249
|
+
if ok then
|
|
1250
|
+
local count = tonumber(count_str) or 0
|
|
1251
|
+
for j = 0, count - 1 do
|
|
1252
|
+
local _, addr_str = reaper.TrackFX_GetNamedConfigParm(track, i, "container_item." .. j)
|
|
1253
|
+
local addr = tonumber(addr_str)
|
|
1254
|
+
if addr then
|
|
1255
|
+
local _, fx_name = reaper.TrackFX_GetFXName(track, addr)
|
|
1256
|
+
if fx_name and fx_name:find(MCP_FX_PREFIX) then
|
|
1257
|
+
-- Found our container — cache and return
|
|
1258
|
+
container_cache[tostring(track)] = { i, fx_count }
|
|
1259
|
+
return i
|
|
1260
|
+
end
|
|
1261
|
+
end
|
|
1262
|
+
end
|
|
1263
|
+
end
|
|
1264
|
+
end
|
|
1265
|
+
end
|
|
1266
|
+
|
|
1267
|
+
-- No existing MCP container — create one at end of chain
|
|
1268
|
+
local idx = reaper.TrackFX_AddByName(track, "Container", false, -1)
|
|
1269
|
+
if idx < 0 then
|
|
1270
|
+
-- Container FX not available (REAPER < 7)
|
|
1271
|
+
container_cache[tostring(track)] = { false, fx_count }
|
|
1272
|
+
return nil
|
|
1273
|
+
end
|
|
1274
|
+
|
|
1275
|
+
-- Name the container "MCP Meters" for easy identification
|
|
1276
|
+
reaper.TrackFX_SetNamedConfigParm(track, idx, "renamed_name", "MCP Meters")
|
|
1277
|
+
|
|
1278
|
+
-- Verify container API works (confirms REAPER 7.06+)
|
|
1279
|
+
local ok = reaper.TrackFX_GetNamedConfigParm(track, idx, "container_count")
|
|
1280
|
+
if not ok then
|
|
1281
|
+
-- API not available — remove the container and fall back
|
|
1282
|
+
reaper.TrackFX_Delete(track, idx)
|
|
1283
|
+
container_cache[tostring(track)] = { false, fx_count }
|
|
1284
|
+
return nil
|
|
1285
|
+
end
|
|
1286
|
+
|
|
1287
|
+
-- Cache the new container (fx_count + 1 because we just added the container)
|
|
1288
|
+
container_cache[tostring(track)] = { idx, reaper.TrackFX_GetCount(track) }
|
|
1289
|
+
return idx
|
|
1290
|
+
end
|
|
1231
1291
|
|
|
1232
1292
|
-- Helper: find or auto-insert a named JSFX on a track.
|
|
1233
|
-
--
|
|
1293
|
+
-- Tries to place inside an "MCP Meters" FX Container (REAPER 7.06+).
|
|
1294
|
+
-- Falls back to direct insertion on older REAPER versions.
|
|
1295
|
+
-- Returns the FX index (possibly container-addressed) on success, or nil + error.
|
|
1234
1296
|
local function ensure_jsfx_on_track(track, fx_name)
|
|
1297
|
+
-- Try container approach first (REAPER 7.06+)
|
|
1298
|
+
local container_idx = find_or_create_mcp_container(track)
|
|
1299
|
+
if container_idx then
|
|
1300
|
+
local ok, count_str = reaper.TrackFX_GetNamedConfigParm(track, container_idx, "container_count")
|
|
1301
|
+
local count = tonumber(count_str) or 0
|
|
1302
|
+
|
|
1303
|
+
-- Search inside container for existing JSFX
|
|
1304
|
+
for i = 0, count - 1 do
|
|
1305
|
+
local _, addr_str = reaper.TrackFX_GetNamedConfigParm(track, container_idx, "container_item." .. i)
|
|
1306
|
+
local addr = tonumber(addr_str)
|
|
1307
|
+
if addr then
|
|
1308
|
+
local _, name = reaper.TrackFX_GetFXName(track, addr)
|
|
1309
|
+
if name and name:find(fx_name, 1, true) then
|
|
1310
|
+
return addr -- Found inside container
|
|
1311
|
+
end
|
|
1312
|
+
end
|
|
1313
|
+
end
|
|
1314
|
+
|
|
1315
|
+
-- Not found — insert inside container
|
|
1316
|
+
-- Use legacy addressing: 0x2000000 + (1-based slot) * (top_chain_count + 1) + (1-based container pos)
|
|
1317
|
+
local top_count = reaper.TrackFX_GetCount(track)
|
|
1318
|
+
local slot = count + 1 -- 1-based, next empty slot
|
|
1319
|
+
local container_pos = container_idx + 1 -- 1-based
|
|
1320
|
+
local insert_addr = 0x2000000 + slot * (top_count + 1) + container_pos
|
|
1321
|
+
local new_idx = reaper.TrackFX_AddByName(track, fx_name, false, insert_addr)
|
|
1322
|
+
if new_idx >= 0 then
|
|
1323
|
+
-- Invalidate cache since FX count changed
|
|
1324
|
+
container_cache[tostring(track)] = nil
|
|
1325
|
+
return new_idx
|
|
1326
|
+
end
|
|
1327
|
+
-- Container insertion failed — fall through to direct
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
-- Fallback: direct insertion (REAPER < 7.06 or container failed)
|
|
1235
1331
|
local fx_count = reaper.TrackFX_GetCount(track)
|
|
1236
1332
|
for i = 0, fx_count - 1 do
|
|
1237
1333
|
local _, name = reaper.TrackFX_GetFXName(track, i)
|
|
@@ -1239,7 +1335,6 @@ local function ensure_jsfx_on_track(track, fx_name)
|
|
|
1239
1335
|
return i
|
|
1240
1336
|
end
|
|
1241
1337
|
end
|
|
1242
|
-
-- Auto-insert
|
|
1243
1338
|
local idx = reaper.TrackFX_AddByName(track, fx_name, false, -1)
|
|
1244
1339
|
if idx < 0 then
|
|
1245
1340
|
return nil, fx_name .. " JSFX not found. Run 'reaper-mcp setup' to install it."
|