oracle-mcp-server
by ElvinCooper
README.md
# oracle-mcp-server
Servidor MCP (Model Context Protocol) en Python para conectar a **Oracle 11g**
usando `python-oracledb` en **modo thick** (obligatorio en 11g, ya que el modo
thin del driver solo soporta Oracle 12.1+). Pensado para usarse desde
[opencode](https://opencode.ai) u otro cliente MCP compatible con servidores
locales (stdio).
**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 por diseño
- Por defecto **solo lectura**: `run_query` valida que la sentencia empiece
con `SELECT`/`WITH` y rechaza múltiples sentencias separadas por `;`.
- `execute_statement` está bloqueada salvo que definas
`ORACLE_CONN_<NOMBRE>_ALLOW_WRITE=true` (o `ORACLE_ALLOW_WRITE=true` en el
modo legacy) en el entorno **y** pases `confirm=true` en la llamada (doble
seguro).
- Las credenciales viven en variables de entorno, nunca hardcodeadas.
## 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 .
```
## 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=mi_usuario
ORACLE_CONN_FACTURACION_PASSWORD=mi_password
ORACLE_CONN_FACTURACION_HOST=192.168.1.10
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=clinica_user
ORACLE_CONN_CLINICA_PASSWORD=clinica_pass
ORACLE_CONN_CLINICA_HOST=192.168.1.20
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=mi_usuario
ORACLE_PASSWORD=mi_password
ORACLE_HOST=192.168.1.10
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", "mi_usuario", "User")
[Environment]::SetEnvironmentVariable("ORACLE_CONN_FACTURACION_PASSWORD", "mi_password", "User")
```
```bash
# Linux / Mac (agrega a ~/.bashrc o ~/.zshrc y recarga: source ~/.bashrc)
export ORACLE_CONN_FACTURACION_USER="mi_usuario"
export ORACLE_CONN_FACTURACION_PASSWORD="mi_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. 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).
## 5. 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": "mi_usuario",
"ORACLE_CONN_FACTURACION_PASSWORD": "mi_password",
"ORACLE_CONN_FACTURACION_HOST": "192.168.1.10",
"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": "clinica_user",
"ORACLE_CONN_CLINICA_PASSWORD": "clinica_pass",
"ORACLE_CONN_CLINICA_HOST": "192.168.1.20",
"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": "mi_usuario",
"ORACLE_CONN_FACTURACION_PASSWORD": "mi_password",
"ORACLE_CONN_FACTURACION_HOST": "192.168.1.10",
"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": "mi_usuario",
"ORACLE_CONN_FACTURACION_PASSWORD": "mi_password",
"ORACLE_CONN_FACTURACION_HOST": "192.168.1.10",
"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 4
## 6. 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()
```
## 7. 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`.
## 8. Estructura del proyecto
```
oracle-mcp-server/
├── pyproject.toml
├── setup.ps1 # Script de instalación automática (Windows)
├── .env.example
├── opencode.json.example
├── README.md
└── 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
```
## 9. 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 |
## 10. 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.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues