MCP Protocol¶
The Vexy Lines macOS/Windows app embeds an MCP server: a JSON-RPC 2.0 endpoint on localhost:47384 over raw TCP with newline-delimited messages. This document covers the wire protocol, all 25 tools, and the bridge binary for Claude Desktop/Cursor.
Connection¶
The server listens on 127.0.0.1:47384 by default. Connect with a TCP socket and perform the MCP handshake before calling tools.
MCPClient handles all of this automatically:
from vexy_lines_api import MCPClient
with MCPClient() as vl:
# handshake done, ready to call tools
info = vl.get_document_info()
Handshake protocol¶
The MCP handshake follows the Model Context Protocol specification:
Step 1. Client sends initialize:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "vexy-lines-apy", "version": "1.0.0"}
}
}
Step 2. Server responds with capabilities:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {"name": "vexy-lines", "version": "..."}
}
}
The client validates that the server's protocolVersion matches "2024-11-05". A mismatch raises MCPError.
Step 3. Client sends notifications/initialized:
{"jsonrpc": "2.0", "method": "notifications/initialized"}
This is a notification (no id, no response expected). The connection is now ready for tool calls.
Wire format¶
Each message is a single line of JSON followed by \n:
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_document_info"}}
Responses follow the same format:
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"{\"width_mm\":210,...}"}]}}
Tool results are wrapped in content[0].text which contains either a JSON string or plain text. The MCPClient.call_tool() method attempts JSON parse first, falling back to the raw string.
Calling a tool¶
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "set_fill_params",
"arguments": {
"id": 42,
"params": {"color": "#ff0000", "interval": 20}
}
}
}
The id field is an auto-incrementing integer assigned by the client. The name field selects the tool. arguments contains tool-specific parameters.
Listing available tools¶
After handshake, you can call tools/list to get the full tool catalog:
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
The response contains a tools array with name, description, and JSON Schema for each tool's parameters. MCPClient does not call this automatically: it uses hardcoded method wrappers instead.
Coordinates¶
All spatial coordinates are in pixels at the document's DPI. Origin is top-left corner. The server converts to internal units automatically:
| Internal unit | Conversion | Applies to |
|---|---|---|
| Millimetres | px * 25.4 / dpi |
thickness, thickness_min |
| Points | px * 72 / dpi |
interval, dispersion |
| Degrees | No conversion | angle, rotate_deg |
| Unitless | No conversion | contrast, break_up, break_down, ratios |
The 25 tools¶
Document tools (5)¶
new_document¶
Create a new document with optional source image.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
width |
float |
no | inferred | Document width in pixels |
height |
float |
no | inferred | Document height in pixels |
dpi |
int |
no | 300 |
Document resolution |
source_image |
string |
no | -- | Absolute path to source image file |
When source_image is provided without width/height, the document dimensions are inferred from the image. The source image is embedded in the document.
Response (JSON): {"status": "ok", "width": 1920, "height": 1080, "dpi": 300, "root_id": 1}
open_document¶
Open a .lines file from disk.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string |
yes | Absolute path to .lines file |
Response (text): status string.
save_document¶
Save the current document. Omit path to save in-place.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string |
no | Absolute path for Save As |
Response (text): status string.
export_document¶
Export the document to a file.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
path |
string |
yes | -- | Absolute output path |
format |
string |
no | inferred from extension | "svg", "pdf", "png", "jpg", or "eps" |
dpi |
int |
no | document DPI | Override resolution for export |
Response (text): status string.
get_document_info¶
Get metadata about the current document. No parameters.
Response (JSON):
{
"width_mm": 210.0,
"height_mm": 297.0,
"resolution": 300.0,
"units": "mm",
"has_changes": false
}
Structure tools (5)¶
get_layer_tree¶
Get the full document tree. No parameters.
Response (JSON): recursive tree structure:
{
"id": 1,
"type": "document",
"caption": "My Document",
"visible": true,
"children": [
{
"id": 2,
"type": "group",
"caption": "Group 1",
"visible": true,
"children": [
{
"id": 3,
"type": "layer",
"caption": "Layer 1",
"visible": true,
"children": [
{
"id": 4,
"type": "fill",
"caption": "Linear Fill",
"visible": true,
"fill_type": "linear",
"children": []
}
]
}
]
}
]
}
Node types: "document" (root, one per document), "group", "layer", "fill" (leaf). Only "fill" nodes have fill_type set.
add_group¶
Add a new group to the document tree.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
parent_id |
int |
no | document root | Parent object ID |
caption |
string |
no | auto-generated | Group display name |
source_image_path |
string |
no | -- | Source image for the group |
Response (JSON): {"id": 5}: object ID of the created group.
add_layer¶
Add a new layer to a group.
| Parameter | Type | Required | Description |
|---|---|---|---|
group_id |
int |
yes | Parent group object ID |
Response (JSON): {"id": 6}: object ID of the created layer.
add_fill¶
Add a fill algorithm to a layer.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
layer_id |
int |
yes | -- | Parent layer object ID |
fill_type |
string |
yes | -- | Algorithm name (see table below) |
color |
string |
no | -- | Hex colour "#RRGGBB" or "#RRGGBBAA" |
params |
object |
no | -- | Initial fill parameters dict |
Valid fill_type values:
| Value | Algorithm | Description |
|---|---|---|
"linear" |
Linear strokes | Parallel straight lines at a given angle |
"wave" |
Sigmoid/wave strokes | Sinusoidal parallel lines |
"circular" |
Circular strokes | Concentric circles from a centre point |
"radial" |
Radial strokes | Lines radiating outward from a centre |
"spiral" |
Spiral strokes | Spiralling outward from a centre |
"scribble" |
Scribble strokes | Random hand-drawn-looking paths |
"halftone" |
Halftone dots | Dot/circle grid simulating halftone printing |
"handmade" |
Free-curve strokes | User-drawn or pattern-based curves |
"fractals" |
Peano/fractal curves | Space-filling fractal curves (slow to render at high depth) |
"trace" |
Traced area | Edge-tracing vectorisation of the source image |
Response (JSON): {"id": 7}: object ID of the created fill.
delete_object¶
Delete any object (group, layer, or fill) from the document tree.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
int |
yes | Object ID to delete |
Response (text): status string.
Fill parameter tools (2)¶
get_fill_params¶
Get all current parameters of a fill.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
int |
yes | Fill object ID |
Response (JSON): dict of parameter names to current values. The keys depend on the fill type (see parameter reference below).
set_fill_params¶
Set one or more parameters on a fill.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
int |
yes | Fill object ID |
params |
object |
yes | Dict of parameter names to new values |
All spatial values must be in pixels. The server converts internally. Example wire message:
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "set_fill_params",
"arguments": {
"id": 42,
"params": {
"color": "#ff0000",
"interval": 20,
"angle": 45,
"thickness": 3.5,
"contrast": 0.8
}
}
}
}
Response (text): status string.
Visual tools (7)¶
set_source_image¶
Set the source image for a group.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
image_path |
string |
yes | -- | Absolute path to image file |
group_id |
int |
no | current group | Target group |
set_caption¶
Rename an object.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
int |
yes | Object ID |
caption |
string |
yes | New display name |
set_visible¶
Toggle object visibility.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
int |
yes | Object ID |
visible |
boolean |
yes | true to show, false to hide |
set_layer_mask¶
Set an SVG vector mask on a layer.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
layer_id |
int |
yes | -- | Target layer |
paths |
array[string] |
yes | -- | SVG path data strings |
mode |
string |
no | "create" |
"create" (replace), "add" (union), "subtract" (difference) |
SVG path data uses standard syntax: "M 0 0 L 100 0 L 100 100 L 0 100 Z". Coordinates are in pixels at document DPI, origin top-left.
get_layer_mask¶
Get the current mask data for a layer.
| Parameter | Type | Required | Description |
|---|---|---|---|
layer_id |
int |
yes | Target layer |
Response (JSON): mask data dict.
transform_layer¶
Apply a 2D affine transform to a layer.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id |
int |
yes | -- | Target layer |
translate_x |
float |
no | 0 |
Horizontal translation in pixels |
translate_y |
float |
no | 0 |
Vertical translation in pixels |
rotate_deg |
float |
no | 0 |
Rotation angle in degrees |
scale_x |
float |
no | 1 |
Horizontal scale factor |
scale_y |
float |
no | 1 |
Vertical scale factor |
set_layer_warp¶
Set perspective warp on a layer by specifying four corner positions.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
int |
yes | Target layer |
top_left |
[float, float] |
yes | [x, y] coordinates in pixels |
top_right |
[float, float] |
yes | [x, y] coordinates in pixels |
bottom_right |
[float, float] |
yes | [x, y] coordinates in pixels |
bottom_left |
[float, float] |
yes | [x, y] coordinates in pixels |
Example: warp a 1000x1000 layer into a trapezoid:
vl.set_layer_warp(
layer_id=3,
top_left=[100, 0],
top_right=[900, 0],
bottom_right=[1000, 1000],
bottom_left=[0, 1000],
)
Control tools (6)¶
render_all¶
Trigger rendering of all layers. No parameters. Returns immediately. Use get_render_status to poll for completion.
Response (text): status string.
get_render_status¶
Check whether the document is currently rendering. No parameters.
Response (JSON): {"rendering": true} or {"rendering": false}.
Note: the rendering flag may not flip to true immediately after render_all. The Python client's wait_for_render() handles this by waiting 0.5 s before polling and detecting completion via consecutive false readings.
undo¶
Undo the last action. No parameters.
redo¶
Redo the last undone action. No parameters.
get_selection¶
Get the currently selected objects. No parameters.
Response (JSON): selection data dict or status string.
select_object¶
Select an object by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
int |
yes | Object to select |
Fill parameter reference¶
Base parameters (all stroke fills except trace)¶
| Parameter | Type | Unit | Description |
|---|---|---|---|
interval |
float |
px (stored as pt) | Line spacing |
angle |
float |
degrees | Stroke angle |
thickness |
float |
px (stored as mm) | Stroke width multiplier |
thickness_min |
float |
px (stored as mm) | Minimum stroke width |
contrast |
float |
unitless | Tone-mapping curve steepness |
smoothness |
float |
unitless | Curve smoothness |
break_up |
float |
0--255 | Upper brightness threshold |
break_down |
float |
0--255 | Lower brightness threshold |
dispersion |
float |
px (stored as pt) | Random perpendicular offset |
vdisp |
float |
px | Random vertical displacement |
color_mode |
int |
enum | Colour mode (0=source, 2=static) |
color_seg_len |
float |
px | Colour segment length |
color_seg_disp |
float |
px | Colour segment dispersion |
color |
string |
hex | Fill colour ("#RRGGBB" or "#RRGGBBAA") |
Wave extras¶
| Parameter | Type | Description |
|---|---|---|
wave_height |
float |
Wave amplitude in pixels |
wave_length |
float |
Wavelength in pixels |
wave_fade |
float |
Fade factor |
phase |
float |
Phase offset |
curviness |
float |
Curve smoothness of wave |
Circular extras¶
| Parameter | Type | Description |
|---|---|---|
x0 |
float |
Centre X in pixels |
y0 |
float |
Centre Y in pixels |
Radial extras¶
| Parameter | Type | Description |
|---|---|---|
x0 |
float |
Centre X in pixels |
y0 |
float |
Centre Y in pixels |
r0 |
float |
Starting radius in pixels |
auto_distance |
bool |
Auto-calculate line distance |
auto_randomize |
bool |
Randomise ray distribution |
Spiral extras¶
| Parameter | Type | Description |
|---|---|---|
x0 |
float |
Centre X in pixels |
y0 |
float |
Centre Y in pixels |
direction_ccw |
bool |
Counter-clockwise direction |
Scribble extras¶
| Parameter | Type | Description |
|---|---|---|
scribble_length |
float |
Scribble path length |
curviness |
float |
Path curvature |
variety |
float |
Path variety |
complexity |
float |
Path complexity |
rotation |
float |
Pattern rotation in degrees |
scribble_pattern |
int |
Pattern preset index |
Halftone extras¶
| Parameter | Type | Description |
|---|---|---|
cell_size |
float |
Cell size in pixels |
rotation |
float |
Grid rotation in degrees |
halftone_mode |
int |
Dot shape mode |
rotation_mode |
int |
Rotation algorithm |
morphing |
float |
Shape morphing factor |
randomization |
float |
Random offset factor |
Handmade extras¶
| Parameter | Type | Description |
|---|---|---|
mode |
int |
Drawing mode preset |
parity_mode |
int |
Parity handling |
is_filled |
bool |
Fill closed paths |
expand_lines |
bool |
Expand line widths |
averaging |
float |
Smoothing factor |
Fractals extras¶
| Parameter | Type | Description |
|---|---|---|
depth |
int |
Recursion depth (higher = slower render) |
kind |
int |
Fractal type preset |
Trace parameters (standalone, no base params)¶
| Parameter | Type | Description |
|---|---|---|
smoothness |
float |
Edge smoothness |
clearing_level |
float |
Detail clearing threshold |
detailing |
float |
Detail level |
color_mode |
int |
Colour mode |
Error handling¶
Server errors are returned as JSON-RPC error objects:
{"jsonrpc": "2.0", "id": 3, "error": {"code": -32000, "message": "No document open"}}
MCPClient raises MCPError with the error code and message. Common error codes:
| Code | Meaning |
|---|---|
-32000 |
Application error (e.g. no document open, invalid object ID) |
-32600 |
Invalid request |
-32601 |
Method not found |
-32602 |
Invalid params |
-32603 |
Internal error |
Bridge binary¶
For Claude Desktop and Cursor integration, the vexy-lines-mcp bridge binary converts between stdio and TCP. It reads JSON-RPC from stdin, forwards to the TCP server at localhost:47384, and writes responses to stdout.
Claude Desktop setup¶
Add to your claude_desktop_config.json:
{
"mcpServers": {
"vexy-lines": {
"command": "/path/to/vexy-lines-mcp"
}
}
}
The bridge binary is bundled with the Vexy Lines app. Typical locations:
- macOS:
/Applications/Vexy Lines.app/Contents/Resources/vexy-lines-mcp - Windows:
C:\Program Files\Vexy Lines\vexy-lines-mcp.exe
Cursor setup¶
Add to .cursor/mcp.json in your project:
{
"mcpServers": {
"vexy-lines": {
"command": "/path/to/vexy-lines-mcp"
}
}
}
The bridge handles the MCP handshake transparently. All 25 tools are exposed with their JSON Schema parameter definitions via tools/list.
See the CLI docs for additional setup instructions and the vexy-lines-cli mcp-serve command, or run the installed vexy-lines-mcp script directly.