# Copyright (c) 2024 Rajesh
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Configuration management for docx-mcp server."""
import os
from pathlib import Path
from typing import Optional
from pydantic import ConfigDict
from pydantic_settings import BaseSettings
class DocxMcpConfig(BaseSettings):
"""Configuration for docx-mcp server."""
model_config = ConfigDict(
env_prefix="DOCX_MCP_",
case_sensitive=False,
)
# Server config
server_name: str = "docx-mcp"
log_level: str = "INFO"
# File system config
project_dir: Path = Path(os.getenv("DOCX_MCP_PROJECT_DIR", "."))
allowed_extensions: list[str] = [".docx", ".doc", ".dotx", ".dot"]
# Security config
max_file_size: int = int(os.getenv("DOCX_MCP_MAX_FILE_SIZE", 52428800)) # 50MB
allow_unsafe_paths: bool = False
# Directories for documents and templates
documents_dir: Optional[Path] = None
templates_dir: Optional[Path] = None
def get_allowed_dir(self, path: Path) -> bool:
"""Check if path is in allowed directories."""
try:
resolved = path.resolve()
# Check if in documents dir
if self.documents_dir:
docs_resolved = self.documents_dir.resolve()
if resolved.is_relative_to(docs_resolved):
return True
# Check if in templates dir
if self.templates_dir:
templates_resolved = self.templates_dir.resolve()
if resolved.is_relative_to(templates_resolved):
return True
# Allow project directory by default
project_resolved = self.project_dir.resolve()
if resolved.is_relative_to(project_resolved):
return not self.allow_unsafe_paths
return self.allow_unsafe_paths
except (ValueError, OSError):
return False
def validate_file_path(self, file_path: str) -> Path:
"""Validate and normalize file path."""
path = Path(file_path)
# Prevent directory traversal
try:
resolved = path.resolve()
if not self.get_allowed_dir(resolved):
raise PermissionError(f"Path not in allowed directories: {file_path}")
except Exception as e:
raise ValueError(f"Invalid file path: {file_path}") from e
# Check extension
if path.suffix.lower() not in self.allowed_extensions:
raise ValueError(f"File extension not allowed: {path.suffix}")
return resolved
# Global config instance
config = DocxMcpConfig()