poietic-mcp
# poietic-mcp
An [MCP](https://modelcontextprotocol.io) server that lets an agent read, edit,
validate and simulate [Poietic](https://www.poietic.org/) Stock and Flow
designs, alongside a human working in
[Poietic Playground](https://github.com/OpenPoiesis/poietic-playground).
The design file is the integration point. The agent edits a `.poietic` file
through this server by driving the
[`poietic` CLI](https://github.com/OpenPoiesis/poietic-tool); the human opens
or reloads the same file in the playground. Agent edits, human views and
direct-manipulates; both sides re-read.
## Install
```bash
# 1. Build and install the poietic CLI (Swift 6.x, macOS 15+ or Linux)
git clone https://github.com/OpenPoiesis/poietic-tool
cd poietic-tool && git checkout v0.9 && ./install # installs to ~/.swiftpm/bin/poietic
# 2. Build this server
git clone https://github.com/soobrosa/poietic-mcp
cd poietic-mcp && npm install && npm run build
```
## Configure
The server works with any MCP client. The command to register is always:
```
node <absolute-path-to>/poietic-mcp/dist/index.js
```
with optional environment variables (see below). Environment:
- `POIETIC_MCP_WORKSPACE` - allowlist root. Design paths and export outputs
must live inside it. Defaults to the server's working directory. Set it to
a dedicated designs folder (e.g. `~/Documents/poietic-designs`) for a
tight fence, or your home directory to work with designs anywhere.
- `POIETIC_BIN` - path to the `poietic` binary. Defaults to
`~/.swiftpm/bin/poietic`.
- `POIETIC_DESIGN` - optional default design path, used when no current design
is set.
### Claude Code
```bash
claude mcp add --scope user poietic \
--env POIETIC_MCP_WORKSPACE=$HOME \
--env POIETIC_BIN=$HOME/.swiftpm/bin/poietic \
-- /opt/homebrew/bin/node /absolute/path/to/poietic-mcp/dist/index.js
```
Verify with `claude mcp list` (should show `poietic: ... - ✔ Connected`) or
`/mcp` inside a session. `--scope user` makes it available in all projects;
use `--scope project` to register it only for one repository (writes to that
repo's `.mcp.json`).
### Claude Desktop
Add the server to `~/Library/Application Support/Claude/claude_desktop_config.json`
(create the `mcpServers` key if absent), then **fully quit and reopen** the
app (Cmd+Q; MCP servers only start at launch):
```json
{
"mcpServers": {
"poietic": {
"command": "/opt/homebrew/bin/node",
"args": ["/absolute/path/to/poietic-mcp/dist/index.js"],
"env": {
"POIETIC_MCP_WORKSPACE": "/Users/you",
"POIETIC_BIN": "/Users/you/.swiftpm/bin/poietic"
}
}
}
}
```
The poietic tools appear under the tools icon in a chat.
### Factory Droid / other clients
Register the same command in the client's MCP settings (e.g. `.mcp.json` in
the workspace for Droid, project config for other clients):
```json
{
"mcpServers": {
"poietic": {
"command": "node",
"args": ["/absolute/path/to/poietic-mcp/dist/index.js"],
"env": {
"POIETIC_MCP_WORKSPACE": "/Users/you/Documents/poietic-designs",
"POIETIC_BIN": "/Users/you/.swiftpm/bin/poietic"
}
}
}
}
```
### Verifying the setup
From a terminal, drive the server directly like an MCP client would:
```bash
node src/test/smoke.mjs # registers, builds a tiny model, simulates
```
## Using it
You do not invoke tools by name; you talk, and the agent picks the
`poietic_*` tools as the conversation turns to building models.
**A typical build request:**
> Create a new Poietic design called fishing.poietic in
> ~/Documents/poietic-designs and model the fishing economy from Thinking
> in Systems: a fleet (capital) that grows 5% per year, fish that regenerate
> as a function of fish density, harvest yield falling as fish get scarce,
> price rising as fish get scarce. Validate and simulate 200 years.
**A typical inspect-and-modify request:**
> Open Capital.poietic in ~/Downloads/_Converted/ThinkingInSystems, show me
> the structure, then double the initial resource and re-run the simulation.
**Working alongside Poietic Playground:** open the same design file in the
playground to see and edit the agent's work by hand. After saving in the
playground, the agent's next write is refused with a fingerprint conflict;
tell it to re-read the design (`poietic_get_design`) and continue. See
[PLAYGROUND.md](PLAYGROUND.md) for the handoff protocol.
**The guided recipe:** ask the agent to use the `build_stock_and_flow_model`
prompt for a description of a system. It encodes node roles (Stock vs
FlowRate vs Auxiliary), Flow vs Parameter edge semantics, formula syntax, and
the validate-then-run ordering - including two lessons learned the hard way:
- Node names can contain spaces. In formulas, wrap those names in curly
braces, for example `{account balance} * {interest rate}`. Name matching is
case-insensitive, and spaces and underscores normalize to the same name.
- `poietic_auto_parameters` does **not** wire Parameter edges into or out of
GraphicalFunction nodes. After auto-parameters, validate; for each issue,
connect the missing edge explicitly and re-validate.
- A stated growth-rate goal is usually a **net** rate. An investment goal of
g% of the stock with d% depreciation yields net growth of (g - d)%; setting
g = d freezes the stock. Gross goal = depreciation + desired growth.
**Tips:**
- Mention the design's folder so the agent stays inside the workspace fence.
- Ask for `poietic_validate` before simulating; the agent will usually do it
unprompted, but a broken model fails in `poietic_run` with the same issues.
- Ask for results as summaries, not raw dumps - `poietic_run` already returns
a per-variable summary with capped sample rows, and can write the full CSV
to a file if you give it an `outputPath`.
## Tools
Session: `poietic_use_design` binds the current design (and snapshots a
content-hash fingerprint as the session baseline); every other tool takes an
optional `design` override.
| Tool | Purpose |
|---|---|
| `poietic_metamodel(type?)` | Metamodel as markdown (cached). Schema discovery. |
| `poietic_get_design()` | Normalized design JSON: `{nodes[], edges[], unstructured[]}`. |
| `poietic_validate()` | `{ok, issues[]}` with per-object formula/parameter errors. |
| `poietic_new_design({path, import?})` | Create an empty design; becomes current. |
| `poietic_add_node({type, attributes})` | Create a node; returns `{objectId, planeId}`. |
| `poietic_connect({type, origin, target, attributes?})` | Create an edge, optionally with v0.9 attributes; returns `{edgeId}`. |
| `poietic_set_attributes({ref, attributes})` | Set attributes; one undo entry (single transaction on poietic-tool v0.8+). |
| `poietic_unset_attributes({ref, attributes})` | Remove stored attribute values in one transaction (v0.9+). |
| `poietic_remove({ref})` | Remove a node or edge (cascades edges). |
| `poietic_auto_parameters()` | Wire required parameter edges, drop unused ones. |
| `poietic_undo()` / `poietic_redo()` | One tool call = one transaction. |
| `poietic_arrange({mode, refs?})` | `circle` layout or align modes. |
| `poietic_run({...})` | Simulate with `startTime`, `finalTime`, `timeStep`, `euler`/`rk4`, and display/normalized names; summarize results or write full CSV. |
| `poietic_export_svg({outputPath})` | SVG diagram export. |
| `poietic_write_dot({outputPath?})` | Graphviz DOT (stdout if no path). |
| `poietic_export_plane` / `poietic_import_plane` | Plane exchange between design files. |
Resources: `poietic://metamodel`, `poietic://builtins` (formula operators,
functions and variables; poietic-tool v0.8+), `poietic://design/current`,
`poietic://design/current/diagram.svg`.
For a v0.9 `Simulation` settings object, use `start_time`, `time_step`, and
`final_time` (replacing `initial_time`, `time_delta`, and `end_time`).
`solver_type` accepts `euler` or the fixed `rk4` solver. `flow_scaling`
accepts `outflow_first` or `inflow_first`; set it when competing flows may
exceed available stock. The `steps` run option is deprecated, so prefer
`finalTime`.
Prompt: `build_stock_and_flow_model` - encodes node roles, Flow vs Parameter
edge semantics, formula syntax, and the validate-then-run ordering.
## Safety model
- **Argv only.** The CLI is spawned with an argv array; formulas containing
`*`, spaces or parentheses are never interpreted by a shell.
- **Per-design mutex.** Every CLI invocation is a whole-file
read-modify-write; concurrent calls are serialized per design.
- **Fingerprint guard.** After the first bind, any external change to the
design file (e.g. the human saving in the playground) blocks writes with a
clear error until the agent re-reads the design. Conflicts are detected,
never merged.
- **Path allowlist.** Designs and outputs must be inside
`POIETIC_MCP_WORKSPACE`.
- **Verbatim errors.** Non-zero CLI exits surface the CLI's stderr unchanged.
- **No silent nulls.** A create whose output cannot be parsed for the new
object ID is a hard error.
## Development
```bash
npm run build # tsc
npm test # parser fixtures + integration (needs the poietic binary)
node src/test/smoke.mjs # drives the built server over stdio
node src/test/from-description.mjs # builds a model from prose only
```
`fixtures/raw/` holds recorded stdout/stderr from the real CLI (see
`fixtures/FINDINGS.md` for observed behavior and deviations from the docs);
`fixtures/capture.sh` reproduces them. `src/test/capital.test.mjs` rebuilds
the Thinking in Systems Capital model through the MCP tools and asserts the
rebuilt simulation reproduces the committed original variable by variable.
### Troubleshooting
- **`poietic binary not found`** - build and install
[poietic-tool](https://github.com/OpenPoiesis/poietic-tool), or point
`POIETIC_BIN` at the binary.
- **`Path is outside the allowed workspace`** - the design path is not under
`POIETIC_MCP_WORKSPACE`; move the file, or update the server's env in the
client config (restart the client afterwards).
- **`The design changed outside this session`** - the file was saved
externally (playground, another agent). Re-read it with
`poietic_get_design`, then retry the write.
- **Validation issues about graphical functions** - run
`poietic_auto_parameters`, then connect the missing GraphicalFunction edges
explicitly (see the from-prose lessons under "Using it").
- **Claude Desktop shows no poietic tools** - the app loads MCP servers only
at launch; quit fully (Cmd+Q) and reopen. Logs:
`~/Library/Logs/Claude/mcp*.log`.
## Known limitations
- **No live playground drive.** The human reopens the file; the playground has
no reload-on-change.
- **Concurrent editing is detected, not merged.** The fingerprint guard
refuses the write; re-read and re-apply.
- **Multi-step agent edits are not atomic.** Each tool call is one
transaction; one `undo` reverses one call. (`poietic_set_attributes` with
several attributes is a single transaction on poietic-tool v0.8+; older
builds create one entry per attribute.)
- **Duplicate object names resolve arbitrarily upstream**, so name-based
references are only safe when uniqueness is known. Prefer numeric IDs.
TDQS
Scored across 18 tools
Each tool targets a distinct capability: schema discovery, design lifecycle, node/edge editing, validation, simulation, layout, and export/import. Even the export tools are clearly separated by output format, and the manual edge creation vs auto-wiring tools serve different workflow stages.
All tools share the poietic_ prefix and mostly use imperative/snake_case names (get_design, add_node, set_attributes, export_svg). A few names are verb-only (validate, connect, run) and poietic_metamodel is a noun-only outlier, so the pattern is not perfectly uniform.
Eighteen tools is slightly above the typical comfortable range, but each maps to a needed operation across model editing, simulation, and exchange. The count feels justified rather than bloated for a domain with both editing and simulation/export workflows.
The set covers schema discovery, design creation/loading/reading, node and edge CRUD-style operations, attribute updates, validation, undo/redo, simulation, layout, and multiple export/import paths. There are no obvious dead ends for a stock-and-flow modeling workflow.