Skip to main content
Glama
smithery.py17.8 kB
import logging from typing import Any, Dict from ..base import ToolProvider, ToolResult, ToolSchema logger = logging.getLogger(__name__) class SmitheryToolProvider(ToolProvider): """Tool provider for smithery operations in FreeCAD.""" def __init__(self, freecad_app=None): """ Initialize the smithery tool provider. Args: freecad_app: Optional FreeCAD application instance. If None, will try to import FreeCAD. """ self.app = freecad_app if self.app is None: try: import FreeCAD import Part self.app = FreeCAD self.Part = Part logger.info("Connected to FreeCAD for smithery operations") except ImportError: logger.warning( "Could not import FreeCAD. Make sure it's installed and in your Python path." ) self.app = None self.Part = None @property def tool_schema(self) -> ToolSchema: return ToolSchema( name="smithery", description="Tools for creating blacksmith and smithery objects in FreeCAD", parameters={ "type": "object", "properties": { "action": { "type": "string", "enum": [ "create_anvil", "create_hammer", "create_tongs", "forge_blade", "create_horseshoe", ], "description": "The smithery action to perform", }, "length": { "type": "number", "description": "Length dimension in mm", }, "width": { "type": "number", "description": "Width dimension in mm", }, "height": { "type": "number", "description": "Height dimension in mm", }, "thickness": { "type": "number", "description": "Thickness in mm", }, }, "required": ["action"], }, returns={ "type": "object", "properties": { "status": {"type": "string"}, "result": {"type": "object"}, "error": {"type": "string"}, }, }, examples=[ {"action": "create_anvil", "length": 400, "width": 120, "height": 200}, { "action": "create_hammer", "handle_length": 300, "handle_diameter": 25, }, ], ) async def execute_tool(self, tool_id: str, params: Dict[str, Any]) -> ToolResult: """ Execute a smithery tool. Args: tool_id: The ID of the tool to execute params: Parameters for the tool Returns: ToolResult containing the execution status and result """ if self.app is None: return self.format_result("error", error="FreeCAD not available") action = params.get("action", tool_id) try: if action == "create_anvil": return await self._create_anvil(params) elif action == "create_hammer": return await self._create_hammer(params) elif action == "create_tongs": return await self._create_tongs(params) elif action == "forge_blade": return await self._forge_blade(params) elif action == "create_horseshoe": return await self._create_horseshoe(params) else: return self.format_result( "error", error=f"Unknown smithery action: {action}" ) except Exception as e: logger.error(f"Smithery tool failed: {e}") return self.format_result("error", error=str(e)) async def _create_anvil(self, params: Dict[str, Any]) -> ToolResult: """Create an anvil model.""" # Get parameters with default values length = params.get("length", 400.0) # mm width = params.get("width", 120.0) # mm height = params.get("height", 200.0) # mm horn_length = params.get("horn_length", 150.0) # mm # Get or create document doc = self._get_active_document() try: # Create the main body body = doc.addObject("Part::Box", "AnvilBody") body.Length = length body.Width = width body.Height = height * 0.7 # Create the tapered top part top = doc.addObject("Part::Box", "AnvilTop") top.Length = length * 0.9 top.Width = width top.Height = height * 0.3 top.Placement.Base = self.app.Vector( (length - top.Length) / 2, 0, body.Height ) # Create the horn (cone) horn = doc.addObject("Part::Cone", "AnvilHorn") horn.Radius1 = width / 3 horn.Radius2 = width / 6 horn.Height = horn_length # Rotate and position the horn horn.Placement.Rotation = self.app.Rotation(self.app.Vector(0, 1, 0), 90) horn.Placement.Base = self.app.Vector( length, width / 2, body.Height + top.Height / 2 ) # Create a fusion fusion = doc.addObject("Part::MultiFuse", "Anvil") fusion.Shapes = [body, top, horn] # Recompute document doc.recompute() return self.format_result( "success", result={ "object_id": fusion.Name, "object_type": "Anvil", "properties": { "Length": length, "Width": width, "Height": height, "HornLength": horn_length, }, }, ) except Exception as e: logger.error(f"Error creating anvil: {str(e)}") return self.format_result( "error", error=f"Failed to create anvil: {str(e)}" ) async def _create_hammer(self, params: Dict[str, Any]) -> ToolResult: """Create a blacksmith hammer model.""" # Get parameters with default values handle_length = params.get("handle_length", 300.0) # mm handle_diameter = params.get("handle_diameter", 25.0) # mm head_length = params.get("head_length", 100.0) # mm head_width = params.get("head_width", 40.0) # mm head_height = params.get("head_height", 30.0) # mm # Get or create document doc = self._get_active_document() try: # Create the handle (cylinder) handle = doc.addObject("Part::Cylinder", "HammerHandle") handle.Radius = handle_diameter / 2 handle.Height = handle_length # Rotate the handle to horizontal position handle.Placement.Rotation = self.app.Rotation(self.app.Vector(0, 1, 0), 90) # Create the hammer head head = doc.addObject("Part::Box", "HammerHead") head.Length = head_length head.Width = head_width head.Height = head_height # Position the head at the end of the handle head.Placement.Base = self.app.Vector( handle.Height - head.Length / 2, -head.Width / 2 + handle.Radius, -head.Height / 2 + handle.Radius, ) # Create a fusion fusion = doc.addObject("Part::MultiFuse", "Hammer") fusion.Shapes = [handle, head] # Recompute document doc.recompute() return self.format_result( "success", result={ "object_id": fusion.Name, "object_type": "Hammer", "properties": { "HandleLength": handle_length, "HandleDiameter": handle_diameter, "HeadLength": head_length, "HeadWidth": head_width, "HeadHeight": head_height, }, }, ) except Exception as e: logger.error(f"Error creating hammer: {str(e)}") return self.format_result( "error", error=f"Failed to create hammer: {str(e)}" ) async def _create_tongs(self, params: Dict[str, Any]) -> ToolResult: """Create blacksmith tongs model.""" # Get parameters with default values handle_length = params.get("handle_length", 300.0) # mm jaw_length = params.get("jaw_length", 80.0) # mm thickness = params.get("thickness", 10.0) # mm width = params.get("width", 20.0) # mm opening_angle = params.get("opening_angle", 15.0) # degrees # Get or create document doc = self._get_active_document() try: # Create the first handle handle1 = doc.addObject("Part::Box", "Handle1") handle1.Length = handle_length handle1.Width = width handle1.Height = thickness # Create the first jaw jaw1 = doc.addObject("Part::Box", "Jaw1") jaw1.Length = jaw_length jaw1.Width = width jaw1.Height = thickness # Position and rotate the jaw jaw1.Placement.Base = self.app.Vector(0, 0, 0) jaw1.Placement.Rotation = self.app.Rotation( self.app.Vector(0, 0, 1), opening_angle ) # Create the second handle handle2 = doc.addObject("Part::Box", "Handle2") handle2.Length = handle_length handle2.Width = width handle2.Height = thickness # Position the second handle handle2.Placement.Base = self.app.Vector(0, 0, thickness * 2) # Create the second jaw jaw2 = doc.addObject("Part::Box", "Jaw2") jaw2.Length = jaw_length jaw2.Width = width jaw2.Height = thickness # Position and rotate the jaw jaw2.Placement.Base = self.app.Vector(0, 0, thickness * 2) jaw2.Placement.Rotation = self.app.Rotation( self.app.Vector(0, 0, 1), -opening_angle ) # Create a pivot point (cylinder) pivot = doc.addObject("Part::Cylinder", "Pivot") pivot.Radius = thickness pivot.Height = thickness * 3 pivot.Placement.Base = self.app.Vector(0, width / 2, 0) pivot.Placement.Rotation = self.app.Rotation(self.app.Vector(1, 0, 0), 90) # Group into a compound object tongs = doc.addObject("Part::Compound", "Tongs") tongs.Links = [handle1, jaw1, handle2, jaw2, pivot] # Recompute document doc.recompute() return self.format_result( "success", result={ "object_id": tongs.Name, "object_type": "Tongs", "properties": { "HandleLength": handle_length, "JawLength": jaw_length, "Thickness": thickness, "Width": width, "OpeningAngle": opening_angle, }, }, ) except Exception as e: logger.error(f"Error creating tongs: {str(e)}") return self.format_result( "error", error=f"Failed to create tongs: {str(e)}" ) async def _forge_blade(self, params: Dict[str, Any]) -> ToolResult: """Create a forged blade model.""" # Get parameters with default values blade_length = params.get("blade_length", 250.0) # mm blade_width = params.get("blade_width", 40.0) # mm thickness = params.get("thickness", 4.0) # mm tang_length = params.get("tang_length", 100.0) # mm tang_width = params.get("tang_width", 15.0) # mm curvature = params.get("curvature", 10.0) # mm, how much the blade curves # Get or create document doc = self._get_active_document() try: if not self.Part: return self.format_result("error", error="Part module not available") # Create blade as a loft between two wires # Base wire (at the guard) base_points = [ self.app.Vector(0, -blade_width / 2, 0), self.app.Vector(0, blade_width / 2, 0), ] base_wire = self.Part.makePolygon(base_points) # Tip wire tip_points = [ self.app.Vector(blade_length, -curvature, 0), self.app.Vector(blade_length, 0, 0), ] tip_wire = self.Part.makePolygon(tip_points) # Create the blade face by lofting between the two wires # This is a simplification - in a real implementation we would use a more complex shape # Create a simple blade using a box for now blade = doc.addObject("Part::Box", "Blade") blade.Length = blade_length blade.Width = blade_width blade.Height = thickness # Create the tang tang = doc.addObject("Part::Box", "Tang") tang.Length = tang_length tang.Width = tang_width tang.Height = thickness tang.Placement.Base = self.app.Vector( -tang_length, (blade_width - tang_width) / 2, 0 ) # Create a fusion fusion = doc.addObject("Part::MultiFuse", "Blade") fusion.Shapes = [blade, tang] # Recompute document doc.recompute() return self.format_result( "success", result={ "object_id": fusion.Name, "object_type": "Blade", "properties": { "BladeLength": blade_length, "BladeWidth": blade_width, "Thickness": thickness, "TangLength": tang_length, "TangWidth": tang_width, "Curvature": curvature, }, }, ) except Exception as e: logger.error(f"Error forging blade: {str(e)}") return self.format_result("error", error=f"Failed to forge blade: {str(e)}") async def _create_horseshoe(self, params: Dict[str, Any]) -> ToolResult: """Create a horseshoe model.""" # Get parameters with default values outer_radius = params.get("outer_radius", 60.0) # mm inner_radius = params.get("inner_radius", 40.0) # mm thickness = params.get("thickness", 10.0) # mm opening_angle = params.get("opening_angle", 60.0) # degrees # Get or create document doc = self._get_active_document() try: if not self.Part: return self.format_result("error", error="Part module not available") # Create the outer circle outer_circle = self.Part.makeCircle(outer_radius) # Create the inner circle inner_circle = self.Part.makeCircle(inner_radius) # Create wires from the circles outer_wire = self.Part.Wire(outer_circle) inner_wire = self.Part.Wire(inner_circle) # Create a face from the wires face = self.Part.Face([outer_wire, inner_wire]) # Extrude the face into a solid solid = face.extrude(self.app.Vector(0, 0, thickness)) # Cut a portion of the horseshoe to create the opening # This is simplified - actual implementation would use more complex geometry # Create a part object horseshoe_part = doc.addObject("Part::Feature", "Horseshoe") horseshoe_part.Shape = solid # Recompute document doc.recompute() return self.format_result( "success", result={ "object_id": horseshoe_part.Name, "object_type": "Horseshoe", "properties": { "OuterRadius": outer_radius, "InnerRadius": inner_radius, "Thickness": thickness, "OpeningAngle": opening_angle, }, }, ) except Exception as e: logger.error(f"Error creating horseshoe: {str(e)}") return self.format_result( "error", error=f"Failed to create horseshoe: {str(e)}" ) def _get_active_document(self): """Get the active document or create a new one if none exists.""" if self.app.ActiveDocument: return self.app.ActiveDocument else: return self.app.newDocument("Smithery")

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/jango-blockchained/mcp-freecad'

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