PyExample-MCP#

This example shows how to implement a minimal MCP server for a hypothetical PyAnsys library named PyExample.

Project structure#

The very first step is to create the project structure. This example uses a standard Python package layout with a src directory:

pyexample-mcp/
├── pyproject.toml
├── README.md
└── src/
    └── pyexample_mcp/
        ├── __init__.py
        ├── __main__.py
        ├── server.py
        └── context.py

Once the structure is in place, you can start implementing the server.

Define the context#

It is recommended to define a custom context class that inherits from ansys.common.mcp.context.PyAnsysBaseAppContext. This context will hold any shared state or resources needed by your MCP tools.

File: src/pyexample_mcp/context.py

# Copyright (C) 2025 - 2026 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Context for PyExample MCP server."""

from dataclasses import dataclass, field
from typing import Any, Optional

from ansys.common.mcp import PyAnsysBaseAppContext


@dataclass
class PyExampleContext(PyAnsysBaseAppContext):
    """Application context for PyExample MCP server.

    Attributes
    ----------
    example_instance : Optional[Any]
        The connected PyExample instance
    simulation_results : dict
        Storage for simulation results

    """

    example_instance: Optional[Any] = None
    simulation_results: dict = field(default_factory=dict)

The context holds shared state accessible from all tools. See Architecture for details on context management.

Implement the server#

The server class inherits from ansys.common.mcp.server.PyAnsysBaseMCP and implements the required methods to handle incoming requests.

File: src/pyexample_mcp/server.py

# Copyright (C) 2025 - 2026 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""MCP server for PyExample."""

from ansys.common.mcp import PersistentPythonSession, PyAnsysBaseMCP
from ansys.common.mcp.logging_config import get_logger
from pyexample_mcp.context import PyExampleContext

logger = get_logger(__name__)


class PyExampleMCP(PyAnsysBaseMCP):
    """MCP Server for PyExample.

    This server enables AI assistants to interact with PyExample
    for simulation and analysis workflows.
    """

    def __init__(self, launch_mode: str = "local", timeout: int = 60, *args, **kwargs):
        """Initialize PyExample MCP server.

        Parameters
        ----------
        launch_mode : str
            Launch mode for PyExample ('local' or 'remote')
        timeout : int
            Connection timeout in seconds
        *args : tuple
            Additional positional arguments passed to parent class
        **kwargs : dict
            Additional keyword arguments passed to parent class

        """
        self.launch_mode = launch_mode
        self.timeout = timeout
        super().__init__(*args, **kwargs)

    def create_context(self) -> PyExampleContext:
        """Create PyExample-specific context.

        Returns
        -------
        PyExampleContext
            Context instance with Python session and command history

        """
        # Custom startup code for PyExample workflows
        startup_code = """
# Standard scientific libraries
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use('Agg')  # Non-interactive backend
import matplotlib.pyplot as plt

print("PyExample MCP session initialized")
"""

        return PyExampleContext(
            python_session=PersistentPythonSession(
                python_executable=self.python_executable,
                working_directory=self.working_directory,
                startup_code=startup_code,
            ),
            command_history=[],
        )

    def product_startup(self):
        """Launch PyExample instance when server starts.

        This method is called automatically during server startup.
        """
        logger.info(f"Launching PyExample in {self.launch_mode} mode...")

        try:
            # Use the mock PyExample library for testing
            from pyexample_mcp.mock_pyexample import launch_pyexample

            self.context.example_instance = launch_pyexample(
                mode=self.launch_mode, timeout=self.timeout
            )

            logger.info(
                f"PyExample {self.context.example_instance.version} "
                f"launched successfully in {self.launch_mode} mode"
            )

        except Exception as e:
            logger.error(f"Failed to launch PyExample: {e}")
            raise

    def product_cleanup(self):
        """Clean up PyExample instance when server stops.

        This method is called automatically during server shutdown.
        """
        if self.context.example_instance:
            try:
                logger.info("Closing PyExample instance...")
                self.context.example_instance.exit()
                logger.info("PyExample instance closed successfully")
            except Exception as e:
                logger.error(f"Error during PyExample cleanup: {e}")


# Create the MCP server instance
app = PyExampleMCP(
    name="pyexample-mcp",
)

The product_startup() and product_cleanup() methods manage the lifecycle of the connection to the product. The example uses a mock connection for demonstration purposes. Those methods are required by the ansys.common.mcp.server.PyAnsysBaseMCP class.

Optional: Override create_context() only if using a custom context class.

Implement tools#

The server can have one or more tools that implement specific functionality.

File: src/pyexample_mcp/tools.py

# Copyright (C) 2025 - 2026 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tools for PyExample MCP server."""

import json
from typing import Optional

from fastmcp.server import Context

from ansys.common.mcp.logging_config import get_logger
from pyexample_mcp import app

logger = get_logger(__name__)


# Define tools for interacting with PyExample instance
@app.tool()
def execute_command(ctx: Context, command: str) -> str:
    """Execute a PyExample command.

    Parameters
    ----------
    ctx : Context
        The FastMCP context
    command : str
        PyExample command to execute

    Returns
    -------
    str
        Command execution result

    """
    app_context = ctx.fastmcp._lifespan_result

    if not app_context.example_instance:
        return "Error: PyExample not connected"

    try:
        result = app_context.example_instance.run_command(command)
        app_context.command_history.append(command)
        logger.info(f"Executed command: {command}")
        return str(result)
    except Exception as e:
        logger.error(f"Command execution failed: {e}")
        return f"Error: {e}"


@app.tool()
def create_model(
    ctx: Context,
    name: str,
    model_type: str = "default",
    parameters: Optional[dict] = None,
) -> str:
    """Create a new model in PyExample.

    Parameters
    ----------
    ctx : Context
        The FastMCP context
    name : str
        Name for the new model
    model_type : str
        Type of model to create
    parameters : Optional[dict]
        Additional model parameters

    Returns
    -------
    str
        Status message

    """
    app_context = ctx.fastmcp._lifespan_result

    if not app_context.example_instance:
        return "Error: PyExample not connected"

    # Create model (simulated)
    params = parameters or {}
    model = app_context.example_instance.create_model(name, model_type, **params)

    # Update command history
    command = f"CREATE MODEL {name} TYPE {model_type}"
    app_context.command_history.append(command)

    logger.info(f"Created model: {name} (type: {model_type})")
    return f"Model '{name}' created successfully\n{model}"


@app.tool()
def run_simulation(
    ctx: Context, model_name: Optional[str] = None, save_results: bool = True
) -> str:
    """Run a simulation on the specified model.

    Parameters
    ----------
    ctx : Context
        The FastMCP context
    model_name : Optional[str]
        Model to simulate (uses active model if not specified)
    save_results : bool
        Whether to save results in context

    Returns
    -------
    str
        Simulation results summary

    """
    app_context = ctx.fastmcp._lifespan_result

    if not app_context.example_instance:
        return "Error: PyExample not connected"

    # Determine which model to use
    target_model = model_name or app_context.example_instance.active_model

    if not target_model:
        return "Error: No model specified or active"

    # Run simulation (simulated)
    command = f"SOLVE MODEL {target_model}"
    result = app_context.example_instance.run_command(command)

    # Save results if requested
    if save_results:
        app_context.simulation_results[target_model] = {"status": "completed", "summary": result}

    app_context.command_history.append(command)
    logger.info(f"Simulation completed for model: {target_model}")

    return f"Simulation completed for '{target_model}'\n{result}"


@app.tool()
def get_command_history(ctx: Context, format: str = "list") -> str:
    """Retrieve command execution history.

    Parameters
    ----------
    ctx : Context
        The FastMCP context
    format : str
        Output format: 'list', 'numbered', or 'json'

    Returns
    -------
    str
        Command history in requested format

    """
    app_context = ctx.fastmcp._lifespan_result

    if not app_context.command_history:
        return "No commands executed yet"

    if format == "numbered":
        lines = [f"{i + 1}. {cmd}" for i, cmd in enumerate(app_context.command_history)]
        return "\n".join(lines)

    elif format == "json":
        return json.dumps(app_context.command_history, indent=2)

    else:  # list format
        return "\n".join(app_context.command_history)


@app.tool()
def execute_python_code(ctx: Context, code: str) -> str:
    """Execute Python code in the persistent session.

    This allows for custom analysis and processing using the
    full Python ecosystem.

    Parameters
    ----------
    ctx : Context
        The FastMCP context
    code : str
        Python code to execute

    Returns
    -------
    str
        Execution output

    """
    app_context = ctx.fastmcp._lifespan_result

    result = app_context.python_session.execute(code)

    if result["success"]:
        output = str(result["stdout"])
        if result["stderr"]:
            output += f"\n\nWarnings:\n{result['stderr']}"
        return output
    else:
        return f"Error: {result['error']}"

See the Architecture for details on context injection patterns.

Initialize the package#

File: src/pyexample_mcp/__init__.py

# Copyright (C) 2025 - 2026 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""PyExample MCP server package."""

from pyexample_mcp.context import PyExampleContext
from pyexample_mcp.server import PyExampleMCP, app

__version__ = "0.1.0"

__all__ = [
    "PyExampleMCP",
    "PyExampleContext",
    "app",
    "__version__",
]

Define the entry point#

File: src/pyexample_mcp/__main__.py

# Copyright (C) 2025 - 2026 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Entry point for running PyExample MCP server."""

import sys

from ansys.common.mcp.logging_config import setup_logging
from pyexample_mcp import app

# Import tools to register them with the app
import pyexample_mcp.tools  # noqa: F401


def main():
    """Run the PyExample MCP server."""
    # Setup logging
    setup_logging(level="INFO")

    # Run the server (app instance already has tools registered)
    app.run()

    return 0


if __name__ == "__main__":
    sys.exit(main())

Configure the package#

File: pyproject.toml

[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "pyexample-mcp"
version = "0.1.0"
description = "MCP server for PyExample"
readme = "README.md"
requires-python = ">=3.10,<4"
license = { file = "LICENSE" }

dependencies = [
    "ansys-common-mcp>=0.0.1",
    "numpy>=2.0",
    "pandas>=3.0",
    "matplotlib>=3.0",
]

[project.scripts]
pyexample-mcp = "pyexample_mcp.__main__:main"

Run the example#

Install the package in a virtual environment using pip:

pip install .

Then, start the server using the entry point:

python -m pyexample_mcp

Another option is to configure the server in VS Code for easier development and debugging. Create a file .vscode/mcp.json with the following content:

{
        "servers": {
                "pyexample-mcp": {
                        "type": "stdio",
                        "command": ".\\.venv\\Scripts\\python.exe",
                        "args": ["-m", "pyexample_mcp"]
            }
        }
}

The server will start and communicate via stdio, ready to accept MCP requests from AI clients.