Public leaderboard

Public assessment

musepy/genable (genable-mcp)

genable-mcp · v1.0.0 · scanned

What changed in the harness

Selection accuracy 98→100, token cost down 5%, unconfirmed writes 0%→0%.

Category breakdown

Where the score comes from.

Earned points across the four signals Gradable measures. Safety and Legibility are scored out of 30; Economics and Discoverability are scored out of 20.

01Safety

0.0 / 30

0.0 out of 30
02Legibility

23.9 / 30

23.9 out of 30
03Economics

7.1 / 20

7.1 out of 20
04Discoverability

11.2 / 20

11.2 out of 20

Highest-impact fix

Estimated gain +30 points

Add explicit identity and permission preflight tools

Expose machine-readable principal/tenant confirmation and a non-mutating permission check so agents can verify both before destructive actions.

Description evidence

Defects and rewrites.

10 defects found across the exposed tool descriptions. Suggested rewrites make purpose, inputs, boundaries, and returns easier for an agent to understand.

Tool Defect types Suggested rewrite
jsx
no_return_description
Create design trees with nested JSX markup. One jsx call builds a complete subtree atomically — nesting is the hierarchy. Keep a single logical unit inside one call; the returned root's children are already built, not stubs to be filled in later. Supports elements frame, text, rect, ellipse, line, icon, image, instance, component, group, section, vector, with layout/fill/effect/typography/grid attributes and rich-text markup as documented in examples. Pass `parent` to append under an existing node, or `replaceId` to atomically swap an existing subtree at the same parent and sibling index (markup must have a single root). Returns the created or replaced root node's id, name, and type, along with its fully built child subtree.
move_node
no_return_description
Relocate a node without recreating it. Preserves IDs, bound variables, and component instances across the move, so callers tracking the node by ID never need to re-discover it. Use for: (a) changing child order within a container, (b) moving a subtree into a different parent, (c) fixing a placement mistake after jsx, (d) renaming a node in place via `name`. Returns the node's id, name, and its resulting parent and sibling index after the move/rename. Examples: move_node({node: "1:3", name: "NewTitle"}) — rename in place move_node({node: "1:3", parent: "1:4"}) — move into parent 1:4 move_node({node: "1:5", index: 0}) — reorder within current parent
clone_node
no_return_description
Deep-copy a node with optional property overrides. Returns the new clone's id, name, and parent so it can be referenced immediately without a follow-up lookup. Examples: clone_node({node: "1:2"}) — clone to page root, same name clone_node({node: "1:2", parent: "/"}) — clone to page root explicitly clone_node({node: "1:2", parent: "/", name: "Hero Copy"}) — clone to root with custom name clone_node({node: "1:2", parent: "1:4"}) — clone into parent node 1:4 clone_node({node: "1:2", parent: "1:4", overrides: {"bg": "#D9D9D9"}})
set_variable_mode
no_return_description
Set a node to use a specific mode of a variable collection. This controls which variable values the node displays — for example, set a frame to use "Dark" mode of the "Theme" collection so all bound variables on it and its descendants show dark values. Returns { data: { ok: true } } confirming the node's mode selection for that collection was updated. Examples: set_variable_mode({node: "1:2", collection: "VariableCollectionId:1:2", mode: "1:1"}) set_variable_mode({node: "1:5", collection: "VariableCollectionId:1:3", mode: "1:2"})
add_component_prop
params_unexplained
Add a component property to a COMPONENT or COMPONENT_SET. Four types: TEXT — overridable string per instance (button label). Auto-binds to a text child via "bind". BOOLEAN — show/hide a layer per instance (icon toggle). Auto-binds to a node's visibility. INSTANCE_SWAP — swap a nested instance per instance (icon family, avatar). Auto-binds to an instance child. Optional "preferredValues" suggests swap candidates. SLOT — open slot filled at instance use site (composable Card body, Modal content). Optional "description" hints what belongs in the slot; optional "preferredValues" suggests content. No "bind" (SLOT is not tied to one child). VARIANT axes (Size, State, Theme) are NOT added here. Figma derives variant axes from the names of the variant components — create one COMPONENT per combination and use combine_components with a `variants` mapping. Calling this tool with type:"VARIANT" returns a redirect; `variantOptions` (list of option strings) is only meaningful on that redirected VARIANT path and is otherwise ignored. Auto-binding (TEXT/BOOLEAN/INSTANCE_SWAP): if "bind" omitted, the handler walks the component's children to match by name. SLOT skips binding (filled by consumers). preferredValues is `{type, key}[]` where `type` is "COMPONENT" or "COMPONENT_SET" and `key` is the PUBLISHED component key (component.key) — NOT a scene-graph node id. Returns: { data: { message, nodeId, property, bound: boolean } }. `bound` is false if auto-binding couldn't find a matching child — the property is still created, but instances won't override anything until you rerun with explicit "bind". Examples: add_component_prop({node: "1:2", name: "Label", type: "TEXT", default: "Click me", bind: "1:5"}) add_component_prop({node: "1:2", name: "Show Icon", type: "BOOLEAN", default: "true"}) add_component_prop({node: "1:2", name: "Icon", type: "INSTANCE_SWAP", default: "1:99", preferredValues: [{type: "COMPONENT", key: "abc123"}]}) add_component_prop({node: "1:2", name: "Footer", type: "SLOT", description: "Replace card footer"})
set_fill
no_return_description
Set fill or background color on a node. `fill` = text color or shape fill. `bg` = frame background. For stroke color, use `set_stroke`. Each call (single or batch item) needs `node` plus at least one of `fill`/`bg`. Batch by passing `nodes: [{node, fill?, bg?}, ...]`. Accepted color formats (for fill or bg): hex "#FFF", "#F5F5F5" gradient string CSS-like subset, not full CSS: "linear-gradient(<angle>deg, <#hex> <pos>%, ...)" "linear-gradient(to <direction>, ...)" directions: top/right/bottom/left + corners "radial-gradient(<stops>)" centered, no position/shape modifiers "radial-gradient(circle, <stops>)" circle shape only "conic-gradient(from <angle>deg, ...)" Rejected: "circle at X% Y%", "ellipse at ...", named colors, hsl(). variable token qualified bare name "$Surface/Card" transparent "transparent" (bg only) Returns, per node: { data: { id, name, type, applied?, noop?, rejected? } } (single) or { data: { count, results, errors?, partial? } } (batch) — `applied` lists the fill/bg keys written, `noop` lists keys already at that value, `rejected` lists keys the node type can't accept.
set_stroke
no_return_description
Set stroke (border) on a node. set_stroke({node: "1:2", stroke: "1 #E0E0E0"}) set_stroke({node: "1:2", stroke: "2 #333 inside"}) set_stroke({node: "1:2", color: "#E0E0E0", weight: 1, align: "inside"}) set_stroke({node: "1:2", color: "linear-gradient(90deg, #8B5CF6 0%, #F97316 100%)", weight: 1.5, align: "inside"}) // Batch — bulk stroke update in one call: set_stroke({nodes: [{node: "1:2", color: "#E0E0E0", weight: 1}, {node: "1:3", color: "#333", weight: 2}]}) Shorthand: "weight color align" (e.g. "1 #E0E0E0 inside"). Hex only in shorthand. Accepted color formats (for the explicit `color` field, not the shorthand): hex "#E0E0E0" gradient string CSS-like subset (see set_fill description for full grammar — same rules). Common: "linear-gradient(<angle>deg, <#hex> <pos>%, ...)", "radial-gradient(<stops>)". Rejected: "circle at X% Y%", named colors, hsl(). variable token qualified bare name "$Border/Default" To bind a variable to the stroke color, use the explicit `color` field — the shorthand parser silently drops bare-name tokens. Returns, per node: { data: { id, name, type, applied?, noop?, rejected? } } (single) or { data: { count, results, errors?, partial? } } (batch), listing which stroke properties were applied, already matched (noop), or rejected as invalid for the node type.
create_vector
params_unexplained no_return_description
Create a vector node from SVG path data or a list of points. Use for chart lines, custom icon paths, freeform curves, or any shape that needs path data. Examples: // Polyline (chart trend line) create_vector({ parent: "1:23", name: "TrendLine", x: 40, y: 20, width: 550, height: 240, points: [[0,144],[90,96],[180,120],[270,64],[360,80],[450,40],[540,72]], stroke: "#6366F1", strokeWeight: 2 }) // Raw SVG path (custom shape) create_vector({ parent: "1:23", name: "Wave", width: 200, height: 60, data: "M 0 30 Q 50 0 100 30 T 200 30", stroke: "linear-gradient(90deg, #8B5CF6 0%, #F97316 100%)", strokeWeight: 1.5 }) Path input — provide ONE of: points: [[x,y], ...] compiled to "M x0 y0 L x1 y1 ..." (polyline shortcut) data: "M ... L ..." raw SVG path (LLM-native; supports M, L, C, Q, A, Z) `windingRule` sets the SVG fill-rule for self-intersecting or overlapping path regions: "NONZERO" (default, fills based on path direction) or "EVENODD" (fills based on crossing count) — only matters when the path is filled (not just stroked) and self-overlaps. Stroke / fill (same formats as set_stroke / set_fill): hex "#6366F1" gradient "linear-gradient(angle, #color stop%, ...)" variable qualified bare name "$Brand/Primary" Default fill is "transparent" so the vector shows only its stroke. Pass an explicit fill if you want it filled. Returns the created vector node's id, name, and type. When NOT to use: - Standard rectangles / ellipses / lines — use jsx <Rect/>, <Ellipse/>, <Line/> elements (simpler, batch-friendly) - Existing vector edits — use edit / set_stroke instead
reconcile_preview
no_return_description
READ-ONLY reconcile dry-run. Given an existing subtree (scope) and a target JSX (markup), computes the minimal create/update/delete/move plan that would turn the live subtree into the target — preserving the id of every matched node — and returns that plan as a list of per-node operations (create/update/delete/move, with the affected node id and the props/position that would change) without touching the canvas. Use it to preview how a declarative edit would be reconciled. Workflow: read_jsx({node}) to get the current subtree → edit that JSX → reconcile_preview({scope: same id, markup: your edited JSX}). Nothing is applied.
reconcile_apply
no_return_description
Apply a declarative edit to an existing subtree, PRESERVING the id of every node you keep (so variable bindings, instance overrides and prototype links survive) — the id-preserving alternative to delete+rebuild. Workflow: read_jsx({node}) → edit that JSX (keep id= on kept nodes, drop id= on new ones, re-nest freely) → reconcile_apply({scope: same id, markup: edited JSX}). Matching is by explicit id only: an id= node is updated/moved in place (re-parenting = move, id kept); a node with no id is created. Props are a PATCH — only props in the markup are written, absent props are left unchanged (never cleared). Existing nodes not in the markup are KEPT unless allowDelete:true. Preview with reconcile_preview first to see the plan. Returns a summary of the applied changes — which node ids were created, updated, moved, and (if allowDelete was true) deleted.

Selection evidence

Confusable tool pairs.

17 pairs where similar names or overlapping descriptions may send an agent toward the wrong tool.

Tool A Tool B Confidence Why they collide
jsx edit_jsx medium Both operate on JSX text for a subtree; a task like 'update this button's rounded corners' could plausibly be attempted with jsx's replaceId instead of the intended read_jsx+edit_jsx patch flow, though jsx is scoped to creation/replace and edit_jsx to in-place property patches.
jsx read_jsx low Same JSX dialect and naming, but one creates/replaces trees and the other only reads — a request to 'see the JSX for this frame' is unlikely to be misrouted to the write tool.
read_jsx edit_jsx low They're a mandated read-then-write pair by design (read_jsx output feeds edit_jsx), so an agent might call read_jsx alone when asked to 'edit' something, but the tools' purposes are clearly distinct (read vs write).
edit edit_jsx medium Both are generic property-update tools named 'edit*'; a task like 'change this node's corner radius and background' could be routed to either edit (direct prop patch) or edit_jsx (text-based patch via read_jsx), especially since edit's description explicitly contrasts itself with edit_jsx for tree-level edits.
add_component_prop edit_component_prop medium A task like 'update the Label property's default value' could be misrouted to add_component_prop instead of edit_component_prop if the agent doesn't realize the prop already exists, since both configure component properties with overlapping parameter names (name, default).
add_component_prop delete_component_prop low Opposite operations (add vs remove) on the same prop concept are unlikely to be confused given clear verb semantics, despite shared component/prop vocabulary.
add_component_prop list_component_props low list is read-only discovery vs add is a mutation; a vague 'what properties does this have' vs 'add a property' are distinguishable asks, low ambiguity despite shared tokens.
edit_component_prop delete_component_prop low Rename/default-change vs full removal are semantically distinct verbs, unlikely to be confused despite operating on the same target type.
list_component_props delete_component_prop low One lists, one deletes — different verbs make confusion unlikely despite shared component/prop tokens.
list_component_props edit_component_prop low Discovery vs mutation are clearly different intents; low risk of an agent picking the wrong one.
set_variable_value set_variable_mode medium Both take a variable/node + mode + 'set' semantics; a task like 'set this to dark mode' could be misrouted to set_variable_value (which sets the value FOR a mode) instead of set_variable_mode (which sets WHICH mode a node uses), since the phrasing 'set mode' is ambiguous between the two tools.
create_collection create_variable medium A request like 'create a Theme variable with Light/Dark values' could be ambiguous about whether to create the collection or the variable first, and an agent might call create_variable expecting it to also create the collection since both are idempotent find-or-create with overlapping 'collection'/'variable' naming.
delete_collection delete_variable medium A task like 'delete the Theme colors' could target either the whole collection or a single variable within it; both cascade-unbind similarly, so the agent must correctly infer scope from user phrasing.
bind_variable set_variable_mode medium Both take node+variable/collection+mode-like args and relate to making a node reflect a variable; a task like 'make this node use the Dark theme variable' could be misrouted to bind_variable when set_variable_mode is actually needed to switch which mode a node displays.
set_fill set_stroke low Both set node color properties with similar signatures, but 'fill/background' vs 'border/stroke' are distinct enough in common usage that confusion is unlikely for a clear task.
discover_props replace_props low One is read-only audit, the other is destructive bulk-write; distinguishable by intent (survey vs replace) despite shared subtree/prop vocabulary.
create_component combine_components low create_component converts a single frame; combine_components merges multiple components into a variant set — different cardinality and purpose make confusion unlikely despite shared 'component' token.

Compare the field

One score is useful.
The evidence makes it actionable.

Back to the leaderboard