Skip to main content
Glama

Mnemosyne MCP

by MumuTW
config.py11.1 kB
""" 配置管理模組 統一管理應用程式的配置,支持環境變數和配置文件。 """ import logging import os from functools import lru_cache from pathlib import Path from typing import Any, Dict, List, Optional import yaml from pydantic import Field, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict from ..interfaces.graph_store import ConnectionConfig logger = logging.getLogger(__name__) def _get_default_falkordb_host() -> str: """ 動態解析 FalkorDB 主機地址的 default_factory 邏輯: - 在 Docker 容器內 → 使用 'falkordb' (容器名稱) - 在本機環境 → 使用 'localhost' (port forwarding) """ # 方法1:檢查環境變數 if os.getenv("RUNNING_IN_DOCKER") == "1": logger.info("FalkorDB host: falkordb (detected via RUNNING_IN_DOCKER env var)") return "falkordb" # 方法2:檢查 /.dockerenv 文件(Docker 容器內會有此文件) if Path("/.dockerenv").exists(): logger.info("FalkorDB host: falkordb (detected via /.dockerenv file)") return "falkordb" # 方法3:檢查 /proc/1/cgroup 內容(更可靠的檢測方式) try: with open("/proc/1/cgroup", "r") as f: for line in f: if "docker" in line or "containerd" in line: logger.info("FalkorDB host: falkordb (detected via /proc/1/cgroup)") return "falkordb" except (FileNotFoundError, PermissionError): pass # 本機環境預設使用 localhost logger.info("FalkorDB host: localhost (local environment default)") return "localhost" class DatabaseSettings(BaseSettings): """資料庫配置""" model_config = SettingsConfigDict(env_prefix="FALKORDB_", case_sensitive=False) host: str = Field(default_factory=_get_default_falkordb_host) port: int = Field(default=6379) database: str = Field(default="mnemosyne") username: Optional[str] = Field(default=None) password: Optional[str] = Field(default=None) connection_pool_size: int = Field(default=10) connection_timeout: int = Field(default=30) query_timeout: int = Field(default=60) def to_connection_config(self) -> ConnectionConfig: """轉換為 ConnectionConfig""" return ConnectionConfig( host=self.host, port=self.port, database=self.database, username=self.username, password=self.password, connection_pool_size=self.connection_pool_size, connection_timeout=self.connection_timeout, query_timeout=self.query_timeout, ) class APISettings(BaseSettings): """API 配置""" model_config = SettingsConfigDict(env_prefix="API_", case_sensitive=False) host: str = Field(default="0.0.0.0") port: int = Field(default=8000) grpc_port: int = Field(default=50051) # CORS 配置 cors_origins: List[str] = Field(default_factory=lambda: ["*"]) cors_allow_credentials: bool = Field(default=True) cors_allow_methods: List[str] = Field(default_factory=lambda: ["*"]) cors_allow_headers: List[str] = Field(default_factory=lambda: ["*"]) # 安全配置 secret_key: str = Field(default="dev-secret-key", alias="SECRET_KEY") api_key_header: str = Field(default="X-API-Key", alias="API_KEY_HEADER") class LoggingSettings(BaseSettings): """日誌配置""" model_config = SettingsConfigDict(env_prefix="LOG_", case_sensitive=False) level: str = Field(default="INFO") format: str = Field(default="json") # 處理器配置 handlers: List[Dict[str, Any]] = Field( default_factory=lambda: [ {"type": "console", "level": "INFO"}, {"type": "file", "level": "DEBUG", "filename": "logs/mnemosyne.log"}, ] ) class SecuritySettings(BaseSettings): """安全配置""" model_config = SettingsConfigDict(case_sensitive=False) secret_key: str = Field( default="dev-secret-key-change-in-production", alias="SECRET_KEY" ) api_key_header: str = Field(default="X-API-Key", alias="API_KEY_HEADER") # JWT 配置(為未來功能預留) jwt_algorithm: str = Field(default="HS256") jwt_expire_minutes: int = Field(default=30) class FeatureSettings(BaseSettings): """功能開關配置""" model_config = SettingsConfigDict(case_sensitive=False) enable_metrics: bool = Field(default=False, alias="ENABLE_METRICS") metrics_port: int = Field(default=9090, alias="METRICS_PORT") enable_tracing: bool = Field(default=False) enable_debug_queries: bool = Field(default=True) class MCPAtlassianSettings(BaseSettings): """MCP Atlassian 配置""" model_config = SettingsConfigDict(env_prefix="MCP_", case_sensitive=False) # 服務端點 service_url: str = Field(default="http://mcp-atlassian:8001") health_check_timeout: int = Field(default=5) # Confluence 配置 confluence_url: Optional[str] = Field(default=None) confluence_username: Optional[str] = Field(default=None) confluence_api_token: Optional[str] = Field(default=None) # Jira 配置 jira_url: Optional[str] = Field(default=None) jira_username: Optional[str] = Field(default=None) jira_api_token: Optional[str] = Field(default=None) # 功能配置 read_only_mode: bool = Field(default=True) enabled_tools: List[str] = Field( default_factory=lambda: ["confluence_search", "jira_search", "jira_get_issue"] ) # 過濾器配置 jira_projects_filter: Optional[str] = Field(default=None) confluence_spaces_filter: Optional[str] = Field(default=None) @property def is_configured(self) -> bool: """檢查是否已配置 Atlassian 服務""" return bool( ( self.confluence_url and self.confluence_username and self.confluence_api_token ) or (self.jira_url and self.jira_username and self.jira_api_token) ) class Settings(BaseSettings): """主配置類""" # 環境配置 environment: str = Field(default="development") debug: bool = Field(default=False) # 子配置 database: DatabaseSettings = Field(default_factory=DatabaseSettings) api: APISettings = Field(default_factory=APISettings) logging: LoggingSettings = Field(default_factory=LoggingSettings) security: SecuritySettings = Field(default_factory=SecuritySettings) features: FeatureSettings = Field(default_factory=FeatureSettings) mcp_atlassian: MCPAtlassianSettings = Field(default_factory=MCPAtlassianSettings) model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore", env_nested_delimiter="__", ) @field_validator("environment") @classmethod def validate_environment(cls, v): """驗證環境配置""" valid_envs = ["development", "testing", "staging", "production"] if v not in valid_envs: raise ValueError(f"Environment must be one of: {valid_envs}") return v @property def is_development(self) -> bool: """是否為開發環境""" return self.environment == "development" @property def is_production(self) -> bool: """是否為生產環境""" return self.environment == "production" @property def config_file_path(self) -> Path: """獲取配置文件路徑""" return Path(f"configs/{self.environment}.yaml") def load_yaml_config_if_exists() -> Dict[str, Any]: """ 從 YAML 配置文件加載配置(如果存在) 提供回退處理,確保應用可以在沒有配置文件的情況下啟動 """ environment = os.getenv("ENVIRONMENT", "development") config_file = Path(f"configs/{environment}.yaml") # 如果 configs 目錄不存在,創建它 config_file.parent.mkdir(parents=True, exist_ok=True) if not config_file.exists(): print( f"Info: Config file {config_file} not found, " "using defaults and environment variables" ) return {} try: with open(config_file, "r", encoding="utf-8") as f: config_data = yaml.safe_load(f) or {} print(f"Info: Loaded config from {config_file}") return config_data except Exception as e: # 配置文件讀取失敗時記錄錯誤但不中斷啟動 print(f"Warning: Failed to load config file {config_file}: {e}") print("Info: Continuing with defaults and environment variables") return {} @lru_cache() def get_settings() -> Settings: """ 獲取配置實例 使用 lru_cache 確保單例模式 """ # 簡單地使用 Pydantic Settings 的默認行為 # 它會自動從 .env 文件和環境變數加載配置 return Settings() def load_config_from_file(config_path: str) -> Dict[str, Any]: """ 從指定文件加載配置 Args: config_path: 配置文件路徑 Returns: Dict[str, Any]: 配置字典 """ config_file = Path(config_path) if not config_file.exists(): raise FileNotFoundError(f"Config file not found: {config_path}") with open(config_file, "r", encoding="utf-8") as f: if config_file.suffix.lower() in [".yaml", ".yml"]: return yaml.safe_load(f) elif config_file.suffix.lower() == ".json": import json return json.load(f) else: raise ValueError(f"Unsupported config file format: {config_file.suffix}") def validate_config(settings: Settings) -> List[str]: """ 驗證配置的完整性和合理性 Args: settings: 配置實例 Returns: List[str]: 驗證錯誤列表,空列表表示驗證通過 """ errors = [] # 驗證資料庫配置 if not settings.database.host: errors.append("Database host is required") if settings.database.port <= 0 or settings.database.port > 65535: errors.append("Database port must be between 1 and 65535") # 驗證 API 配置 if settings.api.port <= 0 or settings.api.port > 65535: errors.append("API port must be between 1 and 65535") if settings.api.grpc_port <= 0 or settings.api.grpc_port > 65535: errors.append("gRPC port must be between 1 and 65535") if settings.api.port == settings.api.grpc_port: errors.append("API port and gRPC port cannot be the same") # 驗證安全配置 if ( settings.is_production and settings.security.secret_key == "dev-secret-key-change-in-production" ): errors.append("Secret key must be changed in production environment") # 驗證日誌配置 valid_log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] if settings.logging.level.upper() not in valid_log_levels: errors.append(f"Log level must be one of: {valid_log_levels}") return errors

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/MumuTW/Mnemosyne-mcp'

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