:class:`PyLumericalMCP` ======================= .. py:class:: ansys.lumerical.mcp.server.PyLumericalMCP(config: Optional[ansys.lumerical.mcp.config.Config] = None, python_executable: Optional[str] = None, working_directory: Optional[str] = None, **kwargs) Bases: :py:obj:`ansys.common.mcp.PyAnsysBaseMCP` MCP server exposing Ansys Lumerical (via PyLumerical) to LLM agents. Hosts a single :class:`PersistentPythonSession` subprocess that owns all live Lumerical handles. Multiple named sessions of any product type (FDTD/MODE/DEVICE/INTERCONNECT) live in that subprocess as ``_lumerical_sessions[name]``. The MCP server only tracks metadata. .. !! processed by numpydoc !! .. py:currentmodule:: PyLumericalMCP Overview -------- .. tab-set:: .. tab-item:: Methods .. list-table:: :header-rows: 0 :widths: auto * - :py:attr:`~create_context` - Build the typed app context with the Lumerical-aware subprocess. * - :py:attr:`~product_startup` - Run framework startup logging after subprocess launch. * - :py:attr:`~cleanup_python_session` - Close Lumerical handles *before* tearing down the subprocess. * - :py:attr:`~product_cleanup` - No-op: Lumerical handle cleanup happens in ``cleanup_python_session``. * - :py:attr:`~product_lifespan` - Lifespan wrapper that keeps the event loop responsive on shutdown. Import detail ------------- .. code-block:: python from ansys.lumerical.mcp.server import PyLumericalMCP Method detail ------------- .. py:method:: create_context() -> ansys.lumerical.mcp.context.PyLumericalContext Build the typed app context with the Lumerical-aware subprocess. Uses :class:`LumericalPersistentPythonSession` so that long, silent Lumerical startup phases (``FDTD()`` / ``MODE()`` / ...). Don't trip the framework's 0.5 s no-data safety break and lose their output. .. !! processed by numpydoc !! .. py:method:: product_startup() -> None Run framework startup logging after subprocess launch. .. !! processed by numpydoc !! .. py:method:: cleanup_python_session() -> None Close Lumerical handles *before* tearing down the subprocess. ``_lum_close_all()`` must run while the subprocess is still alive. It is flushed here (best-effort, bounded by ``_CLOSE_ALL_JOIN_TIMEOUT_S`` via a daemon thread) before delegating to the base teardown. Running it in ``product_cleanup`` would be too late. The subprocess is gone by then, and lumapi's child processes are left orphaned. Stays synchronous so direct unit tests work without an event loop; :meth:`product_lifespan` offloads the call via :func:`asyncio.to_thread`. .. !! processed by numpydoc !! .. py:method:: product_cleanup() -> None No-op: Lumerical handle cleanup happens in ``cleanup_python_session``. .. !! processed by numpydoc !! .. py:method:: product_lifespan(server: fastmcp.FastMCP) -> AsyncIterator[ansys.lumerical.mcp.context.PyLumericalContext] :async: Lifespan wrapper that keeps the event loop responsive on shutdown. Identical to the base :meth:`PyAnsysBaseMCP.product_lifespan` except that the potentially slow :meth:`cleanup_python_session` (which runs ``_lum_close_all()`` against the live subprocess) is dispatched via :func:`asyncio.to_thread`, so the synchronous teardown can't freeze the event loop and block the STDIO transport from flushing its final JSON-RPC writes. Startup stays on the loop -- it's fast and runs before any tool traffic. .. !! processed by numpydoc !!