Skip to content

solidworks_mcp.tools.tool_boundary

solidworks_mcp.tools.tool_boundary

The single error boundary every MCP tool runs inside (spec: tools/error-boundary).

Tools contain business logic only. They:

  • parse input with :func:normalize_input (raises :class:InvalidInputError),
  • check adapter results with :func:require_success (raises :class:ToolOperationError), and
  • return their success payload.

:func:tool_boundary turns anything raised into the uniform error payload - status, message, error_type, hint and execution_time (the adapter's timing, else the tool's own wall time) - using :class:ErrorClassifier to name the failure. :class:ToolRegistrar applies the boundary to every tool at registration, so no tool can forget it. It also adds each tool's support level (from support_levels.py) to its tags and, for a beta or experimental tool, a warning to its successful results.

Attributes

INTERNAL module-attribute

INTERNAL = 'internal'

INVALID_INPUT module-attribute

INVALID_INPUT = 'invalid_input'

NOT_SUPPORTED module-attribute

NOT_SUPPORTED = 'not_supported'

NOT_SUPPORTED_HINT module-attribute

NOT_SUPPORTED_HINT = 'The active adapter cannot perform this operation. Use the real SolidWorks adapter, another tool, or do this step in SolidWorks directly.'

SUPPORT_LEVELS module-attribute

SUPPORT_LEVELS: dict[str, Support] = {'analyze_geometry': _PLANNED, 'calculate_mass_properties': _STABLE, 'check_interference': _STABLE, 'get_mass_properties': _STABLE, 'get_material_properties': _PLANNED, 'verify_model': _STABLE, 'automation_start_macro_recording': _PLANNED, 'automation_stop_macro_recording': _PLANNED, 'batch_process_files': _PLANNED, 'create_template': _PLANNED, 'execute_workflow': _PLANNED, 'generate_vba_code': _STABLE, 'manage_design_table': _PLANNED, 'optimize_performance': _PLANNED, 'discover_solidworks_docs': _STABLE, 'find_related_api_members': _STABLE, 'lookup_api_interface': _STABLE, 'lookup_api_method': _STABLE, 'search_solidworks_api_help': _STABLE, 'add_annotation': _PLANNED, 'add_dimension': _PLANNED, 'add_drawing_view': _STABLE, 'add_note': _STABLE, 'auto_center_marks': _STABLE, 'auto_dimension_view': _PLANNED, 'check_drawing_standards': _PLANNED, 'create_detail_view': _PLANNED, 'create_drawing_view': _STABLE, 'create_section_view': _PLANNED, 'create_technical_drawing': _STABLE, 'list_drawing_views': _STABLE, 'update_sheet_format': _PLANNED, 'update_title_block': _PLANNED, 'analyze_drawing_annotations': _PLANNED, 'analyze_drawing_comprehensive': _PLANNED, 'analyze_drawing_dimensions': _PLANNED, 'analyze_drawing_views': _PLANNED, 'check_drawing_compliance': _PLANNED, 'compare_drawing_versions': _STABLE, 'generate_drawing_report': _PLANNED, 'validate_drawing_completeness': _PLANNED, 'add_datum_tag': _STABLE, 'add_gtol': _STABLE, 'arrange_dimensions': _STABLE, 'generate_drawing_package': _STABLE, 'insert_bom_table': _STABLE, 'insert_model_dimensions': _STABLE, 'set_sheet_scale': _STABLE, 'export_flat_pattern': _STABLE, 'get_custom_properties': _STABLE, 'get_material': _STABLE, 'list_available_materials': _STABLE, 'list_model_dimensions': _STABLE, 'measure_entities': _STABLE, 'set_custom_property': _STABLE, 'set_material': _STABLE, 'batch_export': _PLANNED, 'export_dwg': _STABLE, 'export_iges': _STABLE, 'export_image': _STABLE, 'export_pdf': _STABLE, 'export_step': _STABLE, 'export_stl': _STABLE, 'activate_document': _STABLE, 'batch_file_operations': _PLANNED, 'classify_feature_tree': _STABLE, 'convert_file_format': _PLANNED, 'get_file_properties': _STABLE, 'get_model_info': _STABLE, 'get_selected_entities': _STABLE, 'list_configurations': _STABLE, 'list_features': _STABLE, 'list_open_documents': _STABLE, 'load_assembly': _STABLE, 'load_part': _STABLE, 'manage_file_properties': _PLANNED, 'pack_and_go_assembly': _STABLE, 'save_as': _STABLE, 'save_assembly': _STABLE, 'save_body_as_part': _STABLE, 'save_file': _STABLE, 'save_part': _STABLE, 'analyze_macro': _PLANNED, 'batch_execute_macros': _PLANNED, 'create_macro_library': _PLANNED, 'execute_macro': _STABLE, 'optimize_macro': _PLANNED, 'start_macro_recording': _PLANNED, 'stop_macro_recording': _PLANNED, 'add_chamfer': _STABLE, 'add_fillet': _STABLE, 'add_mate': _STABLE, 'close_model': _STABLE, 'create_assembly': _STABLE, 'create_axis': _STABLE, 'create_cut_extrude': _STABLE, 'create_drawing': _STABLE, 'create_extrusion': _STABLE, 'create_loft': _STABLE, 'create_part': _STABLE, 'create_reference_plane': _STABLE, 'create_reference_point': _STABLE, 'create_revolve': _STABLE, 'create_sweep': _STABLE, 'delete_feature': _STABLE, 'get_dimension': _STABLE, 'insert_component': _STABLE, 'list_components': _STABLE, 'mirror_feature': _STABLE, 'open_model': _STABLE, 'pattern_circular': _STABLE, 'rebuild_model': _STABLE, 'rename_feature': _STABLE, 'select_face_edges': _STABLE, 'select_feature': _STABLE, 'set_component_state': _STABLE, 'set_dimension': _STABLE, 'set_units': _STABLE, 'suppress_feature': _STABLE, 'undo': _STABLE, 'reset_circuit_breaker': _STABLE, 'add_arc': _STABLE, 'add_centerline': _STABLE, 'add_circle': _STABLE, 'add_ellipse': _STABLE, 'add_line': _STABLE, 'add_polygon': _STABLE, 'add_rectangle': _STABLE, 'add_sketch_constraint': _STABLE, 'add_sketch_dimension': _STABLE, 'add_spline': _STABLE, 'check_sketch_fully_defined': _STABLE, 'create_sketch': _STABLE, 'exit_sketch': _STABLE, 'sketch_circular_pattern': _STABLE, 'sketch_linear_pattern': _STABLE, 'sketch_mirror': _STABLE, 'sketch_offset': _STABLE, 'sketch_tutorial_simple_hole': _STABLE, 'tutorial_simple_hole': _PLANNED, 'apply_template': _PLANNED, 'batch_apply_template': _PLANNED, 'compare_templates': _STABLE, 'extract_template': _PLANNED, 'list_template_library': _PLANNED, 'save_to_template_library': _PLANNED, 'generate_vba_assembly_insert': _STABLE, 'generate_vba_assembly_mates': _STABLE, 'generate_vba_batch_export': _STABLE, 'generate_vba_drawing_dimensions': _STABLE, 'generate_vba_drawing_views': _STABLE, 'generate_vba_extrusion': _STABLE, 'generate_vba_file_operations': _STABLE, 'generate_vba_macro_recorder': _STABLE, 'generate_vba_part_modeling': _STABLE, 'generate_vba_revolve': _STABLE}

TOOL_HINTS module-attribute

TOOL_HINTS: dict[str, ToolHints] = {'calculate_mass_properties': _reads('Calculate Mass Properties'), 'get_mass_properties': _reads('Get Mass Properties'), 'check_interference': _reads('Check Assembly Interference'), 'analyze_geometry': _reads('Analyze Geometry'), 'get_material_properties': _reads('Get Material Properties'), 'verify_model': _reads('Verify Model Against Expectations'), 'generate_vba_code': _reads('Generate VBA Code'), 'automation_start_macro_recording': _adds('Start Macro Recording (Automation)'), 'automation_stop_macro_recording': _discards('Stop Macro Recording (Automation)'), 'batch_process_files': _discards('Batch Process Files'), 'manage_design_table': _adds('Manage Design Table'), 'execute_workflow': _discards('Execute Workflow'), 'create_template': _overwrites('Create Template File'), 'optimize_performance': _sets('Optimize SolidWorks Performance'), 'discover_solidworks_docs': _sets('Rebuild SolidWorks API Index'), 'search_solidworks_api_help': _reads('Search SolidWorks API'), 'reset_circuit_breaker': _sets('Reset Circuit Breaker'), 'lookup_api_method': _reads('Look Up API Method'), 'lookup_api_interface': _reads('Look Up API Interface'), 'find_related_api_members': _reads('Find Related API Members'), 'create_drawing_view': _adds('Create Drawing View'), 'add_dimension': _adds('Add Drawing Dimension'), 'add_note': _adds('Add Drawing Note'), 'create_section_view': _adds('Create Section View'), 'create_detail_view': _adds('Create Detail View'), 'update_sheet_format': _sets('Update Sheet Format'), 'auto_dimension_view': _adds('Auto-Dimension Drawing View'), 'check_drawing_standards': _reads('Check Drawing Standards'), 'create_technical_drawing': _adds('Create Technical Drawing'), 'add_drawing_view': _adds('Add Drawing View'), 'add_annotation': _adds('Add Drawing Annotation'), 'update_title_block': _sets('Update Title Block'), 'list_drawing_views': _reads('List Drawing Views'), 'auto_center_marks': _adds('Add Centre Marks'), 'analyze_drawing_comprehensive': _reads('Analyze Drawing (All Checks)'), 'analyze_drawing_dimensions': _reads('Analyze Drawing Dimensions'), 'analyze_drawing_annotations': _reads('Analyze Drawing Annotations'), 'check_drawing_compliance': _reads('Check Drawing Compliance'), 'analyze_drawing_views': _reads('Analyze Drawing Views'), 'generate_drawing_report': _reads('Generate Drawing Report'), 'compare_drawing_versions': _reads('Compare Drawing Files'), 'validate_drawing_completeness': _reads('Validate Drawing Completeness'), 'insert_model_dimensions': _adds('Insert Model Dimensions'), 'arrange_dimensions': _sets('Arrange Drawing Dimensions'), 'insert_bom_table': _adds('Insert BOM Table'), 'add_gtol': _adds('Add GD&T Frame'), 'add_datum_tag': _adds('Add Datum Tag'), 'set_sheet_scale': _sets('Set Sheet Scale'), 'generate_drawing_package': _overwrites('Generate Drawing Package'), 'list_model_dimensions': _reads('List Model Dimensions'), 'measure_entities': _reads('Measure Entities'), 'get_material': _reads('Get Material'), 'set_material': _sets('Set Material'), 'list_available_materials': _reads('List Available Materials'), 'get_custom_properties': _reads('Get Custom Properties'), 'set_custom_property': _overwrites('Set Custom Property'), 'export_flat_pattern': _overwrites('Export Flat Pattern'), 'export_step': _overwrites('Export STEP'), 'export_iges': _overwrites('Export IGES'), 'export_stl': _overwrites('Export STL'), 'export_pdf': _overwrites('Export PDF'), 'export_dwg': _overwrites('Export DWG'), 'export_image': _overwrites('Export Image'), 'batch_export': _overwrites('Batch Export'), 'save_file': _overwrites('Save File'), 'save_as': _overwrites('Save As'), 'get_file_properties': _reads('Get File Properties'), 'get_model_info': _reads('Get Model Info'), 'list_features': _reads('List Features'), 'get_selected_entities': _reads('Get Selected Entities'), 'classify_feature_tree': _reads('Classify Feature Tree'), 'list_configurations': _reads('List Configurations'), 'manage_file_properties': _discards('Manage File Properties'), 'convert_file_format': _overwrites('Convert File Format'), 'batch_file_operations': _discards('Batch File Operations'), 'load_part': _sets('Load Part'), 'load_assembly': _sets('Load Assembly'), 'save_part': _overwrites('Save Part'), 'save_assembly': _overwrites('Save Assembly'), 'pack_and_go_assembly': _overwrites('Pack and Go Assembly'), 'list_open_documents': _reads('List Open Documents'), 'activate_document': _sets('Activate Document'), 'save_body_as_part': _overwrites('Save Body as Part'), 'start_macro_recording': _adds('Start Macro Recording'), 'stop_macro_recording': _discards('Stop Macro Recording'), 'execute_macro': _discards('Run Macro'), 'analyze_macro': _reads('Analyze Macro'), 'batch_execute_macros': _discards('Run Macros in Batch'), 'optimize_macro': _reads('Optimize Macro'), 'create_macro_library': _overwrites('Create Macro Library'), 'open_model': _sets('Open Model'), 'create_part': _adds('Create Part'), 'create_assembly': _adds('Create Assembly'), 'create_drawing': _adds('Create Drawing'), 'close_model': _discards('Close Model'), 'create_extrusion': _adds('Create Extrusion'), 'create_revolve': _adds('Create Revolve'), 'get_dimension': _reads('Get Dimension'), 'set_dimension': _sets('Set Dimension'), 'set_units': _sets('Set Units'), 'create_cut_extrude': _adds('Create Extruded Cut'), 'add_fillet': _adds('Add Fillet'), 'add_chamfer': _adds('Add Chamfer'), 'create_sweep': _adds('Create Sweep'), 'create_loft': _adds('Create Loft'), 'insert_component': _adds('Insert Component'), 'add_mate': _adds('Add Mate'), 'set_component_state': _sets('Set Component State'), 'list_components': _reads('List Components'), 'delete_feature': _discards('Delete Feature'), 'suppress_feature': _sets('Suppress or Unsuppress Feature'), 'rename_feature': _sets('Rename Feature'), 'select_feature': _sets('Select Feature'), 'select_face_edges': _sets('Select Face Edges'), 'rebuild_model': _sets('Rebuild Model'), 'undo': _discards('Undo'), 'create_reference_plane': _adds('Create Reference Plane'), 'create_axis': _adds('Create Reference Axis'), 'create_reference_point': _adds('Create Reference Point'), 'mirror_feature': _adds('Mirror Feature'), 'pattern_circular': _adds('Circular Pattern'), 'create_sketch': _adds('Create Sketch'), 'add_line': _adds('Add Sketch Line'), 'add_circle': _adds('Add Sketch Circle'), 'add_rectangle': _adds('Add Sketch Rectangle'), 'exit_sketch': _sets('Exit Sketch'), 'check_sketch_fully_defined': _reads('Check Sketch Fully Defined'), 'add_arc': _adds('Add Sketch Arc'), 'add_spline': _adds('Add Sketch Spline'), 'add_centerline': _adds('Add Sketch Centerline'), 'add_polygon': _adds('Add Sketch Polygon'), 'add_ellipse': _adds('Add Sketch Ellipse'), 'add_sketch_constraint': _adds('Add Sketch Relation'), 'add_sketch_dimension': _adds('Add Sketch Dimension'), 'sketch_linear_pattern': _adds('Sketch Linear Pattern'), 'sketch_circular_pattern': _adds('Sketch Circular Pattern'), 'sketch_mirror': _adds('Mirror Sketch Entities'), 'sketch_offset': _adds('Offset Sketch Entities'), 'sketch_tutorial_simple_hole': _adds('Tutorial: Hole Sketch'), 'tutorial_simple_hole': _adds('Tutorial: Simple Hole'), 'extract_template': _overwrites('Extract Template'), 'apply_template': _overwrites('Apply Template'), 'batch_apply_template': _overwrites('Apply Template in Batch'), 'compare_templates': _reads('Compare Templates'), 'save_to_template_library': _overwrites('Save to Template Library'), 'list_template_library': _reads('List Template Library'), 'generate_vba_extrusion': _reads('Generate VBA: Extrusion'), 'generate_vba_revolve': _reads('Generate VBA: Revolve'), 'generate_vba_assembly_insert': _reads('Generate VBA: Insert Component'), 'generate_vba_drawing_views': _reads('Generate VBA: Drawing Views'), 'generate_vba_batch_export': _reads('Generate VBA: Batch Export'), 'generate_vba_part_modeling': _reads('Generate VBA: Part Modeling'), 'generate_vba_assembly_mates': _reads('Generate VBA: Assembly Mates'), 'generate_vba_drawing_dimensions': _reads('Generate VBA: Drawing Dimensions'), 'generate_vba_file_operations': _reads('Generate VBA: File Operations'), 'generate_vba_macro_recorder': _reads('Generate VBA: Macro Recorder')}

_CLASSIFIER module-attribute

_CLASSIFIER = ErrorClassifier()

_INVALID_INPUT_HINT module-attribute

_INVALID_INPUT_HINT = "Fix the named field(s) and call the tool again; see the tool's input schema for the expected types and units."

Classes

AdapterResult dataclass

AdapterResult(status: AdapterResultStatus, data: T | None = None, error: str | None = None, execution_time: float | None = None, metadata: dict[str, Any] | None = None)

Result wrapper for adapter operations.

Attributes:

Name Type Description
data T | None

The data value.

error str | None

The error value.

execution_time float | None

The execution time value.

metadata dict[str, Any] | None

The metadata value.

status AdapterResultStatus

The status value.

Attributes
is_error property
is_error: bool

Check if operation had an error.

Returns:

Name Type Description
bool bool

True if error, otherwise False.

is_success property
is_success: bool

Check if operation was successful.

Returns:

Name Type Description
bool bool

True if success, otherwise False.

Classification dataclass

Classification(error_type: str, hint: str)

The category of a failure and the next step to suggest.

ErrorClassifier

ErrorClassifier(rules: tuple[ErrorRule, ...] = DEFAULT_RULES)

Map a failure to a :class:Classification using ordered rules.

Source code in src/solidworks_mcp/tools/error_classifier.py
def __init__(self, rules: tuple[ErrorRule, ...] = DEFAULT_RULES) -> None:
    self._rules = rules
Methods:
classify
classify(message: str, exc: BaseException | None = None, *, fallback: str = OPERATION_FAILED) -> Classification

Classify message (and exc when there is one).

Parameters:

Name Type Description Default
message str

The error text the caller will see.

required
exc BaseException | None

The exception, when the failure was raised rather than returned.

None
fallback str

Category for an unrecognised failure: operation_failed for adapter errors, internal for unexpected exceptions.

OPERATION_FAILED

Returns:

Name Type Description
Classification Classification

The first matching rule, else the fallback.

Source code in src/solidworks_mcp/tools/error_classifier.py
def classify(
    self,
    message: str,
    exc: BaseException | None = None,
    *,
    fallback: str = OPERATION_FAILED,
) -> Classification:
    """Classify ``message`` (and ``exc`` when there is one).

    Args:
        message: The error text the caller will see.
        exc: The exception, when the failure was raised rather than returned.
        fallback: Category for an unrecognised failure: ``operation_failed``
            for adapter errors, ``internal`` for unexpected exceptions.

    Returns:
        Classification: The first matching rule, else the fallback.
    """
    hresult = extract_hresult(message, exc)
    for rule in self._rules:
        if rule.matches(message, hresult):
            return Classification(rule.error_type, rule.hint)
    return Classification(fallback, _FALLBACK_HINTS[fallback])

InvalidInputError

Bases: ValueError

The tool's input failed validation; the adapter was not called.

Support dataclass

Support(level: SupportLevel, reason: str = '')

One tool's support level and why it isn't higher.

Attributes:

Name Type Description
level SupportLevel

The tool's support level.

reason str

One line on the gap; empty only for a stable tool.

Methods:
__post_init__
__post_init__() -> None

Require a reason for every level below stable.

Raises:

Type Description
ValueError

If a level below stable has no reason, or a stable tool has one.

Source code in src/solidworks_mcp/tools/support_levels.py
def __post_init__(self) -> None:
    """Require a reason for every level below stable.

    Raises:
        ValueError: If a level below stable has no reason, or a stable
            tool has one.
    """
    if (self.level is SupportLevel.STABLE) == bool(self.reason):
        raise ValueError(
            "a reason is required below stable, and only there: "
            f"{self.level.value!r} with reason {self.reason!r}"
        )
warning
warning() -> str | None

The sentence a successful beta or experimental result carries.

Returns:

Type Description
str | None

str | None: "Beta: <reason>" or "Experimental: <reason>";

str | None

None for stable and planned tools (a planned tool never succeeds).

Source code in src/solidworks_mcp/tools/support_levels.py
def warning(self) -> str | None:
    """The sentence a successful beta or experimental result carries.

    Returns:
        str | None: ``"Beta: <reason>"`` or ``"Experimental: <reason>"``;
        None for stable and planned tools (a planned tool never succeeds).
    """
    if self.level in (SupportLevel.BETA, SupportLevel.EXPERIMENTAL):
        return f"{self.level.value.capitalize()}: {self.reason}"
    return None

ToolHints dataclass

ToolHints(title: str, read_only: bool, destructive: bool, idempotent: bool)

One tool's title and behaviour hints.

Attributes:

Name Type Description
title str

A short human-readable name for the tool.

read_only bool

The tool changes nothing.

destructive bool

The tool may delete, overwrite or discard work.

idempotent bool

Repeating a call with the same input has no further effect.

Methods:
__post_init__
__post_init__() -> None

Reject combinations that contradict each other.

Raises:

Type Description
ValueError

If a read-only tool is marked destructive or not idempotent.

Source code in src/solidworks_mcp/tools/tool_hints.py
def __post_init__(self) -> None:
    """Reject combinations that contradict each other.

    Raises:
        ValueError: If a read-only tool is marked destructive or not
            idempotent.
    """
    if self.read_only and (self.destructive or not self.idempotent):
        raise ValueError(
            f"{self.title!r}: a read-only tool is never destructive "
            "and always idempotent"
        )
annotations
annotations() -> ToolAnnotations

The hints as MCP tool annotations.

Returns:

Name Type Description
ToolAnnotations ToolAnnotations

title plus the three hints.

Source code in src/solidworks_mcp/tools/tool_hints.py
def annotations(self) -> ToolAnnotations:
    """The hints as MCP tool annotations.

    Returns:
        ToolAnnotations: ``title`` plus the three hints.
    """
    return ToolAnnotations(
        title=self.title,
        readOnlyHint=self.read_only,
        destructiveHint=self.destructive,
        idempotentHint=self.idempotent,
    )

ToolOperationError

ToolOperationError(message: str, execution_time: float | None = None, data: Any = None)

Bases: Exception

An adapter operation reported failure.

Attributes:

Name Type Description
execution_time

The adapter's timing for the failed call, if any.

data

Diagnostic data that explains the failure (e.g. rebuild errors), returned to the caller under data.

Source code in src/solidworks_mcp/tools/tool_boundary.py
def __init__(
    self,
    message: str,
    execution_time: float | None = None,
    data: Any = None,
) -> None:
    super().__init__(message)
    self.execution_time = execution_time
    self.data = data

ToolRegistrar

ToolRegistrar(mcp: Any, classifier: ErrorClassifier = _CLASSIFIER, hints: Mapping[str, ToolHints] = TOOL_HINTS, levels: Mapping[str, Support] = SUPPORT_LEVELS)

A FastMCP stand-in whose tool() applies :func:tool_boundary first.

It also gives each tool its title and behaviour hints from the hints table, its family tag (see :func:tool_family), and its support level from the levels table: a level:<level> tag and, below stable, a warning on success. Everything else is forwarded to the wrapped server unchanged.

Source code in src/solidworks_mcp/tools/tool_boundary.py
def __init__(
    self,
    mcp: Any,
    classifier: ErrorClassifier = _CLASSIFIER,
    hints: Mapping[str, ToolHints] = TOOL_HINTS,
    levels: Mapping[str, Support] = SUPPORT_LEVELS,
) -> None:
    self._mcp = mcp
    self._classifier = classifier
    self._hints = hints
    self._levels = levels
Methods:
of classmethod
of(mcp: Any) -> ToolRegistrar

Wrap mcp unless it already is a registrar (idempotent).

Every register_<area>_tools starts with mcp = ToolRegistrar.of(mcp) so its tools get the boundary however the module is registered.

Source code in src/solidworks_mcp/tools/tool_boundary.py
@classmethod
def of(cls, mcp: Any) -> ToolRegistrar:
    """Wrap ``mcp`` unless it already is a registrar (idempotent).

    Every ``register_<area>_tools`` starts with ``mcp = ToolRegistrar.of(mcp)``
    so its tools get the boundary however the module is registered.
    """
    return mcp if isinstance(mcp, cls) else cls(mcp)
tool
tool(*args: Any, **kwargs: Any) -> Any

Same contract as FastMCP.tool, with the boundary and hints applied.

Source code in src/solidworks_mcp/tools/tool_boundary.py
def tool(self, *args: Any, **kwargs: Any) -> Any:
    """Same contract as ``FastMCP.tool``, with the boundary and hints applied."""
    if args and callable(args[0]) and len(args) == 1 and not kwargs:
        fn = args[0]
        return self._mcp.tool(**self._metadata(fn, {}))(self._wrap(fn, fn.__name__))

    if args and isinstance(args[0], str):
        kwargs = {"name": args[0], **kwargs}
        args = args[1:]

    def decorator(fn: Callable[..., Awaitable[dict[str, Any]]]) -> Any:
        register = self._mcp.tool(*args, **self._metadata(fn, kwargs))
        return register(self._wrap(fn, kwargs.get("name") or fn.__name__))

    return decorator

UnsupportedOperationError

Bases: Exception

The active adapter has no capability for the requested operation.

Functions:

describe_validation_error

describe_validation_error(exc: ValidationError) -> str

Render a pydantic error as field: problem; field: problem.

Source code in src/solidworks_mcp/tools/tool_boundary.py
def describe_validation_error(exc: ValidationError) -> str:
    """Render a pydantic error as ``field: problem; field: problem``."""
    parts = []
    for error in exc.errors():
        field = ".".join(str(p) for p in error.get("loc", ())) or "input"
        parts.append(f"{field}: {error.get('msg', 'invalid')}")
    return "; ".join(parts)

error_payload

error_payload(message: str, classification: Classification, execution_time: float | None = None, data: Any = None) -> dict[str, Any]

Build the spec's error payload.

Source code in src/solidworks_mcp/tools/tool_boundary.py
def error_payload(
    message: str,
    classification: Classification,
    execution_time: float | None = None,
    data: Any = None,
) -> dict[str, Any]:
    """Build the spec's error payload."""
    payload: dict[str, Any] = {
        "status": "error",
        "message": message,
        "error_type": classification.error_type,
        "hint": classification.hint,
    }
    payload["execution_time"] = execution_time
    if data is not None:
        payload["data"] = data
    return payload

has_tool_boundary

has_tool_boundary(fn: Any) -> bool

True when fn was wrapped by :func:tool_boundary.

Source code in src/solidworks_mcp/tools/tool_boundary.py
def has_tool_boundary(fn: Any) -> bool:
    """True when ``fn`` was wrapped by :func:`tool_boundary`."""
    return bool(getattr(fn, "__tool_boundary__", False))

model_parameters

model_parameters(fn: Callable[..., Any]) -> dict[str, type[BaseModel]]

The parameters of fn annotated with a pydantic model.

A parameter counts when its annotation is a model, or a model | None.

Parameters:

Name Type Description Default
fn Callable[..., Any]

The tool function.

required

Returns:

Type Description
dict[str, type[BaseModel]]

dict[str, type[BaseModel]]: Parameter name to its model; empty when

dict[str, type[BaseModel]]

the annotations can't be resolved (a model defined inside a function).

Source code in src/solidworks_mcp/tools/tool_boundary.py
def model_parameters(fn: Callable[..., Any]) -> dict[str, type[BaseModel]]:
    """The parameters of ``fn`` annotated with a pydantic model.

    A parameter counts when its annotation is a model, or a model ``| None``.

    Args:
        fn: The tool function.

    Returns:
        dict[str, type[BaseModel]]: Parameter name to its model; empty when
        the annotations can't be resolved (a model defined inside a function).
    """
    try:
        hints = typing.get_type_hints(fn)
    except NameError:
        return {}
    models: dict[str, type[BaseModel]] = {}
    for name, hint in hints.items():
        options = (
            typing.get_args(hint)
            if isinstance(hint, types.UnionType)
            or typing.get_origin(hint) is typing.Union
            else (hint,)
        )
        found = [o for o in options if isinstance(o, type) and issubclass(o, BaseModel)]
        if name != "return" and len(found) == 1:
            models[name] = found[0]
    return models

normalize_input

normalize_input(input_data: Any, model_type: type[TModel]) -> TModel

Validate input_data as model_type.

Parameters:

Name Type Description Default
input_data Any

A model_type instance, a mapping of its fields, another pydantic model (converted through model_dump), or None for a tool whose fields all have defaults.

required
model_type type[TModel]

The tool's pydantic input model.

required

Returns:

Name Type Description
TModel TModel

The validated model.

Raises:

Type Description
InvalidInputError

Naming the offending field(s).

Source code in src/solidworks_mcp/tools/tool_boundary.py
def normalize_input[TModel: BaseModel](
    input_data: Any, model_type: type[TModel]
) -> TModel:
    """Validate ``input_data`` as ``model_type``.

    Args:
        input_data: A ``model_type`` instance, a mapping of its fields, another
            pydantic model (converted through ``model_dump``), or ``None`` for
            a tool whose fields all have defaults.
        model_type: The tool's pydantic input model.

    Returns:
        TModel: The validated model.

    Raises:
        InvalidInputError: Naming the offending field(s).
    """
    if isinstance(input_data, model_type):
        return input_data
    if input_data is None:
        input_data = {}
    elif not isinstance(input_data, dict) and hasattr(input_data, "model_dump"):
        input_data = input_data.model_dump()
    try:
        return model_type.model_validate(input_data)
    except ValidationError as exc:  # also wraps ValueErrors from validators
        raise InvalidInputError(describe_validation_error(exc)) from exc

removed_inputs

removed_inputs(model: BaseModel) -> list[str]

The removed inputs model was given, each with what to use instead.

Parameters:

Name Type Description Default
model BaseModel

A validated tool input.

required

Returns:

Type Description
list[str]

list[str]: One sentence per removed field present, e.g.

list[str]

"components is no longer an input: call insert_component for each

list[str]

part". Empty when none was sent.

Source code in src/solidworks_mcp/tools/input_compat.py
def removed_inputs(model: BaseModel) -> list[str]:
    """The removed inputs ``model`` was given, each with what to use instead.

    Args:
        model: A validated tool input.

    Returns:
        list[str]: One sentence per removed field present, e.g.
        "`components` is no longer an input: call `insert_component` for each
        part". Empty when none was sent.
    """
    removed = REMOVED_INPUTS.get(type(model).__name__, {})
    extras = model.model_extra or {}
    return [
        f"`{field}` is no longer an input: {pointer}"
        for field, pointer in removed.items()
        if field in extras
    ]

require_capability

require_capability(adapter: Any, method: str, message: str) -> None

Raise :class:UnsupportedOperationError unless adapter has method.

Parameters:

Name Type Description Default
adapter Any

The active SolidWorks adapter.

required
method str

The adapter method the tool needs.

required
message str

What was not done, and what to do instead.

required
Source code in src/solidworks_mcp/tools/tool_boundary.py
def require_capability(adapter: Any, method: str, message: str) -> None:
    """Raise :class:`UnsupportedOperationError` unless ``adapter`` has ``method``.

    Args:
        adapter: The active SolidWorks adapter.
        method: The adapter method the tool needs.
        message: What was not done, and what to do instead.
    """
    if not hasattr(adapter, method):
        raise UnsupportedOperationError(message)

require_success

require_success(result: AdapterResult[TResult], failure_message: str = 'Operation failed', *, context: str | None = None, with_data: bool = False) -> AdapterResult[TResult]

Return result when it succeeded; raise otherwise.

Mirrors the if result.is_success: ... else: error the tools used before the boundary, so anything that is not a success - including a warning - is reported as a failure. Tools that treat a warning as success check result.status themselves before calling this.

Parameters:

Name Type Description Default
result AdapterResult[TResult]

The adapter result to check.

required
failure_message str

Message used when the adapter gave none.

'Operation failed'
context str | None

Prefix naming what failed, e.g. "Failed to create part", giving "<context>: <adapter error>".

None
with_data bool

Return the adapter's data with the error, for tools whose failure data is the diagnosis (e.g. rebuild_model).

False

Raises:

Type Description
ToolOperationError

Carrying the adapter's message and timing.

Source code in src/solidworks_mcp/tools/tool_boundary.py
def require_success[TResult](
    result: AdapterResult[TResult],
    failure_message: str = "Operation failed",
    *,
    context: str | None = None,
    with_data: bool = False,
) -> AdapterResult[TResult]:
    """Return ``result`` when it succeeded; raise otherwise.

    Mirrors the ``if result.is_success: ... else: error`` the tools used before
    the boundary, so anything that is not a success - including a warning - is
    reported as a failure. Tools that treat a warning as success check
    ``result.status`` themselves before calling this.

    Args:
        result: The adapter result to check.
        failure_message: Message used when the adapter gave none.
        context: Prefix naming what failed, e.g. ``"Failed to create part"``,
            giving ``"<context>: <adapter error>"``.
        with_data: Return the adapter's ``data`` with the error, for tools whose
            failure data is the diagnosis (e.g. ``rebuild_model``).

    Raises:
        ToolOperationError: Carrying the adapter's message and timing.
    """
    if not result.is_success:
        message = str(result.error or failure_message)
        raise ToolOperationError(
            f"{context}: {message}" if context else message,
            result.execution_time,
            result.data if with_data else None,
        )
    return result

tool_boundary

tool_boundary(fn: Callable[..., Awaitable[dict[str, Any]]], classifier: ErrorClassifier = _CLASSIFIER, warning: str | None = None) -> Callable[..., Awaitable[dict[str, Any]]]

Wrap an async tool so every failure becomes an error payload.

functools.wraps keeps the signature, so FastMCP builds the same input schema for the wrapped tool.

Parameters:

Name Type Description Default
fn Callable[..., Awaitable[dict[str, Any]]]

The tool function.

required
classifier ErrorClassifier

Names the failure in an error payload.

_CLASSIFIER
warning str | None

Added as warning to every successful result (a beta or experimental tool's level), after any warning of the tool's own; errors carry a hint instead.

None

A dict passed where the tool expects its input model is validated as that model first, so a script can call any tool function with a dict, as an MCP client effectively does. An input the tool no longer takes (input_compat.REMOVED_INPUTS) is refused as invalid input before the tool runs.

Returns:

Type Description
Callable[..., Awaitable[dict[str, Any]]]

The wrapped tool.

Source code in src/solidworks_mcp/tools/tool_boundary.py
def tool_boundary(
    fn: Callable[..., Awaitable[dict[str, Any]]],
    classifier: ErrorClassifier = _CLASSIFIER,
    warning: str | None = None,
) -> Callable[..., Awaitable[dict[str, Any]]]:
    """Wrap an async tool so every failure becomes an error payload.

    ``functools.wraps`` keeps the signature, so FastMCP builds the same input
    schema for the wrapped tool.

    Args:
        fn: The tool function.
        classifier: Names the failure in an error payload.
        warning: Added as ``warning`` to every successful result (a beta or
            experimental tool's level), after any warning of the tool's own;
            errors carry a ``hint`` instead.

    A dict passed where the tool expects its input model is validated as
    that model first, so a script can call any tool function with a dict, as
    an MCP client effectively does. An input the tool no longer takes
    (``input_compat.REMOVED_INPUTS``) is refused as invalid input before the
    tool runs.

    Returns:
        The wrapped tool.
    """
    tool_name = fn.__name__
    models = model_parameters(fn)
    signature = inspect.signature(fn)

    def with_models(
        args: tuple[Any, ...], kwargs: dict[str, Any]
    ) -> inspect.BoundArguments:
        bound = signature.bind_partial(*args, **kwargs)
        for name, model in models.items():
            if isinstance(bound.arguments.get(name), dict):
                bound.arguments[name] = normalize_input(bound.arguments[name], model)
            value = bound.arguments.get(name)
            removed = removed_inputs(value) if isinstance(value, BaseModel) else []
            if removed:
                raise InvalidInputError("; ".join(removed))
        return bound

    @functools.wraps(fn)
    async def wrapper(*args: Any, **kwargs: Any) -> dict[str, Any]:
        started = time.perf_counter()

        def elapsed() -> float:
            return time.perf_counter() - started

        try:
            bound = with_models(args, kwargs)
            result = await fn(*bound.args, **bound.kwargs)
        except InvalidInputError as exc:
            return error_payload(
                f"Invalid input: {exc}",
                Classification(INVALID_INPUT, _INVALID_INPUT_HINT),
                elapsed(),
            )
        except ValidationError as exc:
            return error_payload(
                f"Invalid input: {describe_validation_error(exc)}",
                Classification(INVALID_INPUT, _INVALID_INPUT_HINT),
                elapsed(),
            )
        except UnsupportedOperationError as exc:
            return error_payload(
                str(exc),
                Classification(NOT_SUPPORTED, NOT_SUPPORTED_HINT),
                elapsed(),
            )
        except ToolOperationError as exc:
            message = str(exc)
            # Adapter guards (wrong document type, no active model) reject a
            # call before timing it; fall back to the tool's own wall time.
            timing = exc.execution_time if exc.execution_time is not None else elapsed()
            return error_payload(
                message, classifier.classify(message), timing, exc.data
            )
        except Exception as exc:
            logger.exception("Tool {} raised", tool_name)
            message = f"{tool_name} failed: {type(exc).__name__}: {exc}"
            return error_payload(
                message,
                classifier.classify(str(exc), exc, fallback=INTERNAL),
                elapsed(),
            )
        if warning is not None and result.get("status") == "success":
            # A tool's own warning (e.g. a cut that removed nothing) comes first.
            own = result.get("warning")
            return {**result, "warning": f"{own} {warning}" if own else warning}
        return result

    wrapper.__tool_boundary__ = True  # type: ignore[attr-defined]
    return wrapper

tool_family

tool_family(fn: Callable[..., Any]) -> str

The family tag of a tool: the name of the module that defines it.

Parameters:

Name Type Description Default
fn Callable[..., Any]

The tool function.

required

Returns:

Name Type Description
str str

The module's last dotted part, e.g. "modeling".

Source code in src/solidworks_mcp/tools/tool_boundary.py
def tool_family(fn: Callable[..., Any]) -> str:
    """The family tag of a tool: the name of the module that defines it.

    Args:
        fn: The tool function.

    Returns:
        str: The module's last dotted part, e.g. ``"modeling"``.
    """
    return fn.__module__.rsplit(".", 1)[-1]

with_tool_boundary

with_tool_boundary(mcp: TServer) -> TServer

Return mcp wrapped in a :class:ToolRegistrar (typed as mcp).

The registrar duck-types the FastMCP surface the tool modules use (tool() plus attribute forwarding), so modules keep their annotations.

Source code in src/solidworks_mcp/tools/tool_boundary.py
def with_tool_boundary[TServer](mcp: TServer) -> TServer:
    """Return ``mcp`` wrapped in a :class:`ToolRegistrar` (typed as ``mcp``).

    The registrar duck-types the FastMCP surface the tool modules use
    (``tool()`` plus attribute forwarding), so modules keep their annotations.
    """
    return cast(TServer, ToolRegistrar.of(mcp))