mcpField
A
affinity

affinity

github.com/jablonkaiDesign & UI451 installsUpdated today
Repository

About affinity

affinity is a skill for controlling Affinity's JavaScript SDK through a Node CLI. It is for users scripting document operations, and includes rendering checks and script-library management via Affinity's AI connector endpoint.

Key features

  1. 01Run JavaScript scripts through the CLI
  2. 02Check connectivity and list endpoint tools
  3. 03Render the current spread or selection
  4. 04Add scripts to the Affinity library

Use cases

  1. 01Run document-editing scripts in batches
  2. 02Render a spread or selection to inspect results
  3. 03Search SDK hints while refining scripts
  4. 04Save reusable scripts in the library

MCP support

Affinity's AI connector exposes an MCP-protocol endpoint. The CLI calls it directly over HTTP/SSE without configuring an MCP client.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/jablonkai/skills --skill affinity

After installing, type/affinityin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
api-reference.md
references/sdk-docs/
application.js
artboardinterface.js
artboardproperties.js
baseboxinterface.js
blendmodeinterface.js
brushfillinterface.js
buffer.js
collection.js
colours.js
commands.js
compoundoperationinterface.js
configuration.js
curvesinterface.js
descriptioninterface.js
dialog.js
document.js
documentproperties.js
drawingscale.js
editabilityinterface.js
environment.js
references/sdk-docs/examples/
addGuides.js
addPoints.js
adjustPageItems.js
alignToPage.js
arrowheads.js
artboardGrid.js
bitmapWriter.js
boldItalics.js
breakFrame.js

Details

From the project README

Affinity Control

Affinity (/Applications/Affinity.app, the unified Canva-era app) has no AppleScript dictionary, CLI, or URL scheme, and its file formats are proprietary. Its only automation surface is the AI connector: when enabled, the app serves its JavaScript scripting SDK on http://localhost:6767/sse. The endpoint speaks the MCP protocol, but this skill drives it directly over HTTP/SSE with a zero-dependency Node CLI — no MCP client configuration, no tool schemas loaded into context. Verified end-to-end against Affinity 3.3.0 (build 4850).

  • scripts/affinity-cli.mjs — the CLI (Node ≥ 18, no npm install needed).
  • references/api-reference.md — verified endpoint tool contracts + SDK preamble digest. Read it before writing scripts.
  • references/sdk-map.md — curated SDK digest: module map, node model, creation recipes (text, image, adjustments), gotchas. Read it when writing anything beyond the basic drawing recipe.
  • references/sdk-docs/ — the complete vendored SDK documentation (142 topics, ~1.8 MB). Grep it for exact signatures only after sdk-map.md; never read the big files whole (nodes.js is ~300 KB).
  • Online signature reference for the native *Api layer the vendored JS wraps: https://sdk.affinity.studio/latest/js/ (apis/<ApiName>/<method>.html, enums/<Enum>.html).

The control loop

  1. Preflight — the endpoint cannot be enabled remotely:

    node scripts/affinity-cli.mjs ping     # → "OK http://localhost:6767 — Affinity 1.0.0"
    

    If it fails, ask the user to launch Affinity and enable the AI connector (Affinity Settings → AI connector; see the setup guide — the Claude-side connector steps there are NOT needed, only the in-app toggle).

  2. Write the script — JavaScript against the Affinity SDK. Start from references/api-reference.md, then references/sdk-map.md for the right module and recipe; grep references/sdk-docs/ for exact signatures. Scripts are top-level executable code; output ONLY via console.log() (return values are dropped). Entry point:

    const { app } = require('/application');   // '/application.js' works too
    const doc = app.documents.current;          // null when no document is open
    

    Three things trip up almost every first script, so get them right up front:

    • New documents default to millimetres. NewDocumentOptions setters are write-only; set o.units = UnitType.Pixel (from /units.js) before o.width/height/dpi.
    • Text size is in document pixels, not points: px = pt * doc.dpi / 72.
    • app.documents.all is a plain Array, node .children a lazy Collection — see sdk-map.

    Unsure what the user's settings allow? Log require('/environment').Environment.permissions (→ {fileSystem, network, genAI}) and .fileSystemRoots before writing code that needs them.

    When an API resists, search the crowd-sourced hints pool: node scripts/affinity-cli.mjs search "set blend mode".

  3. Execute:

    node scripts/affinity-cli.mjs run myscript.js
    

    Runs via the endpoint's execute_script and prints the script's console output. Errors come back as text — iterate on the file and re-run.

  4. Verify visually — never assume a script worked:

    node scripts/affinity-cli.mjs render --out /tmp/check.jpg            # current spread
    node scripts/affinity-cli.mjs render --selection --out /tmp/sel.jpg  # selection only
    

    Then Read the JPEG (max 1024 px). The document session uuid is fetched automatically.

  5. Ship — when the user is happy with a script, install it for reuse:

    node scripts/affinity-cli.mjs add --title "My Script" --description "What it does" --file myscript.js
    

    Library scripts appear in Affinity under Window → General → Scripts.

CLI cheatsheet

node scripts/affinity-cli.mjs ping                          # endpoint alive?
node scripts/affinity-cli.mjs run file.js                   # execute script, print console output
node scripts/affinity-cli.mjs run -                       # same, script read from stdin (heredoc)
node scripts/affinity-cli.mjs render [--selection] [--spread N] --out f.jpg
node scripts/affinity-cli.mjs tools [--json]                # list endpoint tools (verified list in api-reference)
node scripts/affinity-cli.mjs call <tool> '{"arg":"val"}'   # generic tool call
node scripts/affinity-cli.mjs add --title "T" --description "D" --file f.js
node scripts/affinity-cli.mjs list                          # library scripts
node scripts/affinity-cli.mjs save --title "T" --out f.js   # export library script to disk
node scripts/affinity-cli.mjs search "query"                # SDK hints search
node scripts/affinity-cli.mjs docs [<topic>]                # list / print SDK doc topics
node scripts/affinity-cli.mjs docs-dump <dir> [--force]     # (re-)vendor SDK docs; resumable

Env overrides: AFFINITY_MCP_URL (default http://localhost:6767), AFFINITY_TIMEOUT_MS (default 120000 — raise for long batch scripts).

Known limitations

  • A PERMISSION_DENIED error (the SDK preamble calls it NOT_ALLOWED) = the user disabled AI / filesystem / networking for scripting in Affinity settings — ask them to enable what's needed; don't probe around it.
  • Script filesystem access is Desktop-only (app.userDesktopPath); no network from script code. Prefer render for verification over script-side exports.
  • Library scripts cannot be deleted through the endpoint — only from the Scripts panel.
  • Setting the current spread clears the selection; set it only when actually switching.
  • doc.close() closes without prompting and discards unsaved changes — only close documents the script created itself, or save() first.
  • Endpoint sessions can die during long batches (POST → HTTP 404) — just re-run; each CLI invocation is a fresh session (the preamble gate is handled automatically).
  • After an Affinity update, check app.version against the version noted above. If it moved, dump into an empty directory (docs-dump /tmp/sdk-new, then replace references/sdk-docs/) — dumping over the old tree keeps stale files and skips changed ones — and refresh the api-reference and sdk-map digests.
  • Fallback if the endpoint route is ever unavailable: macOS GUI automation (System Events keystrokes/menus) works but is brittle and unverifiable — use it only for trivial one-shot actions, never for document editing.

Security

Unlike the other app-automation skills, this one opens no port of its own — the listener is Affinity's. But turning the AI connector on is still a decision worth naming.

  • Enabling the connector makes Affinity serve its full scripting SDK on http://localhost:6767/sse, an endpoint that executes JavaScript against the user's open documents. affinity-cli.mjs is only a client of it.
  • The endpoint carries no authentication: while it is enabled, every local process, and every other user on a shared machine, can drive Affinity through it. The CLI refuses an SSE endpoint that redirects off AFFINITY_MCP_URL's origin, so a hijacked session can't silently repoint it — but that protects this client, not the endpoint.
  • What scripts may touch is gated by Affinity's own AI / filesystem / networking switches (a PERMISSION_DENIED error means one is off). Turning one on widens what any client can do, not just this skill — ask rather than assume.
  • To close it, turn the AI connector off in Affinity's settings. The endpoint goes with it; nothing this skill installed keeps listening.

Comments

Markdown supported