Files
Emil 89c53d7058 Expand material support and checkpoint the completed building toolkit
Expose the runtime block/item registry through compact material search and
single-material descriptions. Preserve private block-entity data through
checked edits and durable undo without sending payloads to model context.

Include the completed terrain tools, isolated world/map plugin, station lift,
ACP streaming and guidance fixes, local camera auto-connect, construction
scripts, and their public documentation, references and verification records.
Active station decoration and private runtime data remain outside this commit.

Validation: 145 Maven tests, 47 Bridge tests, successful camera Gradle build,
and isolated Paper verification of all 1,196 block defaults plus 5,392
independent property cases for placement, same-material edits and restoration.
2026-09-13 16:59:13 +03:00

16 KiB
Raw Permalink Blame History

Prototype protocol

Version: 1. The exact implemented tool catalog is available through MCP tools/list, and the capabilities of the installed Paper plugin through project_context.

Paper connection

The service listens on loopback only, at 127.0.0.1:8765 by default. GET /health returns status and version information without private data. POST /v1/rpc accepts JSON and an Authorization: Bearer <token> header.

Request: { "method": "project_context", "params": { "player_id": "UUID", "project_id": "default" }, "requestId": "correlation-id" }.

Success: { "ok": true, "result": { ... } }. Error: { "ok": false, "error": { "code": "...", "message": "..." } }. An error may come with HTTP 400/403; clients must read the structured body. Tokens are never returned in results.

The administrator key is required for chat_poll, chat_reply, recovery_review, and recovery_abandon. The agent key permits world tools within the bound owner's project, but not administrative recovery. The MCP process injects player_id/project_id from its configuration; the model is not asked to supply them. The plugin rechecks the owner and their current permissions. An isolated test world can explicitly enable allow-local-automation with the console principal; administrative recovery operations also pass through this scope check.

Material discovery

project_context.material_catalog contains {version, materials, blocks, items, search_default_limit, search_max_limit}. Block and item counts overlap for materials that have both forms. This replaces the expanded supported_materials list; no material registry or property list is sent in project context.

material_search and material_describe are read-only, idempotent MCP tools available within the same authorized project scope. material_search accepts optional query (at most 96 printable characters), kind: "block" | "item" | "all" (default block), limit: 1..32 (default 16), and an opaque cursor of at most 100 characters. Search matches all whitespace-separated query tokens against material IDs, case-insensitively. It returns {catalog_version, query, kind, total, results: [{id, block, item}], next_cursor?}. A cursor belongs to its catalog version and normalized query/kind; changed filters or stale catalogs require a new search. Empty queries remain paginated.

material_describe accepts {id: "minecraft:material_name"} with a maximum ID length of 128 characters. It returns {catalog_version, id, block, item, placeable, default_state?, properties?, behavior?}. Block entries include the full default state and properties: {name: [allowed_value, ...]}; the response never enumerates the Cartesian product of all property combinations. Optional behavior hints identify features such as gravity, fluids, attachment, multiple parts or block-entity data. Item-only entries return placeable: false without block states. Direct RPC also accepts an ID without the minecraft: prefix; MCP requires the explicit namespace. Unknown IDs or unavailable property introspection fail explicitly.

Callers should search a focused family and describe only 13 selected materials, reusing those results while the catalog version remains unchanged. See MATERIALS.md for the building workflow and behavior limits.

Reading and writing

region_inspect requires min/max as {x,y,z} and detail: "summary" | "blocks". Coordinates are inclusive integers. The prototype limit is 4096 positions. The response contains a palette with counts and, for blocks, exact {pos, state, snapshot_id?} entries. Block entities include a lowercase 64-character SHA-256 snapshot_id covering their captured data; NBT and inventory contents are not returned to the model. Data comes from loaded chunks without implicit world generation.

build_prepare accepts recipe: {version: 1, operations: [...]}, an optional dependencies array, and an optional part_id for an exact part mask. Geometry: box(min,max,block,hollow?), line(from,to,block), cylinder(center,radius,height,block,hollow?), repeat(count,offset,operations). This is a JSON description, not executable JavaScript.

Ordinary recipes accept registered vanilla block IDs and their valid properties as minecraft:block[property=value,...], at most 1024 characters. Omitted properties use the runtime defaults; the server validates and canonicalizes the result. Item-only materials, unknown IDs/properties/values and raw NBT are rejected. Same-material state edits preserve existing block-entity data; a newly created block entity uses defaults. There is no inventory, sign-text or entity-data editor.

Callers planning from an earlier survey can supply expected_blocks: [{pos: {x,y,z}, state: "minecraft:grass_block", snapshot_id?: "..."}, ...]. When present, the array must cover every compiled desired position exactly once (maximum 4096). Copy each inspected snapshot_id unchanged: it is required for existing block entities, including those with default or empty data. The server checks these original states and digests in the same main-thread task that prepares the plan, returning stale_snapshot if any have changed; a required digest that was omitted is an invalid request. Without expected_blocks, preparation captures the current full state itself. project_context.checked_expected_blocks advertises support. This closes the caller-inspection-to-preparation gap; the usual comparison before applying still protects the prepared plan.

Preparation returns plan_id, plan_hash, changed_blocks, region, and expires_at. The full plan and original blocks stay in the journal. build_apply accepts this ID, hash, and an idempotency_key that remains constant for that logical call. Repeating the same call returns the same operation. A different plan requires a different key.

operation_status(operation_id) returns status, counters, and a bounded sample of conflicts. operation_cancel stops subsequent slices. operation_undo_prepare creates a reverse plan; it goes through the normal build_apply and checks. Losing the response to an apply request does not justify creating a different key and repeating the write: first check project_context or repeat the original call with the same key.

part_define(name,operation_id) registers only blocks actually written by a completed operation. part_get(part_id) returns the name, bounds, count, and protection settings. Bounds are not the mask: build_prepare(part_id) checks every block against the exact mask. Extensions are created as separate parts.

Before a write, the actual states of target blocks and declared dependencies are checked; the check runs again after the intent is persisted. A mismatch is never overwritten automatically. Undo also accounts for known subsequent writes by our operations, even when a block's value again matches the earlier result.

Multi-block structures require explicit states for every part: placing a door's lower half does not construct its upper half. Support and attachment constraints remain the caller's design responsibility. Fluids, gravity, random ticks, plant growth and redstone may cause later game changes. The journal and checked undo cover the direct operation's recorded writes, not every resulting simulation effect.

The plugin observes uncancelled BlockPlaceEvent and BlockBreakEvent events in the configured world. Such an event revokes the earlier operation's right to undo that block, including an edit followed by a change back to the original state (ABA). These notifications are held only in the current process's memory. After a restart, and for external paths without an observed event, current-content checks remain in place; a complete history of other plugins' actions is not promised.

A full server-side journal of external changes is not implemented yet. region_changes returns resync_required; the agent uses fresh, bounded reads. This is an explicit prototype limitation.

Crash recovery

After a restart, an unfinished operation receives recovery_required and prevents new writes from starting. Unfinished slices are not replayed automatically. The administrative RPCs below are not exposed as agent MCP tools and do not roll back the world.

recovery_review accepts operation_id. Its result uses the Java record's field names: operationId, planId, positions, matchesBefore, matchesAfter, foreignStates, currentDigest, sampledAtMillis. The reviewed mask combines confirmed writes from earlier slices with potential writes from the unfinished slice; skipped positions that were not written are excluded. The counters describe how current values match before/after, while currentDigest is the SHA-256 digest of this snapshot. Matching content does not prove who made a change.

recovery_abandon accepts operation_id and expected_digest, taken from the latest review's currentDigest. The server rereads the mask and rejects the request if its contents have changed. If they match, it leaves all world blocks in place, changes the operation to failed, and persists the decision before returning success. The response has the usual operation_status shape.

Abandoning uncertain history permanently disables undo for that operation and preserves the invalidation of earlier undo ownership for blocks in the affected mask. New writes are permitted only after the decisions for all unfinished operations have been durably saved. A failure before persistence leaves recovery required; a lost response requires checking operation_status, not assuming that a rollback occurred. A stale or incorrect digest is returned as an invalid_request RPC error with a reason.

The applied status confirms the write and verification result observed by the running server. Chunk files and the journal do not form a shared transaction; automatic verification that all previously completed operations survived a crash is not implemented yet.

Local schematics

The implemented RPC names are asset_list, schematic_export, and schematic_import_prepare. There are no separate schematic_list or immediate schematic_import routes. Check project_context for these capabilities.

asset_list accepts an optional query for case-insensitive name search and returns { "assets": [...] }. Each entry contains assetId, name, width, height, length, blockCount, dataVersion, offset, sha256, and bytes. The catalog holds up to 64 files; every file is validated when read, so a corrupt schematic may cause the entire list request to fail.

schematic_export accepts name, inclusive min/max, and an optional origin. The name contains 164 printable characters. origin defines the schematic's anchor point and defaults to min. The server reads a dense rectangular region, including air, within the current project area and the max_plan_blocks limit (at most 4096). Registered block states are accepted. Block entities, including empty ones, and entities other than players cause rejection; their data is never silently discarded, and players are not written to the schematic. The result is one object with the same metadata fields as asset_list. The file remains in the schematics subdirectory of the plugin's data directory; the RPC returns neither its contents nor an arbitrary path.

schematic_import_prepare accepts asset_id, target: {x,y,z}, and an optional rotation: 0 | 90 | 180 | 270 (default 0). Rotation is clockwise when viewed from above, around the target anchor; the saved offset is taken into account. Block-state orientation uses the runtime rotation behavior, including directional and axis properties. The result is the usual plan_id/plan_hash/changed_blocks/region/expires_at; writing requires a separate build_apply. Air in the schematic is part of the plan and can remove existing blocks. The area, original contents, surroundings, block policy, and conflicts are checked through the normal preparation and application path. State-only palette entries for block-entity block types use the same new-default or same-material-preservation rules as ordinary recipes.

A limited subset of Sponge Schematic v2 is supported: gzip and NBT with a palette of registered vanilla block states. Limits are 4096 positions, a 1 MiB compressed file, and 4 MiB of decompressed NBT. Entity and block-entity NBT payloads, biomes, unknown top-level fields, required mods, and other format versions are rejected. A DataVersion newer than the current server is not accepted; there is no DataFixer conversion. Full compatibility with every WorldEdit schematic is not claimed.

An external .schem can be placed in the local schematic directory in advance with a name matching [A-Za-z0-9][A-Za-z0-9_-]{0,63}.schem; its filename stem then serves as asset_id. Arbitrary paths, network URLs, and symbolic links are not accepted. The prototype does not support file uploads through RPC.

Chat

chat_poll accepts a client_id that remains stable throughout the Bridge process's lifetime. The response is messages with id, playerId, projectId, text, type, and position and targeted-block information when available. type is either prompt or cancel. The administrator key is required.

chat_reply accepts id, playerId, text, done, and an optional error. The response is routed only to the initiator of the original request. When the Bridge process changes, unfinished requests are not replayed automatically: the plugin stops changes and asks the user to inspect the world. /ai stop pauses new writes regardless of whether the ACP turn has finished.

Camera

camera_capture accepts camera_id or pose: {x,y,z,yaw,pitch,fov?,width?,height?}. The position refers to the observer's feet; the Worker returns eye coordinates separately. after_operation_id checks that the server operation has completed, but does not guarantee that the client has received all of its packets.

Paper serializes requests, moves the configured spectator observer, and sends a request to the local Camera Worker at 127.0.0.1:8766 with a separate key. A pending response contains captureId; poll by calling camera_capture with capture_id. A completed result contains imageBase64 and mimeType, which the Bridge converts into MCP image content rather than a textual base64 string.

Captures include a heuristic assessment of chunk/frame readiness. serverRevisionVerified: false remains in place until strict client acknowledgement is implemented. A missing camera, a timeout, or failure to obtain a fresh frame never counts as visual success.

Worker details: camera-mod/README.md. Threading and journal: world-core/README.md. ACP and isolation: bridge/README.md.

Terrain

terrain_preview(recipe,resolution?) renders and caches a bounded deterministic height-field recipe without reading or writing world blocks. It returns a native PNG through the Bridge, a content-addressed terrain_id, tile layout and sampled statistics. The cache holds 32 recipes until restart. terrain_prepare(terrain_id,tile_index) prepares one bounded tile against live natural terrain and returns either the usual immutable plan summary plus tile_bounds, or status: empty with zero changes. Existing build_apply, cancellation, conflict checks and checked undo apply unchanged. The terrain language, limits, preserve masks and local batch workflow are specified in TERRAFORMING.md. Preview output is explicitly marked world_verified: false.

terrain_brush_prepare(brush) prepares a relative edit of an existing live surface. Its bounded scan is stored as read dependencies (up to 4096), and the response combines a native before/after/delta image with a normal plan summary. plan_state: empty has no plan ID. Actions are raise, lower, flatten and smooth. Input, scan-window semantics and limitations are documented in TERRAFORMING.md.