@map-audio/pam-mcp-server 1.18.1 → 1.22.0

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -3
  3. package/dist/audioAnalysis.d.ts +35 -0
  4. package/dist/audioAnalysis.js +277 -0
  5. package/dist/audioAnalysis.js.map +1 -0
  6. package/dist/bridge.d.ts +2 -0
  7. package/dist/bridge.js +113 -24
  8. package/dist/bridge.js.map +1 -1
  9. package/dist/corpusFingerprints.d.ts +17 -0
  10. package/dist/corpusFingerprints.js +10 -0
  11. package/dist/corpusFingerprints.js.map +1 -0
  12. package/dist/corpusRanges.d.ts +28 -0
  13. package/dist/corpusRanges.js +1029 -0
  14. package/dist/corpusRanges.js.map +1 -0
  15. package/dist/drumSynth.d.ts +96 -0
  16. package/dist/drumSynth.js +193 -0
  17. package/dist/drumSynth.js.map +1 -0
  18. package/dist/drumSynthEngines.json +1736 -0
  19. package/dist/fingerprint.d.ts +70 -0
  20. package/dist/fingerprint.js +240 -0
  21. package/dist/fingerprint.js.map +1 -0
  22. package/dist/generated/variationSnapshot.d.ts +31 -0
  23. package/dist/generated/variationSnapshot.js +62 -0
  24. package/dist/generated/variationSnapshot.js.map +1 -0
  25. package/dist/manifest-cell1.json +5414 -396
  26. package/dist/manifest-mfx.json +12013 -1714
  27. package/dist/manifest.d.ts +24 -0
  28. package/dist/manifest.js +60 -5
  29. package/dist/manifest.js.map +1 -1
  30. package/dist/manifest.json +24881 -547
  31. package/dist/mfxCombos.d.ts +45 -0
  32. package/dist/mfxCombos.js +499 -0
  33. package/dist/mfxCombos.js.map +1 -0
  34. package/dist/mixAssist.d.ts +6 -0
  35. package/dist/mixAssist.js +101 -11
  36. package/dist/mixAssist.js.map +1 -1
  37. package/dist/productProfile.d.ts +9 -0
  38. package/dist/productProfile.js +20 -0
  39. package/dist/productProfile.js.map +1 -0
  40. package/dist/server.js +1979 -342
  41. package/dist/server.js.map +1 -1
  42. package/dist/variationSessionKeys.json +110 -0
  43. package/package.json +10 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Can Ince
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -77,12 +77,12 @@ Use the same `mcpServers` block in the client's MCP config. Any stdio-compatible
77
77
 
78
78
  ## What's in the box
79
79
 
80
- ~137 tools. The high-value entry points:
80
+ ~145 tools. The high-value entry points:
81
81
 
82
82
  - **Read first**: `design_context` (curated parameter cheat sheet, works offline) and `get_design_overview` (compact live snapshot). Prefer these over full `get_state` dumps or broad `get_parameter_info` sweeps.
83
83
  - **Sound design**: `set_parameters`, `update_pattern`, `add_modulation` / `add_param_seq`, `update_effect_config`, `set_master_effects`, `set_drum_synth`, `randomize` / `randomize_samples` / `randomize_patterns`.
84
84
  - **Samples**: `get_default_locations` → `list_samples` → `load_sample`, plus `trim/reverse/gain/normalize_sample`, `analyze_sample_tempo`, `resample_sample_to_bpm`, `build_sample_chain`.
85
- - **Transport & MIDI**: `start_transport` / `stop_transport` / `full_stop_transport`, `set_bpm`, `trigger_midi` (+ CC / program change), `midi_recording`.
85
+ - **Transport, MIDI & devices**: `start_transport` / `stop_transport` / `full_stop_transport`, `set_bpm`, `trigger_midi` (+ CC / program change), `midi_recording`; `get_audio_device_setup` / `set_audio_device` / `set_midi_io` (standalone).
86
86
  - **Presets, variations & songs**: `load_preset` / `save_preset` / `new_preset`, `save_variation` / `load_variation` / `queue_variation`, `song_mode`, `export_song` (full-track bounce), `export_audio` (incl. per-cell stems).
87
87
  - **Looper**: full 8-track control — arm/record/capture, `load_audio_to_looper_track`, warp, looper variations, `live_resample`.
88
88
  - **Plugin host** (PAM builds with hosting): `scan_plugins` → `get_plugin_scan_status` → `list_plugins` → `load_plugin` → `list_loaded_plugins` → `set_plugin_param` / `set_plugin_bypass`.
@@ -90,7 +90,9 @@ Use the same `mcpServers` block in the client's MCP config. Any stdio-compatible
90
90
  - **Video (VX) & UI** — require PAM's window to be open: `get_video_state`, `add_video_layer`, `bind_video_param`, `video_command`, …, and `capture_ui` (deterministic UI screenshot).
91
91
  - **Offline (manifest-only)**: `get_parameter_info`, `design_context`, `list_presets`, `read_preset`, `get_manifest_summary`.
92
92
 
93
- All three product manifests (PAM / CELL1 / MFX) are bundled; the server auto-selects by the connected instance's product, so one config entry drives all three products.
93
+ - **MFX**: `set_mfx_routing` reads and replaces the cable patch (`input` / `sidechain` → `cellN` → `output`). MFX-only; it errors on PAM and CELL1.
94
+
95
+ All three product manifests (PAM / CELL1 / MFX) are bundled; this generic server auto-selects by the connected instance's product, so one development config entry can drive all three products. Preset listing follows that product too — each has its own tree under `MAP Audio`. For a customer-facing MFX-only namespace, use `@map-audio/mfx-mcp-server`; it pins MFX discovery and removes unrelated sampler workflows from tool discovery.
94
96
 
95
97
  ## Agent ergonomics
96
98
 
@@ -0,0 +1,35 @@
1
+ export type WavData = {
2
+ sampleRate: number;
3
+ channels: number;
4
+ /** Mono mix, -1..1. */
5
+ samples: Float32Array;
6
+ durationSeconds: number;
7
+ };
8
+ /** Minimal RIFF/WAVE reader — PCM 16/24/32-bit and 32-bit float, which covers
9
+ * everything PAM's exporters write. Chunks are walked rather than assumed at
10
+ * fixed offsets (exporters interleave LIST/fact chunks). */
11
+ export declare function decodeWav(buf: Buffer): WavData;
12
+ export type MotionMetrics = {
13
+ durationSeconds: number;
14
+ silent: boolean;
15
+ peak: number;
16
+ rmsDb: number;
17
+ /** Spectral flux: how much the spectrum changes frame to frame. */
18
+ fluxMean: number;
19
+ fluxVariability: number;
20
+ /** Brightness wander over the whole file. */
21
+ centroidHzMean: number;
22
+ centroidVariability: number;
23
+ /** Per-bar aggregates — the timescale a listener actually tracks. */
24
+ bars: number | null;
25
+ perBarCentroidVariability: number | null;
26
+ perBarRmsVariability: number | null;
27
+ /** Level movement WITHIN a bar, averaged over bars. Between-bar variance
28
+ * alone misses a bar-synced tremolo, gate or sidechain pump: every bar is
29
+ * identical, so the whole GROOVE layer reads as static without this. */
30
+ intraBarRmsVariability: number | null;
31
+ /** True when nothing meaningfully changes across the file. */
32
+ static: boolean;
33
+ notes: string[];
34
+ };
35
+ export declare function analyzeMotion(wav: WavData, bpm?: number): MotionMetrics;
@@ -0,0 +1,277 @@
1
+ // Time-domain / spectral motion analysis over rendered WAV files.
2
+ //
3
+ // WHY: the only audio feedback the MCP exposed was loudness and balance
4
+ // (get_master_analysis: LUFS/peak/crest; get_mix_analysis: an aggregate Bark
5
+ // spectrum and onset density). Both answer "is it mixed?". Neither answers
6
+ // "does it MOVE?", which is the property that separates a preset from a sample
7
+ // player — so nothing in the design loop ever optimized for it.
8
+ //
9
+ // Everything here is pure Node over a decoded WAV: no C++ change, no plugin
10
+ // connection, and it works on any file the render path produced.
11
+ /** Minimal RIFF/WAVE reader — PCM 16/24/32-bit and 32-bit float, which covers
12
+ * everything PAM's exporters write. Chunks are walked rather than assumed at
13
+ * fixed offsets (exporters interleave LIST/fact chunks). */
14
+ export function decodeWav(buf) {
15
+ if (buf.length < 12 || buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") {
16
+ throw new Error("Not a RIFF/WAVE file");
17
+ }
18
+ let pos = 12;
19
+ let fmt = null;
20
+ let dataStart = -1;
21
+ let dataLen = 0;
22
+ while (pos + 8 <= buf.length) {
23
+ const id = buf.toString("ascii", pos, pos + 4);
24
+ const size = buf.readUInt32LE(pos + 4);
25
+ const body = pos + 8;
26
+ if (id === "fmt ") {
27
+ fmt = {
28
+ audioFormat: buf.readUInt16LE(body),
29
+ channels: buf.readUInt16LE(body + 2),
30
+ sampleRate: buf.readUInt32LE(body + 4),
31
+ bitsPerSample: buf.readUInt16LE(body + 14),
32
+ };
33
+ }
34
+ else if (id === "data") {
35
+ dataStart = body;
36
+ // A streaming writer can leave size at 0 or 0xFFFFFFFF; fall back to the
37
+ // rest of the file rather than decoding nothing.
38
+ dataLen = size === 0 || body + size > buf.length ? buf.length - body : size;
39
+ }
40
+ pos = body + size + (size % 2); // chunks are word-aligned
41
+ if (fmt && dataStart >= 0 && pos >= buf.length)
42
+ break;
43
+ }
44
+ if (!fmt || dataStart < 0)
45
+ throw new Error("WAV missing fmt or data chunk");
46
+ const { channels, sampleRate, bitsPerSample, audioFormat } = fmt;
47
+ const bytesPerSample = bitsPerSample / 8;
48
+ const frames = Math.floor(dataLen / (bytesPerSample * channels));
49
+ const out = new Float32Array(frames);
50
+ const readOne = (off) => {
51
+ if (audioFormat === 3 && bitsPerSample === 32)
52
+ return buf.readFloatLE(off);
53
+ if (bitsPerSample === 16)
54
+ return buf.readInt16LE(off) / 32768;
55
+ if (bitsPerSample === 24) {
56
+ const v = buf.readUInt8(off) | (buf.readUInt8(off + 1) << 8) | (buf.readInt8(off + 2) << 16);
57
+ return v / 8388608;
58
+ }
59
+ if (bitsPerSample === 32)
60
+ return buf.readInt32LE(off) / 2147483648;
61
+ if (bitsPerSample === 8)
62
+ return (buf.readUInt8(off) - 128) / 128;
63
+ throw new Error(`Unsupported WAV format: ${bitsPerSample}-bit, format ${audioFormat}`);
64
+ };
65
+ for (let f = 0; f < frames; f += 1) {
66
+ let acc = 0;
67
+ const base = dataStart + f * bytesPerSample * channels;
68
+ for (let c = 0; c < channels; c += 1)
69
+ acc += readOne(base + c * bytesPerSample);
70
+ out[f] = acc / channels;
71
+ }
72
+ return { sampleRate, channels, samples: out, durationSeconds: frames / sampleRate };
73
+ }
74
+ /** In-place iterative radix-2 FFT. Size must be a power of two. */
75
+ function fft(re, im) {
76
+ const n = re.length;
77
+ for (let i = 1, j = 0; i < n; i += 1) {
78
+ let bit = n >> 1;
79
+ for (; j & bit; bit >>= 1)
80
+ j ^= bit;
81
+ j ^= bit;
82
+ if (i < j) {
83
+ [re[i], re[j]] = [re[j], re[i]];
84
+ [im[i], im[j]] = [im[j], im[i]];
85
+ }
86
+ }
87
+ for (let len = 2; len <= n; len <<= 1) {
88
+ const ang = (-2 * Math.PI) / len;
89
+ const wr = Math.cos(ang);
90
+ const wi = Math.sin(ang);
91
+ for (let i = 0; i < n; i += len) {
92
+ let cr = 1;
93
+ let ci = 0;
94
+ for (let k = 0; k < len / 2; k += 1) {
95
+ const ur = re[i + k];
96
+ const ui = im[i + k];
97
+ const vr = re[i + k + len / 2] * cr - im[i + k + len / 2] * ci;
98
+ const vi = re[i + k + len / 2] * ci + im[i + k + len / 2] * cr;
99
+ re[i + k] = ur + vr;
100
+ im[i + k] = ui + vi;
101
+ re[i + k + len / 2] = ur - vr;
102
+ im[i + k + len / 2] = ui - vi;
103
+ const nr = cr * wr - ci * wi;
104
+ ci = cr * wi + ci * wr;
105
+ cr = nr;
106
+ }
107
+ }
108
+ }
109
+ }
110
+ const FFT_SIZE = 1024;
111
+ const HOP = 512;
112
+ function spectralFrames(samples, sampleRate) {
113
+ const window = new Float64Array(FFT_SIZE);
114
+ for (let i = 0; i < FFT_SIZE; i += 1)
115
+ window[i] = 0.5 - 0.5 * Math.cos((2 * Math.PI * i) / (FFT_SIZE - 1));
116
+ const mag = [];
117
+ const times = [];
118
+ for (let start = 0; start + FFT_SIZE <= samples.length; start += HOP) {
119
+ const re = new Float64Array(FFT_SIZE);
120
+ const im = new Float64Array(FFT_SIZE);
121
+ for (let i = 0; i < FFT_SIZE; i += 1)
122
+ re[i] = samples[start + i] * window[i];
123
+ fft(re, im);
124
+ const half = FFT_SIZE / 2;
125
+ const m = new Float64Array(half);
126
+ for (let i = 0; i < half; i += 1)
127
+ m[i] = Math.hypot(re[i], im[i]);
128
+ mag.push(m);
129
+ times.push(start / sampleRate);
130
+ }
131
+ return { mag, times };
132
+ }
133
+ const mean = (a) => (a.length ? a.reduce((x, y) => x + y, 0) / a.length : 0);
134
+ const stdev = (a) => {
135
+ if (a.length < 2)
136
+ return 0;
137
+ const m = mean(a);
138
+ return Math.sqrt(mean(a.map((x) => (x - m) ** 2)));
139
+ };
140
+ /** Coefficient of variation — a scale-free "how much does this wander" number,
141
+ * so a quiet part and a loud part are judged the same way. */
142
+ const cv = (a) => {
143
+ const m = mean(a);
144
+ return m > 1e-9 ? stdev(a) / m : 0;
145
+ };
146
+ export function analyzeMotion(wav, bpm) {
147
+ const { samples, sampleRate } = wav;
148
+ let peak = 0;
149
+ let sumSq = 0;
150
+ for (let i = 0; i < samples.length; i += 1) {
151
+ const a = Math.abs(samples[i]);
152
+ if (a > peak)
153
+ peak = a;
154
+ sumSq += samples[i] * samples[i];
155
+ }
156
+ const rms = samples.length ? Math.sqrt(sumSq / samples.length) : 0;
157
+ const notes = [];
158
+ // Digital silence is the documented standalone offline-render failure, not a
159
+ // musical result — say so rather than reporting "no motion".
160
+ if (peak < 1e-5) {
161
+ return {
162
+ durationSeconds: wav.durationSeconds,
163
+ silent: true,
164
+ peak,
165
+ rmsDb: -Infinity,
166
+ fluxMean: 0,
167
+ fluxVariability: 0,
168
+ centroidHzMean: 0,
169
+ centroidVariability: 0,
170
+ bars: null,
171
+ perBarCentroidVariability: null,
172
+ perBarRmsVariability: null,
173
+ intraBarRmsVariability: null,
174
+ static: true,
175
+ notes: [
176
+ "The render is digital silence — every sample is zero. This is a render failure, not a static preset.",
177
+ "Known cause: offline export in the STANDALONE build produces silence. Capture with live_resample (source 'fullmix') while the transport rolls, or render from a plugin host instead.",
178
+ ],
179
+ };
180
+ }
181
+ const { mag, times } = spectralFrames(samples, sampleRate);
182
+ if (mag.length < 4) {
183
+ notes.push("Render too short to measure motion — need at least ~0.05 s of audio.");
184
+ }
185
+ const flux = [];
186
+ const centroid = [];
187
+ const frameRms = [];
188
+ const binHz = sampleRate / FFT_SIZE;
189
+ for (let f = 0; f < mag.length; f += 1) {
190
+ const m = mag[f];
191
+ let num = 0;
192
+ let den = 0;
193
+ let energy = 0;
194
+ for (let i = 0; i < m.length; i += 1) {
195
+ num += i * binHz * m[i];
196
+ den += m[i];
197
+ energy += m[i] * m[i];
198
+ }
199
+ centroid.push(den > 1e-9 ? num / den : 0);
200
+ frameRms.push(Math.sqrt(energy / m.length));
201
+ if (f > 0) {
202
+ const prev = mag[f - 1];
203
+ let d = 0;
204
+ // Half-wave rectified: only rising energy counts, which is what an onset
205
+ // or a filter opening looks like.
206
+ for (let i = 0; i < m.length; i += 1) {
207
+ const diff = m[i] - prev[i];
208
+ if (diff > 0)
209
+ d += diff;
210
+ }
211
+ flux.push(d);
212
+ }
213
+ }
214
+ let bars = null;
215
+ let perBarCentroidVariability = null;
216
+ let perBarRmsVariability = null;
217
+ let intraBarRmsVariability = null;
218
+ if (bpm && bpm > 0) {
219
+ const barSeconds = (60 / bpm) * 4;
220
+ bars = Math.floor(wav.durationSeconds / barSeconds);
221
+ if (bars >= 2) {
222
+ const barCentroid = [];
223
+ const barRms = [];
224
+ const withinBar = [];
225
+ for (let b = 0; b < bars; b += 1) {
226
+ const lo = b * barSeconds;
227
+ const hi = lo + barSeconds;
228
+ const idx = times.map((t, i) => ({ t, i })).filter(({ t }) => t >= lo && t < hi).map(({ i }) => i);
229
+ if (!idx.length)
230
+ continue;
231
+ barCentroid.push(mean(idx.map((i) => centroid[i])));
232
+ barRms.push(mean(idx.map((i) => frameRms[i])));
233
+ withinBar.push(cv(idx.map((i) => frameRms[i])));
234
+ }
235
+ perBarCentroidVariability = Math.round(cv(barCentroid) * 10000) / 10000;
236
+ perBarRmsVariability = Math.round(cv(barRms) * 10000) / 10000;
237
+ intraBarRmsVariability = Math.round(mean(withinBar) * 10000) / 10000;
238
+ }
239
+ else {
240
+ notes.push(`Only ${bars} full bar(s) at ${bpm} BPM — per-bar motion needs at least 2. Render longer.`);
241
+ }
242
+ }
243
+ else {
244
+ notes.push("No bpm supplied, so per-bar motion was not computed. Pass bpm for the timescale a listener tracks.");
245
+ }
246
+ const fluxVariability = Math.round(cv(flux) * 10000) / 10000;
247
+ const centroidVariability = Math.round(cv(centroid) * 10000) / 10000;
248
+ // Thresholds are deliberately loose — this flags "nothing happens", it does
249
+ // not grade musicality. A sustained pad legitimately scores low on flux but
250
+ // should still wander in centroid or per-bar level if anything modulates it.
251
+ const isStatic = centroidVariability < 0.05 &&
252
+ (perBarCentroidVariability === null || perBarCentroidVariability < 0.02) &&
253
+ (perBarRmsVariability === null || perBarRmsVariability < 0.05) &&
254
+ // A bar-synced gate/tremolo/pump makes every bar identical, so it only
255
+ // shows up inside the bar.
256
+ (intraBarRmsVariability === null || intraBarRmsVariability < 0.15);
257
+ if (isStatic) {
258
+ notes.push("Nothing changes measurably over time: brightness and per-bar level are both flat. Either no modulation reached an audible target, or its depth is too low to hear.");
259
+ }
260
+ return {
261
+ durationSeconds: Math.round(wav.durationSeconds * 1000) / 1000,
262
+ silent: false,
263
+ peak: Math.round(peak * 10000) / 10000,
264
+ rmsDb: Math.round(20 * Math.log10(Math.max(rms, 1e-12)) * 10) / 10,
265
+ fluxMean: Math.round(mean(flux) * 100) / 100,
266
+ fluxVariability,
267
+ centroidHzMean: Math.round(mean(centroid)),
268
+ centroidVariability,
269
+ bars,
270
+ perBarCentroidVariability,
271
+ perBarRmsVariability,
272
+ intraBarRmsVariability,
273
+ static: isStatic,
274
+ notes,
275
+ };
276
+ }
277
+ //# sourceMappingURL=audioAnalysis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioAnalysis.js","sourceRoot":"","sources":["../src/audioAnalysis.ts"],"names":[],"mappings":"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"}
package/dist/bridge.d.ts CHANGED
@@ -31,6 +31,8 @@ export declare class PluginBridge {
31
31
  getConnectedInstance(): BridgeInfo | null;
32
32
  private static readonly SLOW_ACTIONS;
33
33
  private static readonly VERY_SLOW_ACTIONS;
34
+ private static readonly ACTION_TIMEOUTS_MS;
35
+ private static timeoutFor;
34
36
  send(action: string, ...args: unknown[]): Promise<unknown>;
35
37
  private translateBridgeError;
36
38
  private processBuffer;
package/dist/bridge.js CHANGED
@@ -2,7 +2,9 @@ import { readFile } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
3
  import { createConnection } from "node:net";
4
4
  import { homedir } from "node:os";
5
+ import { mcpProductProfile } from "./productProfile.js";
5
6
  const DISCOVERY_FILE = resolve(homedir(), ".pam-mcp-bridge.json");
7
+ const BRIDGE_PRODUCT = mcpProductProfile();
6
8
  // Minimum PAM build that ships the bridge handlers added in this server version.
7
9
  // Tools registered against actions introduced after this version will return
8
10
  // "unknown action 'X'" from older PAM builds — the wrapper below translates
@@ -147,6 +149,16 @@ const MODEL_ACTIONS = new Set([
147
149
  "modelDelete",
148
150
  "modelList",
149
151
  ]);
152
+ /**
153
+ * net.createConnection() THROWS synchronously on a port outside [0, 65535] (or
154
+ * a non-number). Inside connect()'s Promise executor that surfaces as a
155
+ * rejection, and the startup `bridge.connect()` call has no catch — so one
156
+ * garbled discovery entry used to take the whole stdio server down with
157
+ * "Connection closed" and no diagnostic. Every port is screened through here.
158
+ */
159
+ function isUsablePort(port) {
160
+ return typeof port === "number" && Number.isInteger(port) && port > 0 && port < 65536;
161
+ }
150
162
  export class PluginBridge {
151
163
  socket = null;
152
164
  buffer = "";
@@ -166,6 +178,11 @@ export class PluginBridge {
166
178
  return entries.filter((e) => {
167
179
  if (!e || typeof e.pid !== "number" || e.pid <= 0)
168
180
  return false;
181
+ // A torn/garbled discovery write can carry a port that createConnection
182
+ // rejects synchronously (ERR_SOCKET_BAD_PORT). Drop it here so it can
183
+ // never reach connect().
184
+ if (!isUsablePort(e.port))
185
+ return false;
169
186
  try {
170
187
  process.kill(e.pid, 0);
171
188
  return true;
@@ -218,31 +235,58 @@ export class PluginBridge {
218
235
  else {
219
236
  instance = instances.find((i) => i.port === port) ?? null;
220
237
  }
238
+ // A caller-supplied port (select_instance) never passed through discover()'s
239
+ // screen, so validate before handing it to createConnection.
240
+ if (!isUsablePort(port))
241
+ return false;
221
242
  return new Promise((resolve) => {
222
- const timer = setTimeout(() => {
243
+ // connect() must never reject: the startup call site fires it
244
+ // unawaited, and an unhandled rejection kills the stdio server.
245
+ let timer;
246
+ const settle = (ok) => {
247
+ clearTimeout(timer);
248
+ resolve(ok);
249
+ };
250
+ timer = setTimeout(() => {
223
251
  if (!this.connected) {
224
252
  this.socket?.destroy();
225
- resolve(false);
253
+ settle(false);
226
254
  }
227
255
  }, 2000);
228
- this.socket = createConnection({ host: "127.0.0.1", port }, () => {
229
- clearTimeout(timer);
230
- this.connected = true;
231
- this.connectedInstance = instance;
232
- resolve(true);
233
- });
234
- this.socket.setEncoding("utf-8");
235
- this.socket.on("data", (chunk) => {
256
+ let socket;
257
+ try {
258
+ socket = createConnection({ host: "127.0.0.1", port }, () => {
259
+ this.connected = true;
260
+ this.connectedInstance = instance;
261
+ settle(true);
262
+ });
263
+ }
264
+ catch {
265
+ settle(false);
266
+ return;
267
+ }
268
+ this.socket = socket;
269
+ socket.setEncoding("utf-8");
270
+ socket.on("data", (chunk) => {
271
+ // Late data from a socket we've already replaced would interleave into
272
+ // the current connection's frame buffer.
273
+ if (this.socket !== socket)
274
+ return;
236
275
  this.buffer += chunk;
237
276
  this.processBuffer();
238
277
  });
239
- this.socket.on("error", () => {
240
- clearTimeout(timer);
278
+ socket.on("error", () => {
241
279
  this.connected = false;
242
- resolve(false);
280
+ settle(false);
243
281
  });
244
- this.socket.on("close", () => {
282
+ socket.on("close", () => {
283
+ if (this.socket !== socket)
284
+ return;
245
285
  this.connected = false;
286
+ this.connectedInstance = null;
287
+ // A drop mid-frame leaves a partial line behind; carrying it over would
288
+ // corrupt (and silently swallow) the next connection's first response.
289
+ this.buffer = "";
246
290
  // Reject all pending requests
247
291
  for (const [, req] of this.pending) {
248
292
  req.reject(new Error("Connection closed"));
@@ -256,6 +300,10 @@ export class PluginBridge {
256
300
  this.socket = null;
257
301
  this.connected = false;
258
302
  this.connectedInstance = null;
303
+ this.buffer = "";
304
+ for (const [, req] of this.pending)
305
+ req.reject(new Error("Connection closed"));
306
+ this.pending.clear();
259
307
  }
260
308
  isConnected() {
261
309
  return this.connected;
@@ -278,6 +326,11 @@ export class PluginBridge {
278
326
  "listDirectory",
279
327
  "importVisualFolder",
280
328
  "loadPlugin",
329
+ // Enumerating audio hardware runs currentDeviceType->scanForDevices()
330
+ // (StandaloneAudioDevice.cpp) on the message thread — a blocking CoreAudio
331
+ // rescan that routinely outruns the default 10 s budget.
332
+ "getAudioDeviceSetup",
333
+ "setAudioDevice",
281
334
  // Model lifecycle: trigger is fast, but load/unload/delete can join a
282
335
  // manager bootstrap thread for seconds (non-interruptible ORT/MLX loads).
283
336
  "modelDownload",
@@ -291,17 +344,44 @@ export class PluginBridge {
291
344
  // waits up to 180 s on the sidecar (pipeline rebuild + sequential inpaints),
292
345
  // so the TCP timeout must outlast it.
293
346
  static VERY_SLOW_ACTIONS = new Set(["workbenchRender"]);
347
+ // Per-action budgets that must outlast a bound the C++ handler enforces
348
+ // itself. Undershooting these is worse than a slow call: the tool reports a
349
+ // timeout while the plugin carries on, and an agent that retries queues a
350
+ // SECOND render (the duplicate-export trap in rules/mcp.md).
351
+ static ACTION_TIMEOUTS_MS = {
352
+ // handleAIGenerate blocks the message thread on the generation future for
353
+ // up to 60 s; the kit handler repeats that per request, up to 8 of them.
354
+ aiGenerate: 70_000,
355
+ aiGenerateKit: 500_000,
356
+ // These return immediately, but they kick off (or are polled during) an
357
+ // offline render that saturates the message thread — and the bridge only
358
+ // replies while drainQueue runs on the 30 Hz timer.
359
+ exportAudio: 120_000,
360
+ getExportStatus: 120_000,
361
+ analyzeMix: 120_000,
362
+ getMixAnalysis: 120_000,
363
+ resampleSampleToBPM: 60_000,
364
+ buildSampleChain: 60_000,
365
+ exportSliceForCell: 60_000,
366
+ requestLooperWarp: 60_000,
367
+ };
368
+ static timeoutFor(action) {
369
+ const explicit = PluginBridge.ACTION_TIMEOUTS_MS[action];
370
+ if (explicit !== undefined)
371
+ return explicit;
372
+ if (PluginBridge.VERY_SLOW_ACTIONS.has(action))
373
+ return 190_000;
374
+ if (PluginBridge.SLOW_ACTIONS.has(action))
375
+ return 30_000;
376
+ return 10_000;
377
+ }
294
378
  async send(action, ...args) {
295
379
  if (!this.connected || !this.socket) {
296
- throw new Error("Not connected to PAM plugin. Start PAM and ensure the MCP bridge is enabled.");
380
+ throw new Error(`Not connected to ${BRIDGE_PRODUCT.name}. Start ${BRIDGE_PRODUCT.name} and ensure the MCP bridge is enabled.`);
297
381
  }
298
382
  const id = this.nextId++;
299
383
  const msg = { id, action, args };
300
- const timeoutMs = PluginBridge.VERY_SLOW_ACTIONS.has(action)
301
- ? 190000
302
- : PluginBridge.SLOW_ACTIONS.has(action)
303
- ? 30000
304
- : 10000;
384
+ const timeoutMs = PluginBridge.timeoutFor(action);
305
385
  return new Promise((resolve, reject) => {
306
386
  const timer = setTimeout(() => {
307
387
  if (this.pending.has(id)) {
@@ -321,11 +401,17 @@ export class PluginBridge {
321
401
  // C++ MCPBridge::handleCommand emits this exact prefix for unrecognized actions.
322
402
  if (message.startsWith("unknown action '")) {
323
403
  if (PLUGIN_HOST_ACTIONS.has(action)) {
404
+ if (BRIDGE_PRODUCT.id === "mfx") {
405
+ return "MFX hosted-FX slots are not yet controllable over MCP.";
406
+ }
324
407
  return (`PAM build does not include the plugin host ('${action}' not recognized). ` +
325
408
  `Plugin-host tools require a build with PAM_PLUGIN_HOST=1. ` +
326
409
  `Cell1 does not host third-party plugins; MFX only exposes FX-side hosting and needs product-aware FX-host MCP support.`);
327
410
  }
328
411
  if (MODEL_ACTIONS.has(action)) {
412
+ if (BRIDGE_PRODUCT.id === "mfx") {
413
+ return "AI model managers are not available in MFX.";
414
+ }
329
415
  return (`PAM build does not include the AI model managers ('${action}' not recognized). ` +
330
416
  `Model-management tools require PAM ≥ 1.9.0 with the AIGen/Magenta/Notochord managers; ` +
331
417
  `they are not available on standalone MFX builds.`);
@@ -344,10 +430,13 @@ export class PluginBridge {
344
430
  ? MIN_PAM_VERSION_FOR_NEW_ACTIONS
345
431
  : null;
346
432
  if (requiredVersion) {
347
- return (`PAM build does not support '${action}'. ` +
348
- `This action requires PAM ≥ ${requiredVersion} ` +
349
- `(npm @map-audio/pam-mcp-server ≥ ${requiredVersion}). ` +
350
- `Update PAM to the latest build, or use a tool that doesn't depend on this action.`);
433
+ const packageName = BRIDGE_PRODUCT.id === "mfx"
434
+ ? "@map-audio/mfx-mcp-server"
435
+ : "@map-audio/pam-mcp-server";
436
+ return (`${BRIDGE_PRODUCT.name} build does not support '${action}'. ` +
437
+ `This action requires ${BRIDGE_PRODUCT.name} ≥ ${requiredVersion} ` +
438
+ `(npm ${packageName} ≥ ${requiredVersion}). ` +
439
+ `Update ${BRIDGE_PRODUCT.name} to the latest build, or use a tool that doesn't depend on this action.`);
351
440
  }
352
441
  }
353
442
  return message;