Skip to content
Connect an agent
Use the server

Self-hosting the engine

Run the Plotwork engine inside your own network as one container: the MCP tools for your agents and the REST drawing routes for your services, with no account and no request leaving your network. It is for platform and security teams at companies that cannot send diagram text to a hosted service.

For
  • Docker
  • Claude Code
  • Cursor
  • VS Code

TL;DR

QuestionAnswer
What is it?One container image with the diagram engine, MCP over Streamable HTTP at /mcp, the REST drawing routes under /v1, and /healthz.
Does anything leave my network?No. The server makes no outbound request, sends no telemetry, and stores nothing. Logs go to standard output and never hold diagram text.
What is not in it?Accounts, the portal, hosted diagram links, organisations, brand themes, and icon packs. Those are features of the hosted service.
How do I run it?Put the image in your registry, set PLOTWORK_TOKEN, and start it with Docker, Compose, or Kubernetes.
How does an agent connect?Point the client at http://<host>:3106/mcp with the header Authorization: Bearer <token>.
Is it the same engine?Yes. The tools, their schemas, and the pictures are the same as the hosted service and the npm package.

What is included

PartSelf-hostedHosted service
Every MCP tool of the local server (draw, edit, convert, import, map icons, sync Markdown, patterns, read, compare, validate, render, outline, search icons)YesYes
REST: validate, render, draw, edit, convert Mermaid, sync Markdown, find, get, and draw a patternYes, same paths and bodiesYes
read_diagram and compare_diagrams with a link to a hosted pictureNo: send the SVG text or the sourceYes
A viewUrl on a drawingNo: it points at Plotwork's viewerYes
Publishing, listing, and deleting hosted diagramsNoYes
Accounts, API keys, the portal, organisationsNoYes
Brand themes and icon packsNoYes, on plans that include them
Usage counting of any kindNoAnonymous daily totals

The REST routes take the same request bodies and answer the same shapes as the hosted API, so a client written for one works with the other after a change of base address and credential. The self-hosted server adds two answers the hosted API does not have: 503 engine.busy when every worker is busy and the queue is full, and 504 engine.timeout when a job runs past its deadline.

1. Get the image

Plotwork gives your organisation the image. Push it into your own registry, so your hosts pull it from inside your network. The examples on this page call it plotwork-selfhost.

PropertyValue
BaseDistroless Node 24, pinned by digest
SizeAbout 56 MB compressed
User65532, not root
FilesystemRuns with a read-only root filesystem; writes nothing
Port3106
Health checkBuilt in: GET /healthz over loopback

2. Run it

Make a token and keep it in your secret store:

sh
openssl rand -hex 32

Run the container with the token in its environment:

sh
docker run -d --name plotwork \  --read-only --cap-drop ALL --security-opt no-new-privileges \  -p 127.0.0.1:3106:3106 \  -e PLOTWORK_TOKEN \  plotwork-selfhost

Check it:

sh
curl http://127.0.0.1:3106/healthz

With Compose

This file runs the server on an internal network, which has no route out. Docker publishes no port from an internal network, so the services that call Plotwork, such as an agent gateway or a reverse proxy, join the plotwork network and reach it at http://plotwork:3106. Put PLOTWORK_TOKEN in a .env file beside it, never in the file itself.

yaml
services:  plotwork:    image: plotwork-selfhost    restart: unless-stopped    environment:      PLOTWORK_TOKEN: ${PLOTWORK_TOKEN:?Set PLOTWORK_TOKEN in .env}      PLOTWORK_WORKERS: '2'    networks:      - plotwork    read_only: true    cap_drop:      - ALL    security_opt:      - no-new-privileges:true    mem_limit: 1g    pids_limit: 64
networks:  plotwork:    internal: true

Settings

Every setting is an environment variable. A value the server cannot use stops it at start with a message that names the variable.

VariableDefaultMeaning
PLOTWORK_TOKENnoneThe bearer token for /mcp and /v1, at least 32 characters.
PLOTWORK_HOST0.0.0.0 in the imageThe address to listen on.
PLOTWORK_PORT3106The port to listen on.
PLOTWORK_ALLOW_OPENunset1 serves without a token on an address other than loopback. Use it only where the network itself is the control.
PLOTWORK_ALLOWED_ORIGINSnoneBrowser origins allowed to call, separated by commas.
PLOTWORK_WORKERS2Worker threads that run the engine. One layout uses one thread for up to a second.
PLOTWORK_QUEUE32Jobs that may wait for a worker before more are refused.
PLOTWORK_TIMEOUT_MS30000The deadline of one job, waiting included.
PLOTWORK_WORKER_MEMORY_MB512The heap limit of each worker.
PLOTWORK_MAX_BODY_BYTES4194304The largest request body.
PLOTWORK_MCP_RESULT_LIMIT36000Characters of an MCP drawing answer before its pictures are left out, so an AI client can read it.
PLOTWORK_TOOLSfullcore lists only the eight workflow tools on /mcp, with short descriptions, for an open model with a small context. See Use Plotwork with open and self-hosted models. Any other value stops the start.
PLOTWORK_LICENSEnoneYour licence. /healthz shows whether one is set and a short fingerprint of it, never the value.

Without PLOTWORK_TOKEN, the server listens on loopback only. The image listens on every address, so it does not start without a token unless PLOTWORK_ALLOW_OPEN=1 is set.

3. Connect an agent

Every client below sends the token as a bearer header. Replace plotwork.internal with the address of your server.

Claude Code

sh
claude mcp add --transport http plotwork http://plotwork.internal:3106/mcp \  --header "Authorization: Bearer $PLOTWORK_TOKEN"

Cursor

Add this to ~/.cursor/mcp.json:

json
{  "mcpServers": {    "plotwork": {      "url": "http://plotwork.internal:3106/mcp",      "headers": {        "Authorization": "Bearer ${env:PLOTWORK_TOKEN}"      }    }  }}

VS Code

Add this to .vscode/mcp.json. VS Code asks for the token once and keeps it out of the file:

json
{  "inputs": [    {      "type": "promptString",      "id": "plotwork-token",      "description": "Plotwork token",      "password": true    }  ],  "servers": {    "plotwork": {      "type": "http",      "url": "http://plotwork.internal:3106/mcp",      "headers": {        "Authorization": "Bearer ${input:plotwork-token}"      }    }  }}

A service over REST

sh
curl -X POST http://plotwork.internal:3106/v1/diagrams/render \  -H "Authorization: Bearer $PLOTWORK_TOKEN" \  -H "content-type: application/json" \  -d '{"source": "flow \"Checkout\"\nnode web service \"Web\"\nnode db database \"Orders\"\nweb -> db"}'

4. Security notes

TopicWhat the server doesWhat you do
TransportPlain HTTP.Put TLS in front: a reverse proxy, a service mesh, or an ingress.
AuthenticationOne static bearer token, compared in constant time. A missing or wrong token is a 401.Keep the token in a secret store, give each environment its own, and rotate it by restarting with a new one.
Outbound networkMakes no request. fetch and the socket clients are switched off in the process, and a link sent to a tool is refused.Block egress anyway, with an internal network, a network policy, or a firewall rule, so the container has no route out.
BrowsersRefuses a request whose Origin is not in PLOTWORK_ALLOWED_ORIGINS, which also stops a web page from reaching a server on loopback.List only the origins of your own tools.
LimitsA body cap, a bounded queue, a deadline on every job, and a heap cap on each worker. A job past its deadline is stopped and its worker replaced.Set container memory and CPU limits; about 250 MB at rest with two workers.
LogsOne JSON line per request and per tool call: path, status, tool, outcome, duration, and argument size. Never diagram text, a header value, or an error message.Collect standard output with your log pipeline.
StorageNone. Every request is worked on in memory and forgotten.Run with a read-only root filesystem.
TelemetryNone. PLOTWORK_USAGE, which turns on counting in the npm package, does nothing here.Nothing.

See also

DocWhy
MCP serverThe tools, and how the local and hosted servers differ
Connecting an agentClient settings for the hosted server and the npm package
API referenceRequest and response shapes of the REST routes
Privacy policyWhat the hosted service collects, none of which applies to a self-hosted server