@map-audio/pam-mcp-server 1.0.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.
- package/README.md +60 -0
- package/dist/agent.d.ts +11 -0
- package/dist/agent.js +201 -0
- package/dist/agent.js.map +1 -0
- package/dist/bridge.d.ts +21 -0
- package/dist/bridge.js +147 -0
- package/dist/bridge.js.map +1 -0
- package/dist/manifest.d.ts +41 -0
- package/dist/manifest.js +135 -0
- package/dist/manifest.js.map +1 -0
- package/dist/manifest.json +16344 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +2181 -0
- package/dist/server.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @map-audio/pam-mcp-server
|
|
2
|
+
|
|
3
|
+
MCP server for the [PAM](https://map.audio) audio plugin. Lets AI agents (Claude Desktop, Claude Code, Cursor, etc.) control a running PAM instance — load samples, set parameters, trigger MIDI, edit patterns, manage presets, run the randomizer, and more.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- macOS or Windows with **Node.js 21+** (`node --version`)
|
|
8
|
+
- **PAM** installed and running (standalone or in a DAW)
|
|
9
|
+
- **MCP Bridge enabled** inside PAM: `Settings → Experimental Mode → MCP Bridge` (default port `9847`)
|
|
10
|
+
|
|
11
|
+
When PAM starts with the bridge enabled it writes `~/.pam-mcp-bridge.json` — the server discovers PAM through that file, so nothing else needs configuring.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
You do **not** need to clone anything. Add the server to your MCP client's config file and it will be fetched on first launch via `npx`.
|
|
16
|
+
|
|
17
|
+
### Claude Desktop
|
|
18
|
+
|
|
19
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"pam": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["-y", "@map-audio/pam-mcp-server"]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Fully quit Claude Desktop (`Cmd+Q` / right-click tray icon → Quit) and reopen. PAM's tools will appear in the MCP menu.
|
|
33
|
+
|
|
34
|
+
### Claude Code
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
claude mcp add pam -- npx -y @map-audio/pam-mcp-server
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
or add the same `mcpServers` block to `.mcp.json` at your project root.
|
|
41
|
+
|
|
42
|
+
### Cursor / other MCP clients
|
|
43
|
+
|
|
44
|
+
Use the same `mcpServers` block in the client's MCP config. Any stdio-compatible MCP client works.
|
|
45
|
+
|
|
46
|
+
## Troubleshooting
|
|
47
|
+
|
|
48
|
+
- **"Not connected to PAM plugin"** — PAM isn't running, or MCP Bridge is off. Open PAM → Settings → Experimental Mode → enable MCP Bridge, then restart PAM.
|
|
49
|
+
- **`npx: command not found`** (macOS GUI apps) — GUI apps don't inherit shell PATH. Install Node from [nodejs.org](https://nodejs.org) (not via `nvm`/`asdf`), or give the absolute path: `"command": "/usr/local/bin/npx"` / `"/opt/homebrew/bin/npx"`.
|
|
50
|
+
- **Offline tools fail** (`list_presets`, `get_parameter_info`) — the manifest bundled with the package is used automatically. To target a different PAM install, set `PAM_MANIFEST_PATH` in the server's `env` block.
|
|
51
|
+
|
|
52
|
+
## What's in the box
|
|
53
|
+
|
|
54
|
+
Live tools (require a running PAM): `set_parameters`, `trigger_midi`, `update_pattern`, `load_sample`, `load_preset`, `save_preset`, `add_modulation`, `start_transport`, `stop_transport`, `full_stop_transport`, `set_bpm`, `randomize_samples`, `randomize_patterns`, `load_variation`, and more.
|
|
55
|
+
|
|
56
|
+
Offline tools (manifest-only): `get_parameter_info`, `list_presets`, `list_samples`, `get_manifest_summary`.
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PAM Agent — bridges any OpenAI-compatible local LLM to the PAM MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx tsx packages/mcp-server/src/agent.ts # defaults to Ollama
|
|
7
|
+
* npx tsx packages/mcp-server/src/agent.ts --url http://localhost:1234/v1 # LM Studio
|
|
8
|
+
* npx tsx packages/mcp-server/src/agent.ts --model qwen2.5:14b # specific model
|
|
9
|
+
* PAM_LLM_URL=http://localhost:11434/v1 npx tsx packages/mcp-server/src/agent.ts
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PAM Agent — bridges any OpenAI-compatible local LLM to the PAM MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx tsx packages/mcp-server/src/agent.ts # defaults to Ollama
|
|
7
|
+
* npx tsx packages/mcp-server/src/agent.ts --url http://localhost:1234/v1 # LM Studio
|
|
8
|
+
* npx tsx packages/mcp-server/src/agent.ts --model qwen2.5:14b # specific model
|
|
9
|
+
* PAM_LLM_URL=http://localhost:11434/v1 npx tsx packages/mcp-server/src/agent.ts
|
|
10
|
+
*/
|
|
11
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
12
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
13
|
+
import { createInterface } from "node:readline";
|
|
14
|
+
import { resolve, dirname } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
// --- Config ---
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const SERVER_SCRIPT = resolve(__dirname, "server.ts");
|
|
19
|
+
function parseArgs() {
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
let url = process.env.PAM_LLM_URL || "http://localhost:1234/v1";
|
|
22
|
+
let model = process.env.PAM_LLM_MODEL || "google/gemma-4-26b-a4b";
|
|
23
|
+
for (let i = 0; i < args.length; i++) {
|
|
24
|
+
if (args[i] === "--url" && args[i + 1])
|
|
25
|
+
url = args[i + 1];
|
|
26
|
+
if (args[i] === "--model" && args[i + 1])
|
|
27
|
+
model = args[i + 1];
|
|
28
|
+
}
|
|
29
|
+
return { url, model };
|
|
30
|
+
}
|
|
31
|
+
const config = parseArgs();
|
|
32
|
+
async function resolveModel() {
|
|
33
|
+
if (config.model)
|
|
34
|
+
return;
|
|
35
|
+
try {
|
|
36
|
+
const resp = await fetch(`${config.url}/models`);
|
|
37
|
+
const data = (await resp.json());
|
|
38
|
+
const models = (data.data || []).filter((m) => !m.id.includes("embed"));
|
|
39
|
+
if (models.length > 0) {
|
|
40
|
+
config.model = models[0].id;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// fall through
|
|
45
|
+
}
|
|
46
|
+
if (!config.model) {
|
|
47
|
+
console.error("No model specified and auto-detection failed. Use --model <name>");
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// --- MCP Client ---
|
|
52
|
+
async function createMcpClient() {
|
|
53
|
+
const transport = new StdioClientTransport({
|
|
54
|
+
command: "npx",
|
|
55
|
+
args: ["tsx", SERVER_SCRIPT],
|
|
56
|
+
stderr: "inherit",
|
|
57
|
+
});
|
|
58
|
+
const client = new Client({ name: "pam-agent", version: "1.0.0" });
|
|
59
|
+
await client.connect(transport);
|
|
60
|
+
return client;
|
|
61
|
+
}
|
|
62
|
+
function mcpToolsToOpenAI(tools) {
|
|
63
|
+
return tools.map((t) => ({
|
|
64
|
+
type: "function",
|
|
65
|
+
function: {
|
|
66
|
+
name: t.name,
|
|
67
|
+
description: t.description || "",
|
|
68
|
+
parameters: t.inputSchema,
|
|
69
|
+
},
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
async function chatCompletion(messages, tools) {
|
|
73
|
+
const body = {
|
|
74
|
+
model: config.model,
|
|
75
|
+
messages,
|
|
76
|
+
temperature: 0.7,
|
|
77
|
+
};
|
|
78
|
+
if (tools.length > 0) {
|
|
79
|
+
body.tools = tools;
|
|
80
|
+
body.tool_choice = "auto";
|
|
81
|
+
}
|
|
82
|
+
const resp = await fetch(`${config.url}/chat/completions`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: { "Content-Type": "application/json" },
|
|
85
|
+
body: JSON.stringify(body),
|
|
86
|
+
});
|
|
87
|
+
if (!resp.ok) {
|
|
88
|
+
const text = await resp.text();
|
|
89
|
+
throw new Error(`LLM API error ${resp.status}: ${text}`);
|
|
90
|
+
}
|
|
91
|
+
const data = (await resp.json());
|
|
92
|
+
return data.choices[0].message;
|
|
93
|
+
}
|
|
94
|
+
// --- System Prompt ---
|
|
95
|
+
const SYSTEM_PROMPT = `You are PAM Assistant — an AI that controls the PAM audio plugin (sampler/synthesizer).
|
|
96
|
+
|
|
97
|
+
You have access to tools that let you:
|
|
98
|
+
- Query parameter definitions (ranges, defaults, effects)
|
|
99
|
+
- Read and list presets
|
|
100
|
+
- Set parameters on the running plugin
|
|
101
|
+
- Load samples and presets
|
|
102
|
+
- Trigger MIDI notes
|
|
103
|
+
- Add modulation (LFO, envelopes, vary)
|
|
104
|
+
- Update sequencer patterns
|
|
105
|
+
- Randomize sounds
|
|
106
|
+
|
|
107
|
+
When the user asks you to make a sound or adjust the plugin:
|
|
108
|
+
1. Use get_parameter_info or get_manifest_summary to understand available parameters
|
|
109
|
+
2. Use set_parameters to change values (always check ranges first)
|
|
110
|
+
3. Use trigger_midi to preview the result
|
|
111
|
+
|
|
112
|
+
Be concise. Show what you changed. If the plugin isn't connected, offline tools (parameter info, presets) still work.`;
|
|
113
|
+
// --- Agent Loop ---
|
|
114
|
+
async function run() {
|
|
115
|
+
await resolveModel();
|
|
116
|
+
console.log(`\x1b[36mPAM Agent\x1b[0m — connecting to MCP server...`);
|
|
117
|
+
const client = await createMcpClient();
|
|
118
|
+
const { tools: mcpTools } = await client.listTools();
|
|
119
|
+
const openaiTools = mcpToolsToOpenAI(mcpTools);
|
|
120
|
+
console.log(`\x1b[32mReady\x1b[0m — ${mcpTools.length} tools loaded, using ${config.model} at ${config.url}`);
|
|
121
|
+
console.log(`Type your message (Ctrl+C to quit)\n`);
|
|
122
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
123
|
+
const messages = [{ role: "system", content: SYSTEM_PROMPT }];
|
|
124
|
+
const prompt = () => new Promise((resolve) => rl.question("\x1b[33myou>\x1b[0m ", resolve));
|
|
125
|
+
while (true) {
|
|
126
|
+
const userInput = await prompt();
|
|
127
|
+
if (!userInput.trim())
|
|
128
|
+
continue;
|
|
129
|
+
messages.push({ role: "user", content: userInput });
|
|
130
|
+
// Agent loop — keep going until we get a text response (no tool calls)
|
|
131
|
+
let iterations = 0;
|
|
132
|
+
const MAX_ITERATIONS = 20;
|
|
133
|
+
while (iterations++ < MAX_ITERATIONS) {
|
|
134
|
+
let response;
|
|
135
|
+
try {
|
|
136
|
+
response = await chatCompletion(messages, openaiTools);
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
console.error(`\x1b[31mLLM error:\x1b[0m ${e.message}`);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
messages.push(response);
|
|
143
|
+
// No tool calls — print the text response and break
|
|
144
|
+
if (!response.tool_calls || response.tool_calls.length === 0) {
|
|
145
|
+
if (response.content) {
|
|
146
|
+
console.log(`\n\x1b[36mpam>\x1b[0m ${response.content}\n`);
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
// Execute tool calls
|
|
151
|
+
for (const tc of response.tool_calls) {
|
|
152
|
+
const { name, arguments: argsStr } = tc.function;
|
|
153
|
+
let args;
|
|
154
|
+
try {
|
|
155
|
+
args = JSON.parse(argsStr);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
args = {};
|
|
159
|
+
}
|
|
160
|
+
console.log(` \x1b[90m⚡ ${name}(${JSON.stringify(args)})\x1b[0m`);
|
|
161
|
+
try {
|
|
162
|
+
const result = await client.callTool({ name, arguments: args });
|
|
163
|
+
const contentArr = Array.isArray(result.content) ? result.content : [];
|
|
164
|
+
const text = contentArr
|
|
165
|
+
.filter((c) => c.type === "text")
|
|
166
|
+
.map((c) => c.text || "")
|
|
167
|
+
.join("\n") || "";
|
|
168
|
+
messages.push({
|
|
169
|
+
role: "tool",
|
|
170
|
+
tool_call_id: tc.id,
|
|
171
|
+
content: text,
|
|
172
|
+
});
|
|
173
|
+
// Show tool results for visibility
|
|
174
|
+
if (text.length < 500) {
|
|
175
|
+
console.log(` \x1b[90m→ ${text}\x1b[0m`);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
console.log(` \x1b[90m→ (${text.length} chars)\x1b[0m`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
const errMsg = `Tool error: ${e.message}`;
|
|
183
|
+
messages.push({
|
|
184
|
+
role: "tool",
|
|
185
|
+
tool_call_id: tc.id,
|
|
186
|
+
content: errMsg,
|
|
187
|
+
});
|
|
188
|
+
console.log(` \x1b[31m→ ${errMsg}\x1b[0m`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (iterations >= MAX_ITERATIONS) {
|
|
193
|
+
console.log(`\x1b[31mMax tool iterations reached.\x1b[0m\n`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
run().catch((e) => {
|
|
198
|
+
console.error("Fatal:", e.message);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
});
|
|
201
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface BridgeInfo {
|
|
2
|
+
port: number;
|
|
3
|
+
instanceId: string;
|
|
4
|
+
product: string;
|
|
5
|
+
pid: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class PluginBridge {
|
|
8
|
+
private socket;
|
|
9
|
+
private buffer;
|
|
10
|
+
private nextId;
|
|
11
|
+
private pending;
|
|
12
|
+
private connected;
|
|
13
|
+
discover(): Promise<BridgeInfo[]>;
|
|
14
|
+
connect(port?: number): Promise<boolean>;
|
|
15
|
+
disconnect(): void;
|
|
16
|
+
isConnected(): boolean;
|
|
17
|
+
private static readonly SLOW_ACTIONS;
|
|
18
|
+
send(action: string, ...args: unknown[]): Promise<unknown>;
|
|
19
|
+
private processBuffer;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { createConnection } from "node:net";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
const DISCOVERY_FILE = resolve(homedir(), ".pam-mcp-bridge.json");
|
|
6
|
+
export class PluginBridge {
|
|
7
|
+
socket = null;
|
|
8
|
+
buffer = "";
|
|
9
|
+
nextId = 1;
|
|
10
|
+
pending = new Map();
|
|
11
|
+
connected = false;
|
|
12
|
+
async discover() {
|
|
13
|
+
try {
|
|
14
|
+
const raw = await readFile(DISCOVERY_FILE, "utf-8");
|
|
15
|
+
const data = JSON.parse(raw);
|
|
16
|
+
const entries = Array.isArray(data) ? data : [data];
|
|
17
|
+
// Filter stale entries whose pid is no longer alive so we don't spend
|
|
18
|
+
// 2s on a TCP timeout per dead instance. process.kill(pid, 0) throws
|
|
19
|
+
// ESRCH if the process doesn't exist, EPERM if it exists but we can't
|
|
20
|
+
// signal it (still alive).
|
|
21
|
+
return entries.filter((e) => {
|
|
22
|
+
if (!e || typeof e.pid !== "number" || e.pid <= 0)
|
|
23
|
+
return false;
|
|
24
|
+
try {
|
|
25
|
+
process.kill(e.pid, 0);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
return err.code === "EPERM";
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async connect(port) {
|
|
38
|
+
if (this.connected)
|
|
39
|
+
return true;
|
|
40
|
+
if (!port) {
|
|
41
|
+
const instances = await this.discover();
|
|
42
|
+
if (instances.length === 0)
|
|
43
|
+
return false;
|
|
44
|
+
port = instances[0].port;
|
|
45
|
+
}
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const timer = setTimeout(() => {
|
|
48
|
+
if (!this.connected) {
|
|
49
|
+
this.socket?.destroy();
|
|
50
|
+
resolve(false);
|
|
51
|
+
}
|
|
52
|
+
}, 2000);
|
|
53
|
+
this.socket = createConnection({ host: "127.0.0.1", port }, () => {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
this.connected = true;
|
|
56
|
+
resolve(true);
|
|
57
|
+
});
|
|
58
|
+
this.socket.setEncoding("utf-8");
|
|
59
|
+
this.socket.on("data", (chunk) => {
|
|
60
|
+
this.buffer += chunk;
|
|
61
|
+
this.processBuffer();
|
|
62
|
+
});
|
|
63
|
+
this.socket.on("error", () => {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
this.connected = false;
|
|
66
|
+
resolve(false);
|
|
67
|
+
});
|
|
68
|
+
this.socket.on("close", () => {
|
|
69
|
+
this.connected = false;
|
|
70
|
+
// Reject all pending requests
|
|
71
|
+
for (const [, req] of this.pending) {
|
|
72
|
+
req.reject(new Error("Connection closed"));
|
|
73
|
+
}
|
|
74
|
+
this.pending.clear();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
disconnect() {
|
|
79
|
+
this.socket?.destroy();
|
|
80
|
+
this.socket = null;
|
|
81
|
+
this.connected = false;
|
|
82
|
+
}
|
|
83
|
+
isConnected() {
|
|
84
|
+
return this.connected;
|
|
85
|
+
}
|
|
86
|
+
// Actions that block the message thread for disk I/O + graph rebuild
|
|
87
|
+
static SLOW_ACTIONS = new Set([
|
|
88
|
+
"loadPreset",
|
|
89
|
+
"savePreset",
|
|
90
|
+
"randomize",
|
|
91
|
+
"randomizeSamples",
|
|
92
|
+
"importPreset",
|
|
93
|
+
"exportPresetWithSamples",
|
|
94
|
+
"resetToInitialState",
|
|
95
|
+
"loadSampleToCell",
|
|
96
|
+
"loadVariation",
|
|
97
|
+
"listDirectory",
|
|
98
|
+
"importVisualFolder",
|
|
99
|
+
]);
|
|
100
|
+
async send(action, ...args) {
|
|
101
|
+
if (!this.connected || !this.socket) {
|
|
102
|
+
throw new Error("Not connected to PAM plugin. Start PAM and ensure the MCP bridge is enabled.");
|
|
103
|
+
}
|
|
104
|
+
const id = this.nextId++;
|
|
105
|
+
const msg = { id, action, args };
|
|
106
|
+
const timeoutMs = PluginBridge.SLOW_ACTIONS.has(action) ? 30000 : 10000;
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const timer = setTimeout(() => {
|
|
109
|
+
if (this.pending.has(id)) {
|
|
110
|
+
this.pending.delete(id);
|
|
111
|
+
reject(new Error(`Timeout waiting for response to ${action}`));
|
|
112
|
+
}
|
|
113
|
+
}, timeoutMs);
|
|
114
|
+
this.pending.set(id, {
|
|
115
|
+
resolve: (v) => { clearTimeout(timer); resolve(v); },
|
|
116
|
+
reject: (e) => { clearTimeout(timer); reject(e); },
|
|
117
|
+
});
|
|
118
|
+
this.socket.write(JSON.stringify(msg) + "\n");
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
processBuffer() {
|
|
122
|
+
const lines = this.buffer.split("\n");
|
|
123
|
+
// Keep the last incomplete line in the buffer
|
|
124
|
+
this.buffer = lines.pop() || "";
|
|
125
|
+
for (const line of lines) {
|
|
126
|
+
if (!line.trim())
|
|
127
|
+
continue;
|
|
128
|
+
try {
|
|
129
|
+
const resp = JSON.parse(line);
|
|
130
|
+
const pending = this.pending.get(resp.id);
|
|
131
|
+
if (pending) {
|
|
132
|
+
this.pending.delete(resp.id);
|
|
133
|
+
if (resp.error) {
|
|
134
|
+
pending.reject(new Error(resp.error));
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
pending.resolve(resp.result);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Ignore malformed responses
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface ManifestParameter {
|
|
2
|
+
paramId: string;
|
|
3
|
+
name: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
min: number;
|
|
6
|
+
max: number;
|
|
7
|
+
defaultValue: number;
|
|
8
|
+
step: number;
|
|
9
|
+
skew: number;
|
|
10
|
+
suffix?: string;
|
|
11
|
+
bipolar?: boolean;
|
|
12
|
+
strRepr?: string[];
|
|
13
|
+
tooltip?: string;
|
|
14
|
+
cell?: string;
|
|
15
|
+
effect?: string;
|
|
16
|
+
isPluginParameter?: boolean;
|
|
17
|
+
isModulatable?: boolean;
|
|
18
|
+
version?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface Manifest {
|
|
21
|
+
name: string;
|
|
22
|
+
company: string;
|
|
23
|
+
bundleId: string;
|
|
24
|
+
pluginCode: string;
|
|
25
|
+
parameters: ManifestParameter[];
|
|
26
|
+
window: {
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare function loadManifest(): Promise<Manifest>;
|
|
32
|
+
export declare function getParameterIndex(): Promise<Map<string, ManifestParameter>>;
|
|
33
|
+
export declare function getParametersByCell(cellId: string): Promise<ManifestParameter[]>;
|
|
34
|
+
export declare function getGlobalParameters(): Promise<ManifestParameter[]>;
|
|
35
|
+
export declare function findPresetDirs(): Promise<string[]>;
|
|
36
|
+
export declare function resolvePresetPath(presetPath: string): Promise<string>;
|
|
37
|
+
export declare function walkPresets(dir: string, prefix?: string): Promise<{
|
|
38
|
+
path: string;
|
|
39
|
+
name: string;
|
|
40
|
+
relativePath: string;
|
|
41
|
+
}[]>;
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { resolve, dirname, join, basename, extname, sep } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
let cachedManifest = null;
|
|
6
|
+
let paramIndex = null;
|
|
7
|
+
function getProjectRoot() {
|
|
8
|
+
// Allow override via env var for standalone / outside-repo usage
|
|
9
|
+
if (process.env.PAM_PROJECT_ROOT) {
|
|
10
|
+
return resolve(process.env.PAM_PROJECT_ROOT);
|
|
11
|
+
}
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
// packages/mcp-server/src (or dist) -> project root
|
|
14
|
+
return resolve(__dirname, "..", "..", "..");
|
|
15
|
+
}
|
|
16
|
+
function findManifestPath() {
|
|
17
|
+
const root = getProjectRoot();
|
|
18
|
+
// Primary: manifest.json at project root
|
|
19
|
+
const rootManifest = resolve(root, "manifest.json");
|
|
20
|
+
// Fallback: bundled manifest next to compiled server (dist/manifest.json)
|
|
21
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const bundledManifest = resolve(__dirname, "manifest.json");
|
|
23
|
+
// Check env override
|
|
24
|
+
if (process.env.PAM_MANIFEST_PATH) {
|
|
25
|
+
return resolve(process.env.PAM_MANIFEST_PATH);
|
|
26
|
+
}
|
|
27
|
+
return rootManifest;
|
|
28
|
+
}
|
|
29
|
+
export async function loadManifest() {
|
|
30
|
+
if (cachedManifest)
|
|
31
|
+
return cachedManifest;
|
|
32
|
+
const primaryPath = findManifestPath();
|
|
33
|
+
// Try primary path, then bundled fallback
|
|
34
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
const bundledPath = resolve(__dirname, "manifest.json");
|
|
36
|
+
for (const manifestPath of [primaryPath, bundledPath]) {
|
|
37
|
+
try {
|
|
38
|
+
const raw = await readFile(manifestPath, "utf-8");
|
|
39
|
+
cachedManifest = JSON.parse(raw);
|
|
40
|
+
return cachedManifest;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
throw new Error("PAM manifest.json not found. Set PAM_PROJECT_ROOT to the PAM repo root, " +
|
|
47
|
+
"or PAM_MANIFEST_PATH to the manifest file location. " +
|
|
48
|
+
"Live tools (set_parameters, trigger_midi, etc.) work without the manifest.");
|
|
49
|
+
}
|
|
50
|
+
export async function getParameterIndex() {
|
|
51
|
+
if (paramIndex)
|
|
52
|
+
return paramIndex;
|
|
53
|
+
const manifest = await loadManifest();
|
|
54
|
+
paramIndex = new Map();
|
|
55
|
+
for (const p of manifest.parameters) {
|
|
56
|
+
paramIndex.set(p.paramId, p);
|
|
57
|
+
}
|
|
58
|
+
return paramIndex;
|
|
59
|
+
}
|
|
60
|
+
export async function getParametersByCell(cellId) {
|
|
61
|
+
const manifest = await loadManifest();
|
|
62
|
+
return manifest.parameters.filter((p) => p.cell === cellId);
|
|
63
|
+
}
|
|
64
|
+
export async function getGlobalParameters() {
|
|
65
|
+
const manifest = await loadManifest();
|
|
66
|
+
return manifest.parameters.filter((p) => !p.cell);
|
|
67
|
+
}
|
|
68
|
+
function getUserPresetsDir() {
|
|
69
|
+
if (process.platform === "win32") {
|
|
70
|
+
return resolve(process.env.APPDATA ?? homedir(), "MAP Audio", "PAM", "Presets");
|
|
71
|
+
}
|
|
72
|
+
return resolve(homedir(), "Library", "Application Support", "MAP Audio", "PAM", "Presets");
|
|
73
|
+
}
|
|
74
|
+
export async function findPresetDirs() {
|
|
75
|
+
const dirs = [];
|
|
76
|
+
const repoPresets = resolve(getProjectRoot(), "installer", "prepared-presets", "Presets");
|
|
77
|
+
try {
|
|
78
|
+
await stat(repoPresets);
|
|
79
|
+
dirs.push(repoPresets);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// not available
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const userPresets = getUserPresetsDir();
|
|
86
|
+
await stat(userPresets);
|
|
87
|
+
dirs.push(userPresets);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// not available
|
|
91
|
+
}
|
|
92
|
+
return dirs;
|
|
93
|
+
}
|
|
94
|
+
export async function resolvePresetPath(presetPath) {
|
|
95
|
+
const dirs = await findPresetDirs();
|
|
96
|
+
for (const dir of dirs) {
|
|
97
|
+
const candidate = resolve(dir, presetPath);
|
|
98
|
+
// Path traversal guard: resolved path must stay within the allowed directory
|
|
99
|
+
if (!candidate.startsWith(dir + sep) && candidate !== dir)
|
|
100
|
+
continue;
|
|
101
|
+
try {
|
|
102
|
+
await stat(candidate);
|
|
103
|
+
return candidate;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`Preset not found: ${presetPath}`);
|
|
110
|
+
}
|
|
111
|
+
export async function walkPresets(dir, prefix = "") {
|
|
112
|
+
const results = [];
|
|
113
|
+
try {
|
|
114
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const fullPath = join(dir, entry.name);
|
|
117
|
+
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
118
|
+
if (entry.isDirectory()) {
|
|
119
|
+
results.push(...(await walkPresets(fullPath, relPath)));
|
|
120
|
+
}
|
|
121
|
+
else if (extname(entry.name) === ".preset") {
|
|
122
|
+
results.push({
|
|
123
|
+
path: fullPath,
|
|
124
|
+
name: basename(entry.name, ".preset"),
|
|
125
|
+
relativePath: relPath,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// directory not readable
|
|
132
|
+
}
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
|