GnuCash Read-Only MCP Server
A Python-based Model Context Protocol (MCP) server that exposes a local GnuCash database (MariaDB) as read-only tools for LLMs.
The goal of this project is to allow local or cloud-based models (via an MCP client like Claude Desktop or a local llama.cpp agent) to query personal finance data using natural language, without risking data mutation.
Architecture
- Language: Python 3.10+
- Database: MariaDB (Raw SQL, no ORM)
- Protocol: Official MCP Python SDK (stdio transport)
- LLM Testing:
llama.cppandpromptfoo
The server exposes a subset of SQL queries as MCP tools. The LLM determines which tool to call and provides the arguments, and the Python server executes the query and returns a JSON envelope.
Security Model (Read-Only Enforcement)
Because this tool gives an LLM access to a personal database, it uses defense-in-depth to prevent writes:
- Database Level: The MariaDB user is restricted to
GRANT SELECTon the specific schema. - Session Level: The Python connection explicitly executes
SET SESSION TRANSACTION READ ONLYand sets amax_statement_timebefore running any query. - Application Level:
guard.pyparses the generated SQL, stripping comments and rejecting any statement that is not aSELECTorWITH ... SELECT. It also blocks file writes (INTO OUTFILE) and locking clauses (FOR UPDATE).
Available Tools
The server provides the following tools to the LLM:
| Tool | Description |
|---|---|
list_accounts |
Returns the account tree with GUIDs, names, and types |
get_account_balance |
Calculates the balance of an account and its descendants as of a specific date |
get_recent_transactions |
Returns recent transactions for a given account |
get_spending_by_category |
Aggregates expenses over a date range |
get_income_by_category |
Aggregates income over a date range |
search_transactions |
Case-insensitive search across transaction descriptions and memos |
get_net_worth |
Calculates total assets minus total liabilities |
Installation
Quick Start
# Clone the repository
git clone https://github.com/yourusername/gnucash-mcp.git
cd gnucash-mcp
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install dependencies
pip install -e ".[dev]"
# Create .env file (see .env.example)
cp .env.example .env
# Edit .env with your database credentials
Running the Server
# Start the MCP server
python server.py
The server will run on stdio and connect to your MCP client.
Using with an MCP Client
Claude Desktop
Add to your claude_desktop_config.json:
{
"mcpServers": {
"gnucash-mcp": {
"command": "python",
"args": ["path/to/gnucash-mcp/server.py"],
"env": {
"DB_HOST": "127.0.0.1",
"DB_PORT": "3306",
"DB_USER": "gnucash_ro",
"DB_PASSWORD": "your_password",
"DB_NAME": "gnucash",
"QUERY_TIMEOUT_S": "10"
}
}
}
}
llama.cpp / Ollama
Configure your local LLM client to use the stdio transport and point it to the server process.
Usage Examples
Query Account Balance
"How much money do I have in my EQ Bank account as of 2024-01-15?"
Get Recent Transactions
"Show me my last 10 transactions for the Savings account."
Analyze Spending
"How much did I spend on groceries in December 2023?"
Get Net Worth
"What is my current net worth?"
Search Transactions
"Find all transactions related to 'rent' in 2024."
Implementation Notes
Handling GnuCash Data Formats:
GnuCash stores currency amounts as integer pairs (value_num / value_denom). The SQL queries divide these using CAST(value_num AS DECIMAL) / value_denom to avoid floating-point inaccuracies.
Dates in GnuCash are stored as DATETIME values. The Python helpers convert standard ISO dates (YYYY-MM-DD) into the appropriate query bounds.
Double-Entry Accounting & LLMs: GnuCash stores liabilities and income as negative numbers. Small local models often get confused by this when calculating net worth. To fix this, the server splits tools into two categories:
- Inspection tools (like
get_account_balance) return raw ledger values. - Reporting tools (like
get_net_worth) use SQLABS()and explicit JSON keys (e.g.,total_liabilities_owed) to return human-readable positive values.
Local LLM Evaluation
To ensure the server works reliably with local models, I set up an evaluation suite using promptfoo and llama-swap. The tests cover tool routing, argument formatting, and loop prevention.
Models tested include Qwen3.5-9B, LocoOperator-4B, and Ling-3B. You can find the test configurations in the evals/ directory and the results summary in docs/evaluation-results.md.
Configuration
Create a .env file in the project root:
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=gnucash_ro
DB_PASSWORD=your_secure_password
DB_NAME=gnucash
QUERY_TIMEOUT_S=10
Security Warning: Never commit your .env file! Use .env.example as a template and add your actual credentials separately.
Testing
Unit Tests
pytest tests/
Smoke Test
python smoke_test.py "EQ Bank"
Linting
ruff check .
Full Evaluation
promptfoo eval
Troubleshooting
Connection Refused
- Ensure MariaDB is running and accessible
- Verify the database user has
SELECTpermissions - Check that the database exists and is accessible
Query Timeout
- Increase
QUERY_TIMEOUT_Sin.env - Check for slow queries in your GnuCash database
No Matching Account
- Use the full account path (e.g.,
Assets:Current Assets:Angie:EQ Bank) - Use a substring search (e.g.,
EQ Bank) if the exact path is unknown
SQL Guard Rejection
- The guard blocks non-SELECT queries for security
- Ensure you're not asking for write operations
License
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0) - see the LICENSE file for details.
Contributing
See CONTRIBUTING.md for guidelines on how to contribute.
Security
This project handles sensitive financial data. See SECURITY.md for security policies and responsible disclosure procedures.
Architecture
For detailed architecture documentation, see docs/architecture.md.
Evaluation Results
See docs/evaluation-results.md for LLM performance benchmarks.