Skip to main content
Glama
zinin

sketchup-mcp2

by zinin

get_viewport_screenshot

Capture the current SketchUp viewport as a PNG for visual scene verification between steps. Optionally adjust view preset, style, or zoom extents before capture.

Instructions

Capture the current SketchUp viewport as a PNG and return it as an MCP Image.

Useful for letting Claude visually verify the scene between steps.

Parameters

  • max_size: largest side of the returned PNG (64..4096). Aspect ratio is taken from the current viewport; the smaller side is scaled proportionally.

  • view_preset: switch the camera to a standard view before snapping. current leaves the camera alone.

  • zoom_extents: call view.zoom_extents before snapping.

  • style: temporarily flip a small set of rendering_options keys. default leaves them alone.

  • restore_view: when true (default), camera and rendering_options are snapshotted before mutation and restored after the snapshot, so the user's viewport is unchanged.

Note on operation order (Ruby handler): snapshot → preset → style → zoom_extents → write_image → restore. Restore runs in an outer ensure block, so an exception anywhere between snapshot and write_image still leaves the viewport in its original state.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
max_sizeNo
view_presetNocurrent
zoom_extentsNo
styleNodefault
restore_viewNo

Implementation Reference

  • The actual handler function for the get_viewport_screenshot tool. It's an @mcp.tool()-decorated async function that accepts parameters (max_size, view_preset, zoom_extents, style, restore_view), delegates to _raw_call to communicate with the Ruby backend via JSON-RPC, extracts a base64-encoded PNG from the response, and returns a FastMCP Image object.
    async def get_viewport_screenshot(
        ctx: Context,
        max_size: Annotated[int, Field(ge=64, le=4096)] = 800,
        view_preset: Literal[
            "current", "front", "back", "left", "right",
            "top", "bottom", "iso",
        ] = "current",
        zoom_extents: bool = False,
        style: Literal["default", "shaded", "hidden_line", "wireframe"] = "default",
        restore_view: bool = True,
    ) -> Image:
        """Capture the current SketchUp viewport as a PNG and return it as an MCP Image.
    
        Useful for letting Claude visually verify the scene between steps.
    
        Parameters
        - max_size: largest side of the returned PNG (64..4096). Aspect ratio is
          taken from the current viewport; the smaller side is scaled proportionally.
        - view_preset: switch the camera to a standard view before snapping.
          ``current`` leaves the camera alone.
        - zoom_extents: call view.zoom_extents before snapping.
        - style: temporarily flip a small set of rendering_options keys.
          ``default`` leaves them alone.
        - restore_view: when true (default), camera and rendering_options are
          snapshotted before mutation and restored after the snapshot, so the
          user's viewport is unchanged.
    
        Note on operation order (Ruby handler): snapshot → preset → style →
        zoom_extents → write_image → restore. Restore runs in an outer ``ensure``
        block, so an exception anywhere between snapshot and write_image still
        leaves the viewport in its original state.
        """
        # Delegate connection + send_command to _raw_call so we don't duplicate
        # the transport logic of _call. _raw_call does NOT translate
        # ConnectionError (text-tools and Image-tools have divergent strategies),
        # so we convert here: there is no Image sentinel for "not connected",
        # so raise SketchUpError. See design §5.8 for the error-handling
        # asymmetry rationale.
        try:
            raw = await _raw_call(
                ctx,
                "get_viewport_screenshot",
                max_size=max_size,
                view_preset=view_preset,
                zoom_extents=zoom_extents,
                style=style,
                restore_view=restore_view,
            )
        except ConnectionError as e:
            raise SketchUpError(-32000, f"SketchUp not running: {e}") from e
    
        # Ruby returns MCP-shaped {content: [{type: "text", text: JSON-blob}], ...}.
        # Extract the JSON blob and decode the base64 PNG into raw bytes.
        text: Optional[str] = None
        if isinstance(raw, dict):
            content = raw.get("content")
            if (
                isinstance(content, list)
                and content
                and isinstance(content[0], dict)
            ):
                text = content[0].get("text")
        if not isinstance(text, str):
            raise SketchUpError(
                -32603, f"unexpected screenshot response shape: {raw!r}"
            )
        try:
            payload = json.loads(text)
        except json.JSONDecodeError as e:
            raise SketchUpError(-32603, f"screenshot response not JSON: {e}") from e
        b64 = payload.get("png_base64")
        if not isinstance(b64, str):
            raise SketchUpError(-32603, "screenshot response missing png_base64")
        try:
            png_bytes = base64.b64decode(b64, validate=True)
        except (ValueError, base64.binascii.Error) as e:
            raise SketchUpError(-32603, f"png_base64 decode failed: {e}") from e
        return Image(data=png_bytes, format="png")
  • The function signature defines the input schema via Pydantic: max_size (int 64-4096), view_preset (Literal of camera positions), zoom_extents (bool), style (Literal of rendering options), restore_view (bool). The return type is Image.
    async def get_viewport_screenshot(
        ctx: Context,
        max_size: Annotated[int, Field(ge=64, le=4096)] = 800,
        view_preset: Literal[
            "current", "front", "back", "left", "right",
            "top", "bottom", "iso",
        ] = "current",
        zoom_extents: bool = False,
        style: Literal["default", "shaded", "hidden_line", "wireframe"] = "default",
        restore_view: bool = True,
    ) -> Image:
  • Registration via the @mcp.tool() decorator on line 299. The tools module is imported as a side-effect in app.py (line 51), which itself is imported by server.py.
    @mcp.tool()
    async def get_viewport_screenshot(
  • The tool name "get_viewport_screenshot" is listed in _RETRY_SAFE_TOOLS in connection.py, marking it as read-only/idempotent so it can be safely retried on stale-socket errors.
    _RETRY_SAFE_TOOLS: frozenset[str] = frozenset(
        {
            "get_model_info",
            "list_components",
            "get_component_info",
            "find_components",
            "list_layers",
            "get_selection",
            "get_viewport_screenshot",  # read-only viewport capture; idempotent in
                                        # both restore_view modes (no document state changes)
            "get_version",              # read-only diagnostic; no side effects
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description fully covers behavioral traits: it describes the snapshot process, camera and rendering modifications, the ensure block for restoration, and the order of operations. This gives the agent complete transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a concise purpose, a bulleted parameter list, and a note on operation order. Every sentence adds value without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (5 parameters, view mutation), the description covers purpose, parameters, operation order, restoration safety, and return type (MCP Image). No gaps remain, even without an output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, but the description explains each parameter in detail, including constraints (e.g., max_size range, aspect ratio), enums (view_preset, style), default behavior (current, default), and the restore_view mechanism. This fully compensates for the lack of schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Capture the current SketchUp viewport as a PNG and return it as an MCP Image.' This is a specific verb-resource-output combination, and it is distinct from sibling tools (all modeling operations).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says 'Useful for letting Claude visually verify the scene between steps,' providing a clear use case. It does not explicitly list when not to use or alternatives, but the context and parameter explanations offer sufficient guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zinin/sketchup-mcp2'

If you have feedback or need assistance with the MCP directory API, please join our Discord server