undo
Reverse the last atomic operation in SketchUp to undo your most recent modeling step.
Instructions
Undo the last atomic operation in SketchUp. One MCP tool-call = one undo step.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/sketchup_mcp/tools.py:447-450 (handler)The 'undo' tool handler function. Decorated with @mcp.tool(), it delegates to _call(ctx, 'undo') which sends the command to the Ruby backend via JSON-RPC.
@mcp.tool() async def undo(ctx: Context) -> str: """Undo the last atomic operation in SketchUp. One MCP tool-call = one undo step.""" return await _call(ctx, "undo") - src/sketchup_mcp/tools.py:48-71 (handler)_call() function invoked by the undo handler. Acquires the connection via get_connection(), sends 'undo' command via send_command(), and formats the JSON-RPC response for the LLM.
async def _call(ctx: Context, tool_name: str, /, **kwargs) -> str: """Dispatch a tool call to SketchUp and shape the response for Claude. Same external contract as before — kept for compatibility with the 22 existing string-returning tools. Now delegates to :func:`_raw_call` for connection acquisition and converts the result to a string. Connection failures surface as the canonical legacy string so the LLM sees a stable, actionable hint. """ try: result = await _raw_call(ctx, tool_name, **kwargs) except ConnectionError as e: return f"SketchUp not running or extension not started: {e}" except SketchUpError as e: return format_error(e, debug=config.LOG_LEVEL == "DEBUG") content = result.get("content") if isinstance(result, dict) else None if ( isinstance(content, list) and content and isinstance(content[0], dict) and "text" in content[0] ): return content[0]["text"] return json.dumps(result) - send_command method on SketchUpConnection that dispatches the 'undo' tool name as a JSON-RPC tools/call request to the Ruby side. Note: 'undo' is NOT in _RETRY_SAFE_TOOLS (it's a mutation tool), so stale-socket errors are not retried.
async def send_command(self, name: str, args: dict[str, Any]) -> Any: async with self._lock: try: return await self._send_once(name, args) except _StaleSocketError: # `disconnect()` уже сделан внутри `_send_once`. # Retry ТОЛЬКО для side-effect-free tools: Ruby `write_response` # может закрыть сокет уже после `commit_operation`, и тогда # partial=b"" не гарантирует, что мутации не было. if name not in _RETRY_SAFE_TOOLS: raise return await self._send_once(name, args) - src/sketchup_mcp/app.py:41-52 (registration)FastMCP application instance created at module level. The side-effect import of sketchup_mcp.tools (line 51) triggers registration of the @mcp.tool() decorator on the undo handler.
mcp = FastMCP( "SketchupMCP", instructions="Sketchup integration through the Model Context Protocol", lifespan=server_lifespan, ) # Side-effect import: registers tool handlers on `mcp`. Must come AFTER `mcp` # is constructed (tools.py does `from sketchup_mcp.app import mcp`). Required # here so MCP hosts loading the published `[project.entry-points.mcp]` get a # FastMCP with tools registered, not an empty instance. import sketchup_mcp.tools # noqa: E402, F401 import sketchup_mcp.prompts # noqa: E402, F401 - src/sketchup_mcp/prompts.py:62-63 (helper)Documentation in the modeling strategy prompt explaining that the undo MCP tool internally uses Sketchup.send_action('editUndo:') on the Ruby side.
- Sketchup::Model#undo does not exist. The undo MCP tool uses Sketchup.send_action("editUndo:") internally — just call it.