Implement a custom tool#

Learn how to implement a custom MCP tool for PyMechanical-MCP.

Tool overview#

A tool is a Python function decorated with @app.tool(...) that performs a specific task using an Ansys Mechanical instance. Tools typically:

  • Accept structured input parameters.

  • Perform an operation (usually by calling run_python_script against the Mechanical API).

  • Return structured output as a JSON string or plain text.

MCP clients discover registered tools and call them with the appropriate parameters.

Tool anatomy#

Here is a minimal tool structure:

from mcp.server.fastmcp import Context
from ansys.mechanical.mcp import app
from ansys.mechanical.mcp.tools import REQUIRES_MECHANICAL_TAG

@app.tool(tags={REQUIRES_MECHANICAL_TAG})
def my_analysis_tool(ctx: Context, model_name: str, load_magnitude: float) -> str:
    """Run a simple structural analysis.

    Parameters
    ----------
    ctx : Context
        MCP context with access to the Mechanical instance.
    model_name : str
        Name or description of the model.
    load_magnitude : float
        Load magnitude in Newtons.

    Returns
    -------
    str
        Analysis result summary.
    """
    mechanical = ctx.request_context.lifespan_context.mechanical

    if mechanical is None:
        return "No Mechanical connection available. Use connect_to_mechanical first."

    try:
        result = mechanical.run_python_script(f"str({load_magnitude})")
        return f"Analysis '{model_name}' completed. Result: {result}"
    except Exception as e:
        return f"Error during analysis: {e}"

Key components#

  • ``REQUIRES_MECHANICAL_TAG``: Tags the tool so it is hidden until a Mechanical session is active.

  • ``ctx``: Provides access to the live Mechanical instance via ctx.request_context.lifespan_context.mechanical.

  • Input parameters: Typed Python parameters become the MCP tool input schema automatically.

  • Error handling: Return a plain string error message rather than raising exceptions.

  • Return value: JSON string or plain text received by MCP clients as the tool result.

Connection-gating#

Tools tagged with REQUIRES_MECHANICAL_TAG remain hidden until launch_mechanical or connect_to_mechanical succeeds. Always-available tools (such as check_mechanical_status) must not carry this tag.

Add any always-available tool name to the ALWAYS_AVAILABLE_TOOLS allowlist in tests/test_tools.py::TestRequiresMechanicalVisibility::test_no_tool_surface_drift.

Registering the tool#

Once you write the function:

  1. Place it in src/ansys/mechanical/mcp/tools.py.

  2. Add the tool name to src/ansys/mechanical/mcp/toolsets.py under the appropriate tool group.

  3. Document the tool in Tools and capabilities.

  4. Run pytest -m "not integration" to confirm the surface-drift test still passes.

Best practices#

  • Write a NumPy-style docstring for every tool.

  • Use type hints for all parameters and the return value.

  • Validate inputs at the function boundary and return a descriptive error string.

  • Use json.dumps to safely escape file paths passed into Mechanical scripts.

  • Keep tools focused: one engineering action per tool.

Tool testing#

Test your tool in the following ways:

  • Unit testing: Use mock contexts (MagicMock) to test tool logic without a live Mechanical instance.

  • Integration testing: Run with an actual Mechanical connection using pytest -m integration.

  • Error cases: Test invalid inputs, missing connections, and edge conditions.

  • Surface drift check: Run pytest tests/test_tools.py -k test_no_tool_surface_drift after adding or removing tools.

Tool debugging#

Use these techniques when troubleshooting a tool:

  • Logging: Use logger = get_logger(__name__) from ansys.common.mcp to emit structured log messages.

  • Status messages: Return detailed status strings at intermediate steps.

  • Script tracing: Add ExtAPI.Log.WriteMessage() calls inside Mechanical scripts.

  • Screenshots: Call screenshot after key model-setup steps to verify the state visually.

Advanced topics#

  • Tool composition: Combine multiple low-level script calls into a higher-level orchestration tool.

  • Streaming results: For tools producing large tabular outputs, consider chunking the return string.

  • Persistent session: Use run_python_code to share state between tool calls in the persistent Python session rather than re-importing data on each call.

See also#