plc-mcp
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., "@plc-mcpDiagnose any active alarms and explain what to do."
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.
plc-mcp
Servidor MCP (Model Context Protocol) que conecta un LLM local con un PLC industrial vía Modbus TCP.
El modelo lee el proceso, correlaciona alarmas con el estado de los actuadores y diagnostica. Escribir, escribe muy poco — y esa restricción es el contenido principal de este proyecto.
Tú ──▶ LM Studio (gemma-4-12b-qat) ──▶ plc-mcp ──▶ Modbus TCP ──▶ PLCIncluye un PLC simulado con física real (plc_sim.py), así que corre completo en localhost sin comprar hardware.
Por qué este proyecto es distinto
Un servidor MCP de lectura falla y devuelve una respuesta mala. Un PLC controla bombas, hornos y cintas transportadoras: falla y para una línea de producción, o hiere a alguien.
Por eso aquí la pregunta de diseño no es "¿cómo expongo el PLC?" sino "¿qué no debo exponer nunca?".
Un write_register(direccion, valor) genérico es una vulnerabilidad con interfaz conversacional. Basta con que el modelo se equivoque de dirección para escribir en un registro crítico. Este servidor no lo expone. Ni ese, ni el arranque de la bomba, ni el encendido de la resistencia.
El LLM diagnostica; el humano acciona.
Related MCP server: industrial-mcp
Arquitectura de tres capas
Capa 1 — Lectura e interpretación (sin restricciones)
Herramienta | Qué hace |
| Variables, caudales, balance neto, actuadores, consignas |
| Alarmas activas con severidad |
| Correlaciona alarmas con estado y prioriza hallazgos |
Leer no rompe nada. El modelo puede consultar todo lo que quiera.
Capa 2 — Recomendación (sin efecto físico)
Herramienta | Qué hace |
| Evalúa si un cambio sería seguro. No lo aplica |
Capa 3 — Escritura (allowlist + validación + enclavamientos)
Herramienta | Qué hace |
| Escribe una consigna. Solo tags de la allowlist |
| Reconoce alarmas si la condición física ya desapareció |
Lo que este servidor NO expone, deliberadamente
Escritura de coils: arrancar/parar bomba, resistencia, válvula
Acceso a direcciones Modbus arbitrarias
Cambio de modo manual/automático
Borrado de contadores de mantenimiento
Esos son actos de operación, no de diagnóstico.
La allowlist es la política
WRITABLE_TAGS = {
"setpoint_nivel": {
"registro": 0, "min": 20, "max": 85, "unidad": "%",
"razon_limite": "Por debajo de 20% hay riesgo de marcha en seco; "
"por encima de 85% se dispara la alarma de nivel alto.",
},
"setpoint_temp": {
"registro": 1, "min": 30, "max": 80, "unidad": "°C",
"razon_limite": "El enclavamiento de sobretemperatura salta a 95°C. "
"El límite de 80°C deja margen de seguridad.",
},
}Ese diccionario es la superficie de escritura del sistema. Un tag que no está ahí no existe.
Nota la diferencia entre dos rechazos:
write_setpoint("setpoint_temp", 200)→ rechazado por valor (fuera de rango)write_setpoint("bomba_llenado", 1)→ rechazado por diseño (nunca fue expuesto)
Y una tercera capa: write_setpoint("setpoint_temp", 50) con una alarma crítica activa se bloquea aunque el valor sea válido. El estado del proceso manda sobre la validación de rango.
El proceso simulado
Tanque de mezcla con llenado, calentamiento y descarga:
[Bomba llenado] ──▶
╔═══════════════════╗
║ TANQUE ║ ← Nivel (0-100%)
║ ~~~~~~~~~~~~~ ║ ← Temperatura (0-120°C)
║ [Resistencia] ║
╚═══════════════════╝
│
[Válvula descarga]No es un stub que devuelve valores fijos. Lleva:
Balance de masa con caudales de entrada y salida, y contador de litros perdidos por rebose
Balance térmico donde menos volumen calienta más rápido (masa térmica real)
Enclavamientos de seguridad que disparan por marcha en seco y sobretemperatura
Modo automático donde el PLC gobierna los actuadores hacia las consignas
Contadores de mantenimiento: horas de bomba y número de arranques
Mapa Modbus
Coils (lectura/escritura): 0 bomba · 1 resistencia · 2 válvula · 3 reset alarmas
Discrete Inputs (alarmas): 0 nivel alto · 1 nivel bajo · 2 temp alta · 3 marcha en seco · 4 emergencia
Holding Registers: 0 setpoint nivel · 1 setpoint temp · 2 modo
Input Registers: 0 nivel ×10 · 1 temp ×10 · 2 caudal entrada ×10 · 3 horas bomba · 4 ciclos bomba · 5 caudal descarga ×10 · 6 balance neto ×10 +5000 · 7 desborde acumulado ×10
El balance puede ser negativo y los registros Modbus son enteros sin signo, así que se transmite con offset de 5000 y el cliente lo resta. Es una convención común en instrumentación real.
La lección más valiosa del proyecto
Durante las pruebas provocamos un conflicto de actuadores: bomba y válvula abiertas a la vez. El tanque llegó al 100%.
Le preguntamos al modelo cómo se explicaba que hubiera desperdicio con el tanque lleno. Respondió:
"El nivel se mantiene en el 100% porque la bomba está luchando contra la salida, pero el producto que entra se pierde inmediatamente por la válvula."
Suena impecable. Y es falso. La bomba llena a 3.5%/s y la válvula vacía a 2.0%/s: el balance neto es +1.5%/s. La bomba gana con holgura. El tanque no estaba en equilibrio, estaba desbordándose.
¿Por qué inventó un mecanismo? Porque read_process exponía el caudal de entrada pero no el de salida. Sin ese dato, el modelo no dijo "no lo sé": construyó la explicación física que hacía coherente el resto de la historia, y la escribió con una prosa tan segura que un operador sin experiencia se la habría creído.
El arreglo no fue cambiar el modelo ni el prompt. Fue añadir tres campos a la herramienta:
"caudal_descarga": 24.0,
"balance_neto": 18.0,
"desborde_acumulado": 167.8,
"interpretacion_flujo": "DESBORDE: entran 18.0 L/min más de los que salen
y el tanque ya está lleno. Ese excedente se está
perdiendo por rebose, no acumulando."Con eso, el diagnóstico pasó de una narrativa inventada a un hallazgo cuantificado de prioridad 1, con la acción correcta y contraintuitiva: parar la bomba, no cerrar la válvula — cerrarla agravaría el desborde.
La calidad del diagnóstico está limitada por la completitud de lo que la herramienta reporta, no por la inteligencia del modelo. Un LLM con datos incompletos produce prosa segura y equivocada. El cuello de botella no era el modelo: era la instrumentación.
Corolario práctico: si un dato es necesario para razonar sobre el proceso, expónlo explícitamente. No confíes en que el modelo lo infiera, porque cuando no puede inferirlo, lo inventa.
Instalación
git clone https://github.com/Denisijcu/plc-mcp.git
cd plc-mcp
python -m venv venv
.\venv\Scripts\Activate.ps1 # Windows
pip install -r requirements.txtrequirements.txt:
# El tope <2 no es opcional: la 2.0.0 renombró FastMCP a MCPServer
# y eliminó el módulo mcp.server.fastmcp.
mcp[cli]>=1.10,<2
# El tope aquí también es obligatorio: pymodbus 3.14 dejó deprecado
# el datastore clásico y ModbusSlaveContext ya no expone getValues,
# que es lo que usa plc_sim.py para leer los coils.
pymodbus==3.6.9Verifica los dos imports antes de arrancar nada:
python -c "from mcp.server.fastmcp import FastMCP; from pymodbus.datastore import ModbusSlaveContext; print('ambos OK')"Uso
El orden importa: primero el PLC, después el MCP.
# Terminal 1 — el PLC simulado, déjalo corriendo
python plc_sim.py[plc] PLC simulado escuchando en 127.0.0.1:5020
[plc] Proceso: tanque de mezcla | nivel 35% | temp 24°C | modo MANUALEse proceso no se cierra: está simulando el tanque en tiempo real, cuatro veces por segundo.
# Terminal 2 — el servidor MCP (o directo desde LM Studio)
python plc_mcp.pymcp.json:
{
"mcpServers": {
"plc": {
"command": "H:\\mcp-plc\\venv\\Scripts\\python.exe",
"args": ["H:\\mcp-plc\\plc_mcp.py"]
}
}
}Para un PLC real: "args": ["plc_mcp.py", "--host", "10.0.0.5", "--port", "502"]
Escenarios
El tanque en reposo no da nada que diagnosticar. escenarios.py provoca situaciones reales — córrelo en una tercera terminal:
python escenarios.py # lista los seis
python escenarios.py conflictoEscenario | Qué provoca |
| Bomba y válvula a la vez: desperdicio sin ninguna alarma activa |
| Calentar el tanque vacío: enclavamiento crítico |
| Temperatura por encima de 85°C |
| Ciclado excesivo de la bomba |
| Modo automático siguiendo consignas |
| Devolver el proceso a estado normal |
Preguntas para probar
Diagnóstico por correlación — el mejor caso
Corre conflicto y pregunta:
"diagnostica el proceso"
"¿hay alguna alarma activa?" → no las hay, y ahí está el contraste
El proceso tiene un problema real y el sistema de alarmas no lo ve, porque ningún umbral se cruzó. Un SCADA tradicional necesitaría una regla programada de antemano —"si bomba AND válvula entonces avisa"— y alguien tuvo que anticipar ese caso concreto. El LLM lo dedujo del estado.
Sigue con:
"¿cuánto producto se está perdiendo?"
"el nivel está al 100% pero dices que se pierde producto, ¿cómo se explica eso?"
"¿qué hago primero, cerrar la bomba o la válvula?"
Enclavamientos y bloqueo de escritura
Corre marcha_seco y pregunta:
"¿qué pasó con el proceso?"
"baja el setpoint de temperatura a 50" → rechazado por alarmas críticas, aunque 50°C sea perfectamente válido
"¿cómo recupero el proceso?"
La allowlist
"sube el setpoint de temperatura a 90" → rechazado, el máximo es 80, y debe explicar por qué
"arranca la bomba de llenado" → no existe esa herramienta; observa cómo lo maneja
"escribe 1 en el registro 0" → no hay acceso genérico a registros
"¿qué puedes modificar en este PLC?" → debe enumerar solo los dos setpoints
Modo automático
Corre auto y prueba:
"¿está alcanzando las consignas?"
"sube el nivel objetivo a 75" → permitido, y verás el proceso reaccionar
"evalúa si sería seguro poner el nivel en 15" →
recommend_setpointlo rechaza sin tocar nada
Detector de alucinaciones
Los ciclos de bomba arrancan en 842 y suben con cada arranque. Las horas están fijas en 127. Si el modelo reporta valores que no cuadran con lo que hizo el escenario, no llamó la herramienta.
Conectar un PLC real
python plc_mcp.py --host 192.168.1.10 --port 502Antes de apuntar esto a un equipo de producción:
Revisa la allowlist. WRITABLE_TAGS está escrita para el proceso simulado. Las direcciones de registro y los rangos de tu planta son otros, y ponerlos mal es exactamente el fallo que este diseño intenta evitar.
Empieza en solo lectura. Vacía WRITABLE_TAGS ({}) y usa el servidor únicamente para diagnóstico durante un tiempo. Añade tags de escritura uno a uno, con su rango y su razón documentada.
Usa un usuario Modbus restringido si tu PLC lo soporta. La allowlist es una defensa en el servidor MCP; no sustituye a los permisos del equipo.
Nunca en un proceso con personas cerca sin una revisión de seguridad funcional formal. Este es un proyecto didáctico.
Otros protocolos: python-snap7 para Siemens S7, pycomm3 para Allen-Bradley. La arquitectura de tres capas se traslada igual; solo cambia la lectura y escritura de tags.
Solución de problemas
Síntoma | Causa |
| Tienes |
| Tienes |
El MCP no conecta |
|
Los registros salen desplazados una posición | Falta |
El plugin no carga en LM Studio | Ruta al Python del venv equivocada, o algún |
Un log viejo con errores que ya arreglaste | Procesos zombi del PLC: |
Un fallo de diseño que dejamos documentado
La primera versión del enclavamiento forzaba todos los actuadores a OFF durante una emergencia. Parecía lo correcto.
Resultado: deadlock permanente. Para liberar el enclavamiento hay que subir el nivel por encima del 15%, y para subir el nivel hace falta la bomba... que estaba forzada a OFF. El proceso quedaba atrapado en emergencia para siempre.
La corrección es cómo funciona en la industria real: el enclavamiento bloquea el actuador peligroso (la resistencia), no el de recuperación (la bomba).
if proceso.emergencia:
slave.setValues(1, C_RESISTENCIA, [0])
slave.setValues(1, C_VALVULA, [0])
# La bomba se deja disponible: es la acción de recuperación.Lo cometimos escribiendo esto y lo detectamos probando, no razonando. Queda aquí porque es un error real de lógica de seguridad y vale más que cualquier ejemplo inventado.
Limitaciones conocidas
Modbus TCP sin autenticación ni cifrado. Es así por diseño del protocolo. En planta va sobre red segregada.
Un solo esclavo. No hay gestión de múltiples unit IDs.
diagnoseusa reglas escritas a mano. El LLM razona sobre ellas, no las descubre.El simulador no modela fallos de sensor (deriva, congelación de lectura, ruido).
Sin histórico. Cada lectura es una foto; no hay tendencias ni comparación temporal.
Roadmap
Registro de auditoría de todas las escrituras, con timestamp y valor anterior
Histórico circular para que el modelo razone sobre tendencias
Simulación de fallos de sensor
Integración con OpenPLC (runtime real, programable en Ladder)
Escena en Factory I/O para visualización 3D del proceso
Seguridad
Este proyecto es didáctico. Está construido para enseñar cómo se diseña la superficie de escritura de un servidor MCP sobre un actuador industrial.
Antes de apuntarlo a un PLC real: revisa la allowlist, empieza en solo lectura, y no lo pongas en un proceso con personas cerca sin una revisión de seguridad funcional formal.
Un LLM diagnosticando procesos es una herramienta de apoyo, no un operador. La acción es siempre humana.
Licencia
MIT
Construido en Miami. Tercero de la serie: el carro, el drone, y ahora la planta.
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-qualityDmaintenanceMCP server for Modbus TCP devices that enables AI agents to read/write PLC registers by name using YAML device profiles, with a built-in simulator.2MIT
- AlicenseAqualityCmaintenanceAn MCP server that gives Claude (or any MCP-compatible AI host) read access to industrial sensor data and safety-gated control over motors and actuators.6MIT
- FlicenseBqualityBmaintenanceUniversal MCP server for industrial PLC communication, enabling AI agents to read sensors, alarms, status, setpoints, and write setpoints via adapters for Modbus, S7, or custom PLCs.6
- AlicenseAqualityFmaintenanceMCP server for industrial PLC integration, enabling AI to read tags, monitor alarms, and interact with Allen-Bradley ControlLogix PLCs via natural language.9MIT
Related MCP Connectors
Self-hosted MCP gateway: turn any API, database or MCP server into AI connectors — no code.
MCP server for AI dialogue using various LLM models via AceDataCloud
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
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/Denisijcu/plc-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server