# ============================================================================ # ACTUAL MCP SERVER - ENVIRONMENT CONFIGURATION # ============================================================================ # Copy this file to .env and fill in required values. # Security: never commit .env to version control. # ============================================================================ # ---------------------------------------------------------------------------- # ACTUAL BUDGET CONNECTION (REQUIRED) # ---------------------------------------------------------------------------- # URL of your Actual Budget server # Examples: # Local: http://localhost:5006 # Docker: http://actual-server:5006 # Remote: https://actual.yourdomain.com ACTUAL_SERVER_URL=http://localhost:5006 # Password you use to access Actual Budget ACTUAL_PASSWORD=your_password_here # Budget Sync ID. Find it in Actual Budget: # Settings then Show Advanced Settings then Sync ID # This is the *default* budget (named via BUDGET_DEFAULT_NAME below). ACTUAL_BUDGET_SYNC_ID=your-budget-sync-id-here # Optional: encryption password for E2E-encrypted budgets # Only required if your default budget uses end-to-end encryption # ACTUAL_BUDGET_PASSWORD= # Optional: per-operation timeout in milliseconds for every upstream Actual API # call (init, budget download, sync, and each tool operation). #270: a stalled # upstream call cannot hold the global API mutex forever; it rejects after this # many ms so subsequent tool calls recover instead of hanging. Default 30000 # (30s). Set to 0 to disable the bound (not recommended). # ACTUAL_OP_TIMEOUT_MS=30000 # # Security (#161): when ACTUAL_BUDGET_PASSWORD (or a BUDGET_N_ENCRYPTION_PASSWORD) is set, # the upstream must be https:// so the encryption password is not sent in cleartext. # Set ALLOW_INSECURE_UPSTREAM=true only for a trusted isolated network (e.g. Docker). # ALLOW_INSECURE_UPSTREAM=false # Optional: friendly name for the default budget (shown in actual_budgets_list_available) # Default: "Default" # BUDGET_DEFAULT_NAME=My Main Budget # ---------------------------------------------------------------------------- # ADDITIONAL BUDGETS (Optional, for multi-budget / multi-server switching) # ---------------------------------------------------------------------------- # Define extra budgets the AI can switch to at runtime via actual_budgets_switch. # Use actual_budgets_list_available to see all configured budgets. # # Variables for budget N (N = 1, 2, 3, ...): # BUDGET_N_NAME Human-readable name (required to enable group) # BUDGET_N_SYNC_ID Budget Sync ID (required) # BUDGET_N_SERVER_URL Falls back to ACTUAL_SERVER_URL when omitted # BUDGET_N_PASSWORD Falls back to ACTUAL_PASSWORD when omitted # BUDGET_N_ENCRYPTION_PASSWORD Optional, only for E2E-encrypted budgets # # Fallback rule: only specify what differs from the default ACTUAL_* values. # # Number budgets consecutively from 1, with no gaps: enumeration stops at the first # missing BUDGET_N_NAME, so BUDGET_1 + BUDGET_3 (no BUDGET_2) silently ignores BUDGET_3. # A [CONFIG] ... IGNORED warning is logged at startup if a gap is detected (#289). # # Optional: give the default budget a friendly name (default: "Default") # BUDGET_DEFAULT_NAME=My Main Budget # # Example 1: same server, same password (SERVER_URL + PASSWORD omitted): # BUDGET_1_NAME=Shared Family Account # BUDGET_1_SYNC_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # # Example 2: same server, different password (SERVER_URL omitted): # BUDGET_2_NAME=Business # BUDGET_2_PASSWORD=business-password # BUDGET_2_SYNC_ID=yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy # # Example 3: entirely separate Actual server (all three fields required): # BUDGET_3_NAME=Remote Office # BUDGET_3_SERVER_URL=https://actual-office.example.com:5006 # BUDGET_3_PASSWORD=office-password # BUDGET_3_SYNC_ID=zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz # BUDGET_3_ENCRYPTION_PASSWORD=e2e-password-if-encrypted # ---------------------------------------------------------------------------- # MCP BRIDGE SERVER SETTINGS # ---------------------------------------------------------------------------- # Port the MCP server listens on # Default: 3600 MCP_BRIDGE_PORT=3600 # Host to bind to # 0.0.0.0 = all interfaces (needed in Docker) # 127.0.0.1 = local connections only # Default: 0.0.0.0 MCP_BRIDGE_BIND_HOST=0.0.0.0 # Directory for Actual Budget local data (SQLite files) # Default: ./actual-data MCP_BRIDGE_DATA_DIR=./actual-data # Transport mode: the only supported value is --http # Default: --http # MCP_TRANSPORT_MODE=--http # HTTP endpoint path # Default: /http # MCP_HTTP_PATH=/http # Advertised HTTP path (shown in logs / returned to clients, may differ from # the routing path when behind a reverse proxy with path rewriting) # Default: same as MCP_HTTP_PATH # MCP_BRIDGE_HTTP_PATH=/http # ---------------------------------------------------------------------------- # REVERSE PROXY / TLS SETTINGS (Optional) # ---------------------------------------------------------------------------- # Set these when running behind a reverse proxy (nginx, Traefik, Caddy, etc.) # so that the server announces the correct public URL to clients. # Override the advertised hostname (default: auto-detected local IP) # MCP_BRIDGE_PUBLIC_HOST=mcp.yourdomain.com # Override the advertised scheme (http or https) # MCP_BRIDGE_PUBLIC_SCHEME=https # Force https in the advertised URL (alternative to MCP_BRIDGE_PUBLIC_SCHEME) # Set to 'true' when TLS is terminated upstream # MCP_BRIDGE_USE_TLS=false # Native TLS (terminate TLS directly in the MCP server without a reverse proxy) # When MCP_ENABLE_HTTPS=true, BOTH MCP_HTTPS_CERT and MCP_HTTPS_KEY are required; # the server refuses to start otherwise (#169). # Max incoming JSON body size (#168). Express byte string, e.g. 512kb or 2mb. Default 512kb. # MCP_HTTP_BODY_LIMIT=512kb # MCP_ENABLE_HTTPS=false # MCP_HTTPS_CERT=/app/certs/cert.pem # MCP_HTTPS_KEY=/app/certs/key.pem # ---------------------------------------------------------------------------- # SESSION MANAGEMENT # ---------------------------------------------------------------------------- # Maximum concurrent MCP sessions # Default: 15 # MAX_CONCURRENT_SESSIONS=15 # Session idle timeout in minutes. Sessions with no activity are closed # Default: 5 # SESSION_IDLE_TIMEOUT_MINUTES=5 # Maximum concurrent Actual API operations (connection pool depth) # Default: 5 # ACTUAL_API_CONCURRENCY=5 # Enable connection pooling (set to 'false' to disable, rarely needed) # Default: true # USE_CONNECTION_POOL=true # ---------------------------------------------------------------------------- # AUTHENTICATION # ---------------------------------------------------------------------------- # Static Bearer token for simple single-user setups. # Clients must send: Authorization: Bearer # Generate with: openssl rand -hex 32 # Leave unset to disable token auth (not recommended for production). # MCP_SSE_AUTHORIZATION=your-secure-token-here # Required-by-default HTTP auth (#242). On a non-loopback bind (anything other # than 127.0.0.1/::1/localhost, so the 0.0.0.0 default counts) with no token and # no AUTH_PROVIDER=oidc, the server REFUSES to start. Set this to the exact string # 'true' to deliberately run unauthenticated (for example behind your own # authenticating reverse proxy). Loopback binds and stdio mode are unaffected. # MCP_ALLOW_UNAUTHENTICATED=false # Authentication provider. Options: 'none' (default), 'oidc' # When set to 'oidc', JWT Bearer tokens are validated via JWKS instead of the # static MCP_SSE_AUTHORIZATION token. # AUTH_PROVIDER=none # OIDC issuer URL (required when AUTH_PROVIDER=oidc) # Examples: # Keycloak: https://auth.example.com/realms/myrealm # Azure AD: https://login.microsoftonline.com//v2.0 # Auth0: https://.auth0.com/ # OIDC_ISSUER=https://auth.example.com/realms/myrealm # Allow a plaintext (http) OIDC issuer on a trusted network (#244). Off by default: # an http issuer is refused at startup because a network attacker could swap the # JWKS and forge tokens. Set to 'true' ONLY for local/LAN testing (e.g. a LAN # Casdoor over http). Loopback issuers (localhost/127.0.0.1) never need this. # OIDC_ALLOW_INSECURE_ISSUER=false # This server's resource identifier (audience claim in JWT validation) # Must match the 'aud' claim your IdP puts in issued tokens. # OIDC_RESOURCE=https://actual-mcp.example.com # Extra accepted JWT audiences beyond OIDC_RESOURCE, comma-separated (#245). # For IdPs that put the client-id (not the resource URI) in the 'aud' claim, e.g. # Authentik. Strict allowlist: the accepted set is OIDC_RESOURCE plus these, never # a wildcard. Leave empty unless your IdP needs it. # OIDC_ACCEPTED_AUDIENCES=my-client-id # Opt-in allowlist of cross-origin JWKS hosts, comma-separated host or host:port # entries (#254). Needed only when the IdP legitimately serves its JWKS from a # different host than the issuer, e.g. Google (issuer accounts.google.com, keys # on www.googleapis.com). Exact match, lowercased, no wildcards; https and the # no-credentials checks still apply. Leave empty for same-origin-only (default). # OIDC_JWKS_TRUSTED_HOSTS=www.googleapis.com # Comma-separated required scopes (optional; tokens must have ALL listed) # OIDC_SCOPES= # Per-user budget ACL (optional; restricts which budgets each user may access) # JSON object: principal → list of allowed budget sync-IDs, or ["*"] for all. # Principal key formats: # "alice@example.com" matches 'email' claim # "some-sub-uuid" matches 'sub' claim # "group:admin" matches 'groups' or 'roles' array element # Example: # AUTH_BUDGET_ACL={"alice@example.com":["budget-sync-id-1"],"group:admin":["*"]} # AUTH_BUDGET_ACL= # ---------------------------------------------------------------------------- # LOGGING # ---------------------------------------------------------------------------- # Log output format: json | pretty # Precedence: explicit LOG_FORMAT wins; else NODE_ENV=production selects json; else pretty. # Use json in production so logs can be aggregated/queried by a log backend; pretty # (human-readable, colorized only on a TTY) is the dev default. # Default: pretty (json when NODE_ENV=production) # LOG_FORMAT=json # Log level: error | warn | info | debug # Default: debug in dev, info when NODE_ENV=production (override with this var) MCP_BRIDGE_LOG_LEVEL=debug # Debug-detection toggle, distinct from MCP_BRIDGE_LOG_LEVEL (the winston level). # Setting LOG_LEVEL=debug also turns on extra transport debug output (see utils.ts). # LOG_LEVEL=debug # Service name stamped on every structured (json) log record # Default: actual-mcp-server # MCP_SERVICE_NAME=actual-mcp-server # Write logs to files (in addition to stdout) # Default: false MCP_BRIDGE_STORE_LOGS=false # Directory for log files (only used when MCP_BRIDGE_STORE_LOGS=true) # Default: ./logs MCP_BRIDGE_LOG_DIR=./logs # Log rotation: date pattern for filenames (winston-daily-rotate-file format) # Default: YYYY-MM-DD (e.g. app-2025-11-09.log) # MCP_BRIDGE_ROTATE_DATEPATTERN=YYYY-MM-DD # Maximum size per log file before rotation (e.g. 20m, 100m, 1g) # Default: 20m # MCP_BRIDGE_MAX_LOG_SIZE=20m # How long to retain rotated log files (e.g. 14d, 30d) # Default: 14d # MCP_BRIDGE_MAX_FILES=14d # Enable verbose transport-level debug output (set by --debug flag automatically) # Default: false # MCP_BRIDGE_DEBUG_TRANSPORT=false # ---------------------------------------------------------------------------- # TIMEZONE # ---------------------------------------------------------------------------- # IANA timezone for log timestamps and date operations # Examples: UTC, America/New_York, Europe/London, Asia/Tokyo # Default: UTC # TZ=UTC # ---------------------------------------------------------------------------- # DEVELOPMENT # ---------------------------------------------------------------------------- # Enable debug mode (also set by --debug CLI flag) # DEBUG=false # Node environment # NODE_ENV=production