crypto-transactions-mcp
# crypto-transactions-mcp
MCP server per tracciare transazioni e ordini eseguiti nel mercato crypto.
Espone tool MCP che permettono a un agent LLM (Claude, agy, ecc.) di
registrare trade, swap e trasferimenti con precisione `Decimal` esatta, e di
interrogare lo storico — con persistenza intercambiabile su un file JSON
locale o su un database Notion.
## Quick Start
1. `uv sync` per installare le dipendenze.
2. Scegli un backend:
- **JSON** (default, zero config) — salta al punto 3.
- **Notion** — segui prima la sezione ["Setup Notion da zero"](#setup-notion-da-zero) qui sotto.
3. Aggiungi il server alla configurazione del tuo host MCP: vedi
["Configurazione Claude Desktop"](#configurazione-claude-desktop) o
["Configurazione agy"](#configurazione-agy).
4. Riavvia l'host e prova, ad esempio: *"Ho comprato 0.1 BTC su Binance a
65000 USDT, fee 0.0001 BTC, registralo con log_transaction."*
Architettura onion:
- `domain/` — entità `CryptoTransaction` (Pydantic v2, campi monetari in `Decimal`). `side` è `BUY`/`SELL` (trade, incluso uno swap crypto/crypto) oppure `TRANSFER` (spostamento dello stesso asset tra due custodie); i due casi hanno requisiti diversi validati nel modello stesso (BUY/SELL richiedono `price`/`value`, TRANSFER richiede `to_exchange`)
- `interfaces/` — port `TransactionRepository` e `TransactionFilters`
- `usecases/` — `LogTransactionUseCase` (trade, upsert con merge parziale), `LogTransferUseCase` (transfer, stessa semantica di upsert), `GetTransactionHistoryUseCase`
- `infrastructure/` — adapter concreti del port `TransactionRepository`:
- `JsonTransactionRepository`, file JSON locale, path fisso `data/crypto_transactions.json`
- `NotionTransactionRepository`, database Notion (vedi sotto)
- `delivery/` — server `FastMCP`, tool MCP (`log_transaction`, `log_transfer`, `get_transaction_history`), wiring delle dipendenze in `factories.py`
## Tool MCP
- **`log_transaction`** — trade BUY/SELL, incluso uno swap tra due crypto (es. ETH/BTC): stessa forma di un trade normale, non serve un tool a parte. Supporta `fiat_value`/`fiat_currency` opzionali per fissare il controvalore fiat al momento dello scambio — fortemente consigliato per gli swap crypto/crypto, dove il solo `price` (cambio relativo tra le due crypto) non permette di calcolare a posteriori il P&L realizzato né il cost-basis fiat del nuovo asset ricevuto.
- **`log_transfer`** — spostamento dello stesso asset tra due custodie (`exchange` → `to_exchange`), non un trade: niente `side`/`price`, ma richiede `to_exchange`.
- **`get_transaction_history`** — storico di entrambi, con filtri opzionali (`symbol`, `exchange`, `side` incluso `"TRANSFER"`, `date_from`/`date_to`), ordinato per data decrescente (le più recenti prima) e paginato (`limit` default 50, `offset`); la risposta è `{"transactions": [...], "has_more": bool}`.
- **`get_portfolio_summary`** — per ogni asset (escluso `vs_currency` stesso): quanto ne possiedi, quanto hai investito in totale e in media per unità (cost-basis), valore di mercato, plus/minusvalenza non realizzata. `as_of_date` opzionale per una situazione a una data passata (prezzo storico) invece che live — pensato per la rendicontazione fiscale. `cost_basis_method` (`weighted_average` o `fifo`) è una scelta esplicita, non automatica: possono dare plusvalenze diverse sullo stesso storico, verifica quale ti serve con un commercialista.
- **`get_asset_price`** — quotazione di un singolo asset, corrente o storica a una data, senza calcolare tutto il portafoglio.
## Setup
```bash
uv sync
```
Copia `.env.example` in `.env` e valorizzalo: viene caricato automaticamente
all'avvio (`main.py` chiama `load_dotenv()`). Questo funziona anche quando il
server è lanciato da un host esterno (Claude Desktop, agy, ...) con
`uv run --directory <path> main.py`, perché `--directory` imposta la working
directory del processo sulla root del progetto, dove `load_dotenv()` cerca
`.env` di default — **non serve duplicare i secret nel JSON di config
dell'host**, che spesso è meno protetto di un `.env` locale gitignored.
## Backend di persistenza
Selezionabile con la variabile d'ambiente `TRANSACTION_REPOSITORY_BACKEND`:
- `json` (default) — nessuna configurazione aggiuntiva richiesta.
- `notion` — richiede `NOTION_TOKEN` (token dell'integration Notion) e `NOTION_DATABASE_ID`.
### Setup Notion da zero
Se non hai già un'integration Notion collegata a questo progetto, segui questi
passi in ordine (richiede un account Notion con permessi di creare
integration nel workspace):
1. **Crea l'integration** — vai su [notion.so/my-integrations](https://www.notion.so/my-integrations)
→ "+ New integration" → dai un nome (es. "Crypto Transactions MCP") →
scegli il workspace → tipo "Internal".
2. **Abilita le capabilities** — nella pagina di configurazione
dell'integration, sotto "Capabilities", abilita: **Read content**,
**Update content**, **Insert content**.
3. **Copia il token** — tab "Secrets" → copia il valore di "Internal
Integration Secret" (inizia con `secret_` o `ntn_`). Questo è il tuo
`NOTION_TOKEN`.
4. **Crea il database** — in Notion, crea un nuovo database (full-page o
inline) e configuralo con esattamente le property elencate nella tabella
sotto. La colonna "title" che Notion crea di default (di solito "Name")
va rinominata in `Transaction ID`: ogni database Notion ne ha
obbligatoriamente una, non è il nome del database ma la colonna che
identifica ogni riga.
5. **Condividi il database con l'integration** — apri la pagina del
database → menu "..." in alto a destra → "Connections" → cerca e collega
l'integration creata al punto 1. **Senza questo passaggio l'API risponde
404 anche con un token valido**, perché i permessi Notion sono per-pagina,
non workspace-wide.
6. **Trova il database ID** — è nell'URL della pagina del database:
`https://www.notion.so/<ID-senza-trattini>?v=...`. Ad esempio, da
`https://www.notion.so/abcdef0123456789abcdef0123456789?v=...` l'ID è
`abcdef0123456789abcdef0123456789`. Questo è il tuo `NOTION_DATABASE_ID`.
7. **Configura le variabili** — nel tuo `.env` (vedi sopra), o nel blocco
`env` dell'host MCP:
```
TRANSACTION_REPOSITORY_BACKEND=notion
NOTION_TOKEN=<il secret del punto 3>
NOTION_DATABASE_ID=<l'id del punto 6>
```
Il database Notion deve avere queste property (nome esatto e tipo):
| Property | Tipo |
|-----------------|-----------|
| `Transaction ID`| title |
| `Tx Hash` | rich_text |
| `Exchange` | select |
| `To Exchange` | select |
| `Symbol` | select |
| `Side` | select (`BUY`, `SELL`, `TRANSFER`) |
| `Timestamp` | date (con orario, UTC) |
| `Amount` | number |
| `Price` | number |
| `Value` | number |
| `Fee Amount` | number |
| `Fee Asset` | select |
| `Fiat Value` | number |
| `Fiat Currency` | select |
| `Notes` | rich_text |
`To Exchange`, `Price`, `Value`, `Fiat Value`, `Fiat Currency` possono essere
vuoti (non si applicano a un `TRANSFER`, o sono opzionali su un trade).
Nota: i campi numerici su Notion sono `number` nativi (float64), non `Decimal`
esatti come nel backend JSON — sufficiente per gli importi reali di trading,
ma soggetto ai limiti di precisione di un float a doppia precisione.
## Test
```bash
uv run pytest
```
## Esecuzione manuale
```bash
uv run main.py
```
## Configurazione Claude Desktop
Aggiungi al file `claude_desktop_config.json` (su macOS:
`~/Library/Application Support/Claude/claude_desktop_config.json`). Con un
`.env` già configurato alla root del progetto (vedi sopra) basta questo,
nessun secret nel JSON:
```json
{
"mcpServers": {
"crypto-transactions": {
"command": "uv",
"args": [
"run",
"--directory",
"/path/to/crypto-transactions-mcp",
"main.py"
]
}
}
}
```
In alternativa, se preferisci non usare un file `.env`, puoi passare le
variabili direttamente nel blocco `env` (sovrascrivono quelle di `.env` se
presenti entrambe):
```json
{
"mcpServers": {
"crypto-transactions": {
"command": "uv",
"args": [
"run",
"--directory",
"/path/to/crypto-transactions-mcp",
"main.py"
],
"env": {
"TRANSACTION_REPOSITORY_BACKEND": "notion",
"NOTION_TOKEN": "secret_...",
"NOTION_DATABASE_ID": "abcdef0123456789abcdef0123456789"
}
}
}
}
```
## Configurazione agy
Stesso schema `mcpServers`, ma nel file `.agents/mcp_config.json` alla root del
progetto in cui usi agy (aggiungi la voce `crypto-transactions` accanto agli
altri server già presenti, senza rimuoverli). Vale la stessa cosa: con `.env`
configurato non serve il blocco `env`:
```json
{
"mcpServers": {
"crypto-transactions": {
"command": "uv",
"args": [
"run",
"--directory",
"/path/to/crypto-transactions-mcp",
"main.py"
]
}
}
}
```
TDQS
Scored across 3 tools
Each tool has a distinct and clear purpose: one for querying history, one for logging trades/swaps, and one for logging asset transfers. There is no overlap in functionality.
All tool names follow a consistent verb_noun pattern with lowercase and underscores: get_transaction_history, log_transaction, log_transfer. This is predictable and easy to understand.
With only 3 tools, the set is minimal but covers the core actions of reading and writing transactions and transfers. It feels slightly thin but is still well-scoped for a focused server.
The server covers basic CRUD operations for transactions and transfers, including upsert and filtering. Missing a dedicated tool for single transaction retrieval or deletion, but the existing tools can accommodate those needs.