Skip to main content
Glama

paint-mcp — an MCP server that lets an AI agent actually draw

Give any MCP client a real paint canvas: layers, anti-aliased shapes, brushes, bucket fill, filters, undo — and every drawing tool sends the picture back, so the model can look at what it made, judge it, and fix it. No native dependencies, no image libraries, no Python.

   agent: paint_ops →  [ picture comes back in the tool result ]  →  agent looks, adjusts, redraws

dusk landscape brush study sprite

Three pictures above were produced by node scripts/demo.mjs, i.e. by the exact same operation registry an agent calls over MCP. Re-generate them any time with npm run demo.


Install

One command (any agent / any machine, Node 20+):

git clone https://github.com/Mishaadevv/paint-mcp.git && cd paint-mcp && npm install && npm run build

Then register it with your MCP client (Qoder CLI, Claude Desktop, Cursor, …):

{
  "mcpServers": {
    "paint": {
      "command": "node",
      "args": ["/absolute/path/to/paint-mcp/dist/index.js"],
      "env": { "PAINT_MCP_ROOT": "/absolute/path/where/art/should/land" }
    }
  }
}

Qoder CLI users can do it in one line:

qodercli mcp add paint node /absolute/path/to/paint-mcp/dist/index.js --scope user

Once the package is on npm the same config collapses to npx -y paint-mcp-server (see Development for how a release is cut).

Reload MCP servers in a running session with /mcp reload, then say: “draw a 640×400 sunset over hills and export it”.


Related MCP server: NeuroPaint-X

What you get

Canvas

multi-layer RGBA documents, .paint save/open, PNG/JPG/BMP import-export, SVG export from recorded vector ops

Geometry

rect (rounded), ellipse/arc/pie, line (dashed, arrows), polygon, SVG path data (M L H V C S Q T A Z)

Painting

freehand brush with pressure, hardness, spacing, jitter, flatten+angle nibs; bucket fill with tolerance; exact pixel writes; pixel-art sprites from text rows

Colour

140+ named colours, hex/rgb/hsl, gradients (linear/radial, multi-stop), 12 blend modes, per-op opacity

Editing

18 image filters (blur, sharpen, edge detect, hue, posterize, dither, vignette…), scale/rotate/flip/crop/resize/translate/trim, layer add/remove/reorder/merge

History

undo/redo per operation, batch-aware, with labels

Feedback

every reply carries a PNG thumbnail (preview: auto, thumb, full, none), plus paint_preview and paint_inspect for reading pixels without drawing

Live view

--serve 4173 opens a localhost page that repaints once a second while the agent works

Tools

paint_canvas (create/list/info/open/save/close/files/history) · paint_ops (batch, one call = one scene) · paint_preview · paint_inspect · paint_undo · paint_export · paint_import · paint_help · paint_serve

…plus one tool per operation, generated straight from the op registry so the docs and the behaviour can never drift: paint_rect, paint_ellipse, paint_line, paint_polygon, paint_path, paint_brush, paint_text, paint_pixel_grid, paint_pixels, paint_fill, paint_gradient, paint_stamp, paint_clear, paint_filter, paint_transform, paint_layer.

Token-tight client? Start with --toolset compact to expose only the batch tool and utilities.


A session that actually works

// 1. give yourself a surface
paint_canvas { "action": "create", "canvas": "art", "width": 640, "height": 400, "background": null }

// 2. block out the whole idea in one cheap call, and look at the picture that comes back
paint_ops { "canvas": "art", "ops": [
  { "op": "gradient", "type": "linear", "from": [0, 0], "to": [0, 400], "colors": ["#0a1330", "#2b4d86", "#e7a45d"] },
  { "op": "ellipse",  "cx": 430, "cy": 236, "radius": 26, "fill": "#fff3c4" },
  { "op": "path",     "d": "M0 268 Q90 232 180 262 T360 258 T640 264 L640 400 L0 400 Z", "fill": "#16233d" },
  { "op": "text",     "text": "DUSK", "x": 28, "y": 30, "scale": 5, "color": "#f4f7ff", "outline": 1 }
]}

// 3. not happy with the ridge? roll it back and redo just that piece
paint_undo { "canvas": "art", "steps": 1 }
paint_path { "canvas": "art", "d": "M0 300 Q160 240 320 300 T640 290 L640 400 L0 400 Z", "fill": "#0e1728" }

// 4. ship it
paint_export { "canvas": "art", "format": "png", "path": "out/dusk.png" }

paint_help { "topic": "workflow" } and paint_help { "topic": "examples" } hold the same advice in-server, so an agent never has to leave the protocol to learn the tool.


Configuration

Flag

Env

Default

Meaning

--root <dir>

PAINT_MCP_ROOT

./canvases

where canvases and exports live; paths are confined to it

--preview <mode>

PAINT_MCP_PREVIEW

auto

auto/thumb/full/none image reply

--preview-size <px>

PAINT_MCP_PREVIEW_SIZE

640

longest edge of the returned thumbnail

--history <n>

PAINT_MCP_HISTORY

24

undo steps per canvas

--serve <port>

PAINT_MCP_SERVE

off

localhost live preview page

--auto-save

PAINT_MCP_AUTOSAVE=1

off

write the .paint file after every draw call

--toolset compact

PAINT_MCP_TOOLSET

full

expose only batch + utility tools

--allow-absolute

PAINT_MCP_ALLOW_ABSOLUTE=1

off

let paths leave the paint root

—

PAINT_MCP_MAX_PIXELS

40 000 000

canvas size guard

node dist/index.js --help prints the same list.


Design notes

  • Zero native dependencies. PNG encode/decode (adaptive filtering, palette, grey, 1–16 bit, tRNS), BMP and the compositing engine are implemented in this repo on top of node:zlib; only JPEG leans on the pure-JS jpeg-js. That is what makes "clone → install → build" work on any OS with no toolchain.

  • One registry, many faces. Every operation is declared once (src/core/ops.ts) with a Zod schema, an apply function and metadata. Individual tools, the paint_ops batch runner, paint_help, the SVG exporter and the test suite all read from that single source.

  • Anti-aliasing without a rasteriser library. Fills use sub-row scanline coverage; strokes measure per-pixel distance to each segment capsule, so joins and round caps are smooth for free. Semi-transparent geometry renders into a scratch tile first, so overlapping spans never double-darken.

  • Undo that scales. A step snapshots only the layers it touches, not the whole document.

  • Errors teach. Bad input returns the failing field, the valid alternatives and the name of the tool that fixes it — paint_help is always one call away.

Limits worth knowing

  • Text uses a built-in 5×7 pixel font (ASCII; other scripts are transliterated). It suits pixel art and labels; for lettering, draw paint_path outlines instead. Adding a TTF rasteriser is the obvious next step.

  • Interlaced PNGs and RLE-compressed BMPs are rejected with a clear message.

  • Everything is in-memory per process: canvases survive save/open, not a server restart.

Development

npm install
npm run build      # tsc → dist/
npm test           # 20 tests: engine + real stdio MCP handshake, drawing, undo, export, error paths
npm run demo       # renders canvases/demo/*.png through the op registry

Layout: src/core (engine: colour, raster, paths, filters, codecs, document, ops), src/util (config, preview, live server, tool registration), src/index.ts (MCP entry), test, scripts.

Releasing to npm: store an Automation token once with gh secret set NPM_TOKEN, then git tag v1.0.0 && git push --tags — the release workflow builds, runs the tests and publishes paint-mcp-server.

License

MIT — see LICENSE.

Available Tools

25 tools
paint_brushPaint: Freehand brush strokeA

Mouse-drag feel: soft stamps along a polyline. Each point may carry a third value 0..1 as pressure. flatten+angle make a calligraphy nib, jitter a hand wobble, low hardness a spray.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: color (required), canvas (required), points (required), seed, +11 more.

paint_canvasPaint: canvas lifecycleA
Idempotent

Create, list, inspect, open, save or close canvases.

Actions: create {name, width, height, background} new canvas; background is a colour or null for transparent. Layers start as ["background"? , "layer N"]. info {name} size, layers, active index, undo depth, pixel statistics list {} every open canvas open {path} load a .paint / png / jpg / bmp file from the paint root save {name, path?} write the layered .paint document close {name} drop it from memory (files stay on disk) files {} what is on disk in the paint root history {name} undo/redo stack labels

A canvas must be created (or opened) before any drawing tool can be used.

Inputs: action (required), path, width, canvas, +3 more.

paint_clearPaint: Clear or erase an areaA
Destructive

Wipes a rectangle (whole layer when area omitted) to color; color:null erases to transparency. shape:'ellipse' and radius round the mask.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: canvas (required), area, color, layer, +5 more.

paint_ellipsePaint: Ellipse, circle or arcA

Circle (radius) or ellipse (rx,ry). start_angle/end_angle in degrees cut an arc; pie:true closes it to the centre; rotation tilts the shape.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: cx (required), cy (required), canvas (required), rx, +14 more.

paint_exportPaint: export a fileA
Idempotent

Write the canvas out: png, jpg, bmp, svg (rebuilt from recorded vector operations) or .paint (layered, re-openable). Returns the path under the paint root plus a preview of exactly what was written.

svg keeps rect/ellipse/line/polygon/path/text/gradient operations; raster-only steps (brush, fill, pixels, filters, stamps) cannot be vectorised and are reported. Set embed_raster_fallback to also place the current PNG behind them.

Inputs: canvas (required), path, format, flatten, +4 more.

paint_fillPaint: Bucket fill / recolourA

Paint bucket. tolerance is summed channel distance (0 = exact, 60 ≈ shades of the same hue). contiguous:false hits every matching pixel of the layer; mode:'replace' swaps the colour instead of painting over it.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: x (required), y (required), color (required), canvas (required), +8 more.

paint_filterPaint: Image filterA

Filters a layer: blur, sharpen, edge_detect, brightness, contrast, gamma, saturation, hue_rotate, grayscale, sepia, invert, threshold, posterize, pixelate, noise, vignette, chroma_shift, dither. amount is per filter (blur px, degrees, +/- levels, multiplier); area limits it; level = posterize/dither levels.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: name (required), canvas (required), area, seed, +5 more.

paint_gradientPaint: Gradient washA

Linear or radial gradient over the canvas or an area — skies, glows, metal, shadows.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: canvas (required), colors (required), to, area, +10 more.

paint_helpPaint: reference cardA
Read-onlyIdempotent

In-server documentation, no round trip to GitHub. Topics: ops (every operation and its fields), colors (formats and the named palette), blend, filters, svg, limits, workflow (how to plan a drawing), examples (copy-paste batches).

Inputs: topic.

paint_importPaint: import an imageA

Bring a picture in: path for a file under the paint root (png/jpg/bmp, real format is sniffed from the bytes), or data with base64 when the agent already holds the bytes. Creates a canvas, or adds a layer when the target canvas exists.

Inputs: data, path, canvas, preview, +1 more.

paint_inspectPaint: read pixelsA
Read-onlyIdempotent

Ask exact questions about the picture: colour at points, statistics and bounding box of painted pixels, or a colour histogram of a region. Cheaper than a thumbnail when you only need numbers.

Inputs: canvas (required), top, layer, points, +1 more.

paint_layerPaint: Manage layersA

list | add | remove | select | move | merge | set (rename, opacity, blend, visible). Layers stack bottom→top; index 0 is the bottom one.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: action (required), canvas (required), to, name, +7 more.

paint_linePaint: Straight lineB

One segment, with optional dashes ([8,6]) and arrow head. hardness 0 gives an airbrush edge. For rules, grids and outlines.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: x1 (required), x2 (required), y1 (required), y2 (required), +14 more.

paint_opsPaint: batch of operationsA

Run many drawing operations in ONE call and get ONE picture back — the cheapest way to build a whole scene.

ops is a list of {op: "", ...that op's parameters}. Available ops: rect, ellipse, line, polygon, path, brush, text, pixel_grid, pixels, fill, gradient, stamp, clear, filter, transform, layer. Every op also exists as its own tool (paint_rect, paint_brush, …) with the same fields. Optional per-item "layer" picks the layer; add layers first with paint_layer or inline via {op:"layer", action:"add"}.

Example: ops: [ {op:"gradient", type:"linear", colors:["#0b1d3a","#3b6fb5"]}, {op:"ellipse", cx:600, cy:120, radius:46, fill:"#ffe27a"}, {op:"path", d:"M0 300 L120 220 L260 300 Z", fill:"#233"}, {op:"text", text:"NIGHT RUN", x:320, y:40, scale:6, color:"#fff"} ]

Returns a summary line per operation plus one preview of the result. Undo rolls back the whole batch step by step.

Inputs: ops (required), canvas (required), preview, preview_size, +1 more.

paint_pathPaint: SVG path dataB

Draw SVG path d data (M L H V C S Q T A Z, lowercase = relative) — the compact way for an agent to place curved or complex art. Supports fill, stroke, dashes, even-odd.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: d (required), canvas (required), cap, fill, +11 more.

paint_pixel_gridPaint: Pixel-art sprite from text rowsA

Draws a sprite where each character of rows looks up a colour in palette (space and . are skipped) and fills a cellxcell block, starting at origin. Perfect for sprites, tiles, icons and logos.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: rows (required), canvas (required), palette (required), cell, +6 more.

paint_pixelsPaint: Write single pixelsA

Exact pixel writes (no blending): pixels=[{x,y,color}, {x,y,width,height,color}, or [x,y,0xRRGGBB]]. Use for pixel art and small fixes.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: canvas (required), pixels (required), layer, preview, +1 more.

paint_polygonPaint: Polygon or polylineB

Any many-sided shape from [x,y] points — stars, arrows, speech bubbles. closed:false strokes the outline only.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: canvas (required), points (required), fill, blend, +8 more.

paint_previewPaint: look at the canvasA
Read-onlyIdempotent

Return the current picture without changing anything — use it to check your work, compare against a reference, or re-read a canvas after a long session. Set layer:true to see only the active layer.

Inputs: canvas (required), layer, region, preview, +1 more.

paint_rectPaint: RectangleB

Filled and/or outlined rectangle. radius rounds the corners (number, or [top-left, top-right, bottom-right, bottom-left]). fill/stroke take a colour or a gradient object.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: x (required), y (required), width (required), canvas (required), +10 more.

paint_servePaint: live preview pageA
Idempotent

Start/stop a localhost page that shows every open canvas and repaints once a second, so a human can watch the drawing happen. Only 127.0.0.1 is bound. action:'status' reports the URL.

Inputs: port, action.

paint_stampPaint: Stamp an imageA

Places another canvas or an image file onto this layer. source is a canvas name or a path under the paint root (png/jpg/bmp). Supports scale, rotate, opacity, blend, region crop and tile.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: canvas (required), source (required), x, y, +9 more.

paint_textPaint: Text labelA

Writes text with the built-in 5x7 pixel font (ASCII). scale multiplies the pixel size, align/valign anchor around (x,y), max_width wraps. Non-ASCII is transliterated (or errors) — no font files needed.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: x (required), y (required), text (required), canvas (required), +15 more.

paint_transformPaint: Transform a layer or the canvasA
Destructive

scale | rotate | flip | translate on one layer; crop | resize | trim on the whole canvas. Bilinear resampling; trim shrinks to painted content.

Target the canvas by name (created with paint_canvas) and optionally a layer index. Layers, filters and undo work the same for every drawing tool.

Inputs: mode (required), canvas (required), x, y, +10 more.

paint_undoPaint: undo / redoA
Destructive

Roll a canvas back or forward. Each drawing operation is one step (a paint_ops batch rolls back one step per op). Use action:'steps' to see the stack without changing anything.

Inputs: canvas (required), steps, action, preview.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

    TDQS

    A3.9/5.0

    Scored across 25 tools

    Disambiguation5/5

    Each tool targets a clearly distinct drawing operation or canvas/layer management function. paint_ops batches existing ops but is explicitly differentiated as a batch tool. Minor overlaps like paint_pixels vs paint_pixel_grid are resolved by descriptions.

    Naming Consistency5/5

    All tools use the paint_ prefix with consistent snake_case verb/noun naming. No deviations or mixed conventions.

    Tool Count3/5

    25 tools is heavy for a drawing server, exceeding the typical 3-15 range. While each drawing primitive is distinct, paint_ops already batches all operations, making many individual tools potentially redundant.

    Completeness5/5

    Covers canvas lifecycle, layers, drawing primitives, filters, transforms, undo, import/export, preview, and help. No obvious gaps for a 2D drawing domain.

    Maintenance

    ActivitySlowing
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      maintenance
      Exposes a Pygame-based drawing canvas as an MCP server, allowing LLMs to create digital art using standard shapes and freehand paths. It features a specialized oil paint mode that simulates realistic color mixing, paint depletion, and textured brush strokes.
      -
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI assistants to drive a layered raster image editor with painting, shapes, text, filters, and internet-sourced images and fonts via MCP tool calls, with token-efficient rendering and export.
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables LLM-driven painting agents to control Krita through MCP tools, iteratively reconstructing a target image by observing canvas snapshots, diff heatmaps, and session state. It exposes observation, drawing, brush, color, layer, selection, vector, filter, document I/O, and session tools for closed-loop artwork generation.
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Local vector and raster editing engine for agents: layered documents, saved sessions and exports, as a Rust library, JSON CLI and MCP stdio server.
      22
      1
      MIT