@iflow-mcp/agigante80-actual-mcp-server
v0.4.26
Published
MCP server providing 60 tools for AI-driven financial management with Actual Budget - session management, query, bank sync, and multi-budget support
Readme
Actual MCP Server
A production-ready Model Context Protocol (MCP) server that bridges AI assistants with Actual Budget, enabling natural language financial management through 62 specialized tools covering 84% of the Actual Budget API, including 6 exclusive ActualQL-powered tools designed specifically for this MCP server.
🧪 Tested with Multiple AI Clients: This MCP server has been extensively tested and verified with both LibreChat and LobeChat. All 62 tools load and function correctly. Other MCP clients should work but have not been tested yet.
📋 Table of Contents
- Use Cases & Scenarios
- Connection Architecture
- Features
- Quick Start
- Available Tools
- Missing Tools
- Installation
- AI Client Integration (LibreChat & LobeChat)
- Usage Examples
- Configuration
- Transports & Authentication
- Docker Deployment
- Architecture
- API Coverage
- Testing
- Contributing
- Documentation
- License
💡 Use Cases & Scenarios
What Can You Do with This Integration?
LibreChat ↔ Actual MCP Server ↔ Actual Budget
This MCP server enables conversational financial management by connecting LibreChat's AI capabilities with your Actual Budget data. Here are real-world scenarios:
🗣️ Natural Language Queries
You: "How much did I spend on groceries last month?"
AI: [Uses transactions_filter] "You spent $847.23 on groceries in October 2025"
You: "Show me all transactions over $100 this week"
AI: [Uses transactions_filter] Lists large transactions with dates and payees
You: "What's my checking account balance?"
AI: [Uses accounts_get_balance] "Your checking account has $3,247.89"💰 Budget Management
You: "Set my restaurant budget to $300 for this month"
AI: [Uses budgets_setAmount] "Restaurant budget for November 2025 set to $300"
You: "Transfer $50 from Entertainment to Groceries"
AI: [Uses budgets_transfer] "Transferred $50 from Entertainment to Groceries"
You: "How much budget do I have left for December?"
AI: [Uses budgets_getMonth] Shows remaining budget for all categories📝 Transaction Management
You: "Add a $45.67 transaction at Kroger from my checking account"
AI: [Uses transactions_create] "Transaction created and categorized as Groceries"
You: "Import my bank CSV file" [with file upload]
AI: [Uses transactions_import] "Imported 47 transactions, matched 12 duplicates"
You: "Find all Amazon transactions this year"
AI: [Uses transactions_filter] Lists all Amazon purchases with amounts🏷️ Organization & Automation
You: "Create a rule to automatically categorize Uber as Transportation"
AI: [Uses rules_create] "Rule created: Uber → Transportation"
You: "Merge my duplicate Starbucks payees"
AI: [Uses payees_merge] "Merged 3 Starbucks entries into one"
You: "Create a new category called 'Pet Supplies' under Expenses"
AI: [Uses categories_create] "Pet Supplies category created"🎯 Perfect For
- 💬 Conversational Finance: Chat with your budget naturally
- 📊 Quick Insights: Get financial answers without opening Actual Budget
- 🔄 Bulk Operations: Manage multiple transactions through conversation
- 🤖 Automation: Set up rules and recurring patterns via AI
- 📱 Mobile-Friendly: Use LibreChat mobile app for on-the-go budget access
- ♿ Accessibility: Voice-based financial management for users with disabilities
🔗 Connection Architecture
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ │ HTTPS │ │ API │ │
│ LibreChat │ ◄─────► │ Actual MCP │ ◄─────► │ Actual │
│ (Client) │ MCP │ Server │ calls │ Budget │
│ │Protocol │ │ │ Server │
└─────────────┘ └──────────────────┘ └──────────────┘
│ │ │
│ │ │
User asks Translates Financial
"How much natural language operations
did I spend?" to Actual API calls & data storageHow It Works
- User chats with LibreChat in natural language
- LibreChat sends MCP tool requests to Actual MCP Server
- Actual MCP Server translates requests to Actual Budget API calls
- Actual Budget executes operations and returns data
- Actual MCP Server formats response for AI consumption
- LibreChat presents results in conversational format
Transport Protocols
- HTTP/HTTPS: Production-ready with Bearer token authentication
🐳 Docker Networking Best Practices
For Docker-to-Docker communication (LibreChat, LobeChat, etc.), use internal Docker hostnames instead of host IPs:
✅ RECOMMENDED: http://actual-mcp-server-backend:3600/http
❌ NOT RECOMMENDED: http://192.168.x.x:3600/http
Why internal hostnames are better:
- Direct communication: No extra network hops through host bridge
- Better security: No external network exposure required
- Built-in DNS: Automatic hostname resolution within Docker networks
- Resilient: Works even if host IP changes
- Performance: Lower latency for container-to-container communication
Example configuration for AI clients:
# LibreChat librechat.yaml
mcpServers:
actual-mcp:
url: "http://actual-mcp-server-backend:3600/http" # ✅ Use container name
# LobeChat (via UI)
Server URL: http://actual-mcp-server-backend:3600/http # ✅ Use container nameBoth containers must be on the same Docker network. See Docker Deployment section below for network configuration.
✨ Features
Core Capabilities
Core Capabilities
- 🤖 62 MCP Tools: Comprehensive financial operations via natural language
- Secure: Bearer token OR OIDC/JWT authentication + HTTPS/TLS encryption
- 🛡️ Type-Safe: Full TypeScript implementation with runtime validation (Zod)
- 🔁 Resilient: Automatic retry logic with exponential backoff
- 📊 84% API Coverage: Supports majority of Actual Budget operations
- 🚀 Production-Ready: Docker support, structured logging, health checks
- ✅ LibreChat Verified: All 62 tools tested and working
- ⚡ Exclusive Tools: 6 ActualQL-powered tools for advanced queries and summaries
Advanced Features
- Concurrent Control: Rate-limited API calls prevent overwhelming Actual Budget
- Observability: Prometheus metrics, structured logging with Winston
- Flexible Deployment: Docker, Kubernetes, bare metal, or Docker Compose
- HTTPS Support: TLS encryption with self-signed or CA-signed certificates
- Tested: Unit tests (62 tools + schema), Playwright E2E, and live integration suite
Financial Operations
With conversational AI, you can:
- 💰 Manage Accounts: Create, update, close accounts; check balances
- 💳 Track Transactions: Add, update, delete transactions; import bank data
- 📁 Organize Categories: Create category groups and categories
- 👥 Handle Payees: Manage payees, merge duplicates
- 📅 Budget Planning: Set budget amounts, enable carryover, track spending
- 🔧 Automate Rules: Create rules for automatic transaction categorization
- 🔄 Batch Operations: Efficiently update multiple budget categories
🚀 Quick Start
🐳 Docker Images Available:
- Docker Hub:
agigante80/actual-mcp-server- GitHub Container Registry:
ghcr.io/agigante80/actual-mcp-server
Prerequisites
- Node.js 20+ or Docker
- Actual Budget server (running locally or hosted)
- Actual Budget credentials (password and sync ID)
Install with npm
# Clone the repository
git clone https://github.com/agigante80/actual-mcp-server.git
cd actual-mcp-server
# Install dependencies
npm install
# Configure environment
cp .env.example .env
# Edit .env with your Actual Budget credentials
# Build and run
npm run build
npm run dev -- --debugRun with Docker
Docker images are published on two registries:
- Docker Hub: agigante80/actual-mcp-server
- GitHub Container Registry: ghcr.io/agigante80/actual-mcp-server
Available tags:
latest- Latest stable release from main branchlatest-<sha>- Specific commit from main (e.g.,latest-abc1234)development- Latest development builddevelopment-<sha>- Specific development commit
Both registries have identical images. Use Docker Hub for public access, or GHCR for integration with GitHub workflows.
Note: Docker images use HTTP transport mode.
Quick Start (HTTP)
# Pull and run from Docker Hub (HTTP is the default)
docker run -d \
--name actual-mcp-server \
-p 3600:3600 \
-e ACTUAL_SERVER_URL=http://your-actual-server:5006 \
-e ACTUAL_PASSWORD=your_password \
-e ACTUAL_BUDGET_SYNC_ID=your_sync_id \
-e MCP_SSE_AUTHORIZATION=$(openssl rand -hex 32) \
-v actual-mcp-data:/data \
agigante80/actual-mcp-server:latest
# Or from GitHub Container Registry
docker run -d \
--name actual-mcp-server \
-p 3600:3600 \
-e ACTUAL_SERVER_URL=http://your-actual-server:5006 \
-e ACTUAL_PASSWORD=your_password \
-e ACTUAL_BUDGET_SYNC_ID=your_sync_id \
-e MCP_SSE_AUTHORIZATION=$(openssl rand -hex 32) \
-v actual-mcp-data:/data \
ghcr.io/agigante80/actual-mcp-server:latest
# Check if running
curl http://localhost:3600/healthWith HTTPS (Recommended)
# Generate self-signed certificate first
mkdir -p certs
openssl req -x509 -newkey rsa:4096 -nodes \
-keyout certs/key.pem -out certs/cert.pem \
-days 365 -subj "/CN=your-server-ip" \
-addext "subjectAltName=IP:your-server-ip,DNS:localhost"
# Run with HTTPS enabled
docker run -d \
--name actual-mcp-server \
-p 3600:3600 \
-e ACTUAL_SERVER_URL=http://your-actual-server:5006 \
-e ACTUAL_PASSWORD=your_password \
-e ACTUAL_BUDGET_SYNC_ID=your_sync_id \
-e MCP_SSE_AUTHORIZATION=$(openssl rand -hex 32) \
-e MCP_ENABLE_HTTPS=true \
-e MCP_HTTPS_CERT=/app/certs/cert.pem \
-e MCP_HTTPS_KEY=/app/certs/key.pem \
-v actual-mcp-data:/data \
-v $(pwd)/certs:/app/certs:ro \
agigante80/actual-mcp-server:latest
# Verify HTTPS is working
curl -k https://localhost:3600/healthPull Specific Version
# Latest stable from Docker Hub
docker pull agigante80/actual-mcp-server:latest
# Latest stable from GHCR
docker pull ghcr.io/agigante80/actual-mcp-server:latest
# Development version
docker pull agigante80/actual-mcp-server:development
# Specific commit
docker pull agigante80/actual-mcp-server:latest-abc1234Using docker-compose.yaml (Production profile)
For production deployments, use the production profile in the provided docker-compose.yaml:
# Copy and configure environment
cp .env.example .env
# Edit .env with your Actual Budget server details
# Start production stack (Nginx proxy + MCP server)
docker compose --profile production up -d
# View logs
docker compose --profile production logs -f
# Stop
docker compose --profile production downRun with Docker Compose
# Full stack (Actual Budget + MCP Server)
docker compose --profile fullstack --profile dev up
# Access Actual Budget: http://localhost:5006
# Access MCP Server: http://localhost:3000Next Steps: Connect your AI assistant (LibreChat, etc.) to http://localhost:3000
🛠️ Available Tools
The MCP server exposes 62 tools organized into 12 categories. All tools follow the naming convention actual_<category>_<action>.
⚡ Exclusive ActualQL Tools: This MCP server includes 6 specialized tools powered by ActualQL that are unique to this implementation and not available in standard Actual Budget integrations. These tools provide advanced querying, aggregation, and analysis capabilities.
Accounts (7 tools)
| Tool | Description | Parameters |
|------|-------------|------------|
| actual_accounts_list | List all accounts | - |
| actual_accounts_create | Create new account | name, type, offbudget, closed |
| actual_accounts_update | Update account details | id, name?, type?, offbudget?, closed? |
| actual_accounts_delete | Permanently delete account | id |
| actual_accounts_close | Close account (soft delete) | id |
| actual_accounts_reopen | Reopen closed account | id |
| actual_accounts_get_balance | Get account balance at date | id, cutoff? |
Transactions (12 tools)
Basic Operations (6 tools)
| Tool | Description | Parameters |
|------|-------------|------------|
| actual_transactions_get | Get transactions for account | accountId, startDate?, endDate? |
| actual_transactions_filter | Filter transactions with advanced criteria | accountId?, startDate?, endDate?, minAmount?, maxAmount?, categoryId?, payeeId?, notes?, cleared?, reconciled? |
| actual_transactions_create | Create new transaction(s) | accountId, date, amount, payee?, category?, notes? |
| actual_transactions_import | Import and reconcile transactions | accountId, transactions[] |
| actual_transactions_update | Update transaction | id, amount?, payee?, category?, notes?, date? |
| actual_transactions_delete | Delete transaction | id |
⚡ Exclusive ActualQL-Powered Tools (6 tools) - Only available in this MCP server
| Tool | Description | Parameters |
|------|-------------|------------|
| actual_transactions_search_by_month | Search transactions by month using $month transform | month? (YYYY-MM, defaults to current), accountId?, categoryName?, payeeName?, minAmount?, maxAmount? |
| actual_transactions_search_by_amount | Find transactions by amount range | minAmount?, maxAmount?, startDate?, endDate?, accountId?, categoryName?, limit? |
| actual_transactions_search_by_category | Search transactions by category name | categoryName?, startDate?, endDate?, accountId?, minAmount?, maxAmount?, limit? |
| actual_transactions_search_by_payee | Find transactions by payee/vendor | payeeName?, startDate?, endDate?, accountId?, categoryName?, minAmount?, maxAmount?, limit? |
| actual_transactions_summary_by_category | Get spending summary grouped by category with aggregation | startDate? (defaults to month start), endDate? (defaults to today), accountId?, includeIncome? |
| actual_transactions_summary_by_payee | Analyze top vendors/merchants with totals and counts | startDate? (defaults to month start), endDate? (defaults to today), accountId?, limit? |
These exclusive tools use ActualQL's advanced features like $transform, groupBy, $sum, and $count for efficient queries and aggregations that go beyond standard API capabilities.
Categories (4 tools)
| Tool | Description | Parameters |
|------|-------------|------------|
| actual_categories_get | List all categories | - |
| actual_categories_create | Create new category | name, group_id, is_income? |
| actual_categories_update | Update category | id, name?, group_id?, is_income? |
| actual_categories_delete | Delete category | id |
Category Groups (4 tools)
| Tool | Description | Parameters |
|------|-------------|------------|
| actual_category_groups_get | List all category groups | - |
| actual_category_groups_create | Create category group | name, is_income? |
| actual_category_groups_update | Update group | id, name?, is_income? |
| actual_category_groups_delete | Delete group | id |
Payees (6 tools)
| Tool | Description | Parameters |
|------|-------------|------------|
| actual_payees_get | List all payees | - |
| actual_payees_create | Create new payee | name |
| actual_payees_update | Update payee | id, name?, category? |
| actual_payees_delete | Delete payee | id |
| actual_payees_merge | Merge duplicate payees | targetId, mergeIds[] |
| actual_payee_rules_get | Get rules for a payee | payeeId |
Budgets (10 tools)
| Tool | Description | Parameters |
|------|-------------|------------|
| actual_budgets_list_available | List all budget files on the server with their sync IDs | - |
| actual_budgets_switch | Switch the active budget for all subsequent operations | budgetName (string) |
| actual_budgets_get_all | List all available budget files | - |
| actual_budgets_getMonths | List available budget months | - |
| actual_budgets_getMonth | Get budget for specific month | month |
| actual_budgets_setAmount | Set category budget amount | month, categoryId, amount |
| actual_budgets_transfer | Transfer amount between categories | month, fromCategoryId, toCategoryId, amount |
| actual_budgets_setCarryover | Enable/disable carryover | month, categoryId, flag |
| actual_budgets_holdForNextMonth | Hold funds for next month | month, amount |
| actual_budgets_resetHold | Reset hold status | month |
Rules (4 tools)
| Tool | Description | Parameters |
|------|-------------|------------|
| actual_rules_get | List all rules | - |
| actual_rules_create | Create transaction rule | conditions, actions |
| actual_rules_update | Update rule | id, conditions?, actions? |
| actual_rules_delete | Delete rule | id |
Advanced Query & Sync (2 tools)
| Tool | Description | Parameters |
|------|-------------|------------|
| actual_query_run | Execute custom ActualQL query | query |
| actual_bank_sync | Trigger bank sync (GoCardless/SimpleFIN) | accountId? |
Batch Operations (1 tool)
| Tool | Description | Parameters |
|------|-------------|------------|
| actual_budget_updates_batch | Batch multiple budget updates | updates (function) |
Server Information & Lookup (3 tools)
| Tool | Description | Parameters |
|------|-------------|------------|
| actual_server_info | Get MCP server status, version and build info | - |
| actual_server_get_version | Get Actual Budget server version | - |
| actual_get_id_by_name | Resolve entity name → UUID for accounts, categories, payees, or schedules | type (accounts|categories|payees|schedules), name |
Session Management (2 tools)
| Tool | Description | Parameters |
|------|-------------|------------|
| actual_session_list | List all active MCP sessions | - |
| actual_session_close | Close a specific MCP session | sessionId |
Total: 62 tools across 12 categories (including 6 exclusive ActualQL-powered tools)
🚧 Missing Tools (Not Yet Implemented)
The following Actual Budget API features are not yet exposed as MCP tools:
Schedules (4 API methods available)
getSchedules()- List all scheduled transactionscreateSchedule()- Create recurring transaction schedulesupdateSchedule()- Modify schedule parametersdeleteSchedule()- Remove schedules
These methods require access to Actual's internal API and are not directly exported from the standard methods module.
Note: Most core financial operations (accounts, transactions, budgets, categories, payees, rules, schedules) are fully implemented. The missing features above represent specialized workflows requiring deeper API integration. Contributions welcome!
📦 Installation
Method 1: npm (Development)
# Clone repository
git clone https://github.com/agigante80/actual-mcp-server.git
cd actual-mcp-server
# Install dependencies
npm install
# Configure environment
cp .env.example .env
# Edit .env with your credentials
# Build TypeScript
npm run build
# Run server
npm run dev -- --debug --httpMethod 2: Docker (Recommended for Production)
# Pull image
docker pull ghcr.io/agigante80/actual-mcp-server:latest
# Create secrets directory
mkdir -p secrets
echo "your_password" > secrets/actual_password.txt
chmod 600 secrets/actual_password.txt
# Run container
docker run -d \
--name actual-mcp-server \
-p 3000:3000 \
-e ACTUAL_SERVER_URL=http://your-actual-server:5006 \
-e ACTUAL_PASSWORD=your_actual_password \
-e ACTUAL_BUDGET_SYNC_ID=your_sync_id \
-v actual-mcp-data:/data \
ghcr.io/agigante80/actual-mcp-server:latestMethod 3: Docker Compose (Full Stack)
# Clone repository
git clone https://github.com/agigante80/actual-mcp-server.git
cd actual-mcp-server
# Configure environment
cp .env.example .env
# Edit .env
# Start full stack (Actual Budget + MCP Server)
docker compose --profile fullstack --profile dev up -d
# Or production mode
mkdir -p secrets
echo "your_password" > secrets/actual_password.txt
docker compose --profile production up -d💬 AI Client Integration (LibreChat & LobeChat)
This MCP server has been tested and verified with both LibreChat and LobeChat. All 62 tools work correctly with both clients.
Quick Setup (Docker Environment)
⚠️ Important for Docker Deployments: Always use internal Docker hostnames (not host IPs) for container-to-container communication. This provides better performance, security, and reliability.
✅ CORRECT: http://actual-mcp-server-backend:3600/http
❌ AVOID: http://192.168.x.x:3600/http
1. Start the MCP Server
# Using Docker Compose (recommended)
docker compose up -d mcp-server
# Or standalone Docker
docker run -d --name actual-mcp-server-backend \
--network your-ai-network \
-p 3600:3600 \
-e ACTUAL_SERVER_URL=http://your-actual-server:5006 \
-e ACTUAL_PASSWORD=your_password \
-e ACTUAL_BUDGET_SYNC_ID=your_sync_id \
-e MCP_SSE_AUTHORIZATION=$(openssl rand -hex 32) \
-v actual-mcp-data:/data \
ghcr.io/agigante80/actual-mcp-server:latest2a. Configure LibreChat
Edit your librechat.yaml:
# librechat.yaml
mcpServers:
actual-mcp:
type: "streamable-http"
# ✅ Use container name (not IP) if on same Docker network
url: "http://actual-mcp-server-backend:3600/http"
headers:
Authorization: "Bearer YOUR_TOKEN_HERE"
serverInstructions: true
timeout: 600000 # 10 minutesThen restart LibreChat:
docker restart ai-librechat2b. Configure LobeChat
In LobeChat UI:
- Navigate to Settings → Language Model → Model Context Protocol
- Click Add Plugin
- Fill in the configuration:
- Name: Actual Budget MCP
- Server Type: HTTP
- Server URL:
http://actual-mcp-server-backend:3600/http✅ (use container name) - Authorization:
Bearer YOUR_TOKEN_HERE
- Click Save
LobeChat will automatically discover all 62 tools.
Network Configuration
Both the MCP server and AI client containers must be on the same Docker network:
# docker-compose.yml
networks:
ai-network:
driver: bridge
services:
librechat: # or lobe-chat
networks:
- ai-network
actual-mcp-server-backend:
networks:
- ai-networkVerify connectivity:
# From LibreChat/LobeChat container
docker exec <ai-container> wget -qO- http://actual-mcp-server-backend:3600/health
# Should return: {"status":"ok","initialized":true,...}HTTPS Setup (Optional - For External Access)
Note: HTTPS is not required when using internal Docker networking. Use HTTPS only if:
- Accessing MCP server from outside the Docker network
- Exposing server to the internet
- Compliance requirements mandate encryption
For internal Docker-to-Docker communication, HTTP is secure and simpler.
Option 1: Docker Internal Network (Recommended - No HTTPS Needed)
If both containers are on the same Docker network, use HTTP securely without TLS overhead:
# docker-compose.yml
networks:
ai-network:
driver: bridge
services:
librechat: # or lobe-chat
networks:
- ai-network
actual-mcp-server-backend:
networks:
- ai-networkConfiguration:
# LibreChat librechat.yaml
mcpServers:
actual-mcp:
url: "http://actual-mcp-server-backend:3600/http" # ✅ No HTTPS needed
# LobeChat UI
Server URL: http://actual-mcp-server-backend:3600/http # ✅ No HTTPS neededBenefits: No certificate management, simpler configuration, same security (network isolation).
Option 2: Self-Signed Certificate (Development/External Access)
For testing HTTPS or external access:
# Generate certificate
mkdir -p certs
openssl req -x509 -newkey rsa:4096 -nodes \
-keyout certs/key.pem -out certs/cert.pem \
-days 365 -subj "/CN=your-server-ip" \
-addext "subjectAltName=IP:your-server-ip,DNS:localhost"
# Run MCP server with HTTPS
docker run -d --name actual-mcp-server-backend \
-p 3600:3600 \
-e ACTUAL_SERVER_URL=http://your-actual-server:5006 \
-e ACTUAL_PASSWORD=your_password \
-e ACTUAL_BUDGET_SYNC_ID=your_sync_id \
-e MCP_SSE_AUTHORIZATION=$(openssl rand -hex 32) \
-e MCP_ENABLE_HTTPS=true \
-e MCP_HTTPS_CERT=/app/certs/cert.pem \
-e MCP_HTTPS_KEY=/app/certs/key.pem \
-v actual-mcp-data:/data \
-v $(pwd)/certs:/app/certs:ro \
ghcr.io/agigante80/actual-mcp-server:latest
# Trust certificate in LibreChat (Alpine Linux)
docker cp certs/cert.pem ai-librechat:/tmp/mcp-server.crt
docker exec -u root ai-librechat sh -c "cat /tmp/mcp-server.crt >> /etc/ssl/certs/ca-certificates.crt"
docker restart ai-librechatOption 3: CA-Signed Certificate (Production/Internet-Facing)
# Using Let's Encrypt (requires domain name)
sudo certbot certonly --standalone -d your-domain.com
sudo cp /etc/letsencrypt/live/your-domain.com/fullchain.pem certs/cert.pem
sudo cp /etc/letsencrypt/live/your-domain.com/privkey.pem certs/key.pem
sudo chown $USER:$USER certs/*.pemUpdate AI client to use domain:
# LibreChat librechat.yaml
mcpServers:
actual-mcp:
url: "https://your-domain.com:3600/http" # Use domain name
# LobeChat UI
Server URL: https://your-domain.com:3600/httpVerification
Test the connection: ACTUAL_PASSWORD=your_password ACTUAL_BUDGET_SYNC_ID=your_sync_id
Security (generate with: openssl rand -hex 32)
MCP_SSE_AUTHORIZATION=your_bearer_token_here
Transport mode (Docker only)
MCP_TRANSPORT_MODE=--http
HTTPS (optional but recommended)
MCP_ENABLE_HTTPS=true MCP_HTTPS_CERT=/app/certs/cert.pem MCP_HTTPS_KEY=/app/certs/key.pem
Optional
MCP_BRIDGE_PORT=3600 MCP_BRIDGE_DATA_DIR=/data
### Verification
Test the connection:
```bash
# Health check (adjust URL based on your setup)
curl http://localhost:3600/health
# Or with HTTPS
curl -k https://localhost:3600/health
# Should return: {"status":"ok","initialized":true,...}In your AI client, you should see:
- ✅ 62 tools loaded in the MCP servers list
- ✅ All tools available with
actual_prefix - ✅ Natural language queries working
💡 Usage Examples
Example 1: Check Account Balances
# Using curl with HTTP transport
curl -X POST http://localhost:3000/http \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "actual_accounts_list",
"arguments": {}
}
}'Example 2: Add Transaction
curl -X POST http://localhost:3000/http \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "actual_transactions_create",
"arguments": {
"accountId": "your-account-id",
"date": "2025-11-09",
"amount": -5000,
"payee": "Amazon",
"notes": "Office supplies"
}
}
}'Note: Amounts are in cents (integer). $50.00 = 5000, expenses are negative.
Example 3: Set Monthly Budget
curl -X POST http://localhost:3000/http \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "actual_budgets_setAmount",
"arguments": {
"month": "2025-11",
"categoryId": "your-category-id",
"amount": 50000
}
}
}'Example 4: Using with AI Clients (LibreChat/LobeChat)
- Start the MCP server:
npm run dev -- --http(or use Docker) - Configure your AI client to connect:
- LibreChat: Edit
librechat.yamlwith MCP server URL - LobeChat: Add plugin via Settings → Model Context Protocol
- LibreChat: Edit
- Ask natural language questions: "What's my checking account balance?"
- AI client calls appropriate tools (
actual_accounts_list,actual_accounts_get_balance) - You get conversational responses: "Your checking account balance is $1,234.56"
Both LibreChat and LobeChat work identically - all 62 tools are available for conversational financial management.
⚙️ Configuration
Environment Variables
All configuration is managed via environment variables. See .env.example for a complete reference with detailed descriptions.
Complete Environment Variables Reference
| Variable | Default | Required | Description |
|----------|---------|----------|-------------|
| Actual Budget Connection ||||
| ACTUAL_SERVER_URL | http://localhost:5006 | ✅ Yes | URL of your Actual Budget server |
| ACTUAL_PASSWORD | - | ✅ Yes | Password for Actual Budget server |
| ACTUAL_BUDGET_SYNC_ID | - | ✅ Yes | Budget Sync ID from Actual (Settings → Sync ID) |
| ACTUAL_BUDGET_PASSWORD | - | ❌ No | Optional encryption password for encrypted budgets |
| MCP Server Settings ||||
| MCP_BRIDGE_PORT | 3000 (dev)3600 (Docker) | ❌ No | Port for MCP server to listen on |
| MCP_BRIDGE_BIND_HOST | 0.0.0.0 | ❌ No | Host address to bind server to (0.0.0.0 = all interfaces) |
| MCP_BRIDGE_DATA_DIR | ./actual-data | ❌ No | Directory to store Actual Budget local data (SQLite) |
| MCP_BRIDGE_PUBLIC_HOST | auto-detected | ❌ No | Public hostname/IP for server (shown in logs) |
| MCP_BRIDGE_PUBLIC_SCHEME | auto-detected | ❌ No | Public scheme (http or https) |
| MCP_BRIDGE_USE_TLS | false | ❌ No | Legacy TLS flag (use MCP_ENABLE_HTTPS instead) |
| Transport Configuration ||||
| MCP_TRANSPORT_MODE | --http | ❌ No | Transport mode (only --http supported) - Docker only |
| MCP_HTTP_PATH | /http | ❌ No | HTTP endpoint path |
| Session Management ||||
| USE_CONNECTION_POOL | true | ❌ No | Enable session-based connection pooling |
| MAX_CONCURRENT_SESSIONS | 15 | ❌ No | Maximum concurrent MCP sessions allowed |
| SESSION_IDLE_TIMEOUT_MINUTES | 5 (pool)2 (HTTP) | ❌ No | Minutes before idle session cleanup |
| Security & Authentication ||||
| AUTH_PROVIDER | none | ❌ No | Auth mode: none (static Bearer) or oidc (JWKS-validated JWT) |
| MCP_SSE_AUTHORIZATION | - | ❌ No | Static Bearer token (AUTH_PROVIDER=none; highly recommended) |
| OIDC_ISSUER | - | ⚠️ If OIDC | OIDC issuer URL (e.g., https://sso.example.com) |
| OIDC_RESOURCE | - | ❌ No | Expected aud claim in JWT (your client ID) |
| OIDC_SCOPES | - | ❌ No | Comma-separated required scopes; leave empty for Casdoor |
| AUTH_BUDGET_ACL | - | ❌ No | Per-user budget ACL (see OIDC Authentication) |
| MCP_ENABLE_HTTPS | false | ❌ No | Enable HTTPS/TLS encryption |
| MCP_HTTPS_CERT | - | ⚠️ If HTTPS | Path to TLS certificate file (PEM format) |
| MCP_HTTPS_KEY | - | ⚠️ If HTTPS | Path to TLS private key file (PEM format) |
| Logging Configuration ||||
| MCP_BRIDGE_STORE_LOGS | false | ❌ No | Enable file logging (vs console only) |
| MCP_BRIDGE_LOG_DIR | ./logs | ❌ No | Directory for log files (if STORE_LOGS=true) |
| MCP_BRIDGE_LOG_LEVEL | debug | ❌ No | Log level: error, warn, info, debug |
| Log Rotation (when MCP_BRIDGE_STORE_LOGS=true) ||||
| MCP_BRIDGE_MAX_FILES | 14d | ❌ No | Keep rotated logs for N days (e.g., 14d, 30d) |
| MCP_BRIDGE_MAX_LOG_SIZE | 20m | ❌ No | Rotate when file reaches size (e.g., 20m, 100m) |
| MCP_BRIDGE_ROTATE_DATEPATTERN | YYYY-MM-DD | ❌ No | Date pattern for rotated log filenames |
| Development & Debugging ||||
| DEBUG | false | ❌ No | Enable debug mode (verbose logging, sets --debug flag) |
| LOG_LEVEL | info | ❌ No | Log level override: error, warn, info, debug |
| MCP_BRIDGE_DEBUG_TRANSPORT | false | ❌ No | Enable transport-level debug logging |
| Advanced/Internal ||||
| ACTUAL_API_CONCURRENCY | 5 | ❌ No | Max concurrent Actual API operations |
| USE_CONNECTION_POOL | true | ❌ No | Enable session-based connection pooling |
| VERSION | auto-detected | ❌ No | Server version (auto-set by build/Docker) |
| TZ | UTC | ❌ No | Timezone for timestamps (e.g., America/New_York) |
Quick Start Configuration
Minimum required variables to get started:
# .env file
ACTUAL_SERVER_URL=http://localhost:5006
ACTUAL_PASSWORD=your_password
ACTUAL_BUDGET_SYNC_ID=abc123Recommended production configuration:
# Required
ACTUAL_SERVER_URL=https://actual.yourdomain.com
ACTUAL_PASSWORD=your_password
ACTUAL_BUDGET_SYNC_ID=abc123
# Security (generate token: openssl rand -hex 32)
MCP_SSE_AUTHORIZATION=your_secure_random_token
# HTTPS
MCP_ENABLE_HTTPS=true
MCP_HTTPS_CERT=/path/to/cert.pem
MCP_HTTPS_KEY=/path/to/key.pem
# Logging
MCP_BRIDGE_STORE_LOGS=true
MCP_BRIDGE_LOG_LEVEL=info
# Session Management
MAX_CONCURRENT_SESSIONS=15
SESSION_IDLE_TIMEOUT_MINUTES=5Docker-specific settings:
Docker images use these defaults (can be overridden):
MCP_BRIDGE_PORT=3600(instead of 3000)MCP_TRANSPORT_MODE=--httpMCP_BRIDGE_DATA_DIR=/data(recommended for volume mount)
💰 Multi-Budget Switching
Configure multiple Actual Budget files (on the same server or on different servers) so the AI can switch between them at runtime using actual_budgets_list_available and actual_budgets_switch.
How fallback works: BUDGET_N_SERVER_URL and BUDGET_N_PASSWORD automatically fall back to ACTUAL_SERVER_URL and ACTUAL_PASSWORD when omitted — so budgets on the same server need only NAME and SYNC_ID.
| Variable | Required | Fallback |
|----------|----------|----------|
| BUDGET_DEFAULT_NAME | ❌ No | "Default" |
| BUDGET_N_NAME | ✅ Yes (enables group) | — |
| BUDGET_N_SYNC_ID | ✅ Yes | — |
| BUDGET_N_SERVER_URL | ❌ No | ACTUAL_SERVER_URL |
| BUDGET_N_PASSWORD | ❌ No | ACTUAL_PASSWORD |
| BUDGET_N_ENCRYPTION_PASSWORD | ❌ No | — |
# .env — three budget example
# Default budget (always present)
ACTUAL_SERVER_URL=http://actual-main:5006
ACTUAL_PASSWORD=my-password
ACTUAL_BUDGET_SYNC_ID=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
BUDGET_DEFAULT_NAME=My Main Budget
# Budget 1 — same server, same password (SERVER_URL + PASSWORD omitted)
BUDGET_1_NAME=Shared Family Account
BUDGET_1_SYNC_ID=bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
# Budget 2 — same server, different password (SERVER_URL omitted)
BUDGET_2_NAME=Business
BUDGET_2_PASSWORD=business-password
BUDGET_2_SYNC_ID=cccccccc-cccc-cccc-cccc-cccccccccccc
# Budget 3 — entirely separate Actual server (all fields provided)
BUDGET_3_NAME=Remote Office
BUDGET_3_SERVER_URL=https://actual-office.example.com:5006
BUDGET_3_PASSWORD=office-password
BUDGET_3_SYNC_ID=dddddddd-dddd-dddd-dddd-ddddddddddddOnce configured the AI can ask:
"Switch to the Business budget and show my uncategorized transactions this month."
and the server will call actual_budgets_switch then proceed in the selected budget context for all subsequent operations in that session.
🔌 Transport & Authentication
The MCP server uses HTTP transport with optional Bearer token authentication.
HTTP Transport
Best for: All deployments (only transport supported)
# Start server with HTTP
npm run dev -- --http
# Or with authentication
MCP_SSE_AUTHORIZATION=your_token npm run dev -- --httpLibreChat Configuration:
# librechat.yaml
mcpServers:
actual-mcp:
type: "streamable-http"
url: "http://your-server-ip:3600/http"
headers:
Authorization: "Bearer your_token_here"
serverInstructions: trueFeatures:
- ✅ Full MCP protocol support via
@modelcontextprotocol/sdk - ✅ Bearer token authentication via headers
- ✅ All 62 tools load successfully in LibreChat
- ✅ Session management with
MCP-Session-Idheaders - ✅ Production-ready and tested
Authentication Configuration
Enable Authentication
Set the MCP_SSE_AUTHORIZATION environment variable:
# Generate a secure token
openssl rand -hex 32
# Add to .env
MCP_SSE_AUTHORIZATION=your_generated_token_here
# Restart server
npm run build
npm run dev -- --httpAuthentication Behavior
When MCP_SSE_AUTHORIZATION is set:
- ✅ Server validates Bearer tokens on all requests
- ✅ Returns
401 Unauthorizedfor missing/invalid tokens - ✅ Logs all authentication attempts
- ✅ Optional - if not set, authentication is disabled
Security Best Practices
# ✅ DO: Use strong random tokens (32+ characters)
MCP_SSE_AUTHORIZATION=$(openssl rand -hex 32)
# ✅ DO: Use HTTPS in production
ACTUAL_SERVER_URL=https://actual.yourdomain.com
# ✅ DO: Rotate tokens regularly
# ✅ DO: Use environment variables, never hardcode
# ✅ DO: Monitor authentication failures in logs
# ❌ DON'T: Use weak or predictable tokens
# ❌ DON'T: Commit tokens to version control
# ❌ DON'T: Share tokens between environmentsOIDC Authentication (Multi-User)
For multi-user deployments with an OIDC provider (Casdoor, Keycloak, Auth0, etc.), set AUTH_PROVIDER=oidc.
The server validates JWTs using JWKS from the issuer and optionally enforces per-user budget ACLs.
# .env — OIDC mode
AUTH_PROVIDER=oidc
OIDC_ISSUER=https://sso.yourdomain.com
OIDC_RESOURCE=your-client-id # must match 'aud' claim in JWT
OIDC_SCOPES= # leave empty for Casdoor (no 'scope' claim)
[email protected]:budget-uuid-1,[email protected]:budget-uuid-2Casdoor compatibility note: Casdoor auth-code flow JWTs do not include a scope claim.
Set OIDC_SCOPES= (empty) to disable scope enforcement. The server logs Scopes required: (none).
LibreChat with OIDC — configure the OIDC MCP instance via the LibreChat admin UI (OAuth flow).
For a static-token fallback instance, add to librechat.yaml:
mcpServers:
actual-bearer:
type: "http"
url: "http://your-mcp-server:3600/http"
headers:
Authorization: "Bearer your_token_here"
serverInstructions: falseAUTH_BUDGET_ACL format: comma-separated principal:budget-sync-id pairs.
Principals can be email addresses, OIDC sub values, or group:groupname.
[email protected]:aaa-bbb-ccc,group:admins:ddd-eee-fffTesting Results
| Test Case | Result | Tools Loaded | |-----------|--------|--------------| | HTTP without auth | ✅ Success | 62 tools | | HTTP with auth (Bearer) | ✅ Success | 62 tools | | HTTP with OIDC (Casdoor v2.13) | ✅ Success | 62 tools |
🐳 Docker Deployment
Docker Hub
Pull from GitHub Container Registry:
docker pull ghcr.io/agigante80/actual-mcp-server:latestProduction Deployment
# 1. Create secrets
mkdir -p secrets
echo "your_password" > secrets/actual_password.txt
chmod 600 secrets/actual_password.txt
# 2. Create environment file
cat > .env << EOF
ACTUAL_SERVER_URL=https://actual.yourdomain.com
ACTUAL_BUDGET_SYNC_ID=abc123
MCP_SSE_AUTHORIZATION=$(openssl rand -hex 32)
EOF
# 3. Run with Docker Compose
docker compose --profile production up -d
# 4. Check logs
docker compose logs -f mcp-server-prod
# 5. Verify health
curl http://localhost:3000/healthKubernetes
See docs/deployment.md for Kubernetes manifests with:
- Deployments with health checks
- Services and Ingress
- Secret management
- Resource limits
- Horizontal scaling
🏗️ Architecture
┌──────────────┐ ┌──────────────┐ ┌─────────────┐
│ LibreChat │ MCP │ MCP Server │ REST │ Actual │
│ (AI Client) │◄────────┤ (62 Tools) │◄────────┤ Budget │
│ │ │ +6 Exclusive│ │ │
└──────────────┘ └──────────────┘ └─────────────┘
│
▼
┌──────────────┐
│ SQLite DB │
│ (Cached) │
└──────────────┘Tech Stack
- Runtime: Node.js 20 (Alpine Linux in Docker)
- Language: TypeScript 5.9 (ES2020 + ESM modules)
- MCP SDK: @modelcontextprotocol/sdk ^1.25.2
- API Client: @actual-app/api ^26.3.0
- Validation: Zod (runtime type checking + JSON Schema)
- Transport: Express (HTTP)
- Logging: Winston with daily rotation
- Testing: Playwright, unit tests, integration tests
Key Design Patterns
- Singleton: Tool manager, logger, config
- Adapter: Wraps Actual API with retry/concurrency
- Strategy: Multiple transport implementations
- Factory: Dynamic tool loading and registration
See docs/architecture.md for detailed architecture documentation.
📊 API Coverage
Coverage by Category
| Category | Coverage | Tools | Status | |----------|----------|-------|--------| | Accounts | 100% | 7/7 | ✅ Complete | | Transactions | 100% | 12/12 | ✅ Complete + 6 exclusive ActualQL tools | | Categories | 100% | 4/4 | ✅ Complete | | Category Groups | 100% | 4/4 | ✅ Complete | | Payees | 100% | 6/6 | ✅ Complete | | Budgets | 100% | 10/10 | ✅ Complete (incl. list available + switch) | | Rules | 100% | 4/4 | ✅ Complete | | Query & Sync | 100% | 2/2 | ✅ Complete | | Batch | 100% | 1/1 | ✅ Complete | | Server Info & Lookup | 100% | 3/3 | ✅ Complete |
Overall: 84% API Coverage (62 tools covering all major Actual Budget operations)
⚡ Exclusive Features: This MCP server includes 6 ActualQL-powered tools that provide advanced querying and aggregation capabilities not available in other Actual Budget integrations.
Not Yet Implemented
- ❌ Scheduled transactions (recurring payments)
- ❌ Budget templates
- ❌ Transaction notes/attachments
Note: The actual_query_run tool provides direct ActualQL access for advanced custom queries beyond the 62 pre-built tools.
- ❌ Custom reports
See docs/api-coverage.md for complete API documentation with examples.
🧪 Testing
Quick Test Commands
# Run all tests (adapter + unit + Docker E2E)
npm run test:all
# Run protocol tests only (fast)
npm run test:e2e
# Run Docker integration tests (full stack)
npm run test:e2e:dockerTest Types
| Command | What It Tests | Speed | Requires Docker |
|---------|---------------|-------|-----------------|
| test:adapter | Adapter layer, retry logic, concurrency | ⚡ 30s | No |
| test:unit-js | Unit tests (schema, 62-tool smoke, negative-path) | ⚡ 5s | No |
| test:e2e | MCP protocol compliance | ⚡ 10s | No |
| test:e2e:docker | Full stack integration | 🐢 60s | Yes |
| test:all | All of the above | 🐢 90s | Yes |
Integration Tests (Live Server)
The tests/manual/ suite connects to a running MCP server over HTTP and exercises the full JSON-RPC protocol — no mocking. Six test levels cascade upward:
| Level | Writes? | What runs |
|-------|---------|----------|
| sanity | No | Protocol checks: tool count, server info, SQL, GraphQL rejection |
| smoke | No | Sanity + account balances, categories, recent transactions |
| normal | Yes | Account lifecycle (create → update → close → reopen) |
| extended | Yes | Normal + category groups, categories, payees, transactions |
| full | Yes | Extended + budgets, rules, batch operations, advanced queries |
| cleanup | Yes | Finds and removes all MCP-Test-* / MCP-Cat-* / MCP-Group-* / MCP-Payee-* data |
# Via npm scripts
npm run test:integration # sanity (default)
npm run test:integration:full # full test run
npm run test:integration:cleanup # delete all test data
# Direct invocation
node tests/manual/index.js [MCP_URL] [TOKEN] [LEVEL] [CLEANUP]
# Example:
node tests/manual/index.js http://localhost:3600/http mytoken full yesSee tests/manual/README.md for complete documentation.
Docker E2E Tests
The Docker E2E tests provide comprehensive full-stack validation:
# Run Docker-based integration tests
npm run test:e2e:docker
# Advanced options
./tests/e2e/run-docker-e2e.sh --no-cleanup # Leave containers for debugging
./tests/e2e/run-docker-e2e.sh --verbose # Show detailed outputWhat's tested:
- ✅ Docker build correctness
- ✅ Container networking
- ✅ Real Actual Budget integration (all 62 tools)
- ✅ Session management
- ✅ Production deployment scenario
See docs/DOCKER_E2E_TESTING.md for complete documentation.
🤝 Contributing
We welcome contributions! Please see our Contributing Guide for:
- Development setup
- Code standards
- Testing requirements
- Pull request process
- Auto-merge workflow
Quick Contribution Guide
- Fork the repository
- Create feature branch:
git checkout -b feature/my-feature - Make changes and add tests
- Commit:
git commit -m "feat: add new feature" - Push:
git push origin feature/my-feature - Open Pull Request
📚 Documentation
- Architecture: System design and technical details
- API Coverage: Complete tool reference with examples
- Deployment: Docker, Kubernetes, bare metal guides
- Development: Local development and debugging
- Contributing: Contribution guidelines and workflow
📜 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Actual Budget: Open-source budgeting software
- Model Context Protocol: Standardized AI-app integration
- LibreChat: Open-source ChatGPT alternative
- Community contributors: Thank you for your support!
📞 Support & Community
- GitHub Issues: Report bugs or request features
- GitHub Discussions: Ask questions and share ideas
- Documentation: Comprehensive guides in /docs
Made with ❤️ by the Actual MCP Server community
