blockbench-modeling — Claude Code skill: 3D models in Blockbench
A skill for building and editing 3D models in Blockbench through MCP tools: cubes, meshes, spheres, cylinders, face extrusion and groups.
npx skills add jasonjgardner/blockbench-mcp-project --skill blockbench-modeling -a claude-codeWhat blockbench-modeling does
The skill tells the agent how to build and edit geometry in Blockbench over MCP. Cubes take from and to coordinates, meshes use indexed faces, and there are tools for spheres, cylinders, extruding selected face regions, subdividing triangles and quads, welding vertices, and a headless knife for cubes. A separate set of tools handles hierarchy: add_group, list_outline, duplicate_element, find_elements_by_criteria, filter_by_material.
Before a big geometry redesign, the skill says to pick a priority — appearance, performance, or a balance of both — and set an element and face budget. Faces no required view or pose reveals are meant to be deleted, because a real-time mesh does not have to be watertight. Thin detail can float on planes instead of being cut into the surface when its depth stays fixed as the camera moves.
Meshes need a format with mesh support enabled, normally free (Generic Model), so call get_capabilities first. Extrusion only works on faces; edge and vertex modes are unsupported. The knife over MCP cuts cubes along one axis, and slice_cubes_to_block_grid splits cubes at Bedrock block boundaries. inspect_block_bounds checks elements against the 30×30×30 oversized block limit.
What it helps with
- block out a character silhouette from cubes with exact coordinates
- extrude mesh faces to raise a recessed detail
- start a round part from a sphere or cylinder mesh
- clean up model hierarchy with groups and renames
- check whether cubes exceed the Bedrock oversized block limit
Example prompts for the agent
- place a cube
bodyfrom [-4, 0, -2] to [4, 12, 2] with faces enabled - extrude the selected mesh faces outward by 2 units
- show the model outline and rename the group
arm_lefttoarm_l
How to install blockbench-modeling
1Skills CLI (Vercel)
npx skills add jasonjgardner/blockbench-mcp-project --skill blockbench-modeling -a claude-code2Manually: put the files in place
~/.claude/skills/blockbench-modeling/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/blockbench-modeling/
1Skills CLI (Vercel)
npx skills add jasonjgardner/blockbench-mcp-project --skill blockbench-modeling -a codex2Manually: put the files in place
.agents/skills/blockbench-modeling/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add jasonjgardner/blockbench-mcp-project --skill blockbench-modeling -a cursor2Manually: put the files in place
.cursor/skills/blockbench-modeling/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install jasonjgardner/blockbench-mcp-project blockbench-modeling2Manually: put the files in place
.github/skills/blockbench-modeling/SKILL.md1Skills CLI (Vercel)
npx skills add jasonjgardner/blockbench-mcp-project --skill blockbench-modeling -a gemini-cli2Platform CLI
gemini skills install https://github.com/jasonjgardner/blockbench-mcp-project --path skills/blockbench-modelingManually: put the files in place
.devin/skills/blockbench-modeling/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add jasonjgardner/blockbench-mcp-project --skill blockbench-modeling -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about blockbench-modeling
How do I install blockbench-modeling in Claude?
Run npx skills add jasonjgardner/blockbench-mcp-project --skill blockbench-modeling -a claude-code. The skill file then lands at ~/.claude/skills/blockbench-modeling/SKILL.md.
What does blockbench-modeling do?
It covers 3D modeling in Blockbench through MCP: placing cubes and meshes, creating spheres and cylinders, extruding faces, selecting and moving vertices, grouping elements, and checking block bounds.
What are the tool limitations?
Extrusion supports faces only — edge and vertex modes are unsupported. The knife over MCP does not cut from a point list, and knife_cut_cube splits a cube along a single axis.
Source text
SKILL.md · Apache-2.0
---
name: blockbench-modeling
description: Create and edit 3D models in Blockbench using MCP tools. Use when building geometry with cubes, creating meshes, placing spheres/cylinders, editing vertices, extruding faces, or organizing models with groups. Covers both cube-based Minecraft modeling and freeform mesh editing.
---
# Blockbench Modeling
Build 3D models using cubes and meshes in Blockbench.
## Plan the Shape and Target
Before a new model or substantial geometry redesign, resolve the user's appearance/accuracy, performance, or balanced preference through [Blockbench use](../blockbench-use/SKILL.md). Follow [appearance and performance planning](../blockbench-use/references/appearance-and-performance.md) to set a working element/face budget and choose which details belong in geometry, UV tiles, or PBR. Prototype and count one repeated section before multiplying it; braces, infills, foot plates, and fasteners should not automatically become separate cubes.
Spend faces on silhouette, parallax, and deformation. Delete faces no required view or pose reveals, since real-time meshes need not be watertight. Let parts intersect instead of stitching edge loops around the joint, and float thin detail planes instead of cutting detail in. Keep geometry for recesses and openings whose depth must change as the camera moves. See [real-time asset planning](../blockbench-use/references/real-time-asset-planning.md) sections 4–6 for per-target budgets and face-removal rules.
Use [format and delivery guidance](../blockbench-use/references/formats-and-delivery.md) when selecting a format or exporting. Block out the silhouette and proportions before UV layout and surface detail. Match polygon density to visible shape or deformation needs; automatic subdivision is not a quality step by itself.
For articulated parts, create a hierarchy with pivots at joints and unique, consistent bone names. Group origins are pivots, not translations added to a cube's `from`/`to` coordinates. Place cube bounds in the model's rest coordinate space; the hierarchy applies rotations around those pivots. Mesh vertices are mesh-local, as described below. Check overlapping surfaces for z-fighting and preview moving parts for hidden gaps. See the official [modeling overview](https://blockbench.net/wiki/guides/blockbench-overview-tips/).
When resizing or duplicating textured geometry at a different size, re-evaluate the face UV spans rather than blindly retaining the source rectangle. Follow [UV scale and distortion guidance](../blockbench-texturing/references/uv-scale-and-distortion.md) for side/end proportions, final scale transforms, and shared-material density. Correct mapping within the chosen geometry budget; additional cubes are not the default remedy for texture stretching.
## Available Tools
### Cube Tools
| Tool | Purpose |
|------|---------|
| `place_cube` | Create cubes with position, size, texture |
| `modify_cube` | Edit cube properties (position, rotation, UV, etc.) |
| `get_cube_uv` / `set_cube_uv` | Inspect/edit box or per-face cube UVs; see texturing skill |
### Mesh Tools
| Tool | Purpose |
|------|---------|
| `place_mesh` | Create meshes with indexed faces; returns UUIDs and runtime geometry keys |
| `get_mesh_info` | Inspect mesh-local positions, faces, normals, selection, textures, and optional UVs |
| `create_sphere` | Create sphere mesh |
| `create_cylinder` | Create cylinder mesh |
| `extrude_mesh` | Extrude selected face regions; edge/vertex modes are unsupported |
| `subdivide_mesh` | Subdivide selected triangles/quads into `cuts + 1` segments per edge |
| `select_mesh_elements` | Select vertices/edges/faces |
| `move_mesh_vertices` | Move selected vertices |
| `delete_mesh_elements` | Remove geometry |
| `merge_mesh_vertices` | Weld nearby vertices |
| `create_mesh_face` | Create face from vertices |
| `knife_tool` | Mesh point-list cutting is unsupported through MCP |
| `knife_cut_cube` | Headless Knife tool for cubes: split at positions along one axis |
| `slice_cubes_to_block_grid` | Cut cubes at Bedrock block boundaries and regroup per block |
| `inspect_block_bounds` | Check cubes/groups against the 30×30×30 oversized block limits |
### Element Tools
| Tool | Purpose |
|------|---------|
| `add_group` | Create bone/group |
| `list_outline` | View model hierarchy |
| `duplicate_element` | Copy elements |
| `rename_element` | Rename elements |
| `remove_element` | Delete elements |
| `find_elements_by_criteria` | Query elements by name pattern, type, parent, size |
| `select_all_of_type` | Bulk-select cubes, meshes, or groups |
| `filter_by_material` | Find elements referencing a texture |
## Cube Modeling
`place_cube` supports untextured blockout in the current source plugin; use `get_capabilities: include_tools=true` to identify the loaded build. A new texture can be created after the silhouette is established. Supplying a texture or group requires a valid existing reference. Use returned UUIDs for later edits; the literal `group="root"` or `add_group`'s `parent="root"` means outliner root. Name a real root bone `rig_root`, or use its UUID, to avoid that reserved target.
### Place a Cube
```
place_cube: elements=[{
name: "body",
from: [-4, 0, -2],
to: [4, 12, 2]
}], faces=true # Size-based Auto UV; this does not pack a texture atlas
```
### Place Multiple Cubes
```
place_cube: elements=[
{name: "head", from: [-4, 12, -4], to: [4, 20, 4]},
{name: "arm_left", from: [4, 4, -1], to: [6, 12, 1]},
{name: "arm_right", from: [-6, 4, -1], to: [-4, 12, 1]}
], group="body"
```
### Modify Cube
```
modify_cube: id="body", rotation=[0, 45, 0], origin=[0, 6, 0]
```
### Cube with Texture
```
place_cube: elements=[{name: "block", from: [0,0,0], to: [16,16,16]}],
texture="stone", faces=true
```
### Bedrock Poles, Cylinders, and Tubes
For round primitives in Bedrock cube models, read [Bedrock primitive construction](references/bedrock-primitives.md). It derives polygon side widths and shared-pivot rotations for solid and hollow shapes, following the Shape Generator plugin's octagon and 16-sided constructions. Choose facets within the appearance/performance budget, distinguish flat-to-flat from corner diameter, and verify overlapping end faces and UV scale. `create_cylinder` creates a mesh; use calculated `place_cube` batches when the target requires cubes.
## Mesh Modeling
Check `get_capabilities` before creating meshes. Use a registered format with `format.features.meshes=true`, normally `free` (Generic Model).
### Create a Mesh and Retain Its IDs
The JavaScript examples below are client orchestration pseudocode: `call(name, args)` invokes that MCP tool and checks for `isError`. For JSON results, it returns `structuredContent` or the decoded JSON text; for plain-text results, it returns the text unchanged. It is not code for `risky_eval`. Variables such as `panel.uuid` and `panel.face_keys[0]` must be replaced with their returned values in actual tool arguments, never sent as literal strings.
```js
const placed = await call("place_mesh", {
elements: [{
name: "panel",
vertices: [[0, 0, 0], [4, 0, 0], [4, 4, 0], [0, 4, 0]],
faces: [[0, 1, 2, 3]],
}],
});
const panel = placed.meshes[0];
// panel: {name, uuid, vertex_keys: [...], face_keys: [...]}
```
Input faces use zero-based vertex **indices**. Returned `vertex_keys` and `face_keys` follow input order and hold the actual runtime keys. Later selection, face creation, and UV tools need those keys. A mesh name or UUID identifies the mesh, not its vertices or faces. Retain the UUID to avoid ambiguous names. After topology edits or undo/redo, inspect again before reusing component keys.
### Inspect Geometry and Read Every Page
For primitives or existing meshes, obtain keys with `get_mesh_info`. Vertex items contain `{key, position, selected}`; face items contain `{key, vertices, normal, selected, texture}`. Positions, bounds, and normals are **mesh-local**, before origin, rotation, and parent transforms.
```js
async function readMeshPages(meshId, part, offset = 0) {
// part is "vertices" or "faces". Read one list independently.
const info = await call("get_mesh_info", {
mesh_id: meshId,
include_vertices: part === "vertices",
include_faces: part === "faces",
[part === "vertices" ? "vertex_offset" : "face_offset"]: offset,
limit: 500,
});
const page = info[part];
if (page.next_offset === null) return page.items;
return page.items.concat(await readMeshPages(meshId, part, page.next_offset));
}
```
Each list has its own `next_offset`; `null` means complete. The default limit is 100, maximum 500 per list. Finish paging before mutating geometry because keys are sorted and edits can invalidate offsets. Inspection is read-only and does not select components.
### Create Sphere
```
create_sphere: elements=[{
name: "ball",
position: [0, 8, 0],
diameter: 16,
sides: 12
}]
```
### Create Cylinder
```
create_cylinder: elements=[{
name: "pillar",
position: [0, 0, 0],
diameter: 8,
height: 24,
sides: 12,
capped: true
}]
```
### Extrude Face
For the unrotated, capped cylinder created above, select all cap triangles facing local +Y:
```js
const pillarFaces = await readMeshPages("pillar", "faces");
const capKeys = pillarFaces.filter(face => face.normal[1] > 0.99).map(face => face.key);
if (capKeys.length === 0) throw new Error("No upward cap faces found; inspect the mesh.");
await call("select_mesh_elements", {mesh_id: "pillar", mode: "face", elements: capKeys});
await call("extrude_mesh", {mesh_id: "pillar", mode: "faces", distance: 4});
```
### Subdivide for Detail
```
select_mesh_elements: mesh_id="ball", mode="face" # Select all faces explicitly
subdivide_mesh: mesh_id="ball", cuts=2
```
### Move Vertices
Continue from the `panel` creation example, before changing its topology:
```js
await call("select_mesh_elements", {
mesh_id: panel.uuid, mode: "vertex", elements: panel.vertex_keys.slice(2, 4),
});
await call("move_mesh_vertices", {mesh_id: panel.uuid, offset: [0, 2, 0]});
```
### Merge Close Vertices
```
merge_mesh_vertices: mesh_id="panel", threshold=0.1
```
### Knife Cut
The mesh Knife tool depends on interactive pointer state, so `knife_tool` returns an unsupported-operation error for point lists. For a scripted mesh cut, inspect with `get_mesh_info`, construct the intended replacement vertices/faces with `place_mesh`, verify them, then replace the original geometry within the user's requested scope. Use Blockbench's interactive Knife tool when the user prefers to cut manually.
Cubes can be cut headlessly. `knife_cut_cube` mirrors Blockbench's Knife on cubes: each cut plane is perpendicular to one axis, the original keeps the lower piece, new pieces get unique names (`pole_2`, `pole_3`), and face UVs are shared proportionally. One undo entry covers all cuts.
```
knife_cut_cube({ cubes: ["pole"], axis: "y", positions: [16, 32, 48] })
```
### Oversized Bedrock Blocks
A Bedrock custom block's geometry must fit a 30×30×30 box whose center may sit at most 7 units from the block center, which Blockbench enforces as x/z within ±22 and y within -14…30 (Microsoft's page states the limit more loosely: https://learn.microsoft.com/minecraft/creator/documents/customblockoversized). Larger models must be divided into per-block sections.
1. `inspect_block_bounds` reports model, group, and cube extents against those limits, the block cell each cube sits in, and the grid cut positions that would split it.
2. `slice_cubes_to_block_grid` cuts every cube where it crosses a block boundary (x/z boundaries at ±8, ±24, …; y at 0, 16, 32, …) and, by default, moves the pieces into one group per block cell (`bottom`, `top`, `right_top_front`, `top2`, …) pivoted at that block's origin. Cubes rotated about the other two axes are left uncut and listed in `skipped_rotated`.
3. Export each cell group as its own block geometry, or slice manually with `knife_cut_cube` when the automatic grid is not what the model needs.
```
inspect_block_bounds({})
slice_cubes_to_block_grid({ regroup: true, group_prefix: "goal_" })
```
## Organization
### Create Group Hierarchy
```
add_group: name="rig_root", origin=[0, 0, 0], rotation=[0, 0, 0]
add_group: name="body", parent="rig_root", origin=[0, 12, 0]
add_group: name="head", parent="body", origin=[0, 24, 0]
```
### Add Cubes to Groups
```
place_cube: elements=[{name: "torso", from: [-4, 12, -2], to: [4, 24, 2]}],
group="body"
```
### Duplicate Element
```
duplicate_element: id="arm_left", newName="arm_right", offset=[-8, 0, 0]
```
### View Hierarchy
```
list_outline # Returns all groups and elements
```
## Selection & Filtering
Query the model without loading the full outline. These tools are read-only except `select_all_of_type`.
### Find Elements by Criteria
Combine any of: regex name match, substring match, type, parent-group scope, cube size bounds, selection scope.
```
# All cubes under "body" named like "arm_*"
find_elements_by_criteria: type="cube", parent_group="body", name_pattern="^arm_"
# Small cubes (under 4 units on any axis) in the currently selected elements
find_elements_by_criteria: selected_only=true, max_size=[4, 4, 4]
# Groups whose name contains "hand" (case-insensitive)
find_elements_by_criteria: type="group", name_contains="hand"
```
Returns `{ count, truncated, matches: [{ uuid, name, type, parent }] }`.
### Select All of Type
```
# Replace selection with every cube in the project
select_all_of_type: type="cube"
# Add all meshes under "head" to the current selection
select_all_of_type: type="mesh", parent_group="head", add_to_selection=true
```
### Filter by Material
Find every cube or mesh that references a specific texture. For cubes, the exact face keys are returned.
```
filter_by_material: texture="skin"
# → { texture, count, matches: [{ uuid, name, type: "cube", faces: ["north", "up"] }] }
```
Useful when refactoring textures: find all users before swapping or retiring a texture.
## Common Patterns
### Minecraft Character
This example assumes a cube format with a bone rig and a 32-unit-tall rest pose. It does not prescribe a player-skin template. The cube bounds already include their location in the character; the group pivot does not reposition them.
```
# Create hierarchy
add_group: name="rig_root", origin=[0, 0, 0]
add_group: name="body", parent="rig_root", origin=[0, 24, 0]
add_group: name="head", parent="body", origin=[0, 24, 0]
add_group: name="arm_left", parent="body", origin=[5, 22, 0]
add_group: name="arm_right", parent="body", origin=[-5, 22, 0]
add_group: name="leg_left", parent="rig_root", origin=[2, 12, 0]
add_group: name="leg_right", parent="rig_root", origin=[-2, 12, 0]
# Add geometry
place_cube: elements=[{name: "head_geo", from: [-4, 24, -4], to: [4, 32, 4]}], group="head"
place_cube: elements=[{name: "body_geo", from: [-4, 12, -2], to: [4, 24, 2]}], group="body"
place_cube: elements=[{name: "arm_left_geo", from: [4, 12, -1], to: [6, 22, 1]}], group="arm_left"
place_cube: elements=[{name: "arm_right_geo", from: [-6, 12, -1], to: [-4, 22, 1]}], group="arm_right"
place_cube: elements=[{name: "leg_left_geo", from: [0, 0, -2], to: [4, 12, 2]}], group="leg_left"
place_cube: elements=[{name: "leg_right_geo", from: [-4, 0, -2], to: [0, 12, 2]}], group="leg_right"
```
### Smooth Organic Shape
Only subdivide when the requested shape needs the extra vertices. Subdivision adds topology; it does not by itself smooth the silhouette.
```js
await call("create_sphere", {
elements: [{name: "base", position: [0, 8, 0], diameter: 16, sides: 16}],
});
await call("select_mesh_elements", {mesh_id: "base", mode: "face"});
await call("subdivide_mesh", {mesh_id: "base", cuts: 1});
const vertices = await readMeshPages("base", "vertices");
const upperKeys = vertices.filter(vertex => vertex.position[1] > 0).map(vertex => vertex.key);
await call("move_mesh_vertices", {mesh_id: "base", offset: [0, 4, 0], vertices: upperKeys});
```
The predicate uses local Y, so `> 0` selects the upper half of this sphere even though its origin is at world Y=8. Use a predicate appropriate to the inspected geometry for other shapes.
## Tips
- Use `list_outline` to see current model structure
- Use `find_elements_by_criteria` for targeted queries instead of filtering `list_outline` results client-side
- Set group origins at joint/pivot points for animation
- Use `faces=true` for size-based cube UVs; inspect and arrange the UVs before detailed painting
- Create bone hierarchy before adding geometry
- `duplicate_element` with an offset creates a translated copy, not a mirrored shape or mirrored UV layout
- Mesh editing is more flexible but cubes are simpler for Minecraft-style models
- For a substantial rework, a `save_checkpoint` history marker can help recovery; inspect actual history entries before `undo`
© jasonjgardner · Apache-2.0 · jasonjgardner/blockbench-mcp-project