Build123d Parametric Mounting Bracket

Source: examples/mcp/build123d_parametric_mounting_bracket.py

Introduction

Inspect and exercise the MCP-backed Build123d mounting-bracket problem.

Technical Implementation

This page is generated from the top-of-file module docstring and the example source code. The full script is included below for direct inspection.

  1from __future__ import annotations
  2
  3import asyncio
  4import json
  5from collections.abc import Sequence
  6from textwrap import dedent
  7from typing import TYPE_CHECKING, Any, cast
  8
  9import design_research_problems as derp
 10
 11if TYPE_CHECKING:
 12    from mcp.server.fastmcp import FastMCP
 13
 14PROBLEM_ID = "mcp_build123d_parametric_mounting_bracket"
 15SERVER_NAME = "build123d-mounting-bracket-demo"
 16STARTER_SCRIPT = dedent(
 17    """
 18    from build123d import Align, BuildPart, Box, Cylinder, Location, Locations, Mode, fillet
 19
 20    WIDTH = 80.0
 21    DEPTH = 40.0
 22    BASE_THICKNESS = 6.0
 23    FLANGE_HEIGHT = 40.0
 24    FLANGE_THICKNESS = 6.0
 25    HOLE_DIAMETER = 6.0
 26
 27    with BuildPart() as part:
 28        Box(WIDTH, DEPTH, BASE_THICKNESS, align=(Align.CENTER, Align.CENTER, Align.MIN))
 29        flange_center_y = -DEPTH / 2.0 + FLANGE_THICKNESS / 2.0
 30        with Locations((0.0, flange_center_y, BASE_THICKNESS)):
 31            Box(WIDTH, FLANGE_THICKNESS, FLANGE_HEIGHT, align=(Align.CENTER, Align.CENTER, Align.MIN))
 32
 33        for x_mm, y_mm in ((-30.0, -10.0), (30.0, -10.0), (-30.0, 10.0), (30.0, 10.0)):
 34            with Locations((x_mm, y_mm, 0.0)):
 35                Cylinder(
 36                    radius=HOLE_DIAMETER / 2.0,
 37                    height=BASE_THICKNESS,
 38                    align=(Align.CENTER, Align.CENTER, Align.MIN),
 39                    mode=Mode.SUBTRACT,
 40                )
 41
 42        for z_mm in (16.0, 36.0):
 43            with Locations(Location((0.0, flange_center_y, z_mm), (90.0, 0.0, 0.0))):
 44                Cylinder(
 45                    radius=HOLE_DIAMETER / 2.0,
 46                    height=FLANGE_THICKNESS,
 47                    align=(Align.CENTER, Align.CENTER, Align.CENTER),
 48                    mode=Mode.SUBTRACT,
 49                )
 50
 51        target_y = -DEPTH / 2.0 + FLANGE_THICKNESS
 52        inner_edges = [
 53            edge
 54            for edge in part.edges()
 55            if abs(edge.center().Y - target_y) <= 1e-6 and abs(edge.center().Z - BASE_THICKNESS) <= 1e-6
 56        ]
 57        radius = 4.0
 58        while inner_edges and radius >= 0.5:
 59            try:
 60                fillet(inner_edges, radius)
 61                break
 62            except ValueError:
 63                radius -= 0.5
 64
 65    result = part.part
 66    """
 67).strip()
 68
 69
 70def _extract_structured_payload(payload: object) -> dict[str, object]:
 71    """Normalize one FastMCP ``call_tool`` payload into a dictionary.
 72
 73    Args:
 74        payload: Raw payload returned by ``server.call_tool``.
 75
 76    Returns:
 77        Parsed dictionary payload when available.
 78    """
 79    if isinstance(payload, tuple):
 80        _, structured = payload
 81        if isinstance(structured, dict):
 82            return cast(dict[str, object], structured)
 83        return {}
 84
 85    content = cast(Sequence[Any], payload)
 86    if not content:
 87        return {}
 88    text = getattr(content[0], "text", None)
 89    if not isinstance(text, str):
 90        return {}
 91    parsed = json.loads(text)
 92    if isinstance(parsed, dict):
 93        return cast(dict[str, object], parsed)
 94    return {}
 95
 96
 97def create_server() -> FastMCP:
 98    """Create the MCP proxy server for this packaged problem.
 99
100    Returns:
101        Configured FastMCP server instance.
102    """
103    problem = derp.get_problem(PROBLEM_ID)
104    return problem.to_mcp_server(server_name=SERVER_NAME, include_citation=False)
105
106
107def _is_expected_build123d_runtime_unavailable_error(exc: BaseException) -> bool:
108    """Return whether one exception indicates optional build123d runtime absence.
109
110    Args:
111        exc: Exception raised while exercising the Build123d MCP tools.
112
113    Returns:
114        ``True`` when the error matches expected optional-backend absence paths.
115    """
116    message = str(exc).lower()
117    return (
118        "build123d is not installed" in message
119        or "tcl wasn't installed properly" in message
120        or "upstream mcp tool 'evaluate_scripted_part' failed" in message
121    )
122
123
124async def run_summary(server: FastMCP) -> dict[str, object]:
125    """Collect a short runtime summary from the wrapped MCP server.
126
127    Returns:
128        Summary payload with discovered tools, resources, and final-answer echo.
129    """
130    tools = await server.list_tools()
131    resources = await server.list_resources()
132
133    try:
134        status_payload = await server.call_tool("backend_status", {})
135        status = _extract_structured_payload(status_payload)
136        eval_payload = await server.call_tool(
137            "evaluate_scripted_part",
138            {"script": STARTER_SCRIPT, "result_name": "result", "include_script": False},
139        )
140        evaluation = _extract_structured_payload(eval_payload)
141        last_payload = await server.call_tool("describe_last_script_result", {"include_script": False})
142        last_result = _extract_structured_payload(last_payload)
143        submit_final_payload = await server.call_tool(
144            "submit_final",
145            {"answer": "Submitted a Build123d script that generates and validates the bracket geometry."},
146        )
147        submit_final = _extract_structured_payload(submit_final_payload)
148    finally:
149        close_upstream = getattr(server, "aclose_upstream_session", None)
150        if callable(close_upstream):
151            await close_upstream()
152
153    return {
154        "problem_id": PROBLEM_ID,
155        "tool_count": len(tools),
156        "tool_names": sorted(tool.name for tool in tools),
157        "resource_uris": sorted(str(resource.uri) for resource in resources),
158        "backend_available": status.get("available", False),
159        "result_is_valid": evaluation.get("is_valid"),
160        "volume_mm3": evaluation.get("volume_mm3"),
161        "matches_nominal_envelope": cast(dict[str, object], evaluation.get("constraint_checks", {})).get(
162            "matches_nominal_envelope"
163        ),
164        "last_result_name": last_result.get("result_name"),
165        "submit_final": submit_final.get("answer", "<missing>"),
166    }
167
168
169def main() -> int:
170    """Run the Build123d MCP wrapper demo.
171
172    Returns:
173        Process exit code.
174    """
175    problem = derp.get_problem(PROBLEM_ID)
176    print("Problem id:", problem.metadata.problem_id)
177    print("Upstream command:", problem.command)
178
179    try:
180        server = create_server()
181        summary = asyncio.run(run_summary(server))
182    except (derp.MissingOptionalDependencyError, ModuleNotFoundError) as exc:
183        print(exc)
184        print("Install the optional MCP dependency with: pip install design-research-problems[mcp]")
185        return 0
186    except (FileNotFoundError, RuntimeError) as exc:
187        print(f"build123d backend startup failed: {exc}")
188        return 0
189    except Exception as exc:
190        if _is_expected_build123d_runtime_unavailable_error(exc):
191            print(f"build123d backend startup failed: {exc}")
192            return 0
193        raise
194
195    print("Tool count:", summary["tool_count"])
196    print("Tools:", ", ".join(cast(list[str], summary["tool_names"])))
197    print("Resources:", ", ".join(cast(list[str], summary["resource_uris"])))
198    print("Backend available:", summary["backend_available"])
199    print("Result valid:", summary["result_is_valid"])
200    print("Volume (mm^3):", summary["volume_mm3"])
201    print("Matches nominal envelope:", summary["matches_nominal_envelope"])
202    print("Last result name:", summary["last_result_name"])
203    print("submit_final answer:", summary["submit_final"])
204    return 0
205
206
207if __name__ == "__main__":
208    raise SystemExit(main())

Expected Results

Run Command

PYTHONPATH=src python3 examples/mcp/build123d_parametric_mounting_bracket.py

Run the command shown below from repository root. Output should summarize the problem setup, a baseline solution, or diagnostic values relevant to this example.