Skip to main content
Glama
main.py8.55 kB
import os import tempfile from collections.abc import Callable from functools import wraps from pathlib import Path from typing import Annotated import httpx from fastmcp import Context, FastMCP from fastmcp.exceptions import ToolError from fastmcp.utilities.types import Image from fusion_client import ( ADDIN_CONNECTION_ERROR, ADDIN_TIMEOUT_ERROR, RESPONSE_PARSE_ERROR, UNKNOWN_ERROR, FusionAddinClient, format_error, ) from pydantic import BaseModel, Field, ValidationError _fusion_addin_client: FusionAddinClient | None = None def get_fusion_addin_client() -> FusionAddinClient: """FusionAddinClientのシングルトンインスタンスを取得する""" global _fusion_addin_client if _fusion_addin_client is None: _fusion_addin_client = FusionAddinClient() return _fusion_addin_client # FastMCP サーバーインスタンス mcp = FastMCP( "Fusion MCP Server", instructions="""MCP server that enables AI agents to perform CAD operations in Autodesk Fusion. Provides tools for 3D modeling, sketching, assemblies, simulations, and design automation through the Fusion API.""", ) def handle_tool_error[**P, R](tool_func: Callable[P, R]) -> Callable[P, R]: """MCPツールのエラーハンドリングをおこなうデコレータ""" @wraps(tool_func) async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: try: return await tool_func(*args, **kwargs) except ToolError: # ToolErrorはそのまま再スロー raise except httpx.ConnectError as e: raise ToolError( format_error(ADDIN_CONNECTION_ERROR["type"], ADDIN_CONNECTION_ERROR["message"]), ) from e except httpx.TimeoutException as e: raise ToolError( format_error( ADDIN_TIMEOUT_ERROR["type"], ADDIN_TIMEOUT_ERROR["message"], ), ) from e except ValidationError as e: raise ToolError( format_error( RESPONSE_PARSE_ERROR["type"], RESPONSE_PARSE_ERROR["message"], ), ) from e except Exception as e: func_name = getattr(tool_func, "__name__", "tool") error_msg = f"Failed to execute {func_name}: {e!s}" raise ToolError(format_error(UNKNOWN_ERROR["type"], error_msg)) from e return wrapper @mcp.tool @handle_tool_error async def execute_code( ctx: Context, code: Annotated[ str, Field( description="""Python script to execute in Fusion. Has access to Fusion API objects. Must be syntactically valid Python code. Use 'print()' statements to capture output.""", min_length=1, max_length=20000, ), ], summary: Annotated[ str | None, Field( description="Optional summary of the code being executed", max_length=100, default=None, ), ] = None, ) -> str: """Execute Python code in Autodesk Fusion with full API access. - Create, modify, and analyze CAD models. - Group all model changes into a single, undoable transaction. Pre-initialized objects: - `adsk`: The root API module. - `app`: The application instance. - `design`: The active design document. - `root_comp`: The root component of the design. Returns: All `print()` output as string, including error tracebacks if execution fails. Important: - The environment is reset for each execution. Include all required imports and variables every time. - Always use `print()` to show progress and results. """ connection = get_fusion_addin_client() result = await connection.call_action( "execute_code", {"code": code, "transaction_name": summary}, ) if not result.get("success", False): error_info = result.get("error", {}) error_type = error_info.get("type", "UnknownError") error_msg = error_info.get("message", "An unknown error occurred") raise ToolError(format_error(error_type, error_msg)) output: str = result.get("result", "") return output @mcp.tool @handle_tool_error async def get_viewport_screenshot() -> Image: """Capture a screenshot of the current Fusion viewport. - Captures the viewport's current visual state, including the camera's perspective, orientation, and zoom. - Ideal for verifying modeling results, documenting the design state, or providing visual feedback after a script runs. Returns: Image object with screenshot data. """ connection = get_fusion_addin_client() # 一時ファイルを作成してスクリーンショットを保存 fd, filepath_str = tempfile.mkstemp(prefix="fusion_viewport_screenshot_", suffix=".png") os.close(fd) # パスだけ必要なので、ファイルディスクリプタは閉じる filepath = Path(filepath_str) try: result = await connection.call_action( "get_viewport_screenshot", {"filepath": str(filepath)}, ) if not result.get("success", False): error_info = result.get("error", {}) error_type = error_info.get("type", "UnknownError") error_msg = error_info.get("message", "An unknown error occurred") raise ToolError(format_error(error_type, error_msg)) if not Path.exists(filepath): raise ToolError( format_error( "FusionScreenshotError", f"Screenshot was not created by Fusion Add-in. This may indicate a permission issue or Fusion internal error. Please ask the user to check Fusion's file access permissions and try again. Expected file: {filepath}", ), ) with Path.open(filepath, "rb") as f: image_bytes = f.read() return Image(data=image_bytes) finally: # 一時ファイルを削除 Path(filepath).unlink(missing_ok=True) class FusionParameter(BaseModel): name: str value: float unit: str expression: str comment: str = "" @mcp.tool @handle_tool_error async def list_user_parameters() -> list[FusionParameter]: """List all User Parameters in the active Fusion design. - Retrieves parameters explicitly created by the user. Returns: List of objects with keys: `name`, `value`, `unit`, `expression`, `comment`. """ connection = get_fusion_addin_client() result = await connection.call_action("get_user_parameters") if not result.get("success", False): error_type = result.get("error", {}).get("type", "UnknownError") error_msg = result.get("error", {}).get("message", "An unknown error occurred") raise ToolError(format_error(error_type, error_msg)) params = [FusionParameter.model_validate(param) for param in result.get("result", [])] return params @mcp.tool @handle_tool_error async def set_parameter( param_name: Annotated[ str, Field( description="The name of the user parameter to modify. The parameter must already exist.", min_length=1, ), ], expression: Annotated[ str, Field( description="The expression for the parameter's value. Can be a number (e.g., '10'), a value with units ('10 mm'), or a formula referencing other parameters ('width / 2'). Without units, the document's default is used.", min_length=1, ), ], ) -> FusionParameter: """Update a parameter's expression in Fusion. - Triggers model recomputation with new value. Returns: Object with updated parameter data: `name`, `value`, `unit`, `expression`, `comment`. """ connection = get_fusion_addin_client() result = await connection.call_action( "set_parameter", {"param_name": param_name, "expression": expression}, ) if not result.get("success", False): error_type = result.get("error", {}).get("type", "UnknownError") error_msg = result.get("error", {}).get("message", "An unknown error occurred") raise ToolError(format_error(error_type, error_msg)) changed_param = FusionParameter.model_validate(result.get("result", {})) return changed_param def main() -> None: mcp.run() if __name__ == "__main__": main()

Latest Blog Posts

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/shiguri-01/fusion-mcp'

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