create_finger_joint
Join two boards by creating a finger joint (box joint) with precise dimensions in millimeters.
Instructions
Create a finger joint (box joint) between two components. Dimensions in mm.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| board1_id | Yes | ||
| board2_id | Yes | ||
| width | No | ||
| height | No | ||
| depth | No | ||
| num_fingers | No | ||
| offset_x | No | ||
| offset_y | No | ||
| offset_z | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/sketchup_mcp/tools.py:190-216 (handler)The `create_finger_joint` tool handler: a FastMCP tool annotated with @mcp.tool() that accepts board1_id, board2_id, width, height, depth, num_fingers, and offset_x/y/z, then delegates to the Ruby backend via _call(ctx, 'create_finger_joint', ...). This is the Python-side entry point that registers the tool and defines its schema via Pydantic/Annotated type hints.
@mcp.tool() async def create_finger_joint( ctx: Context, board1_id: Annotated[str, Field(min_length=1)], board2_id: Annotated[str, Field(min_length=1)], width: Annotated[float, Field(gt=0)] = 50.0, height: Annotated[float, Field(gt=0)] = 25.0, depth: Annotated[float, Field(gt=0)] = 10.0, num_fingers: Annotated[int, Field(gt=0)] = 5, offset_x: float = 0.0, offset_y: float = 0.0, offset_z: float = 0.0, ) -> str: """Create a finger joint (box joint) between two components. Dimensions in mm.""" return await _call( ctx, "create_finger_joint", board1_id=board1_id, board2_id=board2_id, width=width, height=height, depth=depth, num_fingers=num_fingers, offset_x=offset_x, offset_y=offset_y, offset_z=offset_z, ) - src/sketchup_mcp/tools.py:191-202 (schema)Input schema for create_finger_joint: board1_id and board2_id (required strings with min_length=1), width (default 50.0, gt=0), height (default 25.0, gt=0), depth (default 10.0, gt=0), num_fingers (default 5, gt=0 int), and offset_x/y/z (default 0.0 floats). These Pydantic Field annotations define the JSON-serializable input contract exposed to the MCP client.
async def create_finger_joint( ctx: Context, board1_id: Annotated[str, Field(min_length=1)], board2_id: Annotated[str, Field(min_length=1)], width: Annotated[float, Field(gt=0)] = 50.0, height: Annotated[float, Field(gt=0)] = 25.0, depth: Annotated[float, Field(gt=0)] = 10.0, num_fingers: Annotated[int, Field(gt=0)] = 5, offset_x: float = 0.0, offset_y: float = 0.0, offset_z: float = 0.0, ) -> str: - src/sketchup_mcp/tools.py:190-190 (registration)Registration via the @mcp.tool() decorator on line 190. The `mcp` FastMCP instance is created in app.py (line 41) and imported at line 14 of tools.py. The side-effect import in app.py line 51 (import sketchup_mcp.tools) ensures tools are registered when the MCP server starts.
@mcp.tool() - src/sketchup_mcp/tools.py:21-50 (helper)The _call helper function that all tool handlers delegate to. It acquires a SketchUp connection via get_connection(), sends the command with its name and kwargs via send_command(name, kwargs), handles ConnectionError and SketchUpError, and unwraps the MCP-shaped response. This is the dispatch layer between Python handlers and the Ruby SketchUp extension.
async def _call(ctx: Context, name: str, **kwargs) -> str: """Dispatch a tool call to SketchUp and shape the response for Claude. - Connection errors → human-readable string, server keeps running. - SketchUpError → ``format_error`` string. - Successful MCP-shaped result ({content: [{text: "..."}]}) → just the text. - Any other dict result → ``json.dumps``. """ try: sketchup = await get_connection() except ConnectionError as e: return f"SketchUp not running or extension not started: {e}" try: result = await sketchup.send_command(name, kwargs) except ConnectionError as e: # Cached connection was stale and reconnect inside send_command failed: # `_connect_or_raise` re-raises OSError as ConnectionError before the # send/recv try-block, so it escapes past the SketchUpError handler. 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) - tests/test_tools.py:186-190 (helper)Test data for create_finger_joint: verifies that calling the Python wrapper with only board1_id='1' and board2_id='2' produces the correct Ruby call with default values (width=50.0, height=25.0, depth=10.0, num_fingers=5, offset_x/y/z=0.0). Ensures the mapping between Python wrapper and Ruby tool name is correct.
("create_finger_joint", {"board1_id": "1", "board2_id": "2"}, "create_finger_joint", {"board1_id": "1", "board2_id": "2", "width": 50.0, "height": 25.0, "depth": 10.0, "num_fingers": 5, "offset_x": 0.0, "offset_y": 0.0, "offset_z": 0.0}),