Files
basecamp-mcp-server/config_paths.py
m3tm3re 503e97e520 fix: resolve read-only file system issues for Nix packaging
- Add config_paths.py for XDG-compliant path resolution
- Update all servers to use user-writable locations (logs, tokens, .env)
- Make .env file optional - server works with environment variables only
- Default paths: ~/.local/share for data, ~/.local/state for logs
- Update Nix wrapper with XDG defaults and helpful postInstall message
- Update README.md with environment variable configuration documentation

Fixes OSError when running in Nix store (read-only filesystem)
2026-01-21 20:42:05 +01:00

93 lines
2.6 KiB
Python

"""
Centralized path resolution for Basecamp MCP Server.
All paths can be overridden via environment variables.
"""
import os
from pathlib import Path
def get_log_directory() -> Path:
"""
Get the directory for log files.
Priority order:
1. BASECAMP_LOG_DIR env variable
2. XDG_STATE_HOME (typically ~/.local/state)
3. ~/.local/state/basecamp-mcp (fallback)
"""
if log_dir := os.getenv("BASECAMP_LOG_DIR"):
return Path(log_dir)
if xdg_state := os.getenv("XDG_STATE_HOME"):
return Path(xdg_state) / "basecamp-mcp"
return Path.home() / ".local/state/basecamp-mcp"
def get_config_directory() -> Path:
"""
Get the directory for configuration files (.env).
Priority order:
1. BASECAMP_CONFIG_DIR env variable
2. XDG_CONFIG_HOME (typically ~/.config)
3. ~/.config/basecamp-mcp (fallback)
NOTE: This is only used if BASECAMP_ENV_FILE is not set and
a .env file exists in the config directory. The server works
entirely with environment variables and doesn't require .env files.
"""
if config_dir := os.getenv("BASECAMP_CONFIG_DIR"):
return Path(config_dir)
if xdg_config := os.getenv("XDG_CONFIG_HOME"):
return Path(xdg_config) / "basecamp-mcp"
return Path.home() / ".config/basecamp-mcp"
def get_env_file_path() -> Path:
"""
Get the path for .env file (optional).
Priority order:
1. BASECAMP_ENV_FILE env variable (explicitly specified)
2. Config directory + / .env
The server does NOT require this file - it's optional.
If not found, environment will be used as-is.
"""
if env_file := os.getenv("BASECAMP_ENV_FILE"):
return Path(env_file)
return get_config_directory() / ".env"
def get_token_file_path() -> Path:
"""
Get the path for OAuth token storage.
Priority order:
1. BASECAMP_TOKEN_FILE env variable
2. XDG_DATA_HOME (typically ~/.local/share)
3. ~/.local/share/basecamp-mcp/oauth_tokens.json (fallback)
"""
if token_file := os.getenv("BASECAMP_TOKEN_FILE"):
return Path(token_file)
if xdg_data := os.getenv("XDG_DATA_HOME"):
return Path(xdg_data) / "basecamp-mcp" / "oauth_tokens.json"
return Path.home() / ".local/share/basecamp-mcp" / "oauth_tokens.json"
def ensure_directories_exist() -> None:
"""
Create all necessary directories if they don't exist.
"""
get_log_directory().mkdir(parents=True, exist_ok=True)
get_config_directory().mkdir(parents=True, exist_ok=True)
get_token_file_path().parent.mkdir(parents=True, exist_ok=True)