mcp-popey
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-popeyList the available business circuits"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MCP-Popey
Servidor MCP de consulta de solo lectura sobre la base de Popey ERP, organizada por circuitos de negocio (venta, compras, stock, etc.).
Estructura
config/circuits.yaml # datos: qué circuitos existen, qué tablas tiene cada uno
src/circuits.py # puerta de entrada a circuits.yaml (lo lee una vez, lo cachea)
src/db.py # acceso a Postgres (pool, guardas de solo-lectura, chequeo de rol)
src/server.py # servidor MCP: junta circuits.py + db.py en 3 tools
requirements.txtcircuits.py y db.py son independientes entre sí — ninguno de los dos sabe
que el otro existe. server.py es el único módulo que conoce a ambos.
Related MCP server: mcp-sqlserver-readonly
Requisitos
Python 3.12+ (probado con esa versión; no se testeó en versiones anteriores).
Acceso de red a una instancia de Postgres con el esquema de Popey ERP.
Un rol de Postgres genuinamente read-only (ver Seguridad — el servidor se niega a arrancar si detecta que el rol tiene algún permiso de escritura).
Instalación
cd "MCP-Popey"
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtSi
python3 -m venvfalla por falta deensurepip/python3-venv(ModuleNotFoundError: No module named 'ensurepip'), instalá el paquete del sistema (sudo apt install python3.12-venven Debian/Ubuntu) o bootstrapeá pip a mano dentro del venv conget-pip.py.
Configuración (variables de entorno)
Variable | Obligatoria | Default | Descripción |
| No |
| Host de Postgres |
| No |
| Puerto de Postgres |
| Sí | — | Nombre de la base |
| Sí | — | Rol de conexión — debe ser read-only, ver abajo |
| Sí | — | Contraseña del rol |
| No |
| Conexiones mínimas del pool |
| No |
| Conexiones máximas del pool |
| No |
|
|
| No |
|
|
Si falta alguna de las 3 obligatorias, el servidor no arranca y lo dice
explícitamente (no hay fallback silencioso a valores de psycopg2).
Historial: el 2026-08-08 existió brevemente un
DB_ALLOW_WRITABLE_ROLEcomo escape hatch para poder arrancar contra dev antes de tener el rol read-only dedicado (langdev, el rol usado hasta entonces, es dueño de tablas y superuser). Se sacó el mismo día al crearmcp_popey_ro— ver creación del rol read-only más abajo. El chequeo de rol no tiene bypass hoy: si el rol conectado tiene cualquier permiso de escritura, el servidor aborta, sin excepciones.
Correr el servidor
export PGHOST=...
export PGDATABASE=...
export PGUSER=...
export PGPASSWORD=...
python src/server.py(Equivalente: cd src && python server.py — la resolución de rutas internas
no depende del directorio desde el que se invoque.)
Corre sobre stdio por default (mcp.run(), transporte "stdio"): queda
esperando mensajes JSON-RPC por stdin/stdout — no es para ejecutar suelto
en una terminal y esperar ver algo, es para que lo levante un cliente MCP.
No usar print() en ningún cambio a este código: con stdio, stdout es
el canal del protocolo.
Conectarlo a un cliente MCP
Ejemplo de configuración (Claude Desktop, Claude Code vía .mcp.json, u
otro cliente que use el mismo formato mcpServers):
{
"mcpServers": {
"popey-erp": {
"command": "/ruta/a/MCP-Popey/.venv/bin/python",
"args": ["/ruta/a/MCP-Popey/src/server.py"],
"env": {
"PGHOST": "...",
"PGDATABASE": "...",
"PGUSER": "...",
"PGPASSWORD": "..."
}
}
}
}Las 3 tools
list_circuits()
Sin parámetros. Devuelve [{name, description}, ...] — los circuitos de
negocio definidos en circuits.yaml.
query_circuit(circuito, sql, params=None)
Ejecuta un único SELECT de solo lectura. circuito (ej. "venta") es
contexto informativo, no una restricción de acceso — ver política abajo.
params son los parámetros de la query (lista para %s, dict para
%(nombre)s); nunca interpolar valores directo en sql.
Devuelve {"rows": [...], "warnings": [...]}.
get_circuit_schema(circuito)
Junta la metadata de negocio de circuits.yaml (role, key_columns, source,
status, notes, pending_columns) con las columnas reales de Postgres
(information_schema.columns, acotado a las tablas del circuito) — pensado
para que el modelo sepa qué puede pedir antes de armar el sql de
query_circuit.
Seguridad y política de acceso
Hay dos capas independientes, ninguna reemplaza a la otra:
1. El rol de Postgres (barrera primaria)
PGUSER tiene que ser un rol read-only real (GRANT SELECT únicamente).
El servidor no confía ciegamente en eso: al arrancar, db.init_pool()
verifica el rol contra Postgres (superusuario, GRANT de escritura propio o
de PUBLIC, ownership de alguna tabla, o CREATE sobre algún schema) y, si
encuentra cualquier permiso de escritura o DDL, loguea CRITICAL y aborta
el arranque — no levanta el servidor con la barrera primaria comprometida.
No hay forma de saltear este chequeo desde configuración (ver historial de
DB_ALLOW_WRITABLE_ROLE arriba).
Creación del rol read-only (mcp_popey_ro)
En el entorno de dev actual, Postgres corre en un contenedor Docker
(lang_docker_database_1) sin volumen persistente: cada vez que se
recrea el contenedor, se pierde el cluster entero, rol incluido. Por eso el
rol read-only se recrea con un script versionado en vez de dejarlo como
comandos sueltos:
docker cp scripts/create_readonly_role.sql lang_docker_database_1:/tmp/
docker exec -it lang_docker_database_1 \
psql -U langdev -d langdev -v ON_ERROR_STOP=1 \
-v ro_password='<elegir una password>' \
-f /tmp/create_readonly_role.sqlCrea (o actualiza la password de) el rol mcp_popey_ro con GRANT SELECT
únicamente sobre los schemas que aparecen en config/circuits.yaml
(administracion, compra, e_plataforma, mercado_libre, public,
servicio_tecnico, stock, util, venta) — no sobre todos los schemas
de la base. Si circuits.yaml suma un circuito en un schema nuevo, hay que
agregar ese schema a scripts/create_readonly_role.sql y volver a correrlo
(los GRANT son idempotentes). Después de correrlo, poner
PGUSER=mcp_popey_ro y esa misma password en PGPASSWORD del .env.
2. La guarda de código (segunda capa, defensa en profundidad)
Independientemente del rol, db.py valida cada SQL antes de mandarlo a
Postgres:
tiene que empezar con
SELECT;una sola sentencia (rechaza
;interno — bloquea múltiples sentencias);sin palabras prohibidas en ningún lugar de la query (
INSERT,UPDATE,DELETE,DROP,ALTER,CREATE,pg_sleep,dblink, etc.).
Un DELETE/UPDATE mandado a query_circuit se rechaza acá, en Python,
antes de llegar a la base — no depende de que Postgres tire un error de
permisos.
circuits.yaml es informativo, no una barrera de acceso
Decisión explícita: el circuito pedido en query_circuit nunca bloquea
qué se puede leer. Tres situaciones generan un aviso en warnings pero la
query se ejecuta igual:
la tabla no pertenece al circuito pedido (pertenece a otro, o a ninguno);
la tabla está marcada
status: needs_reviewencircuits.yaml;la tabla está marcada
status: legacyencircuits.yaml.
Única excepción bloqueante a esta política: si circuito no es uno de
los circuitos existentes (list_circuits()), query_circuit y
get_circuit_schema rechazan antes de ejecutar nada. No es un problema de
status de una tabla — es la ausencia total de un circuito de referencia:
sin eso no hay contra qué avisar, no hay "role/notes" que citar en un
warning informativo. Por eso ahí sí se corta.
Auditoría
Cada llamada a query_circuit que llega a ejecutarse loguea (vía el logger
popey-mcp.server, nivel WARNING si hubo algún aviso, INFO si no): el
circuito pedido, el SQL final ejecutado (con el LIMIT ya aplicado) y las
tablas que generaron warning. Limitación conocida: los intentos
rechazados por la guarda de código (SELECT-only) o que fallan contra
Postgres no quedan auditados — el log se emite justo antes del return, y
una excepción corta el flujo antes de llegar ahí.
Troubleshooting
"Faltan variables de entorno obligatorias para conectar a Postgres" — falta
PGDATABASE,PGUSERoPGPASSWORD.El servidor no arranca y loguea
CRITICALsobre permisos de escritura/DDL —PGUSERno es read-only; corregir losGRANTdel rol en Postgres (no hay forma de saltear este chequeo desde acá a propósito).ModuleNotFoundErroral importarmcp,sqlglotopsycopg2— faltópip install -r requirements.txt(o el venv no está activado).
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityFmaintenanceRead-only MCP server for SQL databases (SQL Server, Postgres, SQLite) with multi-server support and three-layer safety using AST validation and linting.MIT
- Alicense-qualityCmaintenanceRead-only SQL Server MCP server enabling safe database queries, table listing, and schema inspection with built-in security protections.MIT
- Alicense-qualityBmaintenanceRead-only MCP server for PostgreSQL, enabling schema discovery, table metadata, and safe SELECT queries via READ ONLY transactions.26MIT
- Flicense-qualityCmaintenanceA read-only MCP server for exploring and querying Oracle schemas safely. Provides tools for table listing, schema description, column search, and validated SELECT execution.2
Related MCP Connectors
Read-only MCP server for wafergraph.com's semiconductor & AI supply-chain data: 30 tools, no auth.
Read-only MCP server for ClassQuill, a tutoring-business-management platform.
Read-only MCP server for Muovi, Argentina's trust-first local services marketplace (6 tools).
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/sistemasLang/mcp-popey'
If you have feedback or need assistance with the MCP directory API, please join our Discord server