oracle-mcp-server
by ElvinCooper
README.md
# oracle-mcp-server
[](https://github.com/ElvinCooper/oracle-mcp-server/actions/workflows/ci.yml)
[](LICENSE)
> Repositorio: [github.com/ElvinCooper/oracle-mcp-server](https://github.com/ElvinCooper/oracle-mcp-server)
**Oracle Database MCP Server**: servidor MCP (Model Context Protocol) en
**Python** que permite a agentes de IA y clientes compatibles con MCP
interactuar con bases **Oracle Database** a través de **`python-oracledb`**.
> A Python-based Model Context Protocol (MCP) server that enables AI coding
> agents and MCP-compatible clients to interact with Oracle Database
> environments through python-oracledb.
Usa `python-oracledb` en **modo thick** con **Oracle Instant Client**, con
soporte orientado a **entornos Oracle antiguos, incluido Oracle 11g** (el modo
thin del driver solo soporta Oracle 12.1+; las versiones posteriores dependen
del soporte del propio driver). Ver [Notas sobre Oracle 11g y modo thick](#8-notas-sobre-oracle-11g-y-modo-thick).
**Soporta múltiples conexiones a Oracle** y funciona con cualquier cliente MCP: opencode, Claude Code, Cursor, Codex, Continue.dev, etc.
## ⚡ Quick Start (puesta en marcha rápida)
```powershell
# 1. Clonar o copiar el proyecto
cd C:\ruta\oracle-mcp-server
# 2. Ejecutar setup (crea .venv, instala dependencias, copia .env)
.\setup.ps1
# 3. Editar .env con tus credenciales Oracle
notepad .env
# 4. Iniciar el servidor
.\.venv\Scripts\oracle-mcp
```
Si ves `Pools Oracle listos` en la salida, ya está funcionando.
**Requisitos:** Python 3.10+ y Oracle Instant Client instalado.
---
## Tools expuestas
| Tool | Descripción |
|---|---|
| `list_tables` | Lista tablas de un schema (o del usuario actual) |
| `describe_table` | Devuelve columnas, tipo de dato, nulabilidad, longitud |
| `run_query` | Ejecuta un `SELECT` de solo lectura, con límite de filas |
| `execute_statement` | Ejecuta `INSERT/UPDATE/DELETE/DDL`, **deshabilitado por defecto** |
| `list_connections` | Lista todas las conexiones a Oracle disponibles |
**Parámetro `connection`:** Todas las tools anteriores (excepto
`list_connections`) aceptan un parámetro opcional `connection` para especificar
a qué base de datos conectar. Si no se especifica, usa la primera conexión de
la lista (conexión por defecto).
**Parámetros adicionales:**
- `list_tables(schema, connection)`: `schema` es el owner en mayúsculas (ej.
`VENTAS`); si se omite, lista las tablas del usuario actual.
- `describe_table(table_name, schema, connection)`: `schema` indica el owner si
la tabla pertenece a otro usuario.
- `run_query(sql, max_rows, connection)`: `max_rows` limita las filas devueltas
en la llamada (por defecto usa el `_MAX_ROWS` de la conexión). El resultado
incluye `row_count`, `truncated` (si quedaron filas sin devolver) y
`limit_applied`.
### Seguridad
#### Operaciones de lectura (read-only)
`run_query` está diseñada para **operaciones de lectura** y valida cada
consulta: exige que la sentencia comience con `SELECT` o `WITH` y rechaza
múltiples sentencias separadas por `;`, bloqueando por diseño intentos de
inyección con sentencias de escritura encadenadas.
#### Operaciones de escritura (write operations)
`execute_statement` ejecuta `INSERT/UPDATE/DELETE/DDL` y está **bloqueada por
defecto**. Habilitarla requiere **dos condiciones explícitas**:
1. **Configuración:** `ORACLE_CONN_<NOMBRE>_ALLOW_WRITE=true` (o
`ORACLE_ALLOW_WRITE=true` en modo legacy), definida **por conexión**.
2. **Llamada:** pasar `confirm=true` en cada invocación.
El permiso se configura de forma explícita y auditable en cada conexión, y
además se confirma en cada uso (un **doble seguro**). Habilitar operaciones de
escritura debe ser una **decisión consciente**: por defecto todas las
conexiones son de solo lectura y se recomienda conservar `ALLOW_WRITE=false`
salvo que una conexión específica realmente lo requiera.
#### Credenciales
Las credenciales viven en variables de entorno (o en el archivo `.env`), **nunca
hardcodeadas** en el código, y en este documento se muestran únicamente
placeholders de ejemplo (`your_user`, `your_password`).
## 1. Requisitos previos
- Python 3.10+
- Oracle Instant Client ya instalado (dijiste que ya lo tienes disponible).
Necesitas la ruta del directorio con `libclntsh.so` (Linux/Mac) o
`oci.dll` (Windows), por ejemplo `/opt/oracle/instantclient_11_2`.
- Acceso de red al listener de Oracle (host:puerto) y un SID o service_name.
## 2. Instalación
La instalación se realiza con el script **`.\setup.ps1`** (Windows), que crea
el `.venv`, instala las dependencias y copia `.env` desde `.env.example`.
### Automática (Windows)
```powershell
cd C:\ruta\oracle-mcp-server
.\setup.ps1
```
### Manual (Windows / Linux / Mac)
```bash
cd oracle-mcp-server
python3 -m venv .venv
source .venv/bin/activate # Linux/Mac
# .venv\Scripts\activate # Windows
pip install -e .
```
> **Nota de versiones:** el proyecto usa la API de **MCP 1.x**
> (`mcp.server.fastmcp`). Tanto `pyproject.toml` como `requirements.txt`
> fijan `mcp>=1.6.0,<2` para evitar que una instalación resuelva `mcp` 2.x
> (que eliminó esa API). Si en tu entorno aparece `mcp` 2.x, reinstala con el
> pin: `pip install -e ".[dev]"` (o `pip install "mcp>=1.6.0,<2"`).
## 3. Configuración
### Opción A: Múltiples conexiones (Recomendado)
Copia `.env.example` a `.env` y completa tus datos:
```bash
cp .env.example .env
```
Formato para múltiples conexiones:
```bash
# Lista de conexiones (nombres separados por comas)
ORACLE_CONNECTIONS=facturacion,clinica
# Conexion "facturacion"
ORACLE_CONN_FACTURACION_USER=your_user
ORACLE_CONN_FACTURACION_PASSWORD=your_password
ORACLE_CONN_FACTURACION_HOST=your_oracle_host
ORACLE_CONN_FACTURACION_PORT=1521
ORACLE_CONN_FACTURACION_SID=ORCL
ORACLE_CONN_FACTURACION_CLIENT_LIB_DIR=/opt/oracle/instantclient_11_2
ORACLE_CONN_FACTURACION_ALLOW_WRITE=false
ORACLE_CONN_FACTURACION_MAX_ROWS=200
# Conexion "clinica"
ORACLE_CONN_CLINICA_USER=your_user
ORACLE_CONN_CLINICA_PASSWORD=your_password
ORACLE_CONN_CLINICA_HOST=your_oracle_host
ORACLE_CONN_CLINICA_PORT=1521
ORACLE_CONN_CLINICA_SID=ORCL
ORACLE_CONN_CLINICA_CLIENT_LIB_DIR=/opt/oracle/instantclient_11_2
```
### Opción B: Una sola conexión (Legacy - Retrocompatible)
Si `ORACLE_CONNECTIONS` no está definido, el servidor busca variables legacy:
```bash
ORACLE_USER=your_user
ORACLE_PASSWORD=your_password
ORACLE_HOST=your_oracle_host
ORACLE_PORT=1521
ORACLE_SID=ORCL
ORACLE_CLIENT_LIB_DIR=/opt/oracle/instantclient_11_2
ORACLE_ALLOW_WRITE=false
ORACLE_MAX_ROWS=200
```
### Variables por conexión
| Variable | Descripción | Default |
|---|---|---|
| `ORACLE_CONN_<NOMBRE>_USER` | Usuario de Oracle | (requerido) |
| `ORACLE_CONN_<NOMBRE>_PASSWORD` | Contraseña | (requerido) |
| `ORACLE_CONN_<NOMBRE>_HOST` | Host del servidor | (requerido) |
| `ORACLE_CONN_<NOMBRE>_PORT` | Puerto | 1521 |
| `ORACLE_CONN_<NOMBRE>_SID` | SID de Oracle | - |
| `ORACLE_CONN_<NOMBRE>_SERVICE_NAME` | Service name | - |
| `ORACLE_CONN_<NOMBRE>_CLIENT_LIB_DIR` | Ruta al Instant Client | - |
| `ORACLE_CONN_<NOMBRE>_ALLOW_WRITE` | Habilitar escritura | false |
| `ORACLE_CONN_<NOMBRE>_MAX_ROWS` | Límite de filas por query | 200 |
| `ORACLE_CONN_<NOMBRE>_CONNECT_TIMEOUT` | Timeout conexión (seg) | 10 |
| `ORACLE_CONN_<NOMBRE>_POOL_MIN` | Mínimo conexiones en pool | 1 |
| `ORACLE_CONN_<NOMBRE>_POOL_MAX` | Máximo conexiones en pool | 4 |
### Convención de Nombres de Variables
**Regla de oro:** El nombre de la conexión en `ORACLE_CONNECTIONS` define los
nombres de todas las variables de entorno asociadas.
| Paso | Ejemplo |
|------|---------|
| Nombre en `ORACLE_CONNECTIONS` | `afp_pruebas` |
| Convertir a MAYÚSCULAS | `AFP_PRUEBAS` |
| Agregar prefijo | `ORACLE_CONN_AFP_PRUEBAS` |
| Agregar sufijo | `ORACLE_CONN_AFP_PRUEBAS_USER` |
```
ORACLE_CONNECTIONS: "facturacion,afp_pruebas"
↓ ↓
"facturacion" "afp_pruebas"
↓ ↓
ORACLE_CONN_FACTURACION_* ORACLE_CONN_AFP_PRUEBAS_*
```
### Convención Recomendada para Nombres
Para mantener consistencia y facilitar el mantenimiento:
| Tipo de Base | Ejemplo de Nombre | Variable |
|--------------|-------------------|----------|
| Producción | `produccion` | `ORACLE_CONN_PRODUCCION_*` |
| Desarrollo | `desarrollo` | `ORACLE_CONN_DESARROLLO_*` |
| Pruebas | `pruebas` | `ORACLE_CONN_PRUEBAS_*` |
| Por área funcional | `facturacion` | `ORACLE_CONN_FACTURACION_*` |
**Recomendaciones:**
- Usar **snake_case** (minúsculas con guión bajo): `mi_conexion`
- **Sin espacios**: `afp_pruebas` en lugar de `AFP Pruebas`
- **Nombres descriptivos**: que indiquen el propósito de la conexión
- **Evitar caracteres especiales**: solo letras, números y guión bajo
### Fuentes de Variables de Entorno
Las variables pueden provenir de múltiples fuentes (por orden de prioridad):
1. Variables ya definidas en el entorno del proceso (incluye el bloque
`environment` de `opencode.jsonc`) (mayor prioridad)
2. **Variables del sistema Windows** (persistente)
3. **Archivo `.env`** (cargado por python-dotenv desde la raíz del proyecto)
**Recomendación:** usa **una sola fuente de verdad**. La opción recomendada es
mantener **todas las conexiones en el `.env`** (archivo en `.gitignore`, sin
credenciales en `opencode.jsonc`). Para agregar una conexión, añade su nombre a
`ORACLE_CONNECTIONS` y define sus variables `ORACLE_CONN_<NOMBRE>_*` en el
mismo archivo.
**Tolerancia a configuraciones incompletas:** si una conexión en
`ORACLE_CONNECTIONS` no tiene sus variables obligatorias
(`ORACLE_CONN_<NOMBRE>_USER`, `_PASSWORD`, `_HOST`) o tiene valores inválidos
(p. ej. puerto no numérico), el servidor la **omite** al arrancar, registra un
warning en consola con el nombre y el motivo, y continúa con las conexiones
válidas. Solo si **ninguna** conexión es válida, el servidor se detiene con un
error.
### Credenciales fuera del `.env` (variables del sistema)
Si prefieres no guardar credenciales en texto plano dentro del `.env`, puedes
definir el usuario y la contraseña como variables de entorno del sistema. Es
**totalmente opcional**: si no defines estas variables, el servidor usa las
del `.env` como siempre.
> **Importante:** el proceso debe arrancar **después** de crear la variable.
> Reinicia la terminal y el cliente MCP para que hereden el nuevo entorno.
**Opción A — Variable del sistema con el mismo nombre (recomendada):**
El nombre de la variable debe coincidir con el de la conexión
(`ORACLE_CONN_<NOMBRE>_USER` / `ORACLE_CONN_<NOMBRE>_PASSWORD`). Estas
variables tienen prioridad sobre las del `.env` (python-dotenv no sobrescribe
variables ya definidas), por lo que se pueden combinar: los secretos en el
sistema y el resto de la configuración en el `.env`. Ejemplo para la conexión
`facturacion`:
```powershell
# Windows (scope "User": solo tu usuario de Windows)
[Environment]::SetEnvironmentVariable("ORACLE_CONN_FACTURACION_USER", "your_user", "User")
[Environment]::SetEnvironmentVariable("ORACLE_CONN_FACTURACION_PASSWORD", "your_password", "User")
```
```bash
# Linux / Mac (agrega a ~/.bashrc o ~/.zshrc y recarga: source ~/.bashrc)
export ORACLE_CONN_FACTURACION_USER="your_user"
export ORACLE_CONN_FACTURACION_PASSWORD="your_password"
```
**Opción B — Referencia desde el `.env` (nombre libre):**
Puedes usar un nombre de variable propio en el sistema y referenciarlo desde
el `.env` con `${VAR}`:
```dotenv
ORACLE_CONN_FACTURACION_USER=${ORACLE_FACTURACION_USER}
ORACLE_CONN_FACTURACION_PASSWORD=${ORACLE_FACTURACION_PASSWORD}
```
Los valores de `ORACLE_FACTURACION_USER` / `ORACLE_FACTURACION_PASSWORD` se
definen como variables del sistema. Si la variable referenciada no existe,
python-dotenv la deja vacía y el servidor falla con
`Faltan variables obligatorias`, indicando cuál falta.
**Verificar que la variable quedó definida:**
```powershell
[Environment]::GetEnvironmentVariable("ORACLE_CONN_FACTURACION_PASSWORD", "User")
```
```bash
echo $ORACLE_CONN_FACTURACION_PASSWORD
```
También puedes usar los scripts `set-secrets.ps1` (Windows) y `set-secrets.sh`
(Linux/Mac) incluidos en el proyecto para fijar usuario y contraseña de forma
interactiva.
## 4. Pruebas
La suite usa **pytest** y **no necesita Oracle**: `test_server.py` sustituye
los pool/cursor de la base por dobles en memoria.
```bash
pip install -e ".[dev]" # instala pytest y las dependencias de desarrollo
pytest # o: python -m pytest
```
Son **114 tests** que cubren: carga legacy y multi-conexión, omisión de
conexiones incompletas (tolerancia), DSN por SID / SERVICE_NAME, validación
solo-`SELECT` y doble confirmación de escritura (`ALLOW_WRITE` + `confirm`),
serialización de LOBs y las 5 tools del servidor (conexión por defecto,
`max_rows` y truncado incluidos).
En **CI** (GitHub Actions) la suite corre en Python 3.10–3.12 y un segundo job
verifica que el paquete se construye con `python -m build`.
## 5. Probarlo de forma independiente
Puedes levantar el servidor manualmente para verificar que conecta:
```bash
source .venv/bin/activate # Linux/Mac
# .venv\Scripts\activate # Windows
python -m oracle_mcp.server
```
O usando el entry point directo (disponible después de `pip install -e .`):
```bash
oracle-mcp # si el venv está activo
# o desde la ruta absoluta:
.venv/Scripts/oracle-mcp # Windows
.venv/bin/oracle-mcp # Linux/Mac
```
Si ves en el log `Pools Oracle listos: N` seguido de
`Conexiones Oracle disponibles: N` con la lista de conexiones, la conexión
en thick mode funcionó. El proceso queda esperando mensajes MCP por stdio
(Ctrl+C para salir).
## 6. Integración con clientes MCP
El servidor se comunica por **STDIO** (estándar en MCP para procesos locales).
Cada cliente MCP tiene su propia configuración; aquí están las más comunes.
> **Importante:** Usa siempre la ruta **absoluta** al Python del `.venv` (así el
> cliente no depende de que el venv esté activado).
### opencode
Define el servidor en el bloque `mcp` de `opencode.json` (o `~/.config/opencode/opencode.jsonc`):
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"oracle": {
"type": "local",
"command": [
"C:/ruta/completa/oracle-mcp-server/.venv/Scripts/python",
"-m",
"oracle_mcp.server"
],
"enabled": true,
"environment": {
"ORACLE_CONNECTIONS": "facturacion,clinica",
"ORACLE_CONN_FACTURACION_USER": "your_user",
"ORACLE_CONN_FACTURACION_PASSWORD": "your_password",
"ORACLE_CONN_FACTURACION_HOST": "your_oracle_host",
"ORACLE_CONN_FACTURACION_PORT": "1521",
"ORACLE_CONN_FACTURACION_SID": "ORCL",
"ORACLE_CONN_FACTURACION_CLIENT_LIB_DIR": "C:/oracle/instantclient_11_2",
"ORACLE_CONN_CLINICA_USER": "your_user",
"ORACLE_CONN_CLINICA_PASSWORD": "your_password",
"ORACLE_CONN_CLINICA_HOST": "your_oracle_host",
"ORACLE_CONN_CLINICA_PORT": "1521",
"ORACLE_CONN_CLINICA_SID": "ORCL",
"ORACLE_CONN_CLINICA_CLIENT_LIB_DIR": "C:/oracle/instantclient_11_2"
}
}
}
}
```
Un archivo de ejemplo (`opencode.json.example`) está incluido en el proyecto.
> **Nota de seguridad:** el bloque `environment` de arriba incluye credenciales
> solo con fines de ejemplo. La opción recomendada es omitir `environment` y
> definir todas las conexiones en el archivo `.env` (ver sección 3).
### Claude Code
Agrega el servidor en `.claude.json` (en la raíz del proyecto o global en `%USERPROFILE%\.claude\`):
```json
{
"mcpServers": {
"oracle": {
"type": "local",
"command": "C:\\ruta\\oracle-mcp-server\\.venv\\Scripts\\python",
"args": ["-m", "oracle_mcp.server"],
"env": {
"ORACLE_CONNECTIONS": "facturacion",
"ORACLE_CONN_FACTURACION_USER": "your_user",
"ORACLE_CONN_FACTURACION_PASSWORD": "your_password",
"ORACLE_CONN_FACTURACION_HOST": "your_oracle_host",
"ORACLE_CONN_FACTURACION_PORT": "1521",
"ORACLE_CONN_FACTURACION_SID": "ORCL",
"ORACLE_CONN_FACTURACION_CLIENT_LIB_DIR": "C:\\oracle\\instantclient_11_2"
}
}
}
}
```
### Cursor
En `./cursor/mcp.json` del proyecto:
```json
{
"mcpServers": {
"oracle": {
"type": "local",
"command": "C:\\ruta\\oracle-mcp-server\\.venv\\Scripts\\python",
"args": ["-m", "oracle_mcp.server"],
"env": {
"ORACLE_CONNECTIONS": "facturacion",
"ORACLE_CONN_FACTURACION_USER": "your_user",
"ORACLE_CONN_FACTURACION_PASSWORD": "your_password",
"ORACLE_CONN_FACTURACION_HOST": "your_oracle_host",
"ORACLE_CONN_FACTURACION_PORT": "1521",
"ORACLE_CONN_FACTURACION_SID": "ORCL",
"ORACLE_CONN_FACTURACION_CLIENT_LIB_DIR": "C:\\oracle\\instantclient_11_2"
}
}
}
}
```
### Otros clientes MCP
Cualquier cliente que soporte servidores MCP tipo `local`/`stdio` puede usar la misma configuración base:
| Campo | Valor |
|-------|-------|
| command | `C:\ruta\oracle-mcp-server\.venv\Scripts\python` |
| args | `["-m", "oracle_mcp.server"]` |
| env | Tus variables `ORACLE_CONN_*` (o ninguna si usas `.env`) |
**Nota:** Si usas `.env` para las credenciales, el servidor las cargará
automáticamente desde la raíz del proyecto, sin importar el directorio desde
el que se arranque el proceso.
### Puntos importantes
- Usa la ruta **absoluta** al Python del `.venv`
- Reinicia/recarga el cliente MCP después de editar la configuración
- Puedes verificar que el servidor responde con el comando de prueba de la sección 5
## 7. Uso de múltiples conexiones
Una vez configuradas las conexiones, puedes usarlas en las tools:
```python
# Usar conexión por defecto (primera de la lista)
list_tables()
# Usar conexión específica
list_tables(connection="clinica")
# Query en conexión específica
run_query("SELECT * FROM pacientes", connection="clinica")
# Ver conexiones disponibles
list_connections()
```
## 8. Notas sobre Oracle 11g y modo thick
- `oracledb.init_oracle_client(lib_dir=...)` se llama **una sola vez**,
antes de abrir cualquier conexión (lo hace `db.py` automáticamente al
arrancar el servidor). Usa el `client_lib_dir` de la primera conexión
que lo tenga definido.
- Si se te olvida configurar `CLIENT_LIB_DIR` y el Instant Client no está
en el `PATH`/`LD_LIBRARY_PATH`, oracledb lanzará un error indicando que
no encuentra las librerías cliente.
- 11g casi siempre se conecta por **SID**, no por service_name; por eso el
DSN se construye con `oracledb.makedsn(host, port, sid=...)` cuando
defines `SID`.
- Si tu 11g tiene un Oracle Wallet o requiere TNS_ADMIN, puedes exportar
`TNS_ADMIN` en `environment` dentro de `opencode.json` y ajustar `db.py`
para usar `dsn` directamente por alias TNS en vez de `makedsn`.
## 9. Estructura del proyecto
```
oracle-mcp-server/
├── LICENSE
├── README.md
├── pyproject.toml
├── requirements.txt
├── setup.ps1 # Script de instalación automática (Windows)
├── .env.example
├── opencode.json.example
├── .github/
│ └── workflows/
│ └── ci.yml # CI: tests (Python 3.10–3.12) y build del paquete
├── src/
│ └── oracle_mcp/
│ ├── __init__.py
│ ├── config.py # Carga de variables de entorno (multi-conexión)
│ ├── db.py # Init thick mode, multipools, validación SELECT-only
│ └── server.py # Servidor MCP (FastMCP) con tools multi-conexión
└── tests/
├── helpers.py # make_connection() con valores por defecto
├── conftest.py # limpia variables ORACLE_*/OX_* entre tests
├── test_config.py # carga de configuración legacy y multi-conexión
├── test_db.py # thick mode, validación, serialización de LOBs
└── test_server.py # tools MCP con pools ficticios
```
## 10. Solución de Problemas
| Problema | Causa Común | Solución |
|----------|-------------|----------|
| `Conexión 'xxx' no encontrada` | Nombre no coincide | Verificar que `ORACLE_CONNECTIONS` coincida con las variables |
| `Faltan variables obligatorias` | Variable no definida | Verificar nombre de la variable en Windows o `.env` |
| `Usuario/Password incorrecto` | Credenciales erróneas | Verificar valores con `[Environment]::GetEnvironmentVariable("ORACLE_CONN_FACTURACION_USER", "User")` |
| `Pool no disponible` | Conexión no inicializó | Revisar logs del servidor al iniciar |
| `ORA-12541: TNS:no listener` | Oracle no accessible | Verificar HOST, PORT y que el listener esté activo |
| `ORA-12154: TNS:could not resolve connect identifier` | SID/SERVICE_NAME incorrecto | Verificar ORACLE_SID o ORACLE_SERVICE_NAME |
## 11. Próximos pasos sugeridos
- Agregar paginación real (cursor/offset) en `run_query` si necesitas
recorrer resultados grandes en varias llamadas.
- Si vas a exponer `execute_statement` en un entorno compartido, considera
agregar un `ALLOWED_TABLES` o un usuario Oracle con permisos acotados
(grant solo sobre las tablas necesarias) en vez de confiar únicamente en
la validación de la tool.
## 12. Licencia
Este proyecto está bajo la licencia **MIT** (ver [LICENSE](LICENSE)).
Copyright (c) 2026 Elvin Cooper.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues