mcp-local-redes
Servidor MCP local para telemetría de flota
Proyecto 1 del curso CC3067 Redes, sección 10, Universidad del Valle de Guatemala. Fernando Hernández.
Un servidor MCP (Model Context Protocol) que corre en la máquina del operador y expone las consultas de telemetría de una flota vehicular como herramientas que un modelo de lenguaje puede invocar. Con él, un chatbot responde preguntas como "¿dónde está la P-123BCD?" o "¿qué unidad recorrió más kilómetros esta semana?" sin que el usuario abra la plataforma de rastreo.
El protocolo está implementado desde cero sobre stdio con la librería estándar
de Python. No uso el SDK de MCP ni ninguna librería que maneje JSON-RPC; ese es
el requisito central del proyecto y lo explico en la sección
Implementación del protocolo.
Qué es MCP
MCP es un protocolo de capa de aplicación que estandariza cómo un modelo de lenguaje descubre e invoca herramientas externas. Un servidor MCP publica una lista de herramientas, cada una con un nombre, una descripción y un esquema JSON de sus parámetros. El cliente (Claude Desktop, por ejemplo) obtiene esa lista, se la muestra al modelo, y cuando el modelo decide usar una herramienta el cliente la invoca con los argumentos que el modelo eligió y le devuelve el resultado para que lo explique en lenguaje natural.
Mecánicamente, MCP es JSON-RPC 2.0 sobre un transporte. En este proyecto el
transporte es stdio: el cliente arranca el servidor como proceso hijo y los
dos intercambian objetos JSON delimitados por salto de línea a través de stdin y
stdout. La sesión empieza con un handshake (initialize → respuesta →
notifications/initialized) y después el cliente puede llamar tools/list,
tools/call y ping.
Related MCP server: NL-to-SQL MCP
Por qué una flota, y por qué local
Las empresas con flota propia ya tienen GPS en sus unidades y una plataforma de rastreo; el dato existe y está completo. El problema es el acceso: hoy hay que navegar dashboards, aplicar filtros y generar reportes, y quien mejor conoce la operación suele ser quien menos domina la plataforma.
El servidor corre local por diseño y no solo por requisito del curso: las posiciones de una flota revelan rutas comerciales, clientes y horarios. Con el servidor en la máquina del operador, hacia el modelo viaja únicamente el resultado agregado de cada consulta, nunca el histórico de posiciones.
Requisitos previos
Python 3.11 o superior
gitOpcional: una llave de Google Maps (
GOOGLE_MAPS_API_KEY) para geocodificación con Google y para regenerar rutas. Sin ella todo funciona igual.
Instalación
git clone https://github.com/FerAHMz/mcp-local-redes.git
cd mcp-local-redes
python3.11 -m venv .venv
source .venv/bin/activate # en Windows: .venv\Scripts\activate
pip install -r requirements.txtGenerar la base de datos
No uso datos reales de ninguna empresa. El generador simula 15 vehículos durante 7 días sobre rutas reales del área metropolitana de Guatemala, un reporte cada 15 segundos dentro de la jornada de cada unidad, con ruido GPS de σ ≈ 5 m y eventos inyectados que conozco de antemano (paradas prolongadas, excesos de velocidad, pérdidas de señal, entradas y salidas de geocerca).
python datos/generador.pyProduce datos/flota.db (SQLite, ~160 000 posiciones y ~1 300 eventos) en un
par de segundos. Por defecto el set termina en el momento en que se corre, así
que "hoy" y "ayer" en las preguntas se refieren a fechas reales. Para un set
reproducible se fija el instante final:
python datos/generador.py --ahora 2026-08-19T15:30Conviene generarlo en horario laboral (o pasar un --ahora con hora laboral)
para que en unidades_detenidas haya unidades en ruta y no solo apagadas.
Rutas: modo offline y modo con API
Las rutas base están guardadas en datos/rutas/*.json como polilíneas
codificadas (el mismo formato que devuelve la Directions API de Google), junto
con las paradas de cada una. El generador las lee de ahí y no necesita red ni
llave.
Para volver a pedirlas a la Directions API (por ejemplo, para cambiar las paradas editando los JSON):
export GOOGLE_MAPS_API_KEY=...
python datos/generador.py --regenerar-rutasCorrer el servidor con el cliente de prueba
El servidor por sí solo no es interactivo: lee JSON de stdin y escribe JSON en
stdout. Para verlo funcionar escribí cliente_prueba.py, que lo arranca como
subproceso, hace el handshake, lista las herramientas y permite invocarlas,
imprimiendo cada mensaje tal como viaja en cada dirección.
python cliente_prueba.py # interactivo
python cliente_prueba.py --demo # las herramientas de texto y tres casos de error, de corridoEn modo interactivo se escribe el número de la herramienta, se responden sus
parámetros y se ve el request, la respuesta y el resultado. También acepta
ping y lista.
El servidor también se puede probar a mano:
printf '{"jsonrpc":"2.0","id":1,"method":"ping"}\n' | python -m servidor.mainLos logs del servidor van a stderr; con --verbose imprime además cada mensaje
que entra y sale.
Conectarlo a Claude Desktop
Editar el archivo de configuración de Claude Desktop:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
y agregar el servidor con las rutas absolutas del repositorio:
{
"mcpServers": {
"flota": {
"command": "/ruta/absoluta/mcp-local-redes/.venv/bin/python",
"args": ["/ruta/absoluta/mcp-local-redes/servidor/main.py"]
}
}
}En Windows command es C:\\ruta\\mcp-local-redes\\.venv\\Scripts\\python.exe.
Si se quiere geocodificación con Google se agrega
"env": {"GOOGLE_MAPS_API_KEY": "..."} dentro de "flota".
Al reiniciar Claude Desktop aparecen las siete herramientas y se puede preguntar
en lenguaje natural. La base de datos se busca en datos/flota.db relativa al
repositorio; se puede cambiar con la variable MCP_FLOTA_DB.
Herramientas
Herramienta | Pregunta que responde | Parámetros | Devuelve |
| ¿Dónde está la P-123BCD? |
| Dirección, coordenadas, velocidad, rumbo, estado del motor y hora del último reporte |
| ¿Qué unidades llevan más de 30 minutos detenidas? |
| Placa, ubicación, desde cuándo y si tiene motor encendido, por unidad |
| Dame el recorrido de la P-456DEF de ayer |
| Kilómetros, hora de salida y retorno, paradas (número, duración, las más largas), velocidad máxima y promedio, huecos de señal |
| Muéstrame en un mapa el recorrido de la P-456DEF de ayer |
| Imagen PNG con el trazo sobre OpenStreetMap, inicio, fin, paradas con su duración y geocercas |
| ¿Hubo excesos de velocidad esta semana? |
| Conteo por tipo y por unidad, y detalle de los eventos más graves |
| ¿La P-456DEF entró al CEDIS hoy? |
| Si entró, con hora de entrada y salida y minutos dentro por visita |
| ¿Cuál unidad recorrió más kilómetros este mes? |
| Ranking de unidades por kilometraje con días operados y promedio diario |
Fechas en formato AAAA-MM-DD. Tipos de alerta: exceso_velocidad,
parada_prolongada, perdida_senal, geocerca_entrada, geocerca_salida.
Geocercas definidas en el set sintético: CEDIS Zona 12, Bodega Villa Nueva,
Bodega Mixco, CD Zona 18, Bodega Carretera a El Salvador y Centro Histórico.
verificar_geocerca acepta el nombre completo o una parte ("cedis", "mixco").
Ninguna herramienta devuelve datos crudos. Siete días de quince unidades
reportando cada quince segundos son cientos de miles de filas; mandarlas al
modelo es inviable e innecesario. Cada herramienta agrega en SQL o en pandas y
devuelve el resultado calculado. El tope de filas por respuesta es la constante
MAX_FILAS = 200 en servidor/registro.py, y hay una prueba que lo verifica
para cada herramienta.
El mapa del recorrido
mapa_recorrido es la única herramienta que devuelve algo más que texto: su
resultado lleva dos bloques de contenido, un text con el resumen y un image
con el PNG en base64, que Claude Desktop muestra directamente en el chat. El
mapa se dibuja con matplotlib; los tiles de fondo se descargan de
OpenStreetMap con urllib, y si no hay red se dibuja el trazo sobre fondo plano.

Ejemplos de preguntas
¿Dónde está la P-123BCD ahorita?
¿Hay unidades que lleven más de una hora paradas?
Dame el resumen del recorrido de la P-456DEF de ayer.
Muéstrame en un mapa por dónde anduvo la P-789GHJ ayer.
¿Cuántas paradas hizo la P-234KLM el lunes y dónde fue la más larga?
¿Hubo excesos de velocidad esta semana? ¿Qué unidad tuvo más?
¿Qué unidad perdió señal en los últimos siete días?
¿La P-456DEF entró al CEDIS ayer? ¿A qué hora y cuánto estuvo?
¿Cuál unidad recorrió más kilómetros esta semana?
¿Cuántos kilómetros hizo la flota completa del lunes al viernes?
Pruebas
python -m pytest tests -vDos grupos:
tests/test_protocolo.py: handshake correcto, rechazo de métodos antes deinitialize, JSON malformado →-32700, request inválido →-32600, método inexistente →-32601, argumentos inválidos →-32602, una notificación no genera respuesta, elidde la respuesta coincide con el del request, una respuesta nunca llevaresultyerrora la vez, y un arranque real del proceso por stdio con cierre limpio en EOF.tests/test_herramientas.py: cada herramienta contra un set generado en un directorio temporal con semilla fija, verificando contra los eventos que el generador inyectó a propósito (las unidades que dejé detenidas, las paradas prolongadas, los huecos de señal, los excesos de velocidad), los errores de negocio, y que ninguna respuesta superaMAX_FILAS.
Implementación del protocolo
Todo lo que toca el protocolo está escrito a mano con sys, json y logging.
pandas, shapely, geopy y matplotlib son lógica de negocio; requests
solo lo usa el generador de datos.
servidor/main.py, transporte. Lee stdin línea por línea, escribe cada respuesta en stdout seguida de\nyflush(). Todo log va a stderr porque stdout es el canal del protocolo y un solo byte de más lo rompe. En EOF cierra la base y termina con código 0.servidor/jsonrpc.py, JSON-RPC 2.0. Parsea y valida cada mensaje, distingue request de notificación por la presencia de la llaveid(no por su valor, porquenulles unidválido), y arma respuestas y errores con los códigos estándar-32700,-32600,-32601,-32602y-32603.servidor/protocolo.py, MCP. Handshake de inicialización con máquina de estados (NUEVA→INICIALIZANDO→LISTA): cualquier método que no seainitializeopingse rechaza hasta que lleganotifications/initialized. Negociación de versión: si el cliente pide una versión que soporto se la devuelvo, si no le devuelvo la más reciente que sí.tools/list,tools/callyping. Las notificaciones que no manejo se ignoran en silencio, porque responder a una notificación rompe al cliente.servidor/registro.py. Lista de herramientas con suinputSchemay validación de argumentos contra él (tipos,required,enum). Aquí viveMAX_FILAS.
Decidí separar JSON-RPC de MCP porque son dos niveles distintos del protocolo: JSON-RPC define la forma de los mensajes y MCP define qué métodos existen y en qué orden. Separarlos me permitió probar la validación de mensajes sin una sesión y la máquina de estados sin stdin.
La distinción que más cuidé está en tools/call: si la herramienta no existe o
los argumentos no cumplen el esquema, es un error del protocolo y se devuelve
como error JSON-RPC con -32602; si la herramienta existe y corre pero el
resultado es un fallo de negocio (placa inexistente, día sin datos), se devuelve
como result con isError: true y un mensaje legible, para que el modelo se lo
pueda explicar al usuario.
La traza completa de una sesión real, con el JSON exacto de cada mensaje, está
en docs/protocolo.md.
Estructura del repositorio
mcp-local-redes/
├── servidor/
│ ├── main.py # punto de entrada, bucle de stdio
│ ├── jsonrpc.py # construcción y validación de mensajes JSON-RPC 2.0
│ ├── protocolo.py # handshake, máquina de estados, despacho de métodos
│ ├── registro.py # registro de herramientas, validación de argumentos, MAX_FILAS
│ └── herramientas/
│ ├── comun.py # consultas compartidas
│ ├── geocodificacion.py
│ ├── posicion.py
│ ├── detenidas.py
│ ├── recorrido.py
│ ├── mapa.py
│ ├── alertas.py
│ ├── geocercas.py
│ └── kilometraje.py
├── datos/
│ ├── generador.py # set sintético
│ ├── esquema.sql
│ └── rutas/ # polilíneas guardadas para modo offline
├── cliente_prueba.py
├── tests/
│ ├── test_protocolo.py
│ └── test_herramientas.py
├── docs/
│ └── protocolo.md
└── requirements.txtThis 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
- AlicenseNot gradedqualityDmaintenanceMCP tool server providing SQLite database access for AI agents.MIT
- FlicenseNot gradedqualityCmaintenanceMCP server enabling natural-language querying of SQLite databases via schema discovery, GraphRAG retrieval, and safely guarded read-only SQL execution.
- FlicenseNot gradedqualityBmaintenanceMCP server for AI-powered roadside assistance case management, exposing SQLite-backed tools for querying case counts, statuses, and summaries through natural language via Gemini function calling.
- AlicenseAqualityAmaintenanceMCP server for chatting with physical-world data from robotics, drones, automotive, and IoT sources using natural language. It generates auditable SQL queries over Apache Arrow/DuckDB to let you analyze, summarize, and build data pipelines.18393Apache 2.0
Related MCP Connectors
GibsonAI MCP server: manage your databases with natural language
MCP server exposing the Backtest360 engine API as tools for AI agents.
MCP server for managing Prisma Postgres.
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/FerAHMz/mcp-local-redes'
If you have feedback or need assistance with the MCP directory API, please join our Discord server