An MCP server that lets an agent read, edit, validate and simulate Poietic Stock and Flow designs, alongside a human working in Poietic Playground.
The design file is the integration point. The agent edits a .poietic file
through this server by driving the
poietic CLI; the human opens
or reloads the same file in the playground. Agent edits, human views and
direct-manipulates; both sides re-read.
# 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 buildThe 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 thepoieticbinary. Defaults to~/.swiftpm/bin/poietic.POIETIC_DESIGN- optional default design path, used when no current design is set.
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.jsVerify 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).
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):
{
"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.
Register the same command in the client's MCP settings (e.g. .mcp.json in
the workspace for Droid, project config for other clients):
{
"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"
}
}
}
}From a terminal, drive the server directly like an MCP client would:
node src/test/smoke.mjs # registers, builds a tiny model, simulatesYou 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 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_parametersdoes 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_validatebefore simulating; the agent will usually do it unprompted, but a broken model fails inpoietic_runwith the same issues. - Ask for results as summaries, not raw dumps -
poietic_runalready returns a per-variable summary with capped sample rows, and can write the full CSV to a file if you give it anoutputPath.
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.
- 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.
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 onlyfixtures/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.
poietic binary not found- build and install poietic-tool, or pointPOIETIC_BINat the binary.Path is outside the allowed workspace- the design path is not underPOIETIC_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 withpoietic_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.
- 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
undoreverses one call. (poietic_set_attributeswith 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.