From 483523820192cb45992865c3c102c4c7bf18b439 Mon Sep 17 00:00:00 2001 From: choieastsea Date: Tue, 21 Oct 2025 01:40:42 +0900 Subject: [PATCH 1/2] feat: connection manager related tool --- .gitignore | 3 + src/openstack_mcp_server/tools/__init__.py | 3 + src/openstack_mcp_server/tools/base.py | 27 +++----- src/openstack_mcp_server/tools/connection.py | 73 ++++++++++++++++++++ 4 files changed, 87 insertions(+), 19 deletions(-) create mode 100644 src/openstack_mcp_server/tools/connection.py diff --git a/.gitignore b/.gitignore index 726822f..4e436a3 100644 --- a/.gitignore +++ b/.gitignore @@ -194,3 +194,6 @@ cython_debug/ # refer to https://docs.cursor.com/context/ignore-files .cursorignore .cursorindexingignore + +# Openstack Config Files +clouds.yaml diff --git a/src/openstack_mcp_server/tools/__init__.py b/src/openstack_mcp_server/tools/__init__.py index e42a705..5339060 100644 --- a/src/openstack_mcp_server/tools/__init__.py +++ b/src/openstack_mcp_server/tools/__init__.py @@ -1,5 +1,7 @@ from fastmcp import FastMCP +from openstack_mcp_server.tools.connection import ConnectionManager + def register_tool(mcp: FastMCP): """ @@ -17,3 +19,4 @@ def register_tool(mcp: FastMCP): IdentityTools().register_tools(mcp) NetworkTools().register_tools(mcp) BlockStorageTools().register_tools(mcp) + ConnectionManager().register_tools(mcp) diff --git a/src/openstack_mcp_server/tools/base.py b/src/openstack_mcp_server/tools/base.py index 03333ad..d343e1a 100644 --- a/src/openstack_mcp_server/tools/base.py +++ b/src/openstack_mcp_server/tools/base.py @@ -1,27 +1,16 @@ -import openstack +from openstack_mcp_server.tools.connection import ConnectionManager -from openstack import connection -from openstack_mcp_server import config +_connection_manager = ConnectionManager() -class OpenStackConnectionManager: - """OpenStack Connection Manager""" - - _connection: connection.Connection | None = None - - @classmethod - def get_connection(cls) -> connection.Connection: - """OpenStack Connection""" - if cls._connection is None: - openstack.enable_logging(debug=config.MCP_DEBUG_MODE) - cls._connection = openstack.connect(cloud=config.MCP_CLOUD_NAME) - return cls._connection +def get_openstack_conn(): + return _connection_manager.get_connection() -_openstack_connection_manager = OpenStackConnectionManager() +def set_openstack_cloud_name(cloud_name: str) -> None: + _connection_manager.set_cloud_name(cloud_name) -def get_openstack_conn(): - """Get OpenStack Connection""" - return _openstack_connection_manager.get_connection() +def get_openstack_cloud_name() -> str: + return _connection_manager.get_cloud_name() diff --git a/src/openstack_mcp_server/tools/connection.py b/src/openstack_mcp_server/tools/connection.py new file mode 100644 index 0000000..5e8f768 --- /dev/null +++ b/src/openstack_mcp_server/tools/connection.py @@ -0,0 +1,73 @@ +import openstack + +from fastmcp import FastMCP +from openstack import connection +from openstack.config.loader import OpenStackConfig + +from openstack_mcp_server import config + + +class ConnectionManager: + _cloud_name = config.MCP_CLOUD_NAME + + def __init__(self): + openstack.enable_logging(debug=config.MCP_DEBUG_MODE) + + def register_tools(self, mcp: FastMCP): + mcp.tool(self.get_cloud_config) + mcp.tool(self.get_cloud_names) + mcp.tool(self.get_cloud_name) + mcp.tool(self.set_cloud_name) + + def get_connection(self) -> connection.Connection: + return openstack.connect(cloud=self._cloud_name) + + def get_cloud_names(self) -> list[str]: + """List available cloud configurations. + + :return: Names of OpenStack clouds from user's config file. + """ + config = OpenStackConfig() + return list(config.get_cloud_names()) + + def get_cloud_config(self) -> dict: + """Provide cloud configuration with secrets masked of current user's config file. + + :return: Cloud configuration dictionary with credentials masked. + """ + config = OpenStackConfig() + return ConnectionManager._mask_credential( + config.cloud_config, ["password"] + ) + + @staticmethod + def _mask_credential( + config_dict: dict, credential_keys: list[str] + ) -> dict: + masked = {} + for k, v in config_dict.items(): + if k in credential_keys: + masked[k] = "****" + elif isinstance(v, dict): + masked[k] = ConnectionManager._mask_credential( + v, credential_keys + ) + else: + masked[k] = v + return masked + + @classmethod + def get_cloud_name(cls) -> str: + """Return the currently selected cloud name. + + :return: current OpenStack cloud name. + """ + return cls._cloud_name + + @classmethod + def set_cloud_name(cls, cloud_name: str) -> None: + """Set cloud name to use for later connections. Must set name from currently valid cloud config file. + + :param cloud_name: Name of the OpenStack cloud profile to activate. + """ + cls._cloud_name = cloud_name From 6def50b3c3e23368bca0b92cf031fee9569eca20 Mon Sep 17 00:00:00 2001 From: choieastsea Date: Wed, 22 Oct 2025 23:14:14 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20logging=EC=9D=80=20base.py=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=88=98=ED=96=89=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/openstack_mcp_server/tools/base.py | 5 +++++ src/openstack_mcp_server/tools/connection.py | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/openstack_mcp_server/tools/base.py b/src/openstack_mcp_server/tools/base.py index d343e1a..1f35609 100644 --- a/src/openstack_mcp_server/tools/base.py +++ b/src/openstack_mcp_server/tools/base.py @@ -1,8 +1,13 @@ +import openstack + +from openstack_mcp_server import config from openstack_mcp_server.tools.connection import ConnectionManager _connection_manager = ConnectionManager() +openstack.enable_logging(debug=config.MCP_DEBUG_MODE) + def get_openstack_conn(): return _connection_manager.get_connection() diff --git a/src/openstack_mcp_server/tools/connection.py b/src/openstack_mcp_server/tools/connection.py index 5e8f768..075d087 100644 --- a/src/openstack_mcp_server/tools/connection.py +++ b/src/openstack_mcp_server/tools/connection.py @@ -10,9 +10,6 @@ class ConnectionManager: _cloud_name = config.MCP_CLOUD_NAME - def __init__(self): - openstack.enable_logging(debug=config.MCP_DEBUG_MODE) - def register_tools(self, mcp: FastMCP): mcp.tool(self.get_cloud_config) mcp.tool(self.get_cloud_names)