mcp-ibkr
by jotorresro
README.md
# mcp-ibkr
Servidor MCP (Model Context Protocol) propio que conecta Claude Code con dos
capacidades independientes:
- **IBKR** — consultas de solo lectura sobre una cuenta de **Paper Trading**
de Interactive Brokers.
- **Research** — un sistema de investigación que procesa, indexa y permite
buscar dentro de documentos (papers, PDFs) que **el usuario provee
explícitamente**. Nunca busca, descubre ni descarga documentos por su
cuenta.
> Estado del proyecto: IBKR (12 fases) y Research (11 fases) completos. Ver
> sección "Estado actual".
## 1. Qué es este proyecto
Un puente entre Claude Code y dos fuentes de conocimiento/acción distintas:
- Claude Code lanza este servidor, el servidor expone **herramientas**
(funciones con nombre y descripción) y **recursos** (contenido legible por
URI), y Claude los usa cuando el usuario pide información de la cuenta de
IBKR o quiere consultar/analizar documentos que ya ingirió.
- Ninguna herramienta de IBKR habla directamente con `ib_async`: todas pasan
por `src/ibkr/connection.py`.
- Ningún componente de Research habla directamente con Chroma, sqlite o
fastembed: todos pasan por las abstracciones en `src/research/`.
- **IBKR y Research son dominios completamente separados.** Ninguno importa
código del otro. El único punto que conoce ambos es el servidor MCP
(`src/server.py`), que simplemente registra las herramientas/recursos de
los dos.
## 2. Arquitectura
```
Claude Code (cliente MCP)
│ stdio / JSON-RPC
v
Servidor MCP (src/server.py)
│
├──────────────────────────┬───────────────────────────┐
v v v
Tool Registry Resource Registry (ambos registros
(src/tools/*) (src/resources/*) son independientes
│ │ entre si)
v v
┌─────────────┐ ┌───────────────┐
│ IBKR tools │ │ document:// │
│ (account, │ │ {document_id} │
│ market, │ └───────┬───────┘
│ positions, │ │
│ orders) │ v
└──────┬──────┘ ResearchService / IngestionService
│ (src/research/services/*)
v │
Capa de integración IBKR v
(src/ibkr/connection.py) ingestion → chunking → embeddings → vector store
│ ib_async → socket TCP (src/research/*)
v
IB Gateway (Paper Trading, puerto 4002)
│
v
Interactive Brokers
```
Research **nunca** importa nada de `src/ibkr/`, e IBKR **nunca** importa
nada de `src/research/`. Si en el futuro se agrega análisis que combine
ambos (ver sección 9.7), esa orquestación viviría en una capa nueva
separada de las dos, no dentro de ninguna de ellas.
## 3. Estructura de carpetas
```
mcp-ibkr/
├── src/
│ ├── server.py # Punto de entrada: registra tools Y resources
│ ├── ibkr/
│ │ └── connection.py # UNICO lugar que habla con ib_async / IB Gateway
│ ├── research/ # Dominio Research -- nunca importa src/ibkr/
│ │ ├── models.py # Document, DocumentMetadata, Chunk, ResearchResult
│ │ ├── ingestion/
│ │ │ ├── loader.py # Lee el archivo, detecta tipo, calcula hash
│ │ │ ├── parser.py # Extrae texto (PdfParser via pypdf)
│ │ │ ├── normalizer.py # Limpia artefactos de extraccion de PDF
│ │ │ └── metadata.py # Lee /Info del PDF + DOI/arXiv ID del texto
│ │ ├── chunking/
│ │ │ └── chunker.py # StructuralChunker: respeta parrafo/pagina/seccion
│ │ ├── embeddings/
│ │ │ └── provider.py # EmbeddingProvider + FastEmbedProvider (fastembed)
│ │ ├── storage/
│ │ │ ├── vector_store.py # VectorStore + ChromaVectorStore
│ │ │ └── document_store.py # DocumentStore (sqlite): metadata + dedup por hash
│ │ ├── retrieval/
│ │ │ └── retriever.py # Retriever + SemanticRetriever
│ │ └── services/
│ │ ├── ingestion_service.py # Orquesta todo el pipeline de ingestion
│ │ ├── research_service.py # Busqueda + metadata + reconstruccion de texto
│ │ └── factory.py # UNICO lugar que cablea todo lo de arriba
│ ├── tools/
│ │ ├── registry.py # Registro central: conecta archivos de herramienta con el servidor
│ │ ├── account/ # Saldo, resumen de cuenta, verificar conexión (IBKR)
│ │ ├── market/ # Precio, cotización, históricos (IBKR)
│ │ ├── positions/ # Posiciones abiertas, P&L (IBKR)
│ │ ├── orders/ # Consultar (activa) + crear/cancelar (ACTION, deshabilitadas) (IBKR)
│ │ └── research/ # ingest_document, list_documents, get_document, search
│ ├── resources/
│ │ ├── registry.py # Registro central de MCP Resources (paralelo a tools/registry.py)
│ │ └── documents.py # document://{document_id} -- contenido completo de un documento
│ ├── config/
│ │ └── settings.py # Carga .env: load_ibkr_settings() + load_research_settings()
│ └── utils/
│ └── risk.py # RiskLevel: READ_ONLY / ACTION (solo tiene sentido para IBKR)
├── data/
│ └── research/ # Indice vectorial + registro de documentos (gitignored, se crea solo)
├── tests/
├── .env.example
├── .gitignore
├── .mcp.json # Registro del servidor para Claude Code (scope project)
├── pyproject.toml # Configuracion de pytest
├── requirements.txt # Dependencias exactas (pip freeze)
└── README.md
```
## 4. Requisitos
- Python 3.11+ (probado con 3.14).
- Git.
- `curl` (para descargar `get-pip.py` en la sección 5 y el instalador de IB Gateway en la sección 6).
- IB Gateway instalado, con sesión de **Paper Trading** iniciada (ver sección 6) — solo necesario para las herramientas de IBKR.
- Conexión a Internet **una vez**, la primera vez que se usa Research: `fastembed` descarga un modelo de embeddings pequeño (ONNX, no PyTorch) desde Hugging Face la primera vez que se genera un embedding. Después de esa primera vez, Research funciona completamente offline.
## 5. Instalación y configuración
```bash
cd ~/mcp-ibkr
# Crear entorno virtual aislado para este proyecto
python3 -m venv --without-pip .venv
# Instalar pip dentro del venv (no viene incluido con --without-pip)
curl -sS https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py
.venv/bin/python3 /tmp/get-pip.py
# Instalar dependencias del proyecto
.venv/bin/python3 -m pip install -r requirements.txt
# Configuración local (nunca se sube a Git)
cp .env.example .env
```
> Nota sobre `requirements.txt`: instalamos directamente `mcp`, `ib_async`,
> `python-dotenv`, `pytest` (IBKR) y `pypdf`, `fastembed`, `chromadb`
> (Research), pero el archivo tiene muchas más líneas porque `pip freeze`
> incluye también las dependencias de esos paquetes. Es normal — no
> significa que el proyecto use todas esas librerías directamente.
> Nota: en sistemas Debian/Ubuntu, `python3 -m venv` por sí solo puede
> fallar si falta el paquete de sistema `python3-venv` (se instala con
> `sudo apt install python3-venv`). Si no tienes acceso a `sudo`, la
> combinación `--without-pip` + instalar `pip` a mano dentro del venv (los
> pasos de arriba) da un entorno igual de aislado sin necesitar privilegios
> de administrador.
## 6. Conexión con IBKR (Paper Trading)
1. Instala **IB Gateway** (descarga oficial, `stable-standalone`):
```bash
curl -o ibgateway-stable-standalone-linux-x64.sh \
https://download.interactivebrokers.com/installers/ibgateway/stable-standalone/ibgateway-stable-standalone-linux-x64.sh
chmod u+x ibgateway-stable-standalone-linux-x64.sh
./ibgateway-stable-standalone-linux-x64.sh
```
2. Ábrelo e inicia sesión seleccionando explícitamente **"Paper Trading"**
(no "Live Trading"), con tu usuario/contraseña de Paper Trading.
3. Verifica que el puerto de la API sea **4002** (Paper). Puedes confirmarlo así:
```bash
ss -ltnp | grep 4002 # deberia aparecer un proceso "java" escuchando
```
4. Copia `.env.example` a `.env` (si no lo has hecho) y ajusta los valores
si tu configuración es distinta.
**Por qué esto es seguro:** `src/config/settings.py` **rechaza arrancar**
si `IBKR_PORT` no es exactamente `4002`, y también si
`IBKR_PAPER_TRADING_CONFIRMED` no dice `true`. Además, la conexión
(`src/ibkr/connection.py`) se abre con `readonly=True`, que hace que IB
Gateway rechace cualquier intento de enviar órdenes a nivel de API, incluso
antes de que existan herramientas de órdenes.
## 7. Configuración de Claude Code
El servidor está registrado en `.mcp.json` (raíz del proyecto), con scope
`project`. Esto significa que el archivo se versiona en Git y cualquiera que
abra este repo en Claude Code verá el servidor propuesto — pero **no se
ejecuta automáticamente**: Claude Code lo marca como "Pending approval"
hasta que abras una sesión dentro de esta carpeta y lo apruebes.
```bash
cd ~/mcp-ibkr
claude # al iniciar, Claude Code te preguntará si confías en mcp-ibkr
```
Para verificar el estado del servidor en cualquier momento:
```bash
claude mcp list
claude mcp get mcp-ibkr
```
Si alguna vez quieres quitarlo:
```bash
claude mcp remove mcp-ibkr -s project
```
## 8. Herramientas de IBKR
| Herramienta | Categoría | Riesgo | Estado | Descripción |
|---|---|---|---|---|
| `verificar_conexion_ibkr` | `account` | READ_ONLY | Activa | Confirma que hay conexión activa con IB Gateway (Paper Trading) y lista las cuentas visibles. |
| `consultar_resumen_cuenta` | `account` | READ_ONLY | Activa | Valor neto, efectivo disponible, buying power y margen. |
| `consultar_precio_mercado` | `market` | READ_ONLY | Activa | Último precio, bid/ask, cierre anterior y volumen de una acción. |
| `consultar_datos_historicos` | `market` | READ_ONLY | Activa | Velas OHLCV históricas de una acción. |
| `consultar_posiciones` | `positions` | READ_ONLY | Activa | Posiciones abiertas (todas o filtradas por símbolo). |
| `consultar_pnl` | `positions` | READ_ONLY | Activa | P&L diario, no realizado y realizado de la cuenta. |
| `consultar_ordenes` | `orders` | READ_ONLY | Activa | Lista órdenes abiertas y su estado. |
| `crear_orden` | `orders` | **ACTION** | **Deshabilitada** | Crea una orden MKT/LMT. Requiere doble activación (ver sección 12). |
| `cancelar_orden` | `orders` | **ACTION** | **Deshabilitada** | Cancela una orden abierta por `orderId`. Requiere doble activación. |
## 9. Research: documentos provistos por el usuario
### 9.1. Qué es y qué NO es
Research es un sistema de ingestion + RAG (retrieval-augmented generation)
sobre documentos que **vos** le entregás al sistema. El punto de entrada
siempre es un archivo local que ya tenés.
**Research nunca:**
- busca papers en Internet, arXiv, Google Scholar, Semantic Scholar, etc.
- descubre ni descarga literatura por su cuenta.
- ejecuta ninguna acción sobre IBKR (ni siquiera de lectura) — es un
dominio completamente aislado (ver sección 2).
- inventa metadata: si un campo (autor, DOI, fecha de publicación) no está
presente en el documento, queda como desconocido, nunca se completa
adivinando o buscando afuera.
**Research sí:**
- extrae texto, metadata propia del archivo (título/autor/keywords de un
PDF, DOI/arXiv ID si aparecen impresos en el texto), lo divide en chunks
preservando página/sección, genera embeddings, lo indexa, y permite
buscarlo semánticamente con trazabilidad completa hacia la fuente.
### 9.2. Pipeline de ingestion
```
Documento provisto por el usuario (PDF)
│
v
DocumentLoader -- lee bytes, detecta tipo, calcula hash sha256
│
v
DocumentParser -- extrae texto por pagina (PdfParser / pypdf)
│
v
TextNormalizer -- corrige guiones de corte de linea, saltos de linea
│
v
MetadataExtractor -- /Info del PDF + DOI/arXiv ID literales del texto
│
v
StructuralChunker -- chunks respetando parrafo/pagina/seccion
│
v
EmbeddingProvider (fastembed) -- vectores locales, sin API key
│
v
VectorStore (Chroma) + DocumentStore (sqlite)
│
v
research_search / document://{id}
```
`IngestionService` (`src/research/services/ingestion_service.py`) es el
único lugar que conoce este orden completo. Es idempotente por hash: si
volvés a ingerir el mismo archivo, se rechaza con un mensaje claro salvo
que pases `force_reindex=true`.
### 9.3. Formatos soportados
Por ahora, **solo PDF**. La arquitectura ya tiene la abstracción
(`DocumentType`, `DocumentParser`, `MetadataExtractor`) para agregar TXT,
Markdown o DOCX más adelante sin rediseñar nada — se agregan detrás de la
misma interfaz cuando haga falta.
### 9.4. Trazabilidad y citas
Cada resultado de búsqueda (`ResearchResult`) incluye `document_id`,
`chunk_id`, página y sección (cuando se pudieron detectar), score de
similitud, y el título/filename del documento de origen. La detección de
sección es una heurística best-effort (encabezados numerados o nombres de
sección habituales en papers como "Introduction", "Results"); cuando no
hay certeza razonable, queda en `None` en vez de adivinar.
### 9.5. Configuración
Todas las variables son opcionales — Research arranca con valores por
defecto razonables sin tocar `.env` (a diferencia de IBKR, no maneja
dinero real, así que no hay ningún interruptor de seguridad que confirmar):
| Variable | Default | Qué controla |
|---|---|---|
| `RESEARCH_STORAGE_PATH` | `data/research` | Dónde vive el índice vectorial y el registro de documentos (versionado — ver 9.8). |
| `RESEARCH_EMBEDDING_MODEL` | `BAAI/bge-small-en-v1.5` | Modelo de embeddings local (fastembed). |
| `RESEARCH_CHUNK_SIZE` | `1000` | Tamaño máximo de chunk, en caracteres. |
| `RESEARCH_CHUNK_OVERLAP` | `150` | Superposición entre chunks vecinos (debe ser menor que `RESEARCH_CHUNK_SIZE`). |
| `RESEARCH_RETRIEVAL_TOP_K` | `5` | Resultados devueltos por defecto en `research_search`. |
| `RESEARCH_SCORE_THRESHOLD` | `0.0` | Score mínimo (0-1) para considerar un resultado relevante. |
### 9.6. Herramientas y recursos
| Nombre | Tipo | Riesgo | Descripción |
|---|---|---|---|
| `research_ingest_document` | Tool | READ_ONLY* | Ingiere un PDF desde una ruta local. `path`, `force_reindex` opcional. |
| `research_list_documents` | Tool | READ_ONLY | Lista los documentos ya ingeridos. |
| `research_get_document` | Tool | READ_ONLY | Metadata completa de un documento por `document_id`. |
| `research_search` | Tool | READ_ONLY | Búsqueda semántica sobre los documentos ya ingeridos. `query`, `top_k`, `document_id`, `score_threshold` opcionales. |
| `document://{document_id}` | Resource | READ_ONLY | Contenido completo (reconstruido) de un documento, para leerlo directamente en vez de buscarlo. |
\* `research_ingest_document` usa `RiskLevel.READ_ONLY` porque no tiene
ningún riesgo financiero (nunca toca IBKR), pero sus `ToolAnnotations`
marcan `readOnlyHint=False` porque sí escribe en el índice local — son dos
conceptos distintos que conviene no confundir (ver `src/utils/risk.py` vs
`mcp.types.ToolAnnotations`).
No existe una tool `research_get_metadata` separada: `research_get_document`
ya cubre ese caso, y no se agregan herramientas solo para sumar cantidad.
### 9.7. Roadmap de Research (no implementado todavía)
- Más formatos de documento (TXT, Markdown, DOCX) detrás de la misma
interfaz `DocumentParser`.
- `research_compare_documents` / `research_extract_strategy`: análisis
cruzado entre documentos y extracción de parámetros de estrategia
(lookback, rebalanceo, universo, etc.), con sus citas correspondientes.
- Una capa de orquestación/aplicación que combine evidencia de Research
con datos de mercado de IBKR para reproducir análisis — sin que ninguno
de los dos dominios dependa del otro directamente (ver sección 2).
- Búsqueda híbrida (semántica + palabra clave) y filtrado por
autor/fecha/tags en `Retriever`.
### 9.8. `data/research/` versionado, `papers/` no
`data/research/` (índice `vectors/` de Chroma + `documents.db`) **sí se
sube al repo**, para que al clonar el proyecto el MCP ya tenga el
conocimiento indexado sin depender de que cada persona vuelva a conseguir
los mismos 14 papers. `papers/*.pdf` (los PDFs originales) **nunca se
sube** — siguen gitignored.
Para poder versionar el índice sin redistribuir el texto de papers con
copyright (NBER, Journal of Finance, RFS, JFR, Fed), el contenido de cada
chunk en `vectors/` no es el texto extraído literal del PDF: es una nota
parafraseada, generada con Claude (`scripts/paraphrase_research_chunks.py`),
que preserva hallazgos/cifras/metodología pero está redactada con palabras
y estructura propias — no reutiliza más de 5 palabras consecutivas del
original (nombres propios, símbolos de fórmulas y términos técnicos
cortos quedan exentos). Los embeddings de `vectors/` se calcularon sobre
ese texto parafraseado, no sobre el original.
`data/research/_original_chunks_backup.json` guarda el texto original
verbatim de los 2300 chunks (para poder re-parafrasear sin volver a leer
los PDFs) y está explícitamente gitignored — nunca debe subirse.
Si volvés a ingerir un PDF nuevo con `research_ingest_document`, el chunk
que se guarda en Chroma va a ser el texto original (verbatim), no una
versión parafraseada — `IngestionService` no llama a
`scripts/paraphrase_research_chunks.py` automáticamente. Si vas a subir
ese documento nuevo al repo público, corré el script antes de hacer commit
de `data/research/`.
## 10. Cómo agregar / modificar / eliminar / desactivar una herramienta
Cada herramienta es **un archivo** dentro de `src/tools/<categoria>/`, con
esta forma (ver `src/tools/account/verificar_conexion_ibkr.py` como ejemplo real):
```python
from mcp.types import ToolAnnotations
from src.utils.risk import RiskLevel
NAME = "mi_herramienta"
DESCRIPTION = "Que hace, cuando usarla, que devuelve, si modifica la cuenta."
ANNOTATIONS = ToolAnnotations(readOnlyHint=True, openWorldHint=True)
ENABLED = True
RISK_LEVEL = RiskLevel.READ_ONLY # o RiskLevel.ACTION si modifica algo en IBKR
def mi_herramienta(parametro: str) -> str:
return "resultado"
```
Esta convención es la misma para las categorías de IBKR (`account`,
`market`, `positions`, `orders`) y para `research`.
La función debe llamarse **igual que `NAME`** — así el registro central
(`src/tools/registry.py`) la encuentra automáticamente. Si `RISK_LEVEL` es
`ACTION`, además de `ENABLED = True` hace falta `IBKR_ENABLE_ACTION_TOOLS=true`
en `.env` (ver sección 12) — dos llaves independientes, a propósito. Esta
llave es específica de acciones sobre IBKR: las herramientas de Research
siempre son `RiskLevel.READ_ONLY`.
Sobre `ANNOTATIONS`: `destructiveHint` e `idempotentHint` solo son
significativos cuando `readOnlyHint=False` (así lo documenta la spec de
MCP) — por eso en una herramienta de solo lectura alcanza con `readOnlyHint`
y `openWorldHint`. Solo agrégalos si `readOnlyHint=False`, como en
`src/tools/orders/crear_orden.py` o `src/tools/research/research_ingest_document.py`.
### Agregar una herramienta
1. Crear el archivo en la categoría correspondiente (o crear una categoría
nueva, ver más abajo).
2. Agregar el nombre del archivo (sin `.py`) a la lista `TOOLS` en el
`__init__.py` de esa categoría.
3. Reiniciar la sesión de Claude Code para que la recoja (Claude Code lee
las herramientas una sola vez, al arrancar el servidor; `claude mcp list`
solo consulta el estado de la conexión, no recarga nada).
### Modificar una herramienta
Editar directamente su archivo — `DESCRIPTION`, parámetros de la función,
lógica interna, etc. No hay que tocar `registry.py`.
### Eliminar una herramienta
Borrar el archivo y quitar su nombre de `TOOLS` en el `__init__.py` de su
categoría.
### Desactivar una herramienta (sin borrarla)
Poner `ENABLED = False` en su archivo. `registry.py` la salta automáticamente.
### Crear una categoría nueva
Crear la carpeta `src/tools/<categoria>/` con un `__init__.py` que defina
`TOOLS: list[str] = [...]`, y agregar el nombre de la categoría a
`CATEGORIES` en `src/tools/registry.py`.
### Agregar un MCP Resource
Mismo principio, pero en `src/resources/`: crear el archivo con `URI`,
`NAME`, `DESCRIPTION`, `ENABLED` y una función con el mismo nombre que
`NAME` cuyos parámetros coincidan con las variables de la URI (ver
`src/resources/documents.py`), y agregar el nombre del módulo a
`RESOURCES` en `src/resources/registry.py`.
## 11. Testing
```bash
cd ~/mcp-ibkr
.venv/bin/python3 -m pytest tests/ -v
```
**IBKR:**
- `tests/test_server.py` — el servidor arranca y expone las herramientas
esperadas (lo mismo que vería Claude Code al conectarse); las
herramientas de solo lectura no fijan anotaciones que no aplican; y
cada herramienta responde con un mensaje amigable si IBKR no está
disponible, en vez de dejar escapar una excepción.
- `tests/test_settings.py` — la configuración **rechaza** puerto distinto de
4002 y la falta de `IBKR_PAPER_TRADING_CONFIRMED`.
- `tests/test_connection.py` — la capa de conexión serializa los intentos
de conectar (con IBKR simulado, no requiere Gateway real): si dos
herramientas se llaman casi al mismo tiempo, nunca hay dos intentos de
conexión corriendo en paralelo.
- `tests/test_risk_system.py` — las herramientas `ACTION` (`crear_orden`,
`cancelar_orden`) **no se registran** por defecto, y las validaciones de
una orden (cantidad, tipo, precio) rechazan parámetros inválidos.
- `tests/test_ibkr_integration.py` — conexión real contra IB Gateway. Si
Gateway no está corriendo, este test se **salta** (no falla) — es
esperado, no es un error del proyecto.
Todas las pruebas relacionadas con órdenes usan `validar_parametros_orden`
de forma aislada (sin tocar IBKR) o dependen de que `crear_orden` esté
deshabilitada por defecto: **ningún test de este proyecto envía una orden
real**, ni siquiera en Paper Trading.
**Research** (un archivo de test por componente del pipeline):
`test_research_models.py`, `test_research_ingestion.py`,
`test_research_normalizer.py`, `test_research_metadata.py`,
`test_research_chunker.py`, `test_research_embeddings.py`,
`test_research_vector_store.py`, `test_research_document_store.py`,
`test_research_ingestion_service.py`, `test_research_retriever.py`,
`test_research_service.py`, `test_research_mcp_tools.py` (integración MCP:
las 4 tools registradas y funcionando de punta a punta) y
`test_research_resources.py` (integración del resource `document://`).
Ningún test de Research descarga un modelo real ni toca la carpeta real
`data/research/`: todos reemplazan `fastembed.TextEmbedding` por un fake
determinista (`tests/support/fake_embeddings.py`) y usan directorios
temporales (`tmp_path` / `RESEARCH_STORAGE_PATH` sobreescrita). Los PDFs de
prueba se generan a mano, byte a byte (`tests/support/pdf_fixtures.py`),
sin depender de una librería de generación de PDF.
## 12. De Paper Trading a Live Trading
> ⚠️ **Live Trading no está soportado por este proyecto todavía, y
> `crear_orden`/`cancelar_orden` están deshabilitadas por defecto.**
> Lo que sigue es la explicación de las capas de seguridad, no una
> invitación a activarlas sin pensarlo.
Hay **tres capas independientes** que impiden que se envíe una orden real por accidente:
1. **Puerto obligatorio 4002** — `src/config/settings.py` rechaza arrancar
si `IBKR_PORT` no es exactamente el de Paper Trading. Esta versión del
proyecto **no tiene ningún camino de código para usar el puerto 4001**
(Live).
2. **Doble llave para herramientas ACTION** — `crear_orden` y
`cancelar_orden` necesitan `ENABLED = True` en su propio archivo **y**
`IBKR_ENABLE_ACTION_TOOLS=true` en `.env`. Ninguna de las dos está
activada por defecto. Ambas se leen solo al arrancar el servidor: si las
cambias con el servidor ya corriendo, necesitas reiniciar la sesión de
Claude Code para que el cambio tenga efecto.
3. **Conexión de solo lectura a nivel de API** — mientras
`IBKR_ENABLE_ACTION_TOOLS` sea `false`, `src/ibkr/connection.py` se
conecta con `readonly=True`: IB Gateway rechaza cualquier orden aunque
alguien lograra saltarse las dos capas anteriores.
Si en el futuro decides habilitar el envío de órdenes **en Paper Trading**,
el camino sería: revisar y reforzar las validaciones de
`src/tools/orders/crear_orden.py`, poner `IBKR_ENABLE_ACTION_TOOLS=true` en
`.env`, y `ENABLED = True` en los archivos de orden. Soporte para **Live
Trading real no está implementado ni planeado en este proyecto** — haría
falta una revisión de seguridad completamente aparte antes de considerarlo.
**Research nunca participa de este camino.** Ningún archivo de
`src/research/`, `src/tools/research/` o `src/resources/` importa `ib_async`
ni `src/ibkr/`, y ninguna herramienta de Research puede llegar a ejecutar
una orden ni siquiera indirectamente: en el peor caso, devuelve texto o
metadata de un documento.
## 13. Estado actual
**IBKR:**
- [x] Fase 1 — Arquitectura y conceptos
- [x] Fase 2 — Estructura mínima del proyecto
- [x] Fase 3 — Entorno de desarrollo
- [x] Fase 4 — Servidor MCP mínimo
- [x] Fase 5 — Conectar Claude Code con el MCP
- [x] Fase 6 — Primera herramienta de prueba
- [x] Fase 7 — Conexión con IB Gateway (Paper Trading)
- [x] Fase 8 — Herramientas de consulta
- [x] Fase 9 — Sistema de validación y riesgo
- [x] Fase 10 — Herramientas de órdenes (bloqueadas)
- [x] Fase 11 — Testing completo
- [x] Fase 12 — Documentación final
**Research:**
- [x] Fase 1 — Modelos de dominio (Document, Chunk, ResearchResult)
- [x] Fase 2 — Document loader + PDF parser
- [x] Fase 3 — Normalizer + metadata extractor
- [x] Fase 4 — Chunker estructural (página/sección)
- [x] Fase 5 — Embedding provider (fastembed)
- [x] Fase 6 — Vector store (Chroma)
- [x] Fase 7 — Document store (sqlite, dedup por hash)
- [x] Fase 8 — Ingestion service (orquestación del pipeline)
- [x] Fase 9 — Retrieval service con citas
- [x] Fase 10 — MCP tools de Research
- [x] Fase 11 — MCP Resources (`document://`)
- [ ] Más formatos (TXT/Markdown/DOCX), análisis cruzado entre documentos,
e integración con datos de IBKR — ver sección 9.7 (Roadmap).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues