npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ikijs/mcp

v0.26.1

Published

MCP server that lets AI agents build 2D puppet characters: compose AI-drawn part images, check their geometry and auto-rig them into an open .iki model — Iki, the free Live2D alternative.

Readme

@ikijs/mcp

Part of Iki, the open Live2D alternative that AI can build — free and MIT-licensed, with an open .iki format and a Claude Code / Codex plugin that draws and rigs characters.

A stdio MCP server that exposes .iki model tools to AI agents (Claude, LLMs, and any MCP-compatible client): read/validate a model, and auto-rig one from role-named PNG layers.

Tools

| Tool | Description | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | validate_iki | Validate a raw .iki model — accepts an object or a JSON string — fail-fast, one error at a time. | | describe_iki | Return a summary of a valid model (inline or by modelPath): canvas, parameters, parts, deformers, expressions, motions. | | list_standard_parameters | List the recommended standard parameter ids (e.g. ParamAngleX, ParamMouthOpenY) with descriptions. | | auto_rig_from_layers | Auto-rig role-named PNG layers into a renderable .iki written to disk; returns the output path. | | compose_layers_from_parts | Compose generated part PNGs into canvas-aligned, role-named layers, with the geometry report inline. | | measure_layers | Re-run that same geometry report over an already-composed layers directory. | | measure_turn_reference | Measure how far a head turns between a front and a turned image at the same scale, as three ratios. | | render_poses | Render a rigged .iki into the character loop's pose PNGs through the installed Chrome, Edge or Chromium. |

The model input for validate_iki and describe_iki accepts either a plain JSON object or a JSON string — the server normalises both. describe_iki also takes modelPath instead (exactly one of the two; resolved against the working directory, under it), so a written rig never crosses the context, and returns the patch writer's view of it: per part the rest box in model px, the bustBox in bust-render px and its warps with their stops; per warp2d deformer stopsX and stopsY; and faceWidth (the face part's width, the unit of a patch's by) and bustScale.

auto_rig_from_layers

Turns a set of role-named, full-canvas transparent PNG layers into a renderable, validated .iki model with the textures atlased and embedded as a base64 data:image/png URI. Under quantizeColors a nose layer is the exception: the palette rims a soft-shaded nose's feather, so the nose is atlased alone on a second, lossless page. The written model declares the head motions Nod, Shake and Tilt and up to six default expressions (smile, laugh, angry, sad, surprised, shy; shy needs a blush, angry and sad need brows), each with a description a host picks by; with a blush_L/blush_R layer it also declares ParamCheek, which fades the blush from faint at rest to as drawn.

  • layers — array of { path, fileName? }. path is a PNG file path (resolved against the server's working directory); the role is derived from fileName ?? basename(path). Required roles: face, eye_L, eye_R, and mouth or the lip set mouth_inner, lip_lower, lip_upper (a mouth that folds open instead of crossfading; a partial lip set, or one mixed with mouth/mouth_open, is refused). mouth_tongue / mouth_teeth are optional with the lip set only: drawn under its lips and clipped to mouth_inner, the teeth riding the upper line and the tongue the opening's bottom; without the set they are refused. Optional roles include iris_L/iris_R, brow_L/brow_R, hair_front/hair_back, lash_L/lash_R, lash_lower_L/lash_lower_R, body, arm_L/arm_R, forearm_pose_L/forearm_pose_R, etc. An arm — the whole arm drawn hanging, its shoulder at the top — needs the body (it hangs from the body's shoulder) and becomes an elbow cap, an upper arm and a forearm sharing its crop, on ParamArmL/R (the shoulder) and ParamElbowL/R. A pose forearm — the forearm raised, standing up from its elbow — needs its arm: it hangs from the arm's elbow and is swapped in for the hanging forearm by ParamArmPoseL/R, rocked by ParamArmPoseAngleL/R, and drawn above the face and the hair. All layers must share the same canvas size (taken from the first layer).
  • outputPath — optional .iki output path (resolved against the working directory; the parent directory must already exist, and the path must end in .iki). Defaults to auto-rigged-model.iki.
  • turnTargets — optional head-turn cues to fit the rig to, in the units measure_turn_reference reports off a front/turned reference pair: eyeShift (plus noseShift and mouthShift) is how far that feature slides across the head at full turn, as a fraction of the head's half-width — a magnitude, since the rig turns both ways and the sign is the turn's own business; farEyeRatio is the far/near eye width at full turn over that same ratio at rest; silhouetteRatio the head's half-width turned over at rest. An omitted field leaves that part of the turn at the profile the rig defaults to. An eyeShift you pass is fitted by the turn's amount and clamped when it runs past the room the art leaves the far eye — the face plate's edge, or the bangs' side strand over the far iris — since that room is a fact about these layers, not about the reference. Any other target you do pass that it cannot reach is refused, because that number is a measurement rather than a preference. A layer set without a nose still turns its head, on the profile, but fits no target: turnTargets is then inert and the result carries no turn.
  • style — optional per-character tuning from the profile the rig defaults to: turn (the whole head turn, the eyes' foreshortening with it, 0..3, 1 = the profile; ignored when turnTargets.eyeShift is given), featureLead (how far the features lead the face plate, 0..3), hairFollow (the front hair's share of the face's turn, 0..2, profile 1), outlineFollow (its outer edge's share where it draws the head's outline, 0..2, profile 0: the outline holds), blink (how far the upper lid comes down over the eye's height, 0.1..1, profile 0.6) and sway (the hair sway amplitude, 0..5). A value out of range, or a knob not named here, is refused.
  • patches — optional displacements added to existing keyform stops of the freshly generated rig, each { target, at, region, by, falloff?, why? } (RigPatch in @ikijs/editor). target is a feature grid (eyeWarp_L/_R, browWarp_L/_R, noseWarp, mouthWarp; at names ParamAngleX and ParamAngleY, each −30, 0 or 30, never both 0) or a mesh part with warps (face, hair_front, …; at names one parameter at one of its warp stops). region is a rectangle in fractions of a role's rest layer box (top-left origin, y down); by and falloff are in face widths on screen axes (+dx right, +dy down). They are replayed on every rig of these inputs, so they hold through a layout or style retune, and they never touch the rest stop or a stop where the rig shuts a part on purpose; bodyWarp and the matrix deformers are not targets. Aim them with describe_iki.
  • Result — on success the result text is the written file path, then one PATCH <i> <target> at <Param=v …>: <moved> of <of> points (cols a-b, rows c-d), largest move <maxPx> model px line per patch op ((vertices) in place of the columns and rows for a mesh part), then the rig's fold report lines (as render_poses prints them), and structuredContent carries { ok: true, path, canvas, partCount, atlasBytes, headHalfWidth?, headHalfWidthApplied, headEdges?, strandEdges?, noseCore?, turn?, patches?, folds }. patches is what each op moved (index, target, at, moved, of, cols/rows for a grid, maxPx) and is present whenever patches was passed, [] included. atlasBytes is the embedded atlas's data-URI length, summed over both pages when the nose has one of its own. headHalfWidth is the head measured here rather than asked for: the layers' own opaque union at the eye row, under the same alpha >= 128 rule and ±10-row band measure_turn_reference spans a render with, so the rig's own rest render measures that span back; it is absent when that union has no span there at all (translucent art painted below alpha 128 — still a valid layer set, just with nothing to measure). headHalfWidthApplied is false when the measured value is absent, or no wider than the face plate (a hairless set, or one widest at the plate itself) — the generator will not take such a head, so the plate stands in for it and the rig is still produced either way. headEdges is a companion to a measured, applied headHalfWidth (absent otherwise): every role with an opaque pixel in that same eye-row band, per side, each with its own rest x there — passed straight to the generator's own options.headEdges so it can tell which part's own deformation actually carries the union's outermost pixel through the turn. strandEdges is each side's iris and the hair_front run it would slide under on the turn, measured whenever the layers include hair_front, iris_L and iris_R: the iris's opaque span and the run's outer and face-side ends as pixel edges on the row holding the iris's centre (a run that reaches the other iris's centre, a fringe spanning the face, has runFace: null; a run narrower than half the iris's painted width on that row is hair detail the iris may cross, and is skipped as if clear) — passed straight to the generator's own options.strandEdges, which keeps the far iris from sliding under the bangs any further than it is painted. turn is what the solve settled on — { radius, holdBase, depths, achieved: { eyeShift, farEyeRatio, silhouetteRatio }, clamped, strandOverlap? }, where achieved is what the rig does reach and clamped names every target the rig cut down: the turn (eyeShift, passed or the profile's own) where the face plate's edge or the strand cut it short, and a profile noseShift / mouthShift past its room. strandOverlap gives, per side whose far iris the bangs still cover at some far stop, the covered width — how much of the iris's painted row the run covers where that is largest (deg, px, and hh in head half-widths), how much it covers at rest (restPx), and whether the bound held (held: true means the iris is kept no deeper under the run than it is painted; false means the art left no room to — always so for a fringe spanning the face — and px is what stays covered); it is absent when nothing is covered. noseCore is the nose's dense core in canvas px — its pixels at alpha >= 128, not grown, the box handed to the generator as the nose layer's denseCore and its turn landmark — absent without a nose layer, when it has no pixel at that threshold (painted wholly translucent), or when that core is a speck of the nose's crop (under a quarter of its width or height, such as a lone nostril mark), and in both of the last two cases the crop stands in for it. turn is absent without a nose layer. folds is the written model's fold report, the engine's checkFolds over its own pose set ({ poses, thresholds, targets, offenders }, described under render_poses); it never refuses a rig — a model that folds is still written and returned — and each fold names its pose by its parameter settings, which a host reproduces by setting those parameters on a player (IkiPlayer.setParameter). The (potentially multi-MB) model is written to disk, not inlined. Invalid input (a patches op the rig refuses — INVALID: patches[i]…, naming the stops, targets or roles there are; unknown/missing role, an arm without a body, empty layer, mismatched sizes, bad path, an atlas page over 4096 px on a side, or an atlas over 12 MiB; art the rig cannot build on, such as a body's hips with no room under its pivots for a row line or for the body's motion, or an arm too short to hang an elbow under its shoulder; a turnTargets field that is unknown or that this layer set cannot reach — any passed field other than an eyeShift past the art's room; a style knob out of range or unknown; the message names the field and the attainable range) returns { ok: false, error } with a INVALID: … text — not a protocol error. An argument the tool's input schema refuses — a turnTargets or style that is not an object, or one of their named fields that is not a number — never reaches the tool: the MCP SDK answers with an isError result whose text is MCP error -32602: Input validation error: …, and no structuredContent.

The decode/atlas pipeline runs in Node via sharp (a native dependency confined to this package); it mirrors the browser editor app's import flow and reuses the pure @ikijs/editor model + atlas math, so both paths produce the same rig. The fold check runs the workspace @ikijs/engine, inlined into this package's Node build (as it already is into the render_poses page script), while @ikijs/format stays an external dependency.

compose_layers_from_parts

Composes a directory of AI-generated part PNGs into canvas-aligned, role-named layer PNGs ready for auto_rig_from_layers, on a canvas 1100 px wide and canvasHeight tall (1100 by default) with a built-in default layout tuned for the character-generation skill's standard front-facing framing. It ports the composer that shipped as a script (compose.cjs) in the Claude Code plugin.

  • partsDir — directory of the source part PNGs (resolved against the server's working directory). Required sources: face.png, eyewhite.png (split into the sclera, the upper lash and the lower lid — lash_lower_L/lash_lower_R, the dark line and lashes of the eye's lower part, which draw over the iris and are placed on the sclera's frame by the eye_* layout, so they have no layout key of their own; an eyewhite with no dark lower lid writes none and lists them in skipped. A light mark drawn detached above the eye white, such as a double-eyelid crease, is dropped from the sclera — the iris's clip — while dark strokes there stay lash ink. So is the lid's skin drawn attached above the white, a warm light band over the lash, in the columns that have a lash there — a band over a lash-free column, such as the tear duct, stays, as the white's own warm shading would — while the eye's frame stays the drawing's), iris.png, brow.png, hair_front.png, and a mouth by one of two routes: mouth.png (a closed drawing, with an optional mouth_open.png it cross-fades to), or the lip set — mouth_keyed.png (the open mouth, its inside painted ONE flat #00FF00) + mouth_interior.png (the cavity, teeth and tongue alone), which the composer keys and splits into mouth_inner / lip_lower / lip_upper on one frame, so auto_rig_from_layers folds the mouth open like the eyelid. The interior's light paint (luma 100 and over: the teeth, the tongue) is written as mouth_teeth and mouth_tongue on the same frame, split at each column's middle, riding the upper line and the opening's bottom and clipped to the cavity; mouth_inner takes the cavity's fill under it, its edge softened by the key's own coverage. An interior with no light paint writes neither and lists them in skipped. The key reads each antialiased pixel against its own ramp to the art's green, the interior is trimmed of the lip rim a generator draws round it, and the split is cut at the composed size so every boundary meets the rig's per-column contract by construction. The lip set is opt-in: with neither file the legacy route runs; one without the other, or either beside mouth.png / mouth_open.png, is refused naming the file to draw or remove. With the set, mouth and mouth_open are neither written nor listed in skipped. Optional: hair_back.png, body.png, mouth_open.png (legacy route), blush.png, nose.png — a parts dir without them still composes, minus those roles. One blush.png makes both cheeks' blush: blush_R takes it as drawn and blush_L mirrored. It is decoration, so the report does not flag a set without it. One arm.png — the arm on the screen left, drawn hanging with its shoulder at the top — makes both arms of a full body: arm_R takes it as drawn and arm_L mirrored, and each is hung on the body's shoulder (see layout). The arms are opt-in: a parts dir without arm.png composes as a bust does, and skipped never lists them. An arm.png needs a body.png. One forearm_pose.png — the arm's forearm raised, on the screen left, standing up from its elbow with the hand at the top — makes both pose forearms (forearm_pose_R as drawn, forearm_pose_L mirrored); it is opt-in too and needs an arm.png. With one the compose also writes preview-pose.png, the switch at 1 as the rig draws it (each arm with a pose forearm cropped to the rows above its elbow, the pose forearms over everything); preview.png stays the rest pose, where a pose forearm is invisible. Without a forearm_pose.png, preview-pose.png is removed. The nose is what the auto-rig leads the head turn with, and a face supplied alongside it must be drawn without one, or the painted nose stays on the face plate while the real one moves. Each part but the eye pair is cropped to its drawing before it is sized and placed. A faint speck detached from the drawing, one that never reaches alpha 128 and covers under 1 % of the part's pixels (residue a background removal leaves), is cut out with the margin it widened, so it cannot pull the part off its centre. A detached stroke that reaches alpha 128 is kept.
  • outDir — an existing directory (resolved against the working directory, confined to it exactly like auto_rig_from_layers's outputPath; the tool never creates directories) to write the role layer PNGs and a flattened preview.png (and, with a forearm_pose.png, preview-pose.png) into. Reusing a directory across runs is intentional — a role this run does not write (one it skips, e.g. composing a head-only set that omits body, or an arm or pose forearm once arm.png or forearm_pose.png is gone, preview-pose.png included) has its stale <role>.png from an earlier run deleted, so the directory always holds exactly this run's roles.
  • layout — optional per-role override merged over the built-in defaults, e.g. { "eye_L": { "cx": 660 } }, so a character can be retuned by re-running compose rather than editing a script. Each of cx, cy, w, h is optional; w must be an integer in 1..1100 (the canvas width) and h in 1..canvasHeight (the canvas height). The defaults of the face's features — blush, nose, mouth, mouth_open, mouth_inner, eyes, irises, lashes, brows — are proportions of the face: an override of face.cx, face.cy or face.w carries them with it (scaled by the width; face.h stretches the face alone), while a feature's own override names canvas px. Omitting h keeps the part's own aspect ratio, which is what every role wants until one does not: a generated eyewhite often comes back flatter than its reference, and stretching a flat white lens costs nothing where regenerating it is billed and unreliable. The nose is sized and placed by its dense core — its pixels at alpha ≥ 128, the drawing a viewer reads inside a soft nose's feather — rather than its whole part, which for a shaded-bump nose is mostly feather: w (default 20) and h size the core, cx (default 550) centres it, and a set cy centres its rows. By default its cy is unset, and the core's bottom row (the tip) lands 0.66 of the way from the eye row down to the mouth's, so the nose follows a retuned eye or mouth row. A nose with no pixel at alpha ≥ 128 is sized and placed whole, and so is one whose core is a speck of its trimmed part — under a quarter of its width or height, such as a lone nostril mark or highlight, which sized to w would blow the whole nose up — and the report warns of the speck. mouth_inner is the lip set's one frame (mouth_open's place by default): lip_lower, lip_upper, mouth_tongue and mouth_teeth have no key and land on it, so a retune moves them together, and with the set the nose's tip rule reads layout.mouth_inner.cy. Set h on a sclera and its lash together — they share one frame, and the blink fold tears if they diverge, so a pair set to land on different frames is rejected. arm_L and arm_R have no cx/cy/w defaults; each is derived from the placed body. An unset w is 0.4 of the body's width. An unset cx or cy hangs the arm's shoulder pivot — the one auto_rig_from_layers turns it about, read off the resized arm by the rig's own rule — on the body box's shoulder corner on that axis: its left edge for arm_R, its right edge for arm_L, 0.12 of its height under its top. On a torso widest at its shoulders, the shoulder cap's centre then sits on the torso's edge. A set one centres the arm as for every other role. Both values are provisional. forearm_pose_L and forearm_pose_R have no cx/cy/w defaults either: an unset w scales the forearm so its elbow end (the shoulder rule run from its bottom, on the trimmed source) is as wide as the placed arm's elbow run, and an unset cx or cy pins the elbow end's pivot — measured on the resized part — on the arm's placed elbow, the pivot auto_rig_from_layers hangs it from.
  • mirrorParts — optional list of part files to flip left-right as they are read, e.g. ["eyewhite.png"]. Which way a part faces is fixed by the layout, and "left eye" cannot pin it down — it reads as the viewer's left or the character's — so in image terms: eyewhite.png is read as the eye on the screen left, lash wing (outer corner) at the image's left end and lash-free tear duct at its right; brow.png as the brow on the screen right, thick head at the image's left end and tail at its right; blush.png as the blush on the screen left, outer end (toward the face's edge) at the image's left end and inner end at its right; arm.png as the arm on the screen left (the character's right), hanging, its shoulder at the image's top; forearm_pose.png as the raised forearm on the screen left, standing up from its elbow, the hand at the image's top and the thumb toward the body at its right. A part drawn the other way round is fixed here for free instead of regenerated. It flips the source, so every role cut from it flips together and an eye's sclera and lash stay in one frame.
  • canvasHeight — optional height of the canvas in px: an even integer in 1100..4096, default 1100, a square bust canvas — and at 1100 every output is byte-identical to leaving it out. The width stays 1100. The canvas grows downward: every default and every override keeps its canvas px from the top-left, so the head lands where it does on a bust and a full body gets room below. An odd height is refused, since model space is centred on the canvas and it would put the head on a half pixel. It is checked before any part decodes.
  • Result — on success the text is the written layer paths (one per line) followed by the same geometry report measure_layers returns — except that when the composer found the core of the nose's source part a speck, its nose check reports that verdict instead of what the composed file shows, so a speck that resampling erased from the layer is warned about here and not by measure_layers. structuredContent is { ok: true, outDir, layers, skipped, preview, previewPose?, measure } (previewPose only with a forearm_pose.png). With the lip set preview.png shows the mouth closed, as the rig draws the rest pose (the tongue and the teeth left out with the interior), and skipped lists mouth_tongue / mouth_teeth when the interior has no light paint. Invalid input (a missing required part, a canvasHeight that is odd or outside 1100..4096, an unknown layout role, an out-of-range w or h, a lash placed on a different frame from its sclera's, an arm.png without a body.png, a forearm_pose.png without an arm.png, a lip set with one source missing or beside the legacy mouth, a key that fails (no green, green outside the opening, an outline not closed round it, more than one opening, no skin under it, an interior that is only lips or drawn opaque on a non-white or noisy ground), an arm too short for the rig to hang an elbow under its shoulder, a mirrorParts entry that is not a part file, a part that lands entirely off-canvas, a non-existent outDir) returns { ok: false, error } with INVALID: … text.

measure_layers

Reports the same read-only geometry checks compose_layers_from_parts returns inline (iris size/offset relative to the sclera, eye aspect, an eye drawn facing the other way, cropped or flat-cut edges, a nose whose dense core is a speck of its crop, missing optional roles, and for a full body: a body whose feet the bottom of a canvas taller than wide cuts off, an arm without a body, an arm whose shoulder cap — the one auto_rig_from_layers turns it about, read by the rig's own rule — does not reach half its radius under the torso's edge on its shoulder pivot's row, a pose forearm without its arm or whose elbow end is more than 2 px off the arm's elbow, more than 10 % off its elbow run in width, or more than 15 % off its hand's reach in length, and for the lip set: a partial set or one beside mouth / mouth_open, a column of the opening where the interior does not reach the line or the skin does not reach the interior, an opening under 0.3 of the upper lip's width or under 8 rows tall, a line under 2 rows, key green left, an interior with transparent holes, and a mouth_tongue / mouth_teeth with no lip set, on another canvas, or whose box leaves mouth_inner's, where the cavity clips it away), over an already-composed layers directory — for re-checking a layers dir without recomposing it. Its nose check reads the composed file against its crop, which is what auto_rig_from_layers acts on: a speck core there is one the rig does not turn or tilt the nose about. The composer's inline report does the same unless it found the source part's core a speck, which it reports instead, so a speck that resampling erased from the layer shows there and not here.

  • layersDir — directory of role-named layer PNGs (resolved against the working directory). Every preview*.png in it is skipped.
  • Result — the text is the report: a per-layer size/bbox-centre/mass-centre/margins table, then, with a lip set, one lips: line — the opening's width, its height at the centre, the line's rows and the MouthOpen below which no slit shows — then the checks that fired. structuredContent is { ok: true, layersDir, layers, empty, warnings, lips?, passed }. A missing or non-directory layersDir returns { ok: false, error } with INVALID: … text.

measure_turn_reference

Measures how far a head turns between two images of the same character — one facing front, one turned — and reports it as three ratios, so a rigged turn can be compared against a reference turn (or against an earlier rig) without registering the two images. Every number is a front→turned change divided by something measured in the same image, which cancels crop and position — not scale: the pair must already share framing and head size. A yaw never changes iris height, so that is what is checked — a pair whose mean iris height differs by more than 10% is refused rather than measured as if it were a turn.

  • front, turned — PNG file paths (resolved against the server's working directory). Engine renders (transparent backdrop) and opaque reference art both work; see the two foreground modes below.
  • iris — optional colour-window override merged over the violet default (hueMin 230, hueMax 300 degrees, satMin 0.22). The iris is found as a saturated blob inside that hue window, closed and paired on one row, so a character whose eyes are another colour needs its own window — otherwise the tool returns no iris pair found … naming the file and how many candidate blobs it had.
  • debugDir — optional existing directory (confined to the working directory, like every other write) to drop a front-<name>.debug.png and a turned-<name>.debug.png into (role-prefixed, so two inputs that share a basename do not overwrite each other), with the iris boxes, the head edges, the head centre and the eye-pair centre drawn on them. The measurement is only believable once those boxes are seen sitting on the irises.
  • Result — structuredContent is { ok: true, front, turned, farEyeRatio, eyeShift, silhouetteRatio, turnSign, debug? }, with front/turned carrying the raw per-image numbers (iris widths and centres, eye row, head span, which foreground mode was used). A missing file, a missing debugDir, an image with no iris pair, or a pair whose mean iris heights differ by more than 10% (not at the same scale) returns { ok: false, error } with INVALID: … text.

The three ratios:

| Ratio | What it is | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | farEyeRatio | Turned far/near iris width, divided by that same far/near ratio at rest — so a resting asymmetry in the art divides out. | | eyeShift | How far the eye pair slides across the head, in units of the front head half-width (hh). Negative = toward the image's left. | | silhouetteRatio | Head half-width, turned over front. |

turnSign is -1 when the pair moved toward the image's left and +1 when it moved right; the "far" eye is the one on the side it moved toward.

The head span is the silhouette at the eye row, over a ±10-row band — fixed rather than scaled to the image, so compare renders at like resolutions. The foreground it spans is found two ways, chosen per image:

  • alpha — the file has an alpha channel with transparent pixels (an engine render): foreground is alpha >= 128 and nothing else, so the character's near-black hair ink and pale highlights all count.
  • keyed — a fully opaque image (a reference): the flat lavender-grey backdrop (hue 220–260, low saturation, bright) and near-black frame borders are keyed out by colour instead.

The two rules do not agree on the same picture — a near-black outline is silhouette under alpha and background under keyed — so compare renders with renders and references with references.

render_poses

Renders a rigged .iki into the pose PNGs the character loop reviews, with the real engine, so the render step is one tool call. The engine runs in the Chrome, Edge or Chromium already installed on the machine, headless and driven over DevTools by the server itself: no Playwright and no browser download. Set CHROME_PATH to an executable to pick a browser; without it the usual install locations are searched, and with none found the tool returns INVALID: … saying so, and a CHROME_PATH that is not an executable file is refused the same way. A browser that is found but fails to start is a tool error. It needs Node 22 or newer.

  • modelPath — the rigged .iki (resolved against the working directory; must sit under it).
  • outDir — an existing directory under the working directory. One <pose>.png per pose is written into it, replacing a same-named file in place.
  • poses — optional caller-named poses rendered instead of the standard set, each { name, values, crop? }: name becomes <outDir>/<name>.png (letters, digits, _, ., -; unique ignoring case), values are parameter settings (the rest at their defaults), and crop { x, y, w, h } is a rectangle in model px from the top-left — the frame of describe_iki's boxes and the FOLD lines — clamped to the canvas and drawn 720 px wide (an empty list, a bad or repeated name, a non-finite value, or a crop that misses the canvas or would draw under 1 or over 8192 px tall is INVALID: poses…; a parameter id the model lacks is ignored). Each pose is also added to the fold sweep, after the engine's own poses.
  • What is written — the model decides the set. A bust gets 560 × 560 shots: rest, turn-m30, turn-p30, turn-15, nod-15, blink, blink-half, gaze, combined (ParamAngleX/Y/Z all at 30), stress (ParamAngleX/Y/Z all at −30, ParamMouthOpenY 1, ParamEyeLOpen and ParamEyeROpen 0.5, ParamBrowLY −1, ParamBrowRY 1, ParamBrowLAngle 1, ParamBrowRAngle −1, and each of ParamBodyAngleX/Y/Z the model declares at −10: moves that each look fine alone, stacked). A model with a mouth or mouth_inner adds 720 × 480 mouth crops (mouth-open-*-form-*, mouth-laugh, mouth-surprised, mouth-turn-p30-half). A full body (canvas taller than wide) shoots the same squares from the model's top (the head), centred horizontally, and adds whole-canvas full-* shots (body, arm and elbow poses) at 1400 px wide; one with a pose forearm (ParamArmPoseR) adds the full-pose-* shots.
  • Result — the text is the written paths, one per line, then the model's fold report. failedTextures lists the atlas pages the engine could not load; their parts are missing from the renders. structuredContent is { ok: true, outDir, canvas, fullBody, mouth?, failedTextures, poses: [{ name, path }], modelCanvas, folds }. A missing model, an outDir that is missing or outside the working directory, or a file that is not a valid model returns { ok: false, error } with INVALID: … text.
  • Folds — folds is the engine's checkFolds over its own pose set (foldPoses: rest, each parameter alone at its min and max, and the corners of the head and body angles crossed with the eyes, the mouth, the arms and the elbows), not over the rendered shots: { poses, thresholds: { flip: 0, collapse: 0.1 }, targets, offenders }, one target per warp deformer (its grid cells) and per mesh part (its triangles), each { target: { kind, id }, kind, worst?, expected? }: worst and expected are the least ratio of an area at a pose to the same at rest over the poses that are not, and that are, a by-design closure (below), each with its cell or triangle, its pose and its rest point; kind (flip, collapse or ok) is read off worst alone, and offenders are the targets that are not ok. The text prints one FOLD line per offender, worst first — flip (ratio ≤ 0) or collapse (under 0.1), deformer or part, its id, cell (col,row) or triangle i, the ratio (two significant figures under 0.01, so a flip never reads -0.00), the pose in brackets as its sorted id=value settings (rest for none), and where it sits at rest in model space and in model px from the top-left of modelCanvas (the layer PNGs' own pixels, not a render's) — or FOLDS: none over N poses. Then, when one folds, a FOLDS expected: line names the closures the auto-rig draws on purpose, each by part id, ratio and the parameter that shuts it: eye_L and lash_lower_L while ParamEyeLOpen is 0, eye_R and lash_lower_R while ParamEyeROpen is 0, and the lip set (mouth_inner, mouth_tongue, mouth_teeth, lip_lower, lip_upper) while ParamMouthOpenY is 0, read as each pose's effective value, its default included. The sweep covers the engine's pose set plus the caller's poses; a caller's pose is named in the FOLD lines by its sorted id=value settings (rest when empty), never by its PNG name, so a host reproduces any fold by setting those parameters on a player. Report-only: a fold never fails the render.

Usage

Run directly with npx (no install needed):

npx -y @ikijs/mcp

Or install globally and use the iki-mcp bin:

npm install -g @ikijs/mcp
iki-mcp

Claude Desktop / Claude Code / Codex config

Add to your MCP server config:

{ "mcpServers": { "iki": { "command": "npx", "args": ["-y", "@ikijs/mcp"] } } }

For Claude Desktop this goes in claude_desktop_config.json; for Claude Code it goes in .claude/mcp.json (or the equivalent per-project config). For Codex, register it from the shell:

codex mcp add iki -- npx -y @ikijs/mcp

Scope note

Current tools cover read/validate, auto-rigging a model from PNG layers (auto_rig_from_layers), composing/measuring the role layers a generated character needs (compose_layers_from_parts, measure_layers), measuring a rendered head turn against a reference one (measure_turn_reference), and rendering a rigged model's poses (render_poses). PSD input and granular model-mutation primitives (add part, bind parameter, export) are deferred to future slices.

License

MIT © Zeikar