Summary
The MCP tools work well for humans/developers, but several aspects diverge from agent-tool-design best practices described in Anthropic's Writing effective tools for AI agents and the broader ACI (Agent–Computer Interface) philosophy. This is an umbrella enhancement issue tracking improvements that would make the tools more "ergonomic" for LLM agents, reduce token usage, and lower error/hallucination rates.
Motivation
Tools are a contract between deterministic systems and non-deterministic agents. Designing them like ordinary developer APIs (raw UUIDs, unbounded lists, terse specs) increases the chance agents call the wrong tool, pass wrong parameters, or waste context. The items below map directly to the article's five principles.
Proposed Enhancements
1. Token efficiency for list tools (pagination / truncation)
list_entities and list_assets have no limit/offset and no truncation guard. With full=true they can dump the complete JSON of every entity/asset, easily blowing the context window on large scenes. Only store_search currently paginates (skip/limit).
Proposal: add limit (sensible default, e.g. 50) + offset to all list_* tools, cap response size, and when truncated, return a short hint steering the agent toward narrower filtered queries. Pagination must be explicit in the response (_has_more, _next_cursor) — agents do not infer "there is more data" from result counts. (See item11 for where this metadata lives.)
2. Reduce reliance on raw UUIDs (meaningful context)
Entities are addressed exclusively by UUID (EntityIdSchema = z.string().uuid()), and list summaries return parent as a UUID too. The article specifically calls out UUIDs as an anti-pattern and recommends resolving them to semantic/human-readable identifiers.
Proposal: include a human-readable hierarchy path (e.g. Root/Player/Camera) in summaries, and/or support locating entities by name (e.g. a resolve capability) so agents can drive follow-up calls without manually shuttling UUIDs.
3. Treat empty results as success, not error
entities:list / assets:list return { error: 'No entities found' } for an empty match, which surfaces as isError: true. An empty set is a valid result; flagging it as an error misleads agents into thinking the call failed and triggers needless retries. Return an empty array instead. (This falls out naturally once the unified envelope in item11 is adopted: { data: [], meta: { _status: "ok", total: 0 } }.)
4. Close tool asymmetries / consolidate workflows
- Materials can be fully created (
MaterialSchema) but only diffuse can be modified (set_material_diffuse). Add a generic modify_asset / set_material_properties.
add_components (ComponentsSchema) supports only 10 component types, while ComponentNameSchema (used by list filter / remove_components) lists 20 (button, anim, particlesystem, sprite, scrollview, …). Agents can remove/filter by names they cannot add. Bring add_components to parity.
- The scripting flow (
add_script_component_script + set_script_text + script_parse + create script asset) is fragmented; consider a higher-level attach_script.
5. Strengthen tool descriptions & specs (prompt engineering)
- Descriptions are minimal restatements of the tool name (
'Reparent entity', 'Add components to entity'), with no usage guidance, units/coordinate conventions, resource relationships, or examples. Each description should cover: what it does, when to use it, what it returns, and — crucially — when NOT to use it; the "when not to use" clause prevents more bad calls than any other signal.
modify_entities exposes path: string + value: z.any(), directly wrapping entity.set() with no enumerated valid paths and no value typing — high error rate. Provide common path examples (position, rotation, components.light.intensity) and tighter typing. Same applies to ScriptAttributeSchema = z.any().
- Note: descriptions are also the primary surface the eval harness (item9) iterates on — a concrete one-time authoring task and the main lever for ongoing self-tuning.
6. Add MCP tool annotations
None of the tools declare annotations. Add readOnlyHint (list_*, query_*, capture_viewport), destructiveHint (delete_entities, delete_assets), and openWorldHint (store_*). This is especially important given the README recommends enabling auto-run.
7. Consistent namespacing
Naming mixes three styles: create_entities (verb_noun), store_search (resource_verb), query_scene_settings (verb prefix). Only store_ uses a service prefix. Adopt one consistent scheme (e.g. prefix-based: entity_create, asset_list, scene_settings_get) to reduce collisions when multiple MCP servers are loaded. Favor descriptive over short names — agents benefit from unambiguous names, not brevity (list_active_entities beats list_ents); the tool name itself is the discovery mechanism.
8. Actionable error responses
Many errors ('Entity not found', 'Failed to create entity') and the WSS Timeout: '<name>' after 30000ms give agents no recovery path. Make error messages prompt-engineered and actionable (what to do next / how to obtain a valid ID). The error shape is addressed by item11 (inline _status/_message rather than HTTP-style); this item is about the content of _message.
9. Evaluation harness across clients
Add an evaluation set of realistic multi-step tasks (per the article's methodology) and validate across Claude Desktop, Cursor, and Claude Code to drive iteration on descriptions and schemas, and to catch cross-client schema-compatibility regressions early.
10. Runtime observability tools (the missing feedback loop) — harness-critical
All 23 tools are edit-time only. The closest thing to runtime, capture_viewport, just renders a single static frame of the editor's embedded preview app (app.tick() once in extension/main.js); scripts' update loop, physics, animation state machines, and input are never actually executed. As a result the agent can build a scene but cannot verify whether it runs correctly, and any evaluation harness lacks a ground-truth signal beyond edit-time state. The article's methodology (act → observe → eval → iterate) breaks down without observable runtime results.
Proposal: add runtime tools, ordered by how essential they are to closing the loop. Apply the same five principles (consolidation, semantic returns, token budgeting, strict specs, annotations — runtime readers get readOnlyHint, launch_*/input_* are non-read-only):
launch_start / launch_stop — start/stop a real Launch runtime instance (the editor's Launch button), returning a run handle and readiness state. Prerequisite for everything else; ideally support a headless mode for CI/harness.
capture_runtime — screenshot the running instance (not the editor preview). The agent's core "eye" for visual verification; reuse the existing 800px downscale + WebP approach and allow camera/resolution selection.
read_runtime_logs (highest ROI) — capture the runtime's console.log/warn/error, uncaught exceptions, and script stack traces. This is the first-line signal for runtime bugs, cheap to implement and high value. Budget tokens: default to error/warn + last N entries, with level/keyword filtering and pagination.
query_runtime_state — read the actual runtime position/rotation/scale, enabled, current script attribute values, rigidbody velocity, etc., so the agent can distinguish "what I set" from "what it became after running". Align naming/return style with edit-time list_entities, but source from runtime.
step_runtime / time control — advance the instance by a given number of frames / fixed dt (app.tick(dt)), making "launch → step N frames → capture/read state" a reproducible observation sequence. Determinism matters for automated evals (avoids wall-clock flakiness).
inject_input (advanced) — dispatch keyboard/mouse/touch events to the running instance to drive end-to-end verification of interactions ("press W to move", "click a button"). Highest implementation complexity; can come last.
MVP: read_runtime_logs + capture_runtime (with launch_start/launch_stop) is the minimal useful set — a hard signal for "is it correct?" plus a soft signal for "what does it look like?" — which upgrades the agent from blind editing to feedback-driven editing.
11. Unified response envelope (consistent return shape + inline error status)
Agents pattern-match on return shape, not field semantics — so inconsistent shapes across tools are a leading cause of hallucinated field access. Today every call passes through wss.call, which simply JSON.stringifys a wildly varying data: list_* returns object arrays (summary or entity.json()), create_entities returns a bare string array of ids, modify/reparent/delete return a bare boolean true, query_scene_settings returns a single object, and store_* passes through the raw REST response. Errors take a different channel entirely (isError: true + plain-text message, not JSON).
Proposal: standardize one envelope for the inner JSON of every tool, wrapped centrally in wss.call (so the 23 tools don't each change):
Conventions:
data is always present; scalar/boolean operations return a small object ({ "modified": 1 }), never a bare true.
- Errors live in the same envelope — never HTTP-style
{ error: "..." }. Agents treat any top-level key as valid data, so error signaling uses underscore-prefixed metadata (_status/_message) that reads as "metadata, not data". This supersedes the ad-hoc { error } returns and makes item3 fall out for free. (Keep MCP's protocol-level isError too, since some models only read the text block.)
- Image tools (
capture_viewport/capture_runtime) must still return a protocol image content block, but can attach a parallel text block carrying the same meta so "all metadata lives in meta" stays true.
12. Return the new state snapshot alongside the action result
For editor mutations, agents currently have to issue a follow-up list_entities just to learn what their change produced (and to recover the new resource ids) — extra round-trips and extra UUID shuffling. Editor tools should return both the action result and a snapshot of what changed in one response.
Proposal: have create_entities / modify_entities / add_components / reparent_entity return the resulting entity summary inline (e.g. data: [{ id, name, path, components }]) rather than a bare boolean or id list. This directly attacks the UUID-shuttling pain in item2 by collapsing act→observe into a single call. Note this covers edit-time effects only; verifying runtime effects (does it actually run/render correctly?) still requires the launch/log/capture tools in item10.
Suggested priority
3 is a quick, high-impact win and falls out of 11 (unified envelope), which is foundational — every other tool's return shape depends on it, so land 11 early. 10 is harness-critical: without runtime observability the evaluation loop (item 9) has no ground-truth signal — land its MVP (read_runtime_logs + capture_runtime) early. 12 (state snapshot) is high-leverage and cheap once 11 exists. 1, 2, 4, 5 most improve agent success rate. 6–8 harden consistency and long-term quality (8's content pairs with 11's shape).
Several patterns here (unified envelope 11, inline error status folded into 11, explicit pagination metadata 1, "when not to use" descriptions 5, descriptive naming 7, and the state-snapshot pattern 12) were sharpened by @Ben-Home on this issue — thanks!
Summary
The MCP tools work well for humans/developers, but several aspects diverge from agent-tool-design best practices described in Anthropic's Writing effective tools for AI agents and the broader ACI (Agent–Computer Interface) philosophy. This is an umbrella enhancement issue tracking improvements that would make the tools more "ergonomic" for LLM agents, reduce token usage, and lower error/hallucination rates.
Motivation
Tools are a contract between deterministic systems and non-deterministic agents. Designing them like ordinary developer APIs (raw UUIDs, unbounded lists, terse specs) increases the chance agents call the wrong tool, pass wrong parameters, or waste context. The items below map directly to the article's five principles.
Proposed Enhancements
1. Token efficiency for list tools (pagination / truncation)
list_entitiesandlist_assetshave nolimit/offsetand no truncation guard. Withfull=truethey can dump the complete JSON of every entity/asset, easily blowing the context window on large scenes. Onlystore_searchcurrently paginates (skip/limit).Proposal: add
limit(sensible default, e.g. 50) +offsetto alllist_*tools, cap response size, and when truncated, return a short hint steering the agent toward narrower filtered queries. Pagination must be explicit in the response (_has_more,_next_cursor) — agents do not infer "there is more data" from result counts. (See item11 for where this metadata lives.)2. Reduce reliance on raw UUIDs (meaningful context)
Entities are addressed exclusively by UUID (
EntityIdSchema = z.string().uuid()), and list summaries returnparentas a UUID too. The article specifically calls out UUIDs as an anti-pattern and recommends resolving them to semantic/human-readable identifiers.Proposal: include a human-readable hierarchy path (e.g.
Root/Player/Camera) in summaries, and/or support locating entities by name (e.g. aresolvecapability) so agents can drive follow-up calls without manually shuttling UUIDs.3. Treat empty results as success, not error
entities:list/assets:listreturn{ error: 'No entities found' }for an empty match, which surfaces asisError: true. An empty set is a valid result; flagging it as an error misleads agents into thinking the call failed and triggers needless retries. Return an empty array instead. (This falls out naturally once the unified envelope in item11 is adopted:{ data: [], meta: { _status: "ok", total: 0 } }.)4. Close tool asymmetries / consolidate workflows
MaterialSchema) but onlydiffusecan be modified (set_material_diffuse). Add a genericmodify_asset/set_material_properties.add_components(ComponentsSchema) supports only 10 component types, whileComponentNameSchema(used by list filter /remove_components) lists 20 (button,anim,particlesystem,sprite,scrollview, …). Agents can remove/filter by names they cannot add. Bringadd_componentsto parity.add_script_component_script+set_script_text+script_parse+ create script asset) is fragmented; consider a higher-levelattach_script.5. Strengthen tool descriptions & specs (prompt engineering)
'Reparent entity','Add components to entity'), with no usage guidance, units/coordinate conventions, resource relationships, or examples. Each description should cover: what it does, when to use it, what it returns, and — crucially — when NOT to use it; the "when not to use" clause prevents more bad calls than any other signal.modify_entitiesexposespath: string+value: z.any(), directly wrappingentity.set()with no enumerated valid paths and no value typing — high error rate. Provide commonpathexamples (position,rotation,components.light.intensity) and tighter typing. Same applies toScriptAttributeSchema = z.any().6. Add MCP tool annotations
None of the tools declare annotations. Add
readOnlyHint(list_*,query_*,capture_viewport),destructiveHint(delete_entities,delete_assets), andopenWorldHint(store_*). This is especially important given the README recommends enabling auto-run.7. Consistent namespacing
Naming mixes three styles:
create_entities(verb_noun),store_search(resource_verb),query_scene_settings(verb prefix). Onlystore_uses a service prefix. Adopt one consistent scheme (e.g. prefix-based:entity_create,asset_list,scene_settings_get) to reduce collisions when multiple MCP servers are loaded. Favor descriptive over short names — agents benefit from unambiguous names, not brevity (list_active_entitiesbeatslist_ents); the tool name itself is the discovery mechanism.8. Actionable error responses
Many errors (
'Entity not found','Failed to create entity') and the WSSTimeout: '<name>' after 30000msgive agents no recovery path. Make error messages prompt-engineered and actionable (what to do next / how to obtain a valid ID). The error shape is addressed by item11 (inline_status/_messagerather than HTTP-style); this item is about the content of_message.9. Evaluation harness across clients
Add an evaluation set of realistic multi-step tasks (per the article's methodology) and validate across Claude Desktop, Cursor, and Claude Code to drive iteration on descriptions and schemas, and to catch cross-client schema-compatibility regressions early.
10. Runtime observability tools (the missing feedback loop) — harness-critical
All 23 tools are edit-time only. The closest thing to runtime,
capture_viewport, just renders a single static frame of the editor's embedded preview app (app.tick()once inextension/main.js); scripts'updateloop, physics, animation state machines, and input are never actually executed. As a result the agent can build a scene but cannot verify whether it runs correctly, and any evaluation harness lacks a ground-truth signal beyond edit-time state. The article's methodology (act → observe → eval → iterate) breaks down without observable runtime results.Proposal: add runtime tools, ordered by how essential they are to closing the loop. Apply the same five principles (consolidation, semantic returns, token budgeting, strict specs, annotations — runtime readers get
readOnlyHint,launch_*/input_*are non-read-only):launch_start/launch_stop— start/stop a real Launch runtime instance (the editor's Launch button), returning a run handle and readiness state. Prerequisite for everything else; ideally support a headless mode for CI/harness.capture_runtime— screenshot the running instance (not the editor preview). The agent's core "eye" for visual verification; reuse the existing 800px downscale + WebP approach and allow camera/resolution selection.read_runtime_logs(highest ROI) — capture the runtime'sconsole.log/warn/error, uncaught exceptions, and script stack traces. This is the first-line signal for runtime bugs, cheap to implement and high value. Budget tokens: default toerror/warn+ last N entries, with level/keyword filtering and pagination.query_runtime_state— read the actual runtimeposition/rotation/scale,enabled, current script attribute values, rigidbody velocity, etc., so the agent can distinguish "what I set" from "what it became after running". Align naming/return style with edit-timelist_entities, but source from runtime.step_runtime/ time control — advance the instance by a given number of frames / fixeddt(app.tick(dt)), making "launch → step N frames → capture/read state" a reproducible observation sequence. Determinism matters for automated evals (avoids wall-clock flakiness).inject_input(advanced) — dispatch keyboard/mouse/touch events to the running instance to drive end-to-end verification of interactions ("press W to move", "click a button"). Highest implementation complexity; can come last.MVP:
read_runtime_logs+capture_runtime(withlaunch_start/launch_stop) is the minimal useful set — a hard signal for "is it correct?" plus a soft signal for "what does it look like?" — which upgrades the agent from blind editing to feedback-driven editing.11. Unified response envelope (consistent return shape + inline error status)
Agents pattern-match on return shape, not field semantics — so inconsistent shapes across tools are a leading cause of hallucinated field access. Today every call passes through
wss.call, which simplyJSON.stringifys a wildly varyingdata:list_*returns object arrays (summary orentity.json()),create_entitiesreturns a bare string array of ids,modify/reparent/deletereturn a bare booleantrue,query_scene_settingsreturns a single object, andstore_*passes through the raw REST response. Errors take a different channel entirely (isError: true+ plain-text message, not JSON).Proposal: standardize one envelope for the inner JSON of every tool, wrapped centrally in
wss.call(so the 23 tools don't each change):{ "data": <T> | null, // business payload; never omitted; empty set = [] "meta": { "tool": "create_entities", "_status": "ok" | "error", "_message": "...", // actionable on error (see #8) // list/pagination only: "total": 120, "count": 50, "_has_more": true, "_next_cursor": "..." } }Conventions:
datais always present; scalar/boolean operations return a small object ({ "modified": 1 }), never a baretrue.{ error: "..." }. Agents treat any top-level key as valid data, so error signaling uses underscore-prefixed metadata (_status/_message) that reads as "metadata, not data". This supersedes the ad-hoc{ error }returns and makes item3 fall out for free. (Keep MCP's protocol-levelisErrortoo, since some models only read the text block.)capture_viewport/capture_runtime) must still return a protocolimagecontent block, but can attach a paralleltextblock carrying the samemetaso "all metadata lives inmeta" stays true.12. Return the new state snapshot alongside the action result
For editor mutations, agents currently have to issue a follow-up
list_entitiesjust to learn what their change produced (and to recover the new resource ids) — extra round-trips and extra UUID shuffling. Editor tools should return both the action result and a snapshot of what changed in one response.Proposal: have
create_entities/modify_entities/add_components/reparent_entityreturn the resulting entity summary inline (e.g.data: [{ id, name, path, components }]) rather than a bare boolean or id list. This directly attacks the UUID-shuttling pain in item2 by collapsing act→observe into a single call. Note this covers edit-time effects only; verifying runtime effects (does it actually run/render correctly?) still requires the launch/log/capture tools in item10.Suggested priority
3 is a quick, high-impact win and falls out of 11 (unified envelope), which is foundational — every other tool's return shape depends on it, so land 11 early. 10 is harness-critical: without runtime observability the evaluation loop (item 9) has no ground-truth signal — land its MVP (
read_runtime_logs+capture_runtime) early. 12 (state snapshot) is high-leverage and cheap once 11 exists. 1, 2, 4, 5 most improve agent success rate. 6–8 harden consistency and long-term quality (8's content pairs with 11's shape).Several patterns here (unified envelope 11, inline error status folded into 11, explicit pagination metadata 1, "when not to use" descriptions 5, descriptive naming 7, and the state-snapshot pattern 12) were sharpened by @Ben-Home on this issue — thanks!