Skip to main content
Glama
Nightious-Tools

NiimBlue Labels MCP

Official

NiimBlue Labels MCP

MCP server that designs labels for NIIMBOT thermal printers (D11, D110, B1, B21 and others) and hands them to NiimBlue to print.

It renders each label with NiimBlue's own code in headless Chrome or Edge and returns the exact 1-bit image the printhead burns, plus warnings for anything that will print badly. It also returns a link that opens the label in NiimBlue. The server never prints and never talks to a printer: you open the link, check the label, and print from NiimBlue. It can also save a label as a fill-in template and reprint it with new values.

Requirements

  • Node.js 20+.

  • Google Chrome, Chromium or Microsoft Edge. NiimBlue needs one of them to print over Bluetooth anyway. The server finds it in the usual install folders; set NIIMBLUE_CHROME if yours is elsewhere.

No API keys and no network access: the render runs offline from the bundled NiimBlue code and fonts, plus any font files you put in your font folder.

Related MCP server: niimbot-mcp

Install

Claude Code (server plus the niimblue skill):

/plugin marketplace add Nightious-Tools/niimblue-mcp
/plugin install niimblue-mcp@niimblue-mcp

Codex:

codex plugin marketplace add Nightious-Tools/niimblue-mcp

Then run /plugins, install NiimBlue Labels, and start a new session.

Claude Desktop: download niimblue-mcp.mcpb from Releases and open it. It asks for an optional Chrome path, the NiimBlue URL, the templates folder and the font folder.

OpenCode: clone the repo and add this to your opencode.json. OpenCode reads the skill from .claude/skills/.

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "niimblue": {
      "type": "local",
      "command": ["node", "/path/to/niimblue-mcp/dist/index.mjs"]
    }
  }
}

Other MCP clients:

{
  "mcpServers": {
    "niimblue": {
      "command": "npx",
      "args": ["-y", "github:Nightious-Tools/niimblue-mcp"]
    }
  }
}

On Windows, some clients need "command": "cmd", "args": ["/c", "npx", "-y", "github:Nightious-Tools/niimblue-mcp"].

Server only:

claude mcp add niimblue -s user -- npx -y github:Nightious-Tools/niimblue-mcp
codex mcp add niimblue -- npx -y github:Nightious-Tools/niimblue-mcp

Tools

Tool

Purpose

render_label

Render a label and return the print preview and the NiimBlue link. Writes files only when save_as is set.

list_templates

List saved templates: name, title, printer, size in mm, the {key} fields rows must fill, row count, last modified. Read-only.

list_fonts

List the fontFamily values render_label can draw: the default, the built-in fonts with their files and the smallest legible size, and the files in your font folder with any it skipped. Read-only.

get_template

Return render_label args to edit: a saved template by name, or a NiimBlue .json export by path plus printer. Read-only.

render_label

Param

Purpose

printer

Printer model, such as D110, B1 or D11_H. Sets dpi, print direction and printable width.

width_mm, height_mm

Label size as set in NiimBlue, 4–300 mm.

title

Optional label name in NiimBlue's saved labels.

split, split_parts, mirror

Optional cable-flag layout: split into parts, then copy or flip the design into the other part.

tail_pos, tail_length_mm

Optional cable-flag tail.

objects

1–100 objects in paint order: Textbox, Icon (Material Icons name), Rect, Circle, Polyline, QRCode, Barcode (CODE128B or EAN13). Positions and sizes are printer dots (8 per mm at 203 dpi, 11.81 at 300 dpi), origin top-left. Text may hold {dt} or `{dt

fontFamily

Optional on Textbox and Barcode: Noto Sans Variable (the default), a built-in font or a font folder family (see Fonts). Letters, digits, _ and - in words, single spaces, up to 60 characters. Noto defaults to fontWeight 600, any other family to 400.

rows

Optional values for the {key} fields, 1–500 objects of strings, one per label (dt is reserved). The preview shows row 1. With 1 row the link carries the filled text. With more, the link carries them as NiimBlue's CSV and NiimBlue prints one label per row; this replaces the CSV open in NiimBlue. NiimBlue's $times column is not supported.

template

Optional saved template name. Its args are the base; args given here override them, and objects or rows replace the template's wholesale.

save_as, overwrite

Save these args as a template: <name>.args.json and <name>.niimblue.json (importable in NiimBlue's Saved labels) in NIIMBLUE_TEMPLATES_DIR. An existing template is refused unless overwrite is true. The name keeps letters and digits in any script; a name with none is refused.

include_json

Also return the NiimBlue .json export. Set it on the final call.

It returns the 1-bit PNG, then JSON with label (dpi, print direction, printhead width and size in dots), zoom, link, link_chars, boxes (each object's rendered bounding box), warnings, and saved (args_path, niimblue_path) after a save_as.

Opening the link replaces the label open in NiimBlue, after a confirm. Save it first.

Each warning has a code, the object index when it has one, and a message that ends with the fix: HEAD_OVERFLOW, OFF_LABEL, NEAR_EDGE, TEXT_SMALL, TEXT_THIN, STROKE_THIN, TEXT_WRAPPED, GLYPH_FALLBACK, FONT_MISSING, FONT_NOT_IN_LINK, UNFILLED_FIELD, QR_ERR, QR_MODULE_SMALL, QR_QUIET_ZONE, BARCODE_ERR, BARCODE_MODULE_SMALL, BARCODE_QUIET_ZONE.

TEXT_WRAPPED means a Textbox wrapped where its text has no \n; widen it or break the line yourself. UNFILLED_FIELD means a {key} has no value in rows, so it prints literally. FONT_MISSING means a fontFamily is neither built in nor in your font folder, or its file isn't a valid font; the preview used a fallback. FONT_NOT_IN_LINK names the font file to add in NiimBlue before you open the link (see Fonts).

get_template

Pass name for a saved template's args, or path and printer to convert a NiimBlue .json export (Saved labels → Export, or a .niimblue.json, up to 2 MB). The conversion keeps Textbox, Material Icons text, Rect, Circle, Polyline, QRCode and Barcode, and turns the export's CSV into rows. An export with images, ArUco markers, paths or groups is refused with BAD_INPUT; notes lists anything else it dropped.

Errors return ok: false with error.code: BAD_INPUT, CHROME_NOT_FOUND, RENDER_FAILED, RENDER_TIMEOUT or INTERNAL.

The first time NiimBlue opens a link it may lay out text before its font has loaded. If the text in the app looks wrapped differently from the preview, reopen the link.

Fonts

Every NiimBlue has Noto Sans Variable (Latin and Cyrillic) and Material Icons. The link carries only a font's name, so any other fontFamily needs its file added in NiimBlue once: open the Fonts menu (the gear by the font picker) and upload it. Characters a font lacks, such as ł or →, fall back to a system font and print differently on each machine (GLYPH_FALLBACK).

The server bundles five Latin-only fonts. Upload these files from fonts/ once:

fontFamily

Style

File

JetBrains Mono Bold

Monospace, 700

fonts/JetBrains Mono Bold.woff2

Barlow Condensed Bold

Condensed grotesk, 700

fonts/Barlow Condensed Bold.woff2

Archivo Black

Heavy wide display, 400

fonts/Archivo Black.woff2

Archivo ExtraBold

Heavy grotesk, 800

fonts/Archivo ExtraBold.woff2

Bitter Bold

Slab serif, 700

fonts/Bitter Bold.woff2

Each file holds one weight, so leave fontWeight unset: any family but Noto defaults to 400, which draws the file as is, and a higher weight prints a faked bold.

niim.blue currently drops an uploaded font whose name has a space, so the label prints in the fallback font. That covers all five built-ins. A fix has been submitted to NiimBlue; until it is live, set NIIMBLUE_APP_URL to a NiimBlue build that has it.

For your own fonts, put .woff2, .ttf or .otf files (up to 2 MB) in the font folder (NIIMBLUE_FONTS_DIR). The family is the file name up to the first dot, as in NiimBlue's Fonts menu: Inter Bold.ttf is Inter Bold. A folder file overrides a built-in font of the same name. NiimBlue takes only .ttf and .woff2, so rename an .otf to .ttf before you upload it. list_fonts shows what the server found and why it skipped a file. Put only fonts you trust there: the preview's Chrome parses them without its sandbox.

Environment variables

All optional. See .env.example.

Variable

Default

NIIMBLUE_CHROME

Auto-detected. Linux: google-chrome(-stable), chromium(-browser), microsoft-edge(-stable) on PATH. macOS: Chrome, Chromium or Edge in /Applications or ~/Applications. Windows: Chrome, then Edge, in Program Files, Program Files (x86) and LocalAppData.

NIIMBLUE_APP_URL

https://niim.blue/. Must be http(s); any #hash is dropped.

NIIMBLUE_TEMPLATES_DIR

~/Documents/NiimBlue Templates. Where save_as writes; created on first save.

NIIMBLUE_FONTS_DIR

~/Documents/NiimBlue Fonts. Your font files (see Fonts); read only, never created.

NIIMBLUE_LOG_LEVEL

info (silent, error, info, debug; stderr)

Development

Needs Node.js 22.12+ and npm 11+ to develop (vitest 5 and the lockfile's overrides need them); the shipped bundle runs on Node 20.

git clone https://github.com/Nightious-Tools/niimblue-mcp && cd niimblue-mcp
npx -y npm@11 ci
npm test         # render tests need Chrome or Edge
npm run build    # writes dist/index.mjs, which is committed
npm run inspect  # MCP Inspector
npm run pack     # build, then niimblue-mcp.mcpb

License

Source-available, not open source. You may install and run it; you may not redistribute or modify it. See LICENSE. The bundle includes NiimBlue (MIT), fabric, Noto Sans, JetBrains Mono, Barlow Condensed, Archivo, Archivo Black, Bitter (OFL-1.1), Material Icons (Apache-2.0) and other third-party code under their own licenses: see THIRD_PARTY_NOTICES.md. Not affiliated with NIIMBOT or NiimBlue.

Available Tools

4 tools
get_templateGet label template argsA
Read-onlyIdempotent

Return render_label args to edit: a saved template by name, or a NiimBlue .json label export (path + printer) converted to objects and rows. Images, ArUco markers, paths and groups can't be converted (BAD_INPUT); notes lists what else was dropped.

NameRequiredDescriptionDefault
nameNoSaved template name (list_templates)
pathNoOr: a NiimBlue .json label export to convert (Saved labels → Export, or a saved .niimblue.json)
printerNoWith path: the printer to convert for (dots per mm); the export doesn't name one

Output Schema

NameRequiredDescription
okYes
argsYesrender_label args: edit them and render
toolYes
notesYesWhat the conversion dropped or changed

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes beyond annotations by disclosing conversion limitations: images, ArUco markers, paths, and groups cannot be converted and produce BAD_INPUT. It also explains that notes reports additional dropped items, which is useful lossy-conversion behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tightly written sentences, front-loaded with the return purpose and followed by the key conversion caveats. No padding or redundant restatement.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Complete for this tool: annotations cover the safety profile, an output schema exists, and the description supplies the source alternatives, conversion restrictions, and notes behavior needed to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description reinforces the path+printer pairing and conversion behavior, but the schema already documents the individual parameters and their constraints.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: return render_label arguments for editing, not render them. It clearly identifies the two sources (saved template by name or NiimBlue .json export converted to objects and rows), which distinguishes it from render_label.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description makes the use case clear: obtain editable render_label args from either a saved template or an exported label file. It does not explicitly state when to avoid render_label or list_templates, but the edit-oriented framing provides clear context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_fontsList label fontsA
Read-onlyIdempotent

List the fontFamily values render_label can draw: the default (Noto Sans Variable), the built-in fonts with notes and the smallest legible size, and the font files in the user's font folder (NIIMBLUE_FONTS_DIR; .woff2, .ttf or .otf, family = the name before the first dot). The link carries only the family name: the user adds each non-default font's file (path) in NiimBlue → Fonts once.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
okYes
toolYes
folderYes
builtinYes
defaultYesfontFamily when none is given

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare a safe, idempotent, closed-world read, so the description's added value is the behavior behind the list: it reads the user's font folder (NIIMBLUE_FONTS_DIR), filters by .woff2/.ttf/.otf, and derives family names as the text before the first dot. It also flags a real gotcha — the returned link carries only the family name, so non-default fonts need a user-added path before rendering works.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose in the opening clause, then supporting detail. Dense but each clause carries information (default name, smallest legible size, env var, extension list, naming rule, manual step); the parenthetical stacking makes it slightly heavy to scan but nothing is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return structure needn't be restated, and the description covers the remaining unknowns: where fonts come from, how families are named, and why a listed family may still fail to render. Nothing an agent needs in order to call and interpret it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, which is the baseline-4 case; there is nothing for the description to disambiguate. It does not need to explain inputs and correctly spends its words on output semantics instead.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("List the fontFamily values render_label can draw") and immediately scopes the output to what a sibling consumes. The three content groups (default, built-ins, user folder files) make it unmistakable versus list_templates or get_template.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The tie to render_label makes the use case clear: call this to obtain valid fontFamily values before rendering. It also warns that adding a non-default font's file path is a manual step in NiimBlue → Fonts, so the agent knows this tool cannot register fonts. No explicit when-not or named alternative beyond that implied flow.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_templatesList saved label templatesA
Read-onlyIdempotent

List the templates render_label save_as stored: name (pass it as render_label template or get_template name), title, printer, size in mm, the {key} fields rows must fill, row count, last modified.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
okYes
dirYes
toolYes
templatesYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive/closed-world, so the safety profile is covered. The description adds value by explaining what each returned field means and how the template name relates to sibling calls, though most of the returned-field detail overlaps with the existing output schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with a colon-delimited field list; no filler, and the most important token (the template name usable elsewhere) is highlighted first.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With zero parameters, rich annotations, and an output schema present, the description only needs to frame the listing and its downstream use — which it does completely. Nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so per the baseline there is nothing for the description to disambiguate. It sensibly uses the space to describe outputs instead, adding modest value over the empty schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (List) and resource (templates) scoped to those saved by render_label's save_as, then enumerates the exact fields returned. Naming the sibling tools render_label and get_template makes the distinction unambiguous without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly tells the agent what to do with the result: 'pass it as render_label template or get_template name', which chains this tool correctly into the workflow. It stops short of stating when-not to call it or any precondition, so it is not a full 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

render_labelRender NiimBlue labelA
Idempotent

Design a NIIMBOT label for NiimBlue and see exactly what the printhead burns. Returns the 1-bit PNG NiimBlue prints (threshold 140; each dot drawn zoom×zoom px), each object's rendered box, warnings that end with their fix, and a niim.blue #load= link that opens the label in NiimBlue for printing. Units are printer dots: 8 per mm at 203 dpi, 11.81 at 300 dpi; origin top-left; objects paint in array order and use NiimBlue/fabric property names. Floors at 203 dpi (×dpmm/8 at 300 dpi): text ≥16 px, or the font's min_px from list_fonts; weight ≥500 under 24 px; strokes ≥2 px; QR modules ≥3 px with 4 clear modules; Code 128 scaleFactor ≥2 with 10 clear modules; text and codes ≥1 mm from the edges. Colours: black, white, transparent. Fonts: Noto Sans Variable (Latin, Cyrillic) by default; any other fontFamily (list_fonts) travels by name only, so the user adds its file in NiimBlue (FONT_NOT_IN_LINK). Fix every warning and render again. Final call: include_json, for the .json export. The link replaces the label open in NiimBlue after a confirm: tell the user to save it first. {key} fields fill from rows (preview: row 1; more than 1 row travels as NiimBlue's CSV, one label per row). save_as writes the args and an importable .niimblue.json to the templates folder; template starts from saved args.

NameRequiredDescriptionDefault
rowsNoValues for {key} fields, one object per label. The preview shows row 1. 1 row: the link carries the filled text. More: the link carries them as NiimBlue's CSV (it replaces the user's open CSV) and prints one label per row
splitNoSplit the label into parts (cable flags)
titleNoLabel name in NiimBlue's saved labels; travels only in the .json export (include_json, save_as), so after opening the link the user types it when saving
mirrorNoRepeat the design into the other split part, optionally rotated
objectsNoObjects in paint order: the first is the bottom layer
printerNoPrinter model, e.g. D110, B1, D11_H: sets dpi, print direction and printable width
save_asNoSave these args as a template under this name: writes <name>.args.json and an importable <name>.niimblue.json
tail_posNoSide of the cable-flag tail
templateNoSaved template name (list_templates): its args are the base, the args given here override them, objects and rows replace wholesale
width_mmNoLabel width as set in NiimBlue. B-series (top): runs across the head
height_mmNoLabel height as set in NiimBlue. D-series (left): the tape width across the head
overwriteNoReplace an existing save_as template (default false: refuse)
split_partsNoNumber of split parts
include_jsonNoAlso return the NiimBlue .json export; set it on the final call
tail_length_mmNoTail length in mm

Output Schema

NameRequiredDescription
okYes
linkYesOpens the label in NiimBlue (after a confirm that replaces the open label)
toolYes
zoomYesEach printer dot is zoom×zoom PNG pixels
boxesYesRendered bounding boxes in dots, after auto-size
labelYes
savedNosave_as: the template files written
warningsYes
link_charsYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations, the description discloses substantial runtime behavior: threshold 140 and zoom-scaled dots, returned warning text ending in fixes, a link that replaces the currently open label, and save_as writing args and an importable .niimblue.json. It also explains template inheritance and the FONT_NOT_IN_LINK limitation, all of which matter for correct use.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose and outputs, and nearly every sentence carries useful constraints or side-effect information. It is dense and uses many semicolon-separated clauses, but that density is mostly justified by the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 15 parameters, an output schema, and multiple behavioral side effects, the description is complete enough to call the tool correctly: it covers units, DPI floors, paint order, font handling, rows, templates, warnings, and the final-call include_json behavior. The output schema exists, yet the description still adds useful context about the returned PNG, boxes, warnings, and link.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents parameters thoroughly. The description still adds operational meaning: units are printer dots with DPI conversions, the origin is top-left, objects paint in array order, and rows preview row 1 while more rows travel as CSV. These are meaningful additions beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: design a NIIMBOT label for NiimBlue and see the exact 1-bit PNG the printhead burns. It also identifies the outputs (PNG, rendered boxes, warnings, niim.blue link) and distinguishes the tool from support siblings like list_fonts and list_templates by naming them where relevant.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear workflow guidance: fix every warning and render again, and set include_json on the final call. It also warns that the link replaces the label open in NiimBlue after a confirm, so the user should save first. It does not explicitly contrast this tool with get_template or list_templates, but the usage context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

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

  1. 2 tool updatesv1.2.0
    • Addedlist_fonts
    • Changedrender_label1 field changed
      • changedInput schema / properties / objects / items / oneOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "angle": {
        -        "description": "Rotation in degrees around the origin",
        -        "maximum": 360,
        -        "minimum": -360,
        -        "type": "number"
        -      },
        -      "backgroundColor": {
        -        "description": "Box fill behind the text; black + white fill makes an inverse band",
        -        "enum": [
        -          "black",
        -          "white",
        -          "transparent"
        -        ],
        -        "type": "string"
        -      },
        -      "charSpacing": {
        -        "description": "Tracking in 1/1000 em",
        -        "maximum": 1000,
        -        "minimum": -200,
        -        "type": "number"
        -      },
        -      "fill": {
        -        "description": "Text colour (default black)",
        -        "enum": [
        -          "black",
        -          "white",
        -          "transparent"
        -        ],
        -        "type": "string"
        -      },
        -      "fontAutoSize": {
        -        "description": "Shrinks the font only when a field filled in at print time ({dt…} or a rows value) adds lines or widens the text; a long word never shrinks it",
        -        "type": "boolean"
        -      },
        -      "fontSize": {
        -        "description": "Font size in dots; ≥16 at 203 dpi",
        -        "maximum": 999,
        -        "minimum": 6,
        -        "type": "number"
        -      },
        -      "fontStyle": {
        -        "description": "italic is a synthetic oblique",
        -        "enum": [
        -          "normal",
        -          "italic"
        -        ],
        -        "type": "string"
        -      },
        -      "fontWeight": {
        -        "description": "Noto Sans Variable weight (default 600); ≥500 under 24 dots",
        -        "maximum": 900,
        -        "minimum": 100,
        -        "type": "integer"
        -      },
        -      "left": {
        -        "description": "x of the origin point, in dots",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "lineHeight": {
        -        "description": "Line height in font sizes (default 1)",
        -        "maximum": 3,
        -        "minimum": 0.7,
        -        "type": "number"
        -      },
        -      "originX": {
        -        "description": "Which point left refers to (default left)",
        -        "enum": [
        -          "left",
        -          "center",
        -          "right"
        -        ],
        -        "type": "string"
        -      },
        -      "originY": {
        -        "description": "Which point top refers to (default top)",
        -        "enum": [
        -          "top",
        -          "center",
        -          "bottom"
        -        ],
        -        "type": "string"
        -      },
        -      "scaleX": {
        -        "description": "Horizontal scale; 0.8–0.9 gives a condensed look",
        -        "maximum": 1.5,
        -        "minimum": 0.6,
        -        "type": "number"
        -      },
        -      "text": {
        -        "description": "Text; \\n breaks lines. {dt}, {dt|DD.MM.YYYY} or {dt+7d|DD.MM} print the date at print time; {key} fills from rows",
        -        "maxLength": 2000,
        -        "minLength": 1,
        -        "type": "string"
        -      },
        -      "textAlign": {
        -        "description": "Default center, as in the app",
        -        "enum": [
        -          "left",
        -          "center",
        -          "right",
        -          "justify"
        -        ],
        -        "type": "string"
        -      },
        -      "top": {
        -        "description": "y of the origin point, in dots",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "type": {
        -        "const": "Textbox",
        -        "type": "string"
        -      },
        -      "width": {
        -        "description": "Wrap width in dots (long words widen the box)",
        -        "maximum": 5000,
        -        "minimum": 1,
        -        "type": "number"
        -      }
        -    },
        -    "required": [
        -      "type",
        -      "left",
        -      "top",
        -      "text",
        -      "width",
        -      "fontSize"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "angle": {
        -        "description": "Rotation in degrees around the origin",
        -        "maximum": 360,
        -        "minimum": -360,
        -        "type": "number"
        -      },
        -      "fill": {
        -        "description": "Glyph colour (default black)",
        -        "enum": [
        -          "black",
        -          "white",
        -          "transparent"
        -        ],
        -        "type": "string"
        -      },
        -      "fontSize": {
        -        "description": "Glyph size in dots; ≥32 at 203 dpi",
        -        "maximum": 999,
        -        "minimum": 8,
        -        "type": "number"
        -      },
        -      "left": {
        -        "description": "x of the origin point, in dots",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "name": {
        -        "description": "Material Icons name in snake_case, e.g. lan, kitchen, wine_bar",
        -        "maxLength": 60,
        -        "minLength": 1,
        -        "type": "string"
        -      },
        -      "originX": {
        -        "description": "Which point left refers to (default left)",
        -        "enum": [
        -          "left",
        -          "center",
        -          "right"
        -        ],
        -        "type": "string"
        -      },
        -      "originY": {
        -        "description": "Which point top refers to (default top)",
        -        "enum": [
        -          "top",
        -          "center",
        -          "bottom"
        -        ],
        -        "type": "string"
        -      },
        -      "top": {
        -        "description": "y of the origin point, in dots",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "type": {
        -        "const": "Icon",
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "type",
        -      "left",
        -      "top",
        -      "name",
        -      "fontSize"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "angle": {
        -        "description": "Rotation in degrees around the origin",
        -        "maximum": 360,
        -        "minimum": -360,
        -        "type": "number"
        -      },
        -      "fill": {
        -        "description": "Fill (default transparent)",
        -        "enum": [
        -          "black",
        -          "white",
        -          "transparent"
        -        ],
        -        "type": "string"
        -      },
        -      "height": {
        -        "description": "Height in dots",
        -        "maximum": 5000,
        -        "minimum": 1,
        -        "type": "number"
        -      },
        -      "left": {
        -        "description": "x of the origin point, in dots",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "originX": {
        -        "description": "Which point left refers to (default left)",
        -        "enum": [
        -          "left",
        -          "center",
        -          "right"
        -        ],
        -        "type": "string"
        -      },
        -      "originY": {
        -        "description": "Which point top refers to (default top)",
        -        "enum": [
        -          "top",
        -          "center",
        -          "bottom"
        -        ],
        -        "type": "string"
        -      },
        -      "rx": {
        -        "description": "Corner radius x",
        -        "maximum": 500,
        -        "minimum": 0,
        -        "type": "number"
        -      },
        -      "ry": {
        -        "description": "Corner radius y",
        -        "maximum": 500,
        -        "minimum": 0,
        -        "type": "number"
        -      },
        -      "stroke": {
        -        "description": "Outline (default black)",
        -        "enum": [
        -          "black",
        -          "white",
        -          "transparent"
        -        ],
        -        "type": "string"
        -      },
        -      "strokeWidth": {
        -        "description": "Stroke width in dots (default 3); ≥2 at 203 dpi",
        -        "maximum": 50,
        -        "minimum": 0,
        -        "type": "number"
        -      },
        -      "top": {
        -        "description": "y of the origin point, in dots",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "type": {
        -        "const": "Rect",
        -        "type": "string"
        -      },
        -      "width": {
        -        "description": "Width in dots",
        -        "maximum": 5000,
        -        "minimum": 1,
        -        "type": "number"
        -      }
        -    },
        -    "required": [
        -      "type",
        -      "left",
        -      "top",
        -      "width",
        -      "height"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "angle": {
        -        "description": "Rotation in degrees around the origin",
        -        "maximum": 360,
        -        "minimum": -360,
        -        "type": "number"
        -      },
        -      "fill": {
        -        "description": "Fill (default transparent)",
        -        "enum": [
        -          "black",
        -          "white",
        -          "transparent"
        -        ],
        -        "type": "string"
        -      },
        -      "left": {
        -        "description": "x of the origin point, in dots",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "originX": {
        -        "description": "Which point left refers to (default left)",
        -        "enum": [
        -          "left",
        -          "center",
        -          "right"
        -        ],
        -        "type": "string"
        -      },
        -      "originY": {
        -        "description": "Which point top refers to (default top)",
        -        "enum": [
        -          "top",
        -          "center",
        -          "bottom"
        -        ],
        -        "type": "string"
        -      },
        -      "radius": {
        -        "description": "Radius in dots",
        -        "maximum": 2500,
        -        "minimum": 1,
        -        "type": "number"
        -      },
        -      "stroke": {
        -        "description": "Outline (default black)",
        -        "enum": [
        -          "black",
        -          "white",
        -          "transparent"
        -        ],
        -        "type": "string"
        -      },
        -      "strokeWidth": {
        -        "description": "Stroke width in dots (default 3); ≥2 at 203 dpi",
        -        "maximum": 50,
        -        "minimum": 0,
        -        "type": "number"
        -      },
        -      "top": {
        -        "description": "y of the origin point, in dots",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "type": {
        -        "const": "Circle",
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "type",
        -      "left",
        -      "top",
        -      "radius"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "angle": {
        -        "description": "Rotation in degrees around the origin",
        -        "maximum": 360,
        -        "minimum": -360,
        -        "type": "number"
        -      },
        -      "fill": {
        -        "description": "Fills the shape the points enclose, closed implicitly (default transparent): chevrons, arrows, marks; add stroke transparent for a clean edge",
        -        "enum": [
        -          "black",
        -          "white",
        -          "transparent"
        -        ],
        -        "type": "string"
        -      },
        -      "left": {
        -        "description": "Omit: points are absolute",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "originX": {
        -        "description": "Which point left refers to (default left)",
        -        "enum": [
        -          "left",
        -          "center",
        -          "right"
        -        ],
        -        "type": "string"
        -      },
        -      "originY": {
        -        "description": "Which point top refers to (default top)",
        -        "enum": [
        -          "top",
        -          "center",
        -          "bottom"
        -        ],
        -        "type": "string"
        -      },
        -      "points": {
        -        "description": "Absolute canvas points in dots",
        -        "items": {
        -          "additionalProperties": false,
        -          "properties": {
        -            "x": {
        -              "maximum": 5000,
        -              "minimum": -1000,
        -              "type": "number"
        -            },
        -            "y": {
        -              "maximum": 5000,
        -              "minimum": -1000,
        -              "type": "number"
        -            }
        -          },
        -          "required": [
        -            "x",
        -            "y"
        -          ],
        -          "type": "object"
        -        },
        -        "maxItems": 64,
        -        "minItems": 2,
        -        "type": "array"
        -      },
        -      "stroke": {
        -        "description": "Line colour (default black)",
        -        "enum": [
        -          "black",
        -          "white",
        -          "transparent"
        -        ],
        -        "type": "string"
        -      },
        -      "strokeWidth": {
        -        "description": "Line width in dots (default 3); ≥2 at 203 dpi",
        -        "maximum": 50,
        -        "minimum": 1,
        -        "type": "number"
        -      },
        -      "top": {
        -        "description": "Omit: points are absolute",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "type": {
        -        "const": "Polyline",
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "type",
        -      "points"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "angle": {
        -        "description": "Rotation in degrees around the origin",
        -        "maximum": 360,
        -        "minimum": -360,
        -        "type": "number"
        -      },
        -      "ecl": {
        -        "description": "Error correction (default M); L fits more on small labels",
        -        "enum": [
        -          "L",
        -          "M",
        -          "Q",
        -          "H"
        -        ],
        -        "type": "string"
        -      },
        -      "left": {
        -        "description": "x of the origin point, in dots",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "originX": {
        -        "description": "Which point left refers to (default left)",
        -        "enum": [
        -          "left",
        -          "center",
        -          "right"
        -        ],
        -        "type": "string"
        -      },
        -      "originY": {
        -        "description": "Which point top refers to (default top)",
        -        "enum": [
        -          "top",
        -          "center",
        -          "bottom"
        -        ],
        -        "type": "string"
        -      },
        -      "text": {
        -        "description": "Encoded text or URL",
        -        "maxLength": 1200,
        -        "minLength": 1,
        -        "type": "string"
        -      },
        -      "top": {
        -        "description": "y of the origin point, in dots",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "type": {
        -        "const": "QRCode",
        -        "type": "string"
        -      },
        -      "width": {
        -        "description": "Side of the square in dots; modules must be ≥3 dots",
        -        "maximum": 2000,
        -        "minimum": 42,
        -        "type": "number"
        -      }
        -    },
        -    "required": [
        -      "type",
        -      "left",
        -      "top",
        -      "text",
        -      "width"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "angle": {
        -        "description": "Rotation in degrees around the origin",
        -        "maximum": 360,
        -        "minimum": -360,
        -        "type": "number"
        -      },
        -      "encoding": {
        -        "description": "Symbology",
        -        "enum": [
        -          "CODE128B",
        -          "EAN13"
        -        ],
        -        "type": "string"
        -      },
        -      "fontSize": {
        -        "description": "Size of the printed text (default 12)",
        -        "maximum": 60,
        -        "minimum": 6,
        -        "type": "number"
        -      },
        -      "height": {
        -        "description": "Height in dots, including the printed text",
        -        "maximum": 2000,
        -        "minimum": 10,
        -        "type": "number"
        -      },
        -      "left": {
        -        "description": "x of the origin point, in dots",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "originX": {
        -        "description": "Which point left refers to (default left)",
        -        "enum": [
        -          "left",
        -          "center",
        -          "right"
        -        ],
        -        "type": "string"
        -      },
        -      "originY": {
        -        "description": "Which point top refers to (default top)",
        -        "enum": [
        -          "top",
        -          "center",
        -          "bottom"
        -        ],
        -        "type": "string"
        -      },
        -      "printText": {
        -        "description": "Print the text under the bars (default true)",
        -        "type": "boolean"
        -      },
        -      "scaleFactor": {
        -        "description": "Dots per module (default 2); width follows from it",
        -        "maximum": 10,
        -        "minimum": 1,
        -        "type": "integer"
        -      },
        -      "text": {
        -        "description": "CODE128B: printable ASCII, never $ or ). EAN13: 12–13 digits",
        -        "maxLength": 229,
        -        "minLength": 1,
        -        "type": "string"
        -      },
        -      "top": {
        -        "description": "y of the origin point, in dots",
        -        "maximum": 5000,
        -        "minimum": -1000,
        -        "type": "number"
        -      },
        -      "type": {
        -        "const": "Barcode",
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "type",
        -      "left",
        -      "top",
        -      "text",
        -      "encoding",
        -      "height"
        -    ],
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "angle": {
        +        "description": "Rotation in degrees around the origin",
        +        "maximum": 360,
        +        "minimum": -360,
        +        "type": "number"
        +      },
        +      "backgroundColor": {
        +        "description": "Box fill behind the text; black + white fill makes an inverse band",
        +        "enum": [
        +          "black",
        +          "white",
        +          "transparent"
        +        ],
        +        "type": "string"
        +      },
        +      "charSpacing": {
        +        "description": "Tracking in 1/1000 em",
        +        "maximum": 1000,
        +        "minimum": -200,
        +        "type": "number"
        +      },
        +      "fill": {
        +        "description": "Text colour (default black)",
        +        "enum": [
        +          "black",
        +          "white",
        +          "transparent"
        +        ],
        +        "type": "string"
        +      },
        +      "fontAutoSize": {
        +        "description": "Shrinks the font only when a field filled in at print time ({dt…} or a rows value) adds lines or widens the text; a long word never shrinks it",
        +        "type": "boolean"
        +      },
        +      "fontFamily": {
        +        "description": "Font family (default Noto Sans Variable): a list_fonts family. Any other font travels by name only, so the user adds its file in NiimBlue (FONT_NOT_IN_LINK)",
        +        "maxLength": 60,
        +        "minLength": 1,
        +        "pattern": "^-?[\\p{L}_][\\p{L}\\p{N}_-]*(?: -?[\\p{L}_][\\p{L}\\p{N}_-]*)*$",
        +        "type": "string"
        +      },
        +      "fontSize": {
        +        "description": "Font size in dots; ≥16 at 203 dpi",
        +        "maximum": 999,
        +        "minimum": 6,
        +        "type": "number"
        +      },
        +      "fontStyle": {
        +        "description": "italic is a synthetic oblique",
        +        "enum": [
        +          "normal",
        +          "italic"
        +        ],
        +        "type": "string"
        +      },
        +      "fontWeight": {
        +        "description": "Noto Sans Variable weight (default 600); ≥500 under 24 dots. Other fonts are one weight each: leave it unset (400), as ≥600 fakes bold on a regular file",
        +        "maximum": 900,
        +        "minimum": 100,
        +        "type": "integer"
        +      },
        +      "left": {
        +        "description": "x of the origin point, in dots",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "lineHeight": {
        +        "description": "Line height in font sizes (default 1)",
        +        "maximum": 3,
        +        "minimum": 0.7,
        +        "type": "number"
        +      },
        +      "originX": {
        +        "description": "Which point left refers to (default left)",
        +        "enum": [
        +          "left",
        +          "center",
        +          "right"
        +        ],
        +        "type": "string"
        +      },
        +      "originY": {
        +        "description": "Which point top refers to (default top)",
        +        "enum": [
        +          "top",
        +          "center",
        +          "bottom"
        +        ],
        +        "type": "string"
        +      },
        +      "scaleX": {
        +        "description": "Horizontal scale; 0.8–0.9 gives a condensed look",
        +        "maximum": 1.5,
        +        "minimum": 0.6,
        +        "type": "number"
        +      },
        +      "text": {
        +        "description": "Text; \\n breaks lines. {dt}, {dt|DD.MM.YYYY} or {dt+7d|DD.MM} print the date at print time; {key} fills from rows",
        +        "maxLength": 2000,
        +        "minLength": 1,
        +        "type": "string"
        +      },
        +      "textAlign": {
        +        "description": "Default center, as in the app",
        +        "enum": [
        +          "left",
        +          "center",
        +          "right",
        +          "justify"
        +        ],
        +        "type": "string"
        +      },
        +      "top": {
        +        "description": "y of the origin point, in dots",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "type": {
        +        "const": "Textbox",
        +        "type": "string"
        +      },
        +      "width": {
        +        "description": "Wrap width in dots (long words widen the box)",
        +        "maximum": 5000,
        +        "minimum": 1,
        +        "type": "number"
        +      }
        +    },
        +    "required": [
        +      "type",
        +      "left",
        +      "top",
        +      "text",
        +      "width",
        +      "fontSize"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "angle": {
        +        "description": "Rotation in degrees around the origin",
        +        "maximum": 360,
        +        "minimum": -360,
        +        "type": "number"
        +      },
        +      "fill": {
        +        "description": "Glyph colour (default black)",
        +        "enum": [
        +          "black",
        +          "white",
        +          "transparent"
        +        ],
        +        "type": "string"
        +      },
        +      "fontSize": {
        +        "description": "Glyph size in dots; ≥32 at 203 dpi",
        +        "maximum": 999,
        +        "minimum": 8,
        +        "type": "number"
        +      },
        +      "left": {
        +        "description": "x of the origin point, in dots",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "name": {
        +        "description": "Material Icons name in snake_case, e.g. lan, kitchen, wine_bar",
        +        "maxLength": 60,
        +        "minLength": 1,
        +        "type": "string"
        +      },
        +      "originX": {
        +        "description": "Which point left refers to (default left)",
        +        "enum": [
        +          "left",
        +          "center",
        +          "right"
        +        ],
        +        "type": "string"
        +      },
        +      "originY": {
        +        "description": "Which point top refers to (default top)",
        +        "enum": [
        +          "top",
        +          "center",
        +          "bottom"
        +        ],
        +        "type": "string"
        +      },
        +      "top": {
        +        "description": "y of the origin point, in dots",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "type": {
        +        "const": "Icon",
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "type",
        +      "left",
        +      "top",
        +      "name",
        +      "fontSize"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "angle": {
        +        "description": "Rotation in degrees around the origin",
        +        "maximum": 360,
        +        "minimum": -360,
        +        "type": "number"
        +      },
        +      "fill": {
        +        "description": "Fill (default transparent)",
        +        "enum": [
        +          "black",
        +          "white",
        +          "transparent"
        +        ],
        +        "type": "string"
        +      },
        +      "height": {
        +        "description": "Height in dots",
        +        "maximum": 5000,
        +        "minimum": 1,
        +        "type": "number"
        +      },
        +      "left": {
        +        "description": "x of the origin point, in dots",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "originX": {
        +        "description": "Which point left refers to (default left)",
        +        "enum": [
        +          "left",
        +          "center",
        +          "right"
        +        ],
        +        "type": "string"
        +      },
        +      "originY": {
        +        "description": "Which point top refers to (default top)",
        +        "enum": [
        +          "top",
        +          "center",
        +          "bottom"
        +        ],
        +        "type": "string"
        +      },
        +      "rx": {
        +        "description": "Corner radius x",
        +        "maximum": 500,
        +        "minimum": 0,
        +        "type": "number"
        +      },
        +      "ry": {
        +        "description": "Corner radius y",
        +        "maximum": 500,
        +        "minimum": 0,
        +        "type": "number"
        +      },
        +      "stroke": {
        +        "description": "Outline (default black)",
        +        "enum": [
        +          "black",
        +          "white",
        +          "transparent"
        +        ],
        +        "type": "string"
        +      },
        +      "strokeWidth": {
        +        "description": "Stroke width in dots (default 3); ≥2 at 203 dpi",
        +        "maximum": 50,
        +        "minimum": 0,
        +        "type": "number"
        +      },
        +      "top": {
        +        "description": "y of the origin point, in dots",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "type": {
        +        "const": "Rect",
        +        "type": "string"
        +      },
        +      "width": {
        +        "description": "Width in dots",
        +        "maximum": 5000,
        +        "minimum": 1,
        +        "type": "number"
        +      }
        +    },
        +    "required": [
        +      "type",
        +      "left",
        +      "top",
        +      "width",
        +      "height"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "angle": {
        +        "description": "Rotation in degrees around the origin",
        +        "maximum": 360,
        +        "minimum": -360,
        +        "type": "number"
        +      },
        +      "fill": {
        +        "description": "Fill (default transparent)",
        +        "enum": [
        +          "black",
        +          "white",
        +          "transparent"
        +        ],
        +        "type": "string"
        +      },
        +      "left": {
        +        "description": "x of the origin point, in dots",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "originX": {
        +        "description": "Which point left refers to (default left)",
        +        "enum": [
        +          "left",
        +          "center",
        +          "right"
        +        ],
        +        "type": "string"
        +      },
        +      "originY": {
        +        "description": "Which point top refers to (default top)",
        +        "enum": [
        +          "top",
        +          "center",
        +          "bottom"
        +        ],
        +        "type": "string"
        +      },
        +      "radius": {
        +        "description": "Radius in dots",
        +        "maximum": 2500,
        +        "minimum": 1,
        +        "type": "number"
        +      },
        +      "stroke": {
        +        "description": "Outline (default black)",
        +        "enum": [
        +          "black",
        +          "white",
        +          "transparent"
        +        ],
        +        "type": "string"
        +      },
        +      "strokeWidth": {
        +        "description": "Stroke width in dots (default 3); ≥2 at 203 dpi",
        +        "maximum": 50,
        +        "minimum": 0,
        +        "type": "number"
        +      },
        +      "top": {
        +        "description": "y of the origin point, in dots",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "type": {
        +        "const": "Circle",
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "type",
        +      "left",
        +      "top",
        +      "radius"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "angle": {
        +        "description": "Rotation in degrees around the origin",
        +        "maximum": 360,
        +        "minimum": -360,
        +        "type": "number"
        +      },
        +      "fill": {
        +        "description": "Fills the shape the points enclose, closed implicitly (default transparent): chevrons, arrows, marks; add stroke transparent for a clean edge",
        +        "enum": [
        +          "black",
        +          "white",
        +          "transparent"
        +        ],
        +        "type": "string"
        +      },
        +      "left": {
        +        "description": "Omit: points are absolute",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "originX": {
        +        "description": "Which point left refers to (default left)",
        +        "enum": [
        +          "left",
        +          "center",
        +          "right"
        +        ],
        +        "type": "string"
        +      },
        +      "originY": {
        +        "description": "Which point top refers to (default top)",
        +        "enum": [
        +          "top",
        +          "center",
        +          "bottom"
        +        ],
        +        "type": "string"
        +      },
        +      "points": {
        +        "description": "Absolute canvas points in dots",
        +        "items": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "x": {
        +              "maximum": 5000,
        +              "minimum": -1000,
        +              "type": "number"
        +            },
        +            "y": {
        +              "maximum": 5000,
        +              "minimum": -1000,
        +              "type": "number"
        +            }
        +          },
        +          "required": [
        +            "x",
        +            "y"
        +          ],
        +          "type": "object"
        +        },
        +        "maxItems": 64,
        +        "minItems": 2,
        +        "type": "array"
        +      },
        +      "stroke": {
        +        "description": "Line colour (default black)",
        +        "enum": [
        +          "black",
        +          "white",
        +          "transparent"
        +        ],
        +        "type": "string"
        +      },
        +      "strokeWidth": {
        +        "description": "Line width in dots (default 3); ≥2 at 203 dpi",
        +        "maximum": 50,
        +        "minimum": 1,
        +        "type": "number"
        +      },
        +      "top": {
        +        "description": "Omit: points are absolute",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "type": {
        +        "const": "Polyline",
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "type",
        +      "points"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "angle": {
        +        "description": "Rotation in degrees around the origin",
        +        "maximum": 360,
        +        "minimum": -360,
        +        "type": "number"
        +      },
        +      "ecl": {
        +        "description": "Error correction (default M); L fits more on small labels",
        +        "enum": [
        +          "L",
        +          "M",
        +          "Q",
        +          "H"
        +        ],
        +        "type": "string"
        +      },
        +      "left": {
        +        "description": "x of the origin point, in dots",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "originX": {
        +        "description": "Which point left refers to (default left)",
        +        "enum": [
        +          "left",
        +          "center",
        +          "right"
        +        ],
        +        "type": "string"
        +      },
        +      "originY": {
        +        "description": "Which point top refers to (default top)",
        +        "enum": [
        +          "top",
        +          "center",
        +          "bottom"
        +        ],
        +        "type": "string"
        +      },
        +      "text": {
        +        "description": "Encoded text or URL",
        +        "maxLength": 1200,
        +        "minLength": 1,
        +        "type": "string"
        +      },
        +      "top": {
        +        "description": "y of the origin point, in dots",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "type": {
        +        "const": "QRCode",
        +        "type": "string"
        +      },
        +      "width": {
        +        "description": "Side of the square in dots; modules must be ≥3 dots",
        +        "maximum": 2000,
        +        "minimum": 42,
        +        "type": "number"
        +      }
        +    },
        +    "required": [
        +      "type",
        +      "left",
        +      "top",
        +      "text",
        +      "width"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "angle": {
        +        "description": "Rotation in degrees around the origin",
        +        "maximum": 360,
        +        "minimum": -360,
        +        "type": "number"
        +      },
        +      "encoding": {
        +        "description": "Symbology",
        +        "enum": [
        +          "CODE128B",
        +          "EAN13"
        +        ],
        +        "type": "string"
        +      },
        +      "fontFamily": {
        +        "description": "Font family (default Noto Sans Variable): a list_fonts family. Any other font travels by name only, so the user adds its file in NiimBlue (FONT_NOT_IN_LINK)",
        +        "maxLength": 60,
        +        "minLength": 1,
        +        "pattern": "^-?[\\p{L}_][\\p{L}\\p{N}_-]*(?: -?[\\p{L}_][\\p{L}\\p{N}_-]*)*$",
        +        "type": "string"
        +      },
        +      "fontSize": {
        +        "description": "Size of the printed text (default 12)",
        +        "maximum": 60,
        +        "minimum": 6,
        +        "type": "number"
        +      },
        +      "height": {
        +        "description": "Height in dots, including the printed text",
        +        "maximum": 2000,
        +        "minimum": 10,
        +        "type": "number"
        +      },
        +      "left": {
        +        "description": "x of the origin point, in dots",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "originX": {
        +        "description": "Which point left refers to (default left)",
        +        "enum": [
        +          "left",
        +          "center",
        +          "right"
        +        ],
        +        "type": "string"
        +      },
        +      "originY": {
        +        "description": "Which point top refers to (default top)",
        +        "enum": [
        +          "top",
        +          "center",
        +          "bottom"
        +        ],
        +        "type": "string"
        +      },
        +      "printText": {
        +        "description": "Print the text under the bars (default true)",
        +        "type": "boolean"
        +      },
        +      "scaleFactor": {
        +        "description": "Dots per module (default 2); width follows from it",
        +        "maximum": 10,
        +        "minimum": 1,
        +        "type": "integer"
        +      },
        +      "text": {
        +        "description": "CODE128B: printable ASCII, never $ or ). EAN13: 12–13 digits",
        +        "maxLength": 229,
        +        "minLength": 1,
        +        "type": "string"
        +      },
        +      "top": {
        +        "description": "y of the origin point, in dots",
        +        "maximum": 5000,
        +        "minimum": -1000,
        +        "type": "number"
        +      },
        +      "type": {
        +        "const": "Barcode",
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "type",
        +      "left",
        +      "top",
        +      "text",
        +      "encoding",
        +      "height"
        +    ],
        +    "type": "object"
        +  }
        +]
  2. 3 tool updatesv1.1.0
    • First observedget_template
    • First observedlist_templates
    • First observedrender_label

TDQS

A4.4/5.0

Scored across 4 tools

Disambiguation4/5

render_label (design/render) and list_fonts (font enumeration) are clearly distinct, and list_templates vs get_template separate list-all from fetch-one. The only soft spot is get_template, which is named like a template getter but also converts arbitrary NiimBlue .json exports, a dual role that could momentarily confuse selection.

Naming Consistency5/5

All four names follow a clean verb_noun pattern (get_template, list_templates, render_label, list_fonts) in consistent snake_case. The naming scheme is predictable and easy to extend.

Tool Count4/5

Four tools is on the lean side for a label-design server, but each one is a heavyweight operation (rendering, template import, listing, font discovery) so nothing feels padded. A dedicated save/delete template tool would round it out.

Completeness4/5

The lifecycle is mostly covered: list_templates and get_template provide read/update paths, and render_label with save_as creates or overwrites templates. There is no delete/rename template operation, and saving is an implicit argument rather than its own tool, which are minor but real gaps.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables printing PNG labels to a nearby thermal label printer over local serial or Bluetooth, with tools for rendering previews, checking printer status, and printing with confirmation.
    5
    1
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables printing text, QR codes, and images directly to NIIMBOT B1 BLE label printers without vendor apps or cloud, with orientation control, fit validation, and local preview tools.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI clients to prepare and preview labels, then safely submit physical print jobs to an Xprinter XP-330B through local stdio or authenticated remote access, with status, cancellation, and retry receipts.
    MIT