pd-mcp-server

内容来源:README.md(说明文档) · 原始地址 · 查看安装指南

原始内容

puredata-mcp

A reliable Model Context Protocol server for Pure Data. It lets an MCP client (Claude Desktop, etc.) build and drive Pd patches in natural language: create objects, wire them together, toggle audio, and send live messages into a running patch. It also keeps an authoritative in-memory model of the patch, so it can version your work — git-backed checkpoints, standalone .pd export, and a musical graph-level diff between versions.

Why this design is reliable

It speaks Pd's native FUDI protocol over a plain TCP socket and uses Pd vanilla's built-in dynamic patching messages (obj, msg, connect, disconnect, clear). That means:

  • No externals to install (no mrpeach/OSC, no zexy, no namecanvas).
  • No intermediate daemon — the MCP server talks straight to [netreceive].
  • Object ids stay in sync with Pd because the server mirrors Pd's exact creation-index ordering.

Earlier OSC-based Pd MCP attempts were fragile precisely because they stacked an OSC layer plus a relay daemon on top of Pd. This removes both.

Architecture

 Claude / MCP client
        │  (stdio, JSON-RPC)
        ▼
 puredata_mcp.server  ──FUDI over TCP──▶  [netreceive 3000]   (mcp_host.pd)
                                               │
                                          [route __dsp __send]
                                          │     │          └─▶ [send pd-canvas] ─▶ [pd canvas]
                                          │     └─▶ relay ─▶ [send] (to a named [receive])
                                          └─▶ [; pd dsp $1(   (global audio on/off)

Objects you create land inside the [pd canvas] subpatch, addressed by Pd's dynamic-patching receiver pd-canvas.

Install

Pick the channel that matches your client. The repo ships in three formats so each lands cleanly:

Client How to install What it activates
Claude Code (CLI REPL) inside a claude session: /plugin marketplace add jfboisvenue/pd-mcp-server then /plugin install puredata@puredata-marketplace Skill auto-load + 30 MCP tools
Claude Desktop — Cowork sessions Plugins panel → "Add from repository" → paste https://github.com/jfboisvenue/pd-mcp-server Skill auto-load + 30 MCP tools (Cowork chat only)
Claude Desktop — regular chat Download puredata-mcp.mcpb from Releases → double-click → Install via Settings → Extensions 30 MCP tools (no skill — the plugin/skill format isn't loaded by this channel)

You can install both — the plugin (Code + Cowork) and the .mcpb (regular Desktop chat) coexist, share the same source code, and don't conflict.

After install, the skill (where applicable) auto-loads when relevant and the MCP tools become available. You still need to open pd/mcp_host.pd in Pd for the server to have something to talk to.

Why three channels?

Anthropic's plugin marketplace targets Claude Code and Desktop's Cowork sessions. Plugins installed this way load their skill in regular Desktop chat too, but not the MCP server — that's by design across all platforms (macOS, Windows, Linux), not a Linux-specific bug. For the MCP to appear in regular Desktop chat, you need either a .mcpb Desktop Extension or a manual entry in ~/.config/Claude/claude_desktop_config.json (see Manual MCP install below).

Local dev / testing

For iteration on the code itself:

claude --plugin-dir /absolute/path/to/pd-mcp-server   # per-session

Or symlink once, persistent across sessions and surfaces:

mkdir -p ~/.claude/skills && \
ln -s /absolute/path/to/pd-mcp-server ~/.claude/skills/puredata

Requirements

For the MCP core (object/wire/DSP tools):

  • Pure Data vanilla 0.51+
  • Python 3.10+ to run the MCP server
  • uv (recommended) or pip

For the Python tools (pd_create_python_object, pd_update_python_script) the user's Pd host machine additionally needs:

  • Pure Data vanilla ≥ 0.56 — py4pd 1.2.3 uses pd_snprintf, missing in 0.54.x and earlier (symptom: undefined symbol: pd_snprintf / couldn't create).
  • py4pd external ≥ 1.2.3 installed in Pd (Help → Find externals → "py4pd").
  • Python 3.14 installed system-wide — py4pd's Deken build links against libpython3.14.so.1.0. On Ubuntu 24.04 (which ships 3.12 by default) you may need to install 3.14 separately (e.g. deadsnakes PPA).
  • The host patch must contain [declare -path <scripts dir> -lib py4pd]. The bundled pd/mcp_host.pd already does this; custom host patches need it added.

Verified working stack: Pd 0.56-3 + py4pd 1.2.3 + Python 3.14.5 + .pd_py classes inheriting pd.NewObject.

Manual MCP install (fallback)

If the .mcpb install isn't an option (corporate restriction, edge case, you want to point Desktop at your dev checkout), register the server directly in ~/.config/Claude/claude_desktop_config.json — the same file Desktop reads other classic MCPs from:

{
  "mcpServers": {
    "puredata": {
      "command": "/home/YOU/.local/bin/uv",
      "args": [
        "--directory",
        "/absolute/path/to/pd-mcp-server",
        "run",
        "python",
        "-m",
        "puredata_mcp.server"
      ],
      "env": {
        "PD_HOST": "127.0.0.1",
        "PD_PORT": "3000"
      }
    }
  }
}

Use the absolute path to uv (which uv → that path). GUI launchers don't reliably inherit the shell's PATH, so a bare "uv" sometimes fails to resolve even when uv is installed.

Then fully quit Claude Desktop (right-click tray icon → Quit, or pkill claude-desktop on Linux) and relaunch. Verify the server started:

grep puredata ~/.config/Claude/logs/mcp.log | tail -5
# expect: [puredata] Server started and connected successfully

This route bypasses the plugin/.mcpb mechanisms entirely. It's the most reliable path when debugging or pinning to a local checkout, at the cost of hardcoded paths you maintain manually. The skill won't auto-load this way — the manual config only registers the MCP.

Releasing a new .mcpb (maintainer only)

Tag a version matching pyproject.toml and push:

git tag v0.1.0
git push --tags

GitHub Actions (.github/workflows/release-mcpb.yml) validates the manifest, packs the bundle via mcpb pack, and publishes a GitHub release with puredata-mcp.mcpb attached. End users install it via Settings → Extensions.

The .mcpbignore file controls what ships in the bundle (currently 10 files / ~23KB). Dev artifacts (.venv/, tests, plugin manifests, CI config, etc.) are excluded. To preview locally:

npx --yes @anthropic-ai/mcpb pack . /tmp/puredata-mcp.mcpb
unzip -l /tmp/puredata-mcp.mcpb

Tools

The agent must call pd_init first — every other tool refuses to run until it has. pd_init returns the orientation guide (wire model, id contract, common-object cheat sheet, cookbook, safety notes), so the LLM has a single, server-authored source of truth instead of having to infer conventions from individual tool docstrings.

Tool What it does
pd_init Mandatory first call. Returns the orientation guide and unlocks the rest; pass project_dir to bind the session so this patch gets its own checkpoints/scripts
pd_create_object Create [type args…] at (x,y); returns its id
pd_create_message Create a message box [atoms…(
pd_create_comment Create a text comment (spaces preserved)
pd_create_floatatom Create a typeable number atom
pd_create_bang Create a bang button [bng]
pd_create_toggle Create a toggle [tgl]
pd_create_number_box Create a bounded number box [nbx]
pd_create_slider Create a slider (horizontal [hsl] or vertical [vsl])
pd_create_python_object Write <name>.pd_py defining a pd.NewObject class and create [<name>] on the canvas (atomic, py4pd 1.2.3+)
pd_update_python_script Rewrite an existing <name>.pd_py (re-create the object on the canvas to pick up changes)
pd_connect Wire source_id:outlet → target_id:inlet
pd_disconnect Remove a connection
pd_set_dsp Start/stop global audio DSP
pd_send_message Send atoms to a named [receive] in the patch
pd_clear_canvas Delete everything; reset ids to 0
pd_resync_index Realign the id counter after a manual canvas edit
pd_get_state List objects the server has created
pd_snapshot Commit the current patch as a git-backed checkpoint (use branch to A/B sound variants)
pd_restore Re-render the canvas from a saved checkpoint (clears first), addressed by label/hash/branch
pd_list_checkpoints List all checkpoints across branches in the checkpoints repo
pd_export_pd Write a standalone, openable .pd file from the current patch or a checkpoint
pd_diff Musical graph-level diff between two checkpoints, or a checkpoint vs. the live patch
pd_recover Re-render the autosaved working IR after a restart (needs a bound project_dir)
pd_save_preset Save a named bag of parameter values ({receiver: atoms}) into the IR
pd_apply_preset Re-send a saved preset's values into the live patch (non-destructive — no re-render)
pd_list_presets List saved presets and their values
pd_save_template Capture the patch (or a subset by id) as a reusable sub-graph; parameterize with ${token} holes
pd_apply_template Stamp a template into the current patch (append), substituting ${tokens}; returns the id-map for boundary wiring
pd_list_templates List saved templates with their object/connection counts and required ${params}

pd_snapshotpd_recover are the versioning layer: the server holds an authoritative in-memory model of the patch (the IR) and serializes to .pd, never parses from it. Each patch gets its own checkpoints repo — bind it once with pd_init(project_dir="/path/to/patch") and checkpoints land in <project_dir>/checkpoints/ (and .pd_py scripts in <project_dir>/scripts/). Without a binding, the server falls back to PD_CHECKPOINTS_DIR then a single bundled checkpoints/ shared across patches. Each commit pairs patch.json (the IR) with patch.pd (the openable file); branches double as A/B variants.

Binding a project_dir also turns on autosave: the live IR is written to <project_dir>/.pd_session.json after every change (a single rolling file, not versioned history). If the server or Pd restarts, the in-memory model is gone but that file survives — pd_recover reloads and re-renders it so the canvas matches again. Autosave is unsaved-work recovery; pd_snapshot/pd_restore remain the way to keep named, versioned states.

If your project_dir is itself a git repo, add .pd_session.json to its .gitignore — it's a transient working file, not something to commit. (The checkpoints/ repo is separate and already its own git history.)

pd_save_presetpd_list_presets are the presets layer: build your patch so every tweakable parameter is fed by an [r <name>], and parameter values become data you can name, recall, and version. Recalling a preset is a pure message send, never a re-render. When a project_dir is bound, presets form a durable per-project library at <project_dir>/presets.json — a plain, human-readable {name: {receiver: atoms}} file you can open, written on every save and reloaded automatically at pd_init (presets imply no canvas objects, so hydrating them is safe without a re-render). They also ride in the IR, so snapshots version them and pd_clear_canvas keeps them. Unlike the transient .pd_session.json, presets.json is meaningful data — fine to commit to your project repo. See pd_init's guide for the full workflow.

pd_save_templatepd_list_templates are the templates layer: where a preset recalls values, a template stamps structure. Build a unit once — a synth voice, a filter+envelope, a meter — capture it (whole canvas, or a subset by ids, keeping only the wiring internal to that selection), then pd_apply_template appends copies into any patch. Because Pd's creation index is append-only, a stamped instance just takes the next free ids, and the tool returns an id_map (template-local id → new canvas id) so you can wire the copy's boundary into the rest of the patch. Parameterize with ${token} substitution — not a new language, just named holes in object args / message atoms / comment text (e.g. [r freq_${v}]), filled at apply so multiple instances get unique, non-colliding receiver and buffer names. Because a template is reusable tooling — a synth voice you want in every patch, not one project's content — the library is global, not per-project: each template is saved as one <name>.json file in your Pd user folder, which the server auto-locates (it reads Pd's own search paths from ~/.pdsettings / the macOS plist, landing on e.g. ~/Documents/Pd/templates; override with PD_TEMPLATES_DIR). No project binding is needed — they persist and are reusable everywhere, the save response reports the exact path, and pd_init announces what it loaded. Unlike presets they do not ride in the IR. See pd_init's guide for the two-voice cookbook.

Python class files (.pd_py) written by pd_create_python_object live in pd/scripts/ by default, which the host patch declares via [declare -path scripts -lib py4pd]. The location is overridable per call (scripts_dir parameter) or globally (PD_SCRIPTS_DIR env var). The Python tools require py4pd ≥ 1.2.3 by Charles Neimog (Pd → Help → Find externals → "py4pd"), which itself requires Pd ≥ 0.55 and Python 3.14 on the host system (the Deken build links against libpython3.14.so.1.0). Mental model: one .pd_py file defines one pd.NewObject subclass and becomes one custom Pd object; multiple objects = multiple files.

Object ids are Pd creation indices (0, 1, 2, …). pd_connect uses them.

Example: 440 Hz sine to the DAC

pd_create_object  type="osc~"  args=["440"]   x=40 y=40   -> id 0
pd_create_object  type="*~"    args=["0.2"]   x=40 y=90   -> id 1
pd_create_object  type="dac~"                 x=40 y=140  -> id 2
pd_connect        0:0 -> 1:0
pd_connect        1:0 -> 2:0      (left channel)
pd_connect        1:0 -> 2:1      (right channel)
pd_set_dsp        on=true

Testing

The wire format, FUDI escaping, and id bookkeeping are covered by tests that run without Pd, using a mock [netreceive]:

pip install pytest
python -m pytest tests/ -v

Known limitations (Pd vanilla)

  • No single-object delete. Vanilla has no "delete object N" editing message; use pd_clear_canvas and rebuild, or restructure. (This is a Pd constraint, not a server bug.)
  • The server mirrors creation order, it does not read Pd back. If you edit the [pd canvas] subpatch by hand while the server runs, ids can drift — call pd_resync_index(next_index=N) to realign the counter.
  • pd_send_message requires a matching [receive <name>] already present in your patch.
  • Default transport is TCP. For UDP, change the patch object to netreceive -u 3000 and adjust the client accordingly.

Layout

pd-mcp-server/
├── manifest.json               # .mcpb manifest (Desktop Extensions channel)
├── .mcpbignore                 # controls what ships in the .mcpb bundle
├── .github/workflows/
│   └── release-mcpb.yml        # CI: pack + publish .mcpb on tag push
├── .claude-plugin/
│   ├── plugin.json             # plugin manifest (Code + Cowork channel)
│   └── marketplace.json        # this repo is its own marketplace
├── .mcp.json                   # plugin's MCP server declaration (stdio)
├── skills/
│   └── puredata/
│       └── SKILL.md            # auto-triggers on Pd/synth/audio intents
├── bin/
│   └── puredata-mcp            # plugin launcher (finds uv across locations)
├── puredata_mcp/
│   ├── server.py               # FastMCP server + tools + init gate
│   ├── guide.py                # orientation guide returned by pd_init
│   ├── fudi.py                 # FUDI TCP client + escaping
│   ├── patch_state.py          # authoritative IR (nodes/edges) + init flag
│   ├── builders.py             # single source of truth for the wire/file format
│   ├── versioning.py           # git-backed snapshot/restore (dedicated repo)
│   ├── pd_serialize.py         # IR -> standalone .pd text
│   └── pd_diff.py              # graph-level diff + musical formatting
├── pd/mcp_host.pd              # the Pd host patch (open this in Pd)
├── checkpoints/                # versioning git repo (gitignored, auto-created)
├── pd/scripts/                 # .pd_py files dropped by pd_create_python_object
├── tests/                      # mock-Pd round-trip + unit tests
├── pyproject.toml
└── claude_desktop_config.example.json  # legacy example -- see "Manual MCP install"

License

MIT