finanzas-pyme
# finanzas-mcp — SME finance assistant (MCP server)
An **MCP (Model Context Protocol) server** that lets an LLM answer real finance
questions for a small business owner with no accountant: *what did I spend last
month and on what, are my sales growing, and will I have enough money next
month?*
It runs locally over the **stdio** transport and speaks JSON-RPC 2.0, so any MCP
host — Claude Desktop, VS Code, or a custom chatbot — can use it.
## What it does
The server does not just run SELECTs. Each tool applies a documented method and
returns a report that states its own assumptions:
| Tool | Question it answers | Method |
|------|--------------------|--------|
| `desglose_gastos` | "How much did I spend in July, and on what?" | Category breakdown with shares, fixed/variable split, comparison against the previous month and the 3-month average |
| `tendencia_ingresos` | "Are my sales growing or falling?" | Month-over-month, year-over-year, and a least-squares trend reported **with its R²** |
| `proyeccion_flujo_caja` | "Will I have enough money next month?" | Income as a damped seasonal trend; fixed costs as level × seasonal index; variable costs as a median share of income |
| `estado_resultados` | "Did I make or lose money in May?" | Profit-and-loss statement with net margin |
| `detectar_gastos_atipicos` | "Was there any unusual expense?" | Per-category z-scores against each category's own history |
| `salud_financiera` | "How is my business doing overall?" | Trailing averages, fixed-cost coverage, payroll weight, loss-making months |
## The simulated business
"Panadería La Espiga", a small bakery. Covering **2025-01 to 2026-08** (20 months, 1,088 transactions).
## Installation
Requires **Python 3.10+**. No database server and no API key: the SQLite file is
built automatically on first run from the two bundled SQL scripts.
> The dependency is pinned to `mcp>=1.27,<2`. The MCP Python SDK v2 renamed
> `FastMCP` to `MCPServer` and changed several public field names; an unpinned
> install picks up 2.x and fails at import.
### Use it as a dependency
If you only want to *use* this server, one command is enough — no clone, no
database setup, no API key:
```bash
pip install git+https://github.com/JuanDsm04/mcp-finanzas-pyme.git
```
That installs the package and creates a `finanzas-mcp` executable in your
environment, which is what you point your MCP host at.
### Develop on it
```bash
git clone https://github.com/JuanDsm04/mcp-finanzas-pyme.git
cd mcp-finanzas-pyme
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
```
Verify it starts:
```bash
python -m finanzas_mcp.server --help
```
## Connecting it to a host
**Any MCP host** (generic stdio entry). After installing, either of these works —
the console script, or the module, which is handy when you want to be explicit
about which interpreter runs it:
```json
{
"command": "finanzas-mcp",
"args": []
}
```
```json
{
"command": "python",
"args": ["-m", "finanzas_mcp.server"]
}
```
No working directory or `PYTHONPATH` is needed in either case.
**If your host does not inherit the active virtual environment** — Claude Desktop
does not, and some editors do not either — use absolute paths:
```json
{
"command": "C:\\path\\to\\your\\.venv\\Scripts\\finanzas-mcp.exe",
"args": []
}
```
```json
{
"command": "/absolute/path/to/.venv/bin/python",
"args": ["-m", "finanzas_mcp.server"]
}
```
**chatbot-redes** — install into the same virtual environment as the chatbot,
then flip the registry entry to enabled:
```bash
pip install -e ../mcp-finanzas-pyme
```
```json
"finanzas-pyme": {
"enabled": true,
"transport": "stdio",
"description": "Servidor MCP propio: asistente financiero para PYMES",
"command": "python",
"args": ["-m", "finanzas_mcp.server"],
"env": {}
}
```
Check the connection without spending API credits with
`python scripts/check_servers.py` from the chatbot repo.
**Claude Desktop** — add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"finanzas-pyme": {
"command": "/absolute/path/to/.venv/bin/finanzas-mcp",
"args": []
}
}
}
```
**MCP Inspector** — the quickest way to check the server before wiring it into a
host:
```bash
npx @modelcontextprotocol/inspector finanzas-mcp
```
### Where the database lives
With an editable install the database goes to `data/finanzas.db` inside the
repository. With a regular install the package lives in `site-packages`, so it
lands inside the virtual environment instead. Both work; to choose the location
explicitly, pass the flag in `args`:
```json
{
"command": "finanzas-mcp",
"args": ["--db-path", "C:\\Users\\you\\finanzas.db"]
}
```
Running `finanzas-mcp` by hand just blocks: it is waiting for JSON-RPC messages
on stdin. That is expected — the host is what launches it. Ctrl+C exits.
## Usage
Questions a user can ask in plain language, and the tool that answers them:
| You ask | The model calls |
|---------|-----------------|
| "¿Cuánto gasté en agosto?" | `desglose_gastos(mes="2026-08")` |
| "¿En qué se me fue el dinero el mes pasado?" | `desglose_gastos()` |
| "¿Mis ventas están subiendo o bajando?" | `tendencia_ingresos()` |
| "¿Gané dinero en marzo?" | `estado_resultados(mes="2026-03")` |
| "¿Me va a alcanzar el próximo mes? Tengo Q45,000" | `proyeccion_flujo_caja(meses=1, saldo_inicial=45000)` |
| "¿Hubo algún gasto raro este año?" | `detectar_gastos_atipicos()` |
| "¿Cómo va mi negocio?" | `salud_financiera()` |
## Configuration
| Variable / flag | Default | Purpose |
|-----------------|---------|---------|
| `FINANZAS_DB_PATH` | `data/finanzas.db` | Where the SQLite file lives |
| `--db-path PATH` | — | Same, as a flag (takes precedence) |
| `--rebuild` | off | Delete and rebuild the database before starting |
## Server specification
### Identity and transport
| Field | Value |
|-------|-------|
| Server name | `finanzas-pyme` |
| Implementation | `finanzas-mcp` 0.1.0 |
| Protocol | Model Context Protocol over JSON-RPC 2.0 |
| Transport | stdio (the host launches the server as a subprocess) |
| Launch command | `python -m finanzas_mcp.server` |
| SDK | MCP Python SDK v1 (`mcp>=1.27,<2`), `FastMCP` |
Capabilities advertised at `initialize`:
```json
{
"tools": { "listChanged": false },
"resources": { "subscribe": false, "listChanged": false },
"prompts": { "listChanged": false }
}
```
The server also returns an `instructions` string telling the host that amounts
are in GTQ, that months use `YYYY-MM`, and that omitting the month selects the
latest month with data. It writes nothing to stdout, since on the stdio transport
stdout is the protocol channel; diagnostics go to stderr.
### Tools
**`desglose_gastos`** — breaks one month's expenses down by category.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `mes` | `string \| null` | `null` | Month, `YYYY-MM`. Null = latest month with data. |
| `incluir_comparacion` | `boolean` | `true` | Add comparison vs previous month and 3-month average. |
Returns the total and movement count; the fixed/variable split; a table per
category with amount, share, movements and a bar; optionally the comparison
block; the top 5 suppliers; the top 5 individual movements. Errors on a
malformed month or a month outside the available period.
**`tendencia_ingresos`** — analyses whether income is growing or shrinking.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `mes` | `string \| null` | `null` | Last month of the window, `YYYY-MM`. |
| `meses` | `integer` | `6` | Window size, 3 to 24. |
Returns the month-over-month change; the year-over-year comparison when 12
months of history exist; the least-squares trend with slope per month, slope as
a percentage of the window average, and **R²** with a reliability label (`alta`
≥ 0.7, `media` ≥ 0.4, `baja` below). When R² < 0.4 it explicitly warns that the
slope is indicative, not predictive. Errors when `meses` is outside 3..24 or
there are fewer than 3 months of history.
**`proyeccion_flujo_caja`** — projects income, expenses and cash balance.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `meses` | `integer` | `1` | Months to project, 1 to 6. |
| `saldo_inicial` | `number \| null` | `null` | Cash on hand in GTQ. Null = use the accumulated result of the last 6 months as a proxy (stated in the output). |
| `ventana` | `integer` | `6` | Trailing months used to fit, 3 to 18. |
Method, printed in the output so it is auditable:
1. **Income** — least squares over the last `ventana` months, extrapolated, then
multiplied by the seasonal index of the target calendar month, damped by 0.5
because the history is short.
2. **Fixed costs** — per category as `level × seasonal_index`, where the level is
the mean of the trailing window over a zero-filled series and the index is not
damped.
3. **Variable costs** — the median share of income they absorbed over the window,
applied to projected income.
Returns a table per projected month (income, fixed, variable, net flow, running
balance); the composition of the first month's fixed costs; a verdict — `SI`
(covers everything), `AJUSTADO` (covers fixed but not variable), or `NO`; the
coverage ratio, runway in months, lowest projected balance; and a warning that
the projection ignores commitments not present in the data. Errors on `meses`
outside 1..6, `ventana` outside 3..18, or a negative `saldo_inicial`.
**`estado_resultados`** — profit-and-loss statement for one month.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `mes` | `string \| null` | `null` | Month, `YYYY-MM`. |
Returns income by category with shares; expenses by category tagged fixed or
variable, each as a percentage of income; subtotals; net profit or loss and net
margin; the previous month for reference.
**`detectar_gastos_atipicos`** — flags months where a category deviates from its
own norm.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `meses` | `integer` | `12` | Trailing months to inspect, minimum 3. |
| `umbral_z` | `number` | `2.0` | Minimum absolute z-score to report. |
For each expense category with at least four observations in the window, monthly
totals are converted to standard scores against that category's own mean and
population standard deviation. Returns a table sorted by |z| plus the individual
transactions explaining the three largest deviations. When nothing exceeds the
threshold it says so and suggests a lower one. Errors on `meses` < 3 or
`umbral_z` ≤ 0.
**`salud_financiera`** — one-screen snapshot. No parameters. Returns the latest
closed month and available history; 6-month averages for income, expenses and
result; average net margin; average fixed costs and coverage ratio; payroll cost
and its weight over income; loss-making months in the last year; the active
roster.
### Resources
| URI | MIME type | Content |
|-----|-----------|---------|
| `finanzas://esquema` | `text/plain` | The complete DDL (5 tables and the monthly view) |
| `finanzas://catalogo/categorias` | `text/plain` | `nombre\|tipo\|es_fijo\|descripcion` |
| `finanzas://meses` | `text/plain` | Months with data, one per line |
### Prompts
**`revision_mensual`** (`mes: string`, default `""` = latest month) renders a
reusable instruction that walks the model through a full monthly review: income
statement, expense breakdown, income trend, anomaly detection, then three
conclusions and one actionable recommendation in plain language.
### Data model
```
categorias(id, nombre, tipo, es_fijo, descripcion)
proveedores(id, nombre, categoria_id -> categorias, dias_credito, activo)
clientes(id, nombre, segmento, fecha_alta, activo)
empleados(id, nombre, puesto, salario_mensual, fecha_ingreso, fecha_salida, activo)
transacciones(id, fecha, tipo, monto, categoria_id -> categorias,
proveedor_id -> proveedores, cliente_id -> clientes,
metodo_pago, descripcion)
v_resumen_mensual: monthly totals per category
```
`categorias.tipo` and `transacciones.tipo` are both constrained to
`'ingreso' | 'gasto'`; `categorias.es_fijo` is the flag the projection depends
on; `transacciones.monto` must be positive, with direction carried by `tipo`.
Indexes exist on `fecha`, `(tipo, fecha)` and `(categoria_id, fecha)`, the three
access patterns every tool uses.
The database is built on first use into `data/finanzas.db` via a temporary file
that is renamed on success, so a crash mid-import cannot leave a half-populated
database.
### Security notes
Every SQL statement is parameterised; no tool interpolates model-provided strings
into SQL. The server is read-only — no tool writes to the ledger. The database is
local and synthetic: no network access, no credentials, no personal data.
## Regenerating the dataset
`seed.sql` is generated, not hand-written. The generator is deterministic, so
re-running it reproduces the same file:
```bash
python scripts/generate_seed.py
rm -f data/finanzas.db # rebuilt automatically on the next run
```
To change the simulated business, edit the constants at the top of
[`scripts/generate_seed.py`](scripts/generate_seed.py): period, categories,
suppliers, customers, payroll, seasonality, growth rate and planted anomalies.
## Project structure
```
mcp-finanzas-pyme/
├── src/finanzas_mcp/
│ ├── schema.sql DDL: 5 tables + 1 view
│ ├── seed.sql DML: generated, 1,088 transactions
│ ├── db.py SQLite access; builds the database on first use
│ ├── analytics.py Trend, seasonality, projection, z-scores
│ ├── formatting.py Text rendering helpers
│ ├── errors.py ToolInputError
│ ├── tools/
│ │ ├── gastos_por_categoria.py desglose_gastos, detectar_gastos_atipicos
│ │ ├── tendencia_ingresos.py tendencia_ingresos, estado_resultados
│ │ └── proyeccion_flujo.py proyeccion_flujo_caja, salud_financiera
│ └── server.py MCP registration + stdio entry point
├── scripts/generate_seed.py
└── docs/ejemplos.md Worked examples with real output
```
The layering is deliberate: `analytics.py` knows nothing about SQL, the tools
know nothing about MCP, and `server.py` is a thin registration wrapper.
## References
* [MCP architecture](https://modelcontextprotocol.io/docs/learn/architecture)
* [MCP specification](https://modelcontextprotocol.io/specification/2025-06-18)
* [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
* [JSON-RPC 2.0](https://www.jsonrpc.org/)
TDQS
Scored across 6 tools
Each tool targets a distinct financial aspect: expense breakdown, anomaly detection, income statement, revenue trend, cash flow projection, and overall health. While some overlap in scope (e.g., estado_resultados and salud_financiera both provide summaries), their purposes and outputs are clearly differentiated, making misselection unlikely.
All tool names are in Spanish snake_case, following a descriptive noun or verb-noun pattern. They are consistent in style and clarity, though not all adhere to a strict verb_noun convention. The naming is predictable and readable.
Six tools is well-scoped for a financial analysis server. Each tool covers a meaningful analytical function without redundancy, and the count is appropriate for the domain, allowing agents to choose the right tool without overwhelming options.
The tool surface covers core financial analysis needs: expense breakdown, anomaly detection, income statement, revenue trend, cash flow projection, and a health summary. Minor gaps include lack of a dedicated budget comparison or year-over-year P&L tool, but the existing set handles most common queries without dead ends.