npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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

License: MIT Node.js Version TypeScript MCP Protocol Docker Pulls Docker Image Size GitHub Actions CI GitHub stars

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

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 storage

How It Works

  1. User chats with LibreChat in natural language
  2. LibreChat sends MCP tool requests to Actual MCP Server
  3. Actual MCP Server translates requests to Actual Budget API calls
  4. Actual Budget executes operations and returns data
  5. Actual MCP Server formats response for AI consumption
  6. 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 name

Both 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:

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 -- --debug

Run with Docker

Docker images are published on two registries:

Available tags:

  • latest - Latest stable release from main branch
  • latest-<sha> - Specific commit from main (e.g., latest-abc1234)
  • development - Latest development build
  • development-<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/health

With 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/health

Pull 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-abc1234

Using 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 down

Run 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:3000

Next 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 transactions
  • createSchedule() - Create recurring transaction schedules
  • updateSchedule() - Modify schedule parameters
  • deleteSchedule() - 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 --http

Method 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:latest

Method 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:latest

2a. 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 minutes

Then restart LibreChat:

docker restart ai-librechat

2b. Configure LobeChat

In LobeChat UI:

  1. Navigate to SettingsLanguage ModelModel Context Protocol
  2. Click Add Plugin
  3. 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
  4. 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-network

Verify 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-network

Configuration:

# 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 needed

Benefits: 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-librechat

Option 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/*.pem

Update 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/http

Verification

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)

  1. Start the MCP server: npm run dev -- --http (or use Docker)
  2. Configure your AI client to connect:
    • LibreChat: Edit librechat.yaml with MCP server URL
    • LobeChat: Add plugin via Settings → Model Context Protocol
  3. Ask natural language questions: "What's my checking account balance?"
  4. AI client calls appropriate tools (actual_accounts_list, actual_accounts_get_balance)
  5. 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=abc123

Recommended 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=5

Docker-specific settings:

Docker images use these defaults (can be overridden):

  • MCP_BRIDGE_PORT=3600 (instead of 3000)
  • MCP_TRANSPORT_MODE=--http
  • MCP_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-dddddddddddd

Once 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 -- --http

LibreChat Configuration:

# librechat.yaml
mcpServers:
  actual-mcp:
    type: "streamable-http"
    url: "http://your-server-ip:3600/http"
    headers:
      Authorization: "Bearer your_token_here"
    serverInstructions: true

Features:

  • ✅ Full MCP protocol support via @modelcontextprotocol/sdk
  • ✅ Bearer token authentication via headers
  • ✅ All 62 tools load successfully in LibreChat
  • ✅ Session management with MCP-Session-Id headers
  • ✅ 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 -- --http

Authentication Behavior

When MCP_SSE_AUTHORIZATION is set:

  • ✅ Server validates Bearer tokens on all requests
  • ✅ Returns 401 Unauthorized for 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 environments

OIDC 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-2

Casdoor 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: false

AUTH_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-fff

Testing 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:latest

Production 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/health

Kubernetes

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:docker

Test 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 yes

See 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 output

What'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

  1. Fork the repository
  2. Create feature branch: git checkout -b feature/my-feature
  3. Make changes and add tests
  4. Commit: git commit -m "feat: add new feature"
  5. Push: git push origin feature/my-feature
  6. Open Pull Request

📚 Documentation


📜 License

This project is licensed under the MIT License - see the LICENSE file for details.


🙏 Acknowledgments


📞 Support & Community


Made with ❤️ by the Actual MCP Server community