iflow-mcp_davidpiazza-cdp_mcp 1.0.0__py3-none-any.whl
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.
- CDP_MCP_v7.py +604 -0
- iflow_mcp_davidpiazza_cdp_mcp-1.0.0.dist-info/METADATA +212 -0
- iflow_mcp_davidpiazza_cdp_mcp-1.0.0.dist-info/RECORD +6 -0
- iflow_mcp_davidpiazza_cdp_mcp-1.0.0.dist-info/WHEEL +4 -0
- iflow_mcp_davidpiazza_cdp_mcp-1.0.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_davidpiazza_cdp_mcp-1.0.0.dist-info/licenses/LICENSE +21 -0
CDP_MCP_v7.py
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CDP MCP Server v7 - Ultra-Rigid Workflow
|
|
4
|
+
Direct CDP usage exposure with zero interpretation
|
|
5
|
+
Now with 4 core tools including data file creation
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import tempfile
|
|
11
|
+
import platform
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import List, Dict, Any, Optional, Tuple
|
|
14
|
+
import soundfile as sf
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
from mcp.server.fastmcp import FastMCP
|
|
18
|
+
|
|
19
|
+
# Initialize MCP server
|
|
20
|
+
mcp = FastMCP("CDP Sound Transformer v7")
|
|
21
|
+
|
|
22
|
+
# Configuration
|
|
23
|
+
CDP_PATH = os.environ.get("CDP_PATH", "/Users/davidpiazza/cdpr8/_cdp/_cdprogs")
|
|
24
|
+
TEMP_DIR = Path(tempfile.gettempdir()) / "cdp_mcp"
|
|
25
|
+
TEMP_DIR.mkdir(exist_ok=True)
|
|
26
|
+
|
|
27
|
+
# Detect if we're on Apple Silicon
|
|
28
|
+
IS_APPLE_SILICON = platform.machine() == "arm64" and platform.system() == "Darwin"
|
|
29
|
+
|
|
30
|
+
# CDP program categories for organization
|
|
31
|
+
CDP_CATEGORIES = {
|
|
32
|
+
"Spectral Processing": [
|
|
33
|
+
"blur", "clean", "combine", "cross", "focus", "formants",
|
|
34
|
+
"gate", "get", "hilite", "morph", "pitch", "spec", "strange", "stretch"
|
|
35
|
+
],
|
|
36
|
+
"Time Domain": [
|
|
37
|
+
"modify", "distort", "envel", "extend", "filter", "grain",
|
|
38
|
+
"sfedit", "zigzag"
|
|
39
|
+
],
|
|
40
|
+
"Synthesis": [
|
|
41
|
+
"synth", "texture", "fracture"
|
|
42
|
+
],
|
|
43
|
+
"Analysis and Utility": [
|
|
44
|
+
"pvoc", "sndinfo", "housekeep", "submix", "mchshred"
|
|
45
|
+
],
|
|
46
|
+
"Other": [] # For any programs not categorized
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# ===== HELPER FUNCTIONS =====
|
|
50
|
+
|
|
51
|
+
def scan_cdp_programs() -> Dict[str, List[str]]:
|
|
52
|
+
"""Scan CDP installation directory for available programs"""
|
|
53
|
+
cdp_dir = Path(CDP_PATH)
|
|
54
|
+
if not cdp_dir.exists():
|
|
55
|
+
return {"error": [f"CDP directory not found: {CDP_PATH}"]}
|
|
56
|
+
|
|
57
|
+
# Get all executable files
|
|
58
|
+
programs = []
|
|
59
|
+
try:
|
|
60
|
+
for file in cdp_dir.iterdir():
|
|
61
|
+
if file.is_file() and os.access(file, os.X_OK):
|
|
62
|
+
# Skip obvious non-CDP files
|
|
63
|
+
name = file.name
|
|
64
|
+
if not name.startswith('.') and not name.endswith('.txt'):
|
|
65
|
+
programs.append(name)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
return {"error": [f"Failed to scan CDP directory: {str(e)}"]}
|
|
68
|
+
|
|
69
|
+
# Categorize programs
|
|
70
|
+
categorized = {cat: [] for cat in CDP_CATEGORIES}
|
|
71
|
+
uncategorized = []
|
|
72
|
+
|
|
73
|
+
for prog in sorted(programs):
|
|
74
|
+
categorized_flag = False
|
|
75
|
+
for category, prog_list in CDP_CATEGORIES.items():
|
|
76
|
+
if prog in prog_list:
|
|
77
|
+
categorized[category].append(prog)
|
|
78
|
+
categorized_flag = True
|
|
79
|
+
break
|
|
80
|
+
if not categorized_flag:
|
|
81
|
+
uncategorized.append(prog)
|
|
82
|
+
|
|
83
|
+
# Add uncategorized to "Other"
|
|
84
|
+
if uncategorized:
|
|
85
|
+
categorized["Other"] = uncategorized
|
|
86
|
+
|
|
87
|
+
# Remove empty categories
|
|
88
|
+
categorized = {k: v for k, v in categorized.items() if v}
|
|
89
|
+
|
|
90
|
+
return categorized
|
|
91
|
+
|
|
92
|
+
def run_cdp_for_usage(program: str, subprogram: Optional[str] = None) -> Tuple[int, str, str]:
|
|
93
|
+
"""Run CDP program without arguments to get usage information"""
|
|
94
|
+
cmd_path = Path(CDP_PATH) / program
|
|
95
|
+
if not cmd_path.exists():
|
|
96
|
+
return -1, "", f"Program '{program}' not found at {cmd_path}"
|
|
97
|
+
|
|
98
|
+
# Build command - just program and optional subprogram
|
|
99
|
+
args = []
|
|
100
|
+
if subprogram:
|
|
101
|
+
args.append(subprogram)
|
|
102
|
+
|
|
103
|
+
# Build command with architecture prefix if needed
|
|
104
|
+
if IS_APPLE_SILICON:
|
|
105
|
+
cmd = ["arch", "-x86_64", str(cmd_path)] + args
|
|
106
|
+
else:
|
|
107
|
+
cmd = [str(cmd_path)] + args
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
# Run with no input, expecting usage output
|
|
111
|
+
result = subprocess.run(
|
|
112
|
+
cmd,
|
|
113
|
+
capture_output=True,
|
|
114
|
+
text=True,
|
|
115
|
+
timeout=5 # Prevent hanging
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# CDP typically outputs usage to stdout or stderr
|
|
119
|
+
# Exit code 255 is normal for usage display
|
|
120
|
+
output = result.stdout if result.stdout else result.stderr
|
|
121
|
+
|
|
122
|
+
return result.returncode, output, result.stderr
|
|
123
|
+
|
|
124
|
+
except subprocess.TimeoutExpired:
|
|
125
|
+
return -1, "", "Command timed out - program may be waiting for input"
|
|
126
|
+
except Exception as e:
|
|
127
|
+
return -1, "", f"Failed to execute: {str(e)}"
|
|
128
|
+
|
|
129
|
+
def run_cdp_command(command: List[str]) -> Tuple[int, str, str]:
|
|
130
|
+
"""Execute a CDP command given as array"""
|
|
131
|
+
if not command:
|
|
132
|
+
return -1, "", "Empty command array"
|
|
133
|
+
|
|
134
|
+
program = command[0]
|
|
135
|
+
cmd_path = Path(CDP_PATH) / program
|
|
136
|
+
|
|
137
|
+
if not cmd_path.exists():
|
|
138
|
+
return -1, "", f"Program '{program}' not found at {cmd_path}"
|
|
139
|
+
|
|
140
|
+
# Build full command with architecture prefix if needed
|
|
141
|
+
if IS_APPLE_SILICON:
|
|
142
|
+
full_cmd = ["arch", "-x86_64", str(cmd_path)] + command[1:]
|
|
143
|
+
else:
|
|
144
|
+
full_cmd = [str(cmd_path)] + command[1:]
|
|
145
|
+
|
|
146
|
+
# Log for debugging
|
|
147
|
+
import sys
|
|
148
|
+
print(f"Executing: {' '.join(full_cmd)}", file=sys.stderr)
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
result = subprocess.run(
|
|
152
|
+
full_cmd,
|
|
153
|
+
capture_output=True,
|
|
154
|
+
text=True,
|
|
155
|
+
cwd=TEMP_DIR # Use temp dir as working directory
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return result.returncode, result.stdout, result.stderr
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
return -1, "", f"Execution failed: {str(e)}"
|
|
162
|
+
|
|
163
|
+
def get_sound_info(filepath: str) -> Dict[str, Any]:
|
|
164
|
+
"""Get basic information about a sound file"""
|
|
165
|
+
try:
|
|
166
|
+
info = sf.info(filepath)
|
|
167
|
+
data, _ = sf.read(filepath)
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
"duration": info.duration,
|
|
171
|
+
"sample_rate": info.samplerate,
|
|
172
|
+
"channels": info.channels,
|
|
173
|
+
"peak_amplitude": float(np.max(np.abs(data))),
|
|
174
|
+
"format": info.format,
|
|
175
|
+
"frames": info.frames
|
|
176
|
+
}
|
|
177
|
+
except Exception as e:
|
|
178
|
+
return {"error": str(e)}
|
|
179
|
+
|
|
180
|
+
# ===== CORE MCP TOOLS =====
|
|
181
|
+
|
|
182
|
+
@mcp.tool()
|
|
183
|
+
def list_cdp_programs() -> Dict[str, List[str]]:
|
|
184
|
+
"""
|
|
185
|
+
List all available CDP programs organized by category.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Dictionary with categories as keys and program lists as values
|
|
189
|
+
|
|
190
|
+
Example response:
|
|
191
|
+
{
|
|
192
|
+
"Spectral Processing": ["blur", "focus", "morph", ...],
|
|
193
|
+
"Time Domain": ["modify", "distort", "envel", ...],
|
|
194
|
+
"Synthesis": ["synth", "texture"],
|
|
195
|
+
"Analysis and Utility": ["pvoc", "housekeep", ...]
|
|
196
|
+
}
|
|
197
|
+
"""
|
|
198
|
+
return scan_cdp_programs()
|
|
199
|
+
|
|
200
|
+
@mcp.tool()
|
|
201
|
+
def get_cdp_usage(
|
|
202
|
+
program: str,
|
|
203
|
+
subprogram: Optional[str] = None
|
|
204
|
+
) -> Dict[str, str]:
|
|
205
|
+
"""
|
|
206
|
+
Get usage information for a CDP program by running it without arguments.
|
|
207
|
+
This returns the raw usage text directly from CDP.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
program: CDP program name (e.g., 'blur', 'modify')
|
|
211
|
+
subprogram: Optional subprogram (e.g., 'brassage' for modify)
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Dictionary with usage text and program info
|
|
215
|
+
|
|
216
|
+
Example:
|
|
217
|
+
get_cdp_usage('blur')
|
|
218
|
+
# Returns the full blur usage text showing all modes and parameters
|
|
219
|
+
|
|
220
|
+
get_cdp_usage('modify', 'brassage')
|
|
221
|
+
# Returns usage for modify brassage specifically
|
|
222
|
+
"""
|
|
223
|
+
exit_code, output, stderr = run_cdp_for_usage(program, subprogram)
|
|
224
|
+
|
|
225
|
+
# Build response
|
|
226
|
+
response = {
|
|
227
|
+
"program": program,
|
|
228
|
+
"subprogram": subprogram or "none",
|
|
229
|
+
"usage_text": output if output else stderr,
|
|
230
|
+
"exit_code": exit_code
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# Add hints based on common patterns
|
|
234
|
+
if output or stderr:
|
|
235
|
+
text = output if output else stderr
|
|
236
|
+
|
|
237
|
+
# Check for common indicators
|
|
238
|
+
if "USAGE:" in text or "Usage:" in text:
|
|
239
|
+
response["has_usage"] = True
|
|
240
|
+
|
|
241
|
+
if "MODES:" in text or "Modes:" in text:
|
|
242
|
+
response["has_modes"] = True
|
|
243
|
+
|
|
244
|
+
if any(flag in text for flag in ["-", "FLAGS:", "Options:"]):
|
|
245
|
+
response["has_flags"] = True
|
|
246
|
+
|
|
247
|
+
# Check for double syntax
|
|
248
|
+
if f"{program} {program}" in text.lower():
|
|
249
|
+
response["note"] = f"This program uses double syntax: {program} {program}"
|
|
250
|
+
|
|
251
|
+
return response
|
|
252
|
+
|
|
253
|
+
@mcp.tool()
|
|
254
|
+
def execute_cdp(command: List[str]) -> Dict[str, Any]:
|
|
255
|
+
"""
|
|
256
|
+
Execute a CDP command given as an array of strings.
|
|
257
|
+
This is direct execution with no interpretation.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
command: Complete command as array (e.g., ["blur", "blur", "in.ana", "out.ana", "50"])
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Execution result with status, output, and errors
|
|
264
|
+
|
|
265
|
+
Examples:
|
|
266
|
+
execute_cdp(["blur", "blur", "input.ana", "output.ana", "50"])
|
|
267
|
+
execute_cdp(["modify", "speed", "1", "input.wav", "output.wav", "2.0"])
|
|
268
|
+
execute_cdp(["housekeep", "chans", "4", "stereo.wav", "mono.wav"])
|
|
269
|
+
"""
|
|
270
|
+
if not command:
|
|
271
|
+
return {
|
|
272
|
+
"status": "failed",
|
|
273
|
+
"error": "Empty command array provided"
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# Execute the command
|
|
277
|
+
exit_code, stdout, stderr = run_cdp_command(command)
|
|
278
|
+
|
|
279
|
+
# Determine success
|
|
280
|
+
# CDP often returns non-zero codes even on success
|
|
281
|
+
success = (
|
|
282
|
+
exit_code == 0 or
|
|
283
|
+
(exit_code == 1 and stdout and not stderr) or
|
|
284
|
+
(len(command) > 2 and Path(command[-1]).exists()) # Output file created
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
result = {
|
|
288
|
+
"status": "success" if success else "failed",
|
|
289
|
+
"exit_code": exit_code,
|
|
290
|
+
"command": " ".join(command),
|
|
291
|
+
"stdout": stdout if stdout else "",
|
|
292
|
+
"stderr": stderr if stderr else ""
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# Add output file info if it appears to be a file operation
|
|
296
|
+
if len(command) > 2:
|
|
297
|
+
possible_output = command[-1]
|
|
298
|
+
if not possible_output.startswith('-') and '.' in possible_output:
|
|
299
|
+
result["output_file"] = possible_output
|
|
300
|
+
if Path(possible_output).exists():
|
|
301
|
+
result["output_exists"] = True
|
|
302
|
+
|
|
303
|
+
return result
|
|
304
|
+
|
|
305
|
+
@mcp.tool()
|
|
306
|
+
def create_data_file(
|
|
307
|
+
filepath: str,
|
|
308
|
+
content: str
|
|
309
|
+
) -> Dict[str, Any]:
|
|
310
|
+
"""
|
|
311
|
+
Create a data file for CDP programs that require them.
|
|
312
|
+
Data files are text files containing parameters specific to each CDP program.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
filepath: Output path for the data file (should end in .txt)
|
|
316
|
+
content: The exact text content to write to the file
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Dictionary with creation status and file path
|
|
320
|
+
|
|
321
|
+
Examples:
|
|
322
|
+
# For tesselate - two lines with repeat counts and delays
|
|
323
|
+
create_data_file("tess_data.txt", "5 5 5 5\\n0.0 0.1 0.2 0.3")
|
|
324
|
+
|
|
325
|
+
# For texture - note data with MIDI pitches and timing
|
|
326
|
+
create_data_file("texture_notes.txt", "0.0 60 0.5 100\\n0.5 64 0.5 100")
|
|
327
|
+
|
|
328
|
+
# For extend repetitions - times file
|
|
329
|
+
create_data_file("times.txt", "0.0\\n1.5\\n3.2\\n4.8")
|
|
330
|
+
"""
|
|
331
|
+
try:
|
|
332
|
+
# Ensure filepath is absolute or relative to temp dir
|
|
333
|
+
if not os.path.isabs(filepath):
|
|
334
|
+
filepath = str(TEMP_DIR / filepath)
|
|
335
|
+
|
|
336
|
+
# Write the content
|
|
337
|
+
Path(filepath).write_text(content)
|
|
338
|
+
|
|
339
|
+
# Verify it was created
|
|
340
|
+
if Path(filepath).exists():
|
|
341
|
+
lines = content.strip().split('\n')
|
|
342
|
+
return {
|
|
343
|
+
"status": "success",
|
|
344
|
+
"filepath": filepath,
|
|
345
|
+
"lines": len(lines),
|
|
346
|
+
"size": len(content),
|
|
347
|
+
"preview": content[:200] + "..." if len(content) > 200 else content
|
|
348
|
+
}
|
|
349
|
+
else:
|
|
350
|
+
return {
|
|
351
|
+
"status": "failed",
|
|
352
|
+
"error": "File was not created"
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
except Exception as e:
|
|
356
|
+
return {
|
|
357
|
+
"status": "failed",
|
|
358
|
+
"error": f"Failed to create data file: {str(e)}"
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
@mcp.tool()
|
|
362
|
+
def analyze_sound(filepath: str) -> Dict[str, Any]:
|
|
363
|
+
"""
|
|
364
|
+
Analyze a sound file and return its properties.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
filepath: Path to the sound file
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
Dictionary with duration, sample_rate, channels, peak_amplitude, etc.
|
|
371
|
+
"""
|
|
372
|
+
if not Path(filepath).exists():
|
|
373
|
+
return {"error": f"File not found: {filepath}"}
|
|
374
|
+
|
|
375
|
+
return get_sound_info(filepath)
|
|
376
|
+
|
|
377
|
+
@mcp.tool()
|
|
378
|
+
def prepare_spectral(
|
|
379
|
+
input_file: str,
|
|
380
|
+
output_file: str,
|
|
381
|
+
window_size: int = 2048
|
|
382
|
+
) -> Dict[str, Any]:
|
|
383
|
+
"""
|
|
384
|
+
Helper to prepare spectral file using PVOC analysis.
|
|
385
|
+
This is a convenience wrapper around execute_cdp.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
input_file: Input audio file path
|
|
389
|
+
output_file: Output analysis file path (.ana)
|
|
390
|
+
window_size: FFT window size (powers of 2: 64-8192)
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
Result of PVOC analysis execution
|
|
394
|
+
"""
|
|
395
|
+
# Check if input is already spectral
|
|
396
|
+
if input_file.endswith('.ana'):
|
|
397
|
+
return {
|
|
398
|
+
"status": "info",
|
|
399
|
+
"message": "Input is already a spectral file",
|
|
400
|
+
"ana_file": input_file
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
# Check input file
|
|
404
|
+
if not Path(input_file).exists():
|
|
405
|
+
return {
|
|
406
|
+
"status": "failed",
|
|
407
|
+
"error": f"Input file not found: {input_file}"
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
# Build PVOC command
|
|
411
|
+
command = [
|
|
412
|
+
"pvoc", "anal", "1",
|
|
413
|
+
input_file,
|
|
414
|
+
output_file,
|
|
415
|
+
f"-c{window_size}"
|
|
416
|
+
]
|
|
417
|
+
|
|
418
|
+
# Execute
|
|
419
|
+
result = execute_cdp(command)
|
|
420
|
+
|
|
421
|
+
if result["status"] == "success":
|
|
422
|
+
result["ana_file"] = output_file
|
|
423
|
+
|
|
424
|
+
return result
|
|
425
|
+
|
|
426
|
+
# ===== RESOURCES =====
|
|
427
|
+
|
|
428
|
+
@mcp.resource("cdp://workflow")
|
|
429
|
+
def workflow_guide() -> str:
|
|
430
|
+
"""CDP v7 Workflow - Ultra-rigid approach"""
|
|
431
|
+
return """# CDP MCP v7 - Ultra-Rigid Workflow
|
|
432
|
+
|
|
433
|
+
## The Core Process
|
|
434
|
+
|
|
435
|
+
### 1. List Available Programs
|
|
436
|
+
```python
|
|
437
|
+
programs = list_cdp_programs()
|
|
438
|
+
# Returns categorized list of all CDP programs
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### 2. Get Usage for Selected Program
|
|
442
|
+
```python
|
|
443
|
+
usage = get_cdp_usage('blur')
|
|
444
|
+
# Returns exact CDP usage text - no interpretation!
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### 3. Create Data Files if Needed
|
|
448
|
+
```python
|
|
449
|
+
# When usage mentions DATAFILE, create one:
|
|
450
|
+
create_data_file('data.txt', '5 5 5 5\\n0.0 0.1 0.2 0.3')
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### 4. Execute Exact Command
|
|
454
|
+
```python
|
|
455
|
+
result = execute_cdp(['blur', 'blur', 'input.ana', 'output.ana', '50'])
|
|
456
|
+
# Direct execution of command array
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Why This Works
|
|
460
|
+
|
|
461
|
+
1. **No Parsing** - We don't interpret CDP's output
|
|
462
|
+
2. **No Guessing** - You see exactly what CDP shows
|
|
463
|
+
3. **Full Control** - You build the exact command array
|
|
464
|
+
4. **Simple Server** - Just four core tools, minimal code
|
|
465
|
+
|
|
466
|
+
## Example Workflow
|
|
467
|
+
|
|
468
|
+
```python
|
|
469
|
+
# User wants to blur a spectrum
|
|
470
|
+
# Step 1: Find blur program
|
|
471
|
+
programs = list_cdp_programs()
|
|
472
|
+
# See 'blur' in "Spectral Processing" category
|
|
473
|
+
|
|
474
|
+
# Step 2: Get blur usage
|
|
475
|
+
usage = get_cdp_usage('blur')
|
|
476
|
+
# Read CDP's exact usage text showing:
|
|
477
|
+
# "blur blur infile outfile blur"
|
|
478
|
+
|
|
479
|
+
# Step 3: Execute
|
|
480
|
+
result = execute_cdp(['blur', 'blur', 'input.ana', 'output.ana', '20'])
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Tips
|
|
484
|
+
|
|
485
|
+
- Always check usage before executing
|
|
486
|
+
- CDP exit codes can be non-zero even on success
|
|
487
|
+
- Check if output file exists to verify success
|
|
488
|
+
- Use exact command arrays - no interpretation
|
|
489
|
+
|
|
490
|
+
## Common Patterns
|
|
491
|
+
|
|
492
|
+
### Simple command:
|
|
493
|
+
```python
|
|
494
|
+
execute_cdp(['program', 'mode', 'infile', 'outfile', 'params...'])
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Compound program:
|
|
498
|
+
```python
|
|
499
|
+
execute_cdp(['program', 'subprogram', 'mode', 'infile', 'outfile', 'params...'])
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### With flags:
|
|
503
|
+
```python
|
|
504
|
+
execute_cdp(['program', 'mode', 'infile', 'outfile', '-flag', 'value'])
|
|
505
|
+
```
|
|
506
|
+
"""
|
|
507
|
+
|
|
508
|
+
@mcp.resource("cdp://quickstart")
|
|
509
|
+
def quickstart_examples() -> str:
|
|
510
|
+
"""Quick examples for common CDP operations"""
|
|
511
|
+
return """# CDP v7 Quick Start Examples
|
|
512
|
+
|
|
513
|
+
## Get Started in 3 Steps
|
|
514
|
+
|
|
515
|
+
### 1. See What's Available
|
|
516
|
+
```python
|
|
517
|
+
list_cdp_programs()
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### 2. Learn How to Use It
|
|
521
|
+
```python
|
|
522
|
+
get_cdp_usage('modify', 'speed')
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### 3. Execute It
|
|
526
|
+
```python
|
|
527
|
+
execute_cdp(['modify', 'speed', '1', 'input.wav', 'output.wav', '2.0'])
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
## Common Operations
|
|
531
|
+
|
|
532
|
+
### Time Stretch (Spectral)
|
|
533
|
+
```python
|
|
534
|
+
# Check usage
|
|
535
|
+
get_cdp_usage('stretch')
|
|
536
|
+
|
|
537
|
+
# Execute
|
|
538
|
+
execute_cdp(['stretch', 'time', '1', 'input.ana', 'output.ana', '2.0'])
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Spectral Blur
|
|
542
|
+
```python
|
|
543
|
+
# Check usage
|
|
544
|
+
get_cdp_usage('blur')
|
|
545
|
+
|
|
546
|
+
# Execute (note double syntax)
|
|
547
|
+
execute_cdp(['blur', 'blur', 'input.ana', 'output.ana', '50'])
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Granular Synthesis
|
|
551
|
+
```python
|
|
552
|
+
# Check usage
|
|
553
|
+
get_cdp_usage('modify', 'brassage')
|
|
554
|
+
|
|
555
|
+
# Execute
|
|
556
|
+
execute_cdp(['modify', 'brassage', '4', 'input.wav', 'output.wav', '0.02', '-0.5', '-r200'])
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Convert to Mono
|
|
560
|
+
```python
|
|
561
|
+
# Check usage
|
|
562
|
+
get_cdp_usage('housekeep', 'chans')
|
|
563
|
+
|
|
564
|
+
# Execute
|
|
565
|
+
execute_cdp(['housekeep', 'chans', '4', 'stereo.wav', 'mono.wav'])
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Spectral Analysis
|
|
569
|
+
```python
|
|
570
|
+
# Using helper
|
|
571
|
+
prepare_spectral('input.wav', 'output.ana', 2048)
|
|
572
|
+
|
|
573
|
+
# Or directly
|
|
574
|
+
execute_cdp(['pvoc', 'anal', '1', 'input.wav', 'output.ana', '-c2048'])
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Tesselate with Data File
|
|
578
|
+
```python
|
|
579
|
+
# Check usage
|
|
580
|
+
get_cdp_usage('tesselate')
|
|
581
|
+
# See it needs a DATAFILE with repeat counts and delays
|
|
582
|
+
|
|
583
|
+
# Create the data file
|
|
584
|
+
create_data_file('tess_data.txt', '5 5 5 5\\n0.0 0.1 0.2 0.3')
|
|
585
|
+
|
|
586
|
+
# Execute with data file
|
|
587
|
+
execute_cdp(['tesselate', 'tesselate', '1', 'in1.wav', 'in2.wav', 'in3.wav', 'in4.wav', 'output.wav', '0.5', 'tess_data.txt'])
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
## Remember
|
|
591
|
+
|
|
592
|
+
- Read usage first with get_cdp_usage()
|
|
593
|
+
- Create data files when usage mentions DATAFILE
|
|
594
|
+
- Build exact command arrays
|
|
595
|
+
- Check output file existence for success
|
|
596
|
+
- CDP may return non-zero exit codes even when successful
|
|
597
|
+
"""
|
|
598
|
+
|
|
599
|
+
def main():
|
|
600
|
+
"""Entry point for running the MCP server"""
|
|
601
|
+
mcp.run()
|
|
602
|
+
|
|
603
|
+
if __name__ == "__main__":
|
|
604
|
+
main()
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: iflow-mcp_davidpiazza-cdp_mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: CDP MCP Server - Direct access to Composers' Desktop Project sound transformation programs
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Requires-Dist: mcp>=1.0.0
|
|
8
|
+
Requires-Dist: numpy>=1.24.0
|
|
9
|
+
Requires-Dist: soundfile>=0.12.1
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# CDP MCP Server
|
|
13
|
+
|
|
14
|
+
A Model Context Protocol (MCP) server that provides direct access to the Composers' Desktop Project (CDP) sound transformation programs. This server offers an ultra-rigid workflow with zero interpretation, exposing CDP's raw functionality through simple, reliable tools.
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
CDP MCP Server v7 implements a minimalist approach to CDP integration:
|
|
19
|
+
- **Direct execution** - No command parsing or interpretation
|
|
20
|
+
- **Raw usage text** - See exactly what CDP shows
|
|
21
|
+
- **Simple tools** - Just 6 core functions
|
|
22
|
+
- **Data file support** - Create parameter files for complex operations
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- 🎵 **Full CDP Access** - Execute any CDP program with complete control
|
|
27
|
+
- 📚 **Program Discovery** - List all available CDP programs by category
|
|
28
|
+
- 📖 **Usage Information** - Get raw usage text directly from CDP
|
|
29
|
+
- 📄 **Data File Creation** - Create parameter files required by CDP programs
|
|
30
|
+
- 🎛️ **Spectral Preparation** - Helper for PVOC analysis
|
|
31
|
+
- 📊 **Sound Analysis** - Get basic properties of audio files
|
|
32
|
+
|
|
33
|
+
## Requirements
|
|
34
|
+
|
|
35
|
+
### System Requirements
|
|
36
|
+
- macOS, Linux, or Windows
|
|
37
|
+
- Python 3.8 or higher
|
|
38
|
+
- CDP (Composers' Desktop Project) installed
|
|
39
|
+
|
|
40
|
+
### Python Dependencies
|
|
41
|
+
```
|
|
42
|
+
mcp
|
|
43
|
+
soundfile
|
|
44
|
+
numpy
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### CDP Installation
|
|
48
|
+
1. Download CDP from the [official website](https://www.unstablesound.net/cdp.html)
|
|
49
|
+
2. Install CDP following the platform-specific instructions
|
|
50
|
+
3. Set the `CDP_PATH` environment variable to your CDP programs directory:
|
|
51
|
+
```bash
|
|
52
|
+
export CDP_PATH="/path/to/cdp/programs"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
### 1. Clone the Repository
|
|
58
|
+
```bash
|
|
59
|
+
git clone https://github.com/DavidPiazza/CDP_MCP.git
|
|
60
|
+
cd CDP_MCP
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. Install Dependencies
|
|
64
|
+
```bash
|
|
65
|
+
pip install -r requirements.txt
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Or install individually:
|
|
69
|
+
```bash
|
|
70
|
+
pip install mcp soundfile numpy
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3. Configure CDP Path
|
|
74
|
+
Set your CDP installation path:
|
|
75
|
+
```bash
|
|
76
|
+
export CDP_PATH="/Users/yourname/cdpr8/_cdp/_cdprogs" # macOS example
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 4. Run the Server
|
|
80
|
+
```bash
|
|
81
|
+
python CDP_MCP_v7.py
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## MCP Client Configuration
|
|
85
|
+
|
|
86
|
+
To use this server with an MCP client (like Claude Desktop), add to your configuration:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"mcpServers": {
|
|
91
|
+
"cdp": {
|
|
92
|
+
"command": "python",
|
|
93
|
+
"args": ["/path/to/CDP_MCP_v7.py"],
|
|
94
|
+
"env": {
|
|
95
|
+
"CDP_PATH": "/path/to/cdp/programs"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Usage
|
|
103
|
+
|
|
104
|
+
### Basic Workflow
|
|
105
|
+
|
|
106
|
+
1. **List Available Programs**
|
|
107
|
+
```python
|
|
108
|
+
list_cdp_programs()
|
|
109
|
+
# Returns categorized list of all CDP programs
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
2. **Get Program Usage**
|
|
113
|
+
```python
|
|
114
|
+
get_cdp_usage('blur')
|
|
115
|
+
# Returns exact CDP usage text
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
3. **Execute Commands**
|
|
119
|
+
```python
|
|
120
|
+
execute_cdp(['blur', 'blur', 'input.ana', 'output.ana', '50'])
|
|
121
|
+
# Direct execution with no interpretation
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Example Operations
|
|
125
|
+
|
|
126
|
+
#### Time Stretch
|
|
127
|
+
```python
|
|
128
|
+
# Check usage
|
|
129
|
+
get_cdp_usage('stretch')
|
|
130
|
+
|
|
131
|
+
# Execute
|
|
132
|
+
execute_cdp(['stretch', 'time', '1', 'input.ana', 'output.ana', '2.0'])
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### Spectral Blur
|
|
136
|
+
```python
|
|
137
|
+
# Note the double syntax for blur
|
|
138
|
+
execute_cdp(['blur', 'blur', 'input.ana', 'output.ana', '50'])
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### Granular Synthesis
|
|
142
|
+
```python
|
|
143
|
+
execute_cdp(['modify', 'brassage', '4', 'input.wav', 'output.wav', '0.02', '-0.5', '-r200'])
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
#### Using Data Files
|
|
147
|
+
```python
|
|
148
|
+
# Create data file for tesselate
|
|
149
|
+
create_data_file('tess_data.txt', '5 5 5 5\n0.0 0.1 0.2 0.3')
|
|
150
|
+
|
|
151
|
+
# Execute with data file
|
|
152
|
+
execute_cdp(['tesselate', 'tesselate', '1', 'in1.wav', 'in2.wav', 'in3.wav', 'in4.wav', 'output.wav', '0.5', 'tess_data.txt'])
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Tools Reference
|
|
156
|
+
|
|
157
|
+
### `list_cdp_programs()`
|
|
158
|
+
Lists all available CDP programs organized by category (Spectral Processing, Time Domain, Synthesis, etc.)
|
|
159
|
+
|
|
160
|
+
### `get_cdp_usage(program, subprogram=None)`
|
|
161
|
+
Returns raw usage information for a CDP program by running it without arguments.
|
|
162
|
+
|
|
163
|
+
### `execute_cdp(command)`
|
|
164
|
+
Executes a CDP command given as an array of strings. No parsing or interpretation.
|
|
165
|
+
|
|
166
|
+
### `create_data_file(filepath, content)`
|
|
167
|
+
Creates text data files required by certain CDP programs.
|
|
168
|
+
|
|
169
|
+
### `prepare_spectral(input_file, output_file, window_size=2048)`
|
|
170
|
+
Helper function to perform PVOC analysis for spectral processing.
|
|
171
|
+
|
|
172
|
+
### `analyze_sound(filepath)`
|
|
173
|
+
Returns basic properties of a sound file (duration, sample rate, channels, etc.)
|
|
174
|
+
|
|
175
|
+
## Architecture
|
|
176
|
+
|
|
177
|
+
The server follows an ultra-rigid design philosophy:
|
|
178
|
+
- **No command parsing** - Commands are arrays, not strings
|
|
179
|
+
- **No parameter validation** - CDP handles all validation
|
|
180
|
+
- **No output interpretation** - Raw CDP output is returned
|
|
181
|
+
- **Minimal abstraction** - Direct CDP access only
|
|
182
|
+
|
|
183
|
+
## Tips
|
|
184
|
+
|
|
185
|
+
- Always check usage with `get_cdp_usage()` before executing
|
|
186
|
+
- CDP may return non-zero exit codes even on success
|
|
187
|
+
- Check if output files exist to verify successful execution
|
|
188
|
+
- Use exact command arrays - the server does no interpretation
|
|
189
|
+
- Create data files when CDP usage mentions DATAFILE requirements
|
|
190
|
+
|
|
191
|
+
## Troubleshooting
|
|
192
|
+
|
|
193
|
+
### CDP Not Found
|
|
194
|
+
Ensure `CDP_PATH` environment variable points to your CDP programs directory.
|
|
195
|
+
|
|
196
|
+
### Apple Silicon Issues
|
|
197
|
+
The server automatically handles x86_64 emulation on Apple Silicon Macs using `arch -x86_64`.
|
|
198
|
+
|
|
199
|
+
### Command Failures
|
|
200
|
+
1. Check the exact usage with `get_cdp_usage()`
|
|
201
|
+
2. Verify all file paths exist
|
|
202
|
+
3. Ensure proper command array format
|
|
203
|
+
4. Check CDP's stderr output for specific errors
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
MIT License - See LICENSE file for details
|
|
208
|
+
|
|
209
|
+
## Acknowledgments
|
|
210
|
+
|
|
211
|
+
- [Composers' Desktop Project](https://www.unstablesound.net/cdp.html) for the amazing sound transformation tools
|
|
212
|
+
- [Model Context Protocol](https://github.com/anthropics/model-context-protocol) for the MCP framework
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
CDP_MCP_v7.py,sha256=6pZ9v9JsB3U_Gn1D6PciahrT2EGIknraEl0SgZBvKgI,17058
|
|
2
|
+
iflow_mcp_davidpiazza_cdp_mcp-1.0.0.dist-info/METADATA,sha256=9MJorLRB5k_80Fakcdsc_YzzYVlIflfRTDwn4xIcry8,5869
|
|
3
|
+
iflow_mcp_davidpiazza_cdp_mcp-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
4
|
+
iflow_mcp_davidpiazza_cdp_mcp-1.0.0.dist-info/entry_points.txt,sha256=4vvKjhtzLKS24PmCjStnODd7hxNwaMG9qV-YzYAYfC0,44
|
|
5
|
+
iflow_mcp_davidpiazza_cdp_mcp-1.0.0.dist-info/licenses/LICENSE,sha256=I6_PEg9fzBmtm5ATTtiLbCAISyzuLATnjOTCUsdtNa0,1069
|
|
6
|
+
iflow_mcp_davidpiazza_cdp_mcp-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 David Piazza
|
|
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.
|