Skip to main content
Glama

Local MCP server for fleet telemetry

Project 1 of the course CC3067 Networks, section 10, Universidad del Valle de Guatemala. Fernando Hernández.

An MCP (Model Context Protocol) server that runs on the operator's machine and exposes fleet vehicle telemetry queries as tools that a language model can invoke. With it, a chatbot answers questions like "where is the P-123BCD?" or "which unit drove the most kilometers this week?" without the user opening the tracking platform.

The protocol is implemented from scratch over stdio using Python's standard library. I don't use the MCP SDK or any library that handles JSON-RPC; that is the central requirement of the project and I explain it in the Protocol implementation section.

What MCP is

MCP is an application-layer protocol that standardizes how a language model discovers and invokes external tools. An MCP server publishes a list of tools, each with a name, a description, and a JSON schema of its parameters. The client (Claude Desktop, for example) fetches that list, shows it to the model, and when the model decides to use a tool the client invokes it with the arguments the model chose and returns the result so the model can explain it in natural language.

Mechanically, MCP is JSON-RPC 2.0 over a transport. In this project the transport is stdio: the client starts the server as a child process and the two exchange newline-delimited JSON objects through stdin and stdout. The session starts with a handshake (initialize → response → notifications/initialized) and then the client can call tools/list, tools/call, and ping.

Related MCP server: NL-to-SQL MCP

Why a fleet, and why local

Companies with their own fleet already have GPS on their units and a tracking platform; the data exists and is complete. The problem is access: today you have to navigate dashboards, apply filters, and generate reports, and the person who knows the operation best is often the one who is least proficient with the platform.

The server runs locally by design, not just as a course requirement: fleet positions reveal commercial routes, customers, and schedules. With the server on the operator's machine, only the aggregated result of each query travels to the model, never the position history.

Prerequisites

  • Python 3.11 or higher

  • git

  • Optional: a Google Maps key (GOOGLE_MAPS_API_KEY) for geocoding with Google and for regenerating routes. Without it everything works the same.

Installation

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.txt

Generating the database

I don't use real data from any company. The generator simulates 15 vehicles over 7 days on real routes in the metropolitan area of Guatemala, one report every 15 seconds within each unit's workday, with GPS noise of σ ≈ 5 m and injected events that I know in advance (prolonged stops, speeding, signal loss, geofence entries and exits).

python datos/generador.py

It produces datos/flota.db (SQLite, ~160 000 positions and ~1 300 events) in a couple of seconds. By default the dataset ends at the moment it is run, so "today" and "yesterday" in questions refer to real dates. For a reproducible dataset the final instant is fixed:

python datos/generador.py --ahora 2026-08-19T15:30

It's best to generate it during working hours (or pass an --ahora with working hours) so that unidades_detenidas has units on route and not just turned off.

Routes: offline mode and API mode

The base routes are stored in datos/rutas/*.json as encoded polylines (the same format returned by the Google Directions API), along with the stops for each one. The generator reads them from there and needs neither network nor a key.

To request them again from the Directions API (for example, to change the stops by editing the JSONs):

export GOOGLE_MAPS_API_KEY=...
python datos/generador.py --regenerar-rutas

Running the server with the test client

The server by itself is not interactive: it reads JSON from stdin and writes JSON to stdout. To see it working I wrote cliente_prueba.py, which starts it as a subprocess, performs the handshake, lists the tools, and allows invoking them, printing each message exactly as it travels in each direction.

python cliente_prueba.py          # interactivo
python cliente_prueba.py --demo   # las herramientas de texto y tres casos de error, de corrido

In interactive mode you type the tool number, answer its parameters, and see the request, the response, and the result. It also accepts ping and lista.

The server can also be tested by hand:

printf '{"jsonrpc":"2.0","id":1,"method":"ping"}\n' | python -m servidor.main

Server logs go to stderr; with --verbose it also prints every message that comes in and goes out.

Connecting it to Claude Desktop

Edit the Claude Desktop configuration file:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

and add the server with the absolute paths of the repository:

{
  "mcpServers": {
    "flota": {
      "command": "/ruta/absoluta/mcp-local-redes/.venv/bin/python",
      "args": ["/ruta/absoluta/mcp-local-redes/servidor/main.py"]
    }
  }
}

On Windows command is C:\\ruta\\mcp-local-redes\\.venv\\Scripts\\python.exe. If Google geocoding is wanted, add "env": {"GOOGLE_MAPS_API_KEY": "..."} inside "flota".

When Claude Desktop restarts, the seven tools appear and you can ask in natural language. The database is looked up in datos/flota.db relative to the repository; it can be changed with the MCP_FLOTA_DB variable.

Tools

Tool

Question it answers

Parameters

Returns

posicion_actual

Where is the P-123BCD?

placa

Address, coordinates, speed, heading, engine state, and time of the last report

unidades_detenidas

Which units have been stopped for more than 30 minutes?

minutos_minimos (optional, default 30)

Plate, location, since when, and whether the engine is on, per unit

resumen_recorrido

Give me the route of the P-456DEF from yesterday

placa, fecha

Kilometers, departure and return time, stops (number, duration, the longest ones), maximum and average speed, signal gaps

mapa_recorrido

Show me on a map the route of the P-456DEF from yesterday

placa, fecha

PNG image with the trace over OpenStreetMap, start, end, stops with their duration, and geofences

alertas

Were there speeding events this week?

tipo (optional), fecha_inicio, fecha_fin

Count by type and by unit, and details of the most serious events

verificar_geocerca

Did the P-456DEF enter the CEDIS today?

placa, nombre_geocerca, fecha

Whether it entered, with entry and exit time and minutes inside per visit

reporte_kilometraje

Which unit drove the most kilometers this month?

fecha_inicio, fecha_fin

Ranking of units by mileage with days operated and daily average

Dates in AAAA-MM-DD format. Alert types: exceso_velocidad, parada_prolongada, perdida_senal, geocerca_entrada, geocerca_salida.

Geofences defined in the synthetic dataset: CEDIS Zona 12, Bodega Villa Nueva, Bodega Mixco, CD Zona 18, Bodega Carretera a El Salvador, and Centro Histórico. verificar_geocerca accepts the full name or a part of it ("cedis", "mixco").

No tool returns raw data. Seven days of fifteen units reporting every fifteen seconds are hundreds of thousands of rows; sending them to the model is unfeasible and unnecessary. Each tool aggregates in SQL or pandas and returns the calculated result. The row limit per response is the constant MAX_FILAS = 200 in servidor/registro.py, and there is a test that verifies it for each tool.

The route map

mapa_recorrido is the only tool that returns something more than text: its result carries two content blocks, a text with the summary and an image with the PNG in base64, which Claude Desktop displays directly in the chat. The map is drawn with matplotlib; the background tiles are downloaded from OpenStreetMap with urllib, and if there is no network the trace is drawn on a flat background.

Route map

Example questions

  • Where is the P-123BCD right now?

  • Are there units that have been stopped for more than an hour?

  • Give me the route summary of the P-456DEF from yesterday.

  • Show me on a map where the P-789GHJ went yesterday.

  • How many stops did the P-234KLM make on Monday and where was the longest one?

  • Were there speeding events this week? Which unit had the most?

  • Which unit lost signal in the last seven days?

  • Did the P-456DEF enter the CEDIS yesterday? At what time and how long was it there?

  • Which unit drove the most kilometers this week?

  • How many kilometers did the entire fleet cover from Monday to Friday?

Tests

python -m pytest tests -v

Two groups:

  • tests/test_protocolo.py: correct handshake, rejection of methods before initialize, malformed JSON → -32700, invalid request → -32600, nonexistent method → -32601, invalid arguments → -32602, a notification generates no response, the response id matches the request's, a response never carries result and error at the same time, and a real process startup over stdio with clean shutdown at EOF.

  • tests/test_herramientas.py: each tool against a dataset generated in a temporary directory with a fixed seed, verifying against the events the generator injected on purpose (the units I left stopped, the prolonged stops, the signal gaps, the speeding events), business errors, and that no response exceeds MAX_FILAS.

Protocol implementation

Everything that touches the protocol is written by hand with sys, json, and logging. pandas, shapely, geopy, and matplotlib are business logic; requests is only used by the data generator.

  • servidor/main.py, transport. Reads stdin line by line, writes each response to stdout followed by \n and flush(). All logs go to stderr because stdout is the protocol channel and a single extra byte breaks it. At EOF it closes the database and exits with code 0.

  • servidor/jsonrpc.py, JSON-RPC 2.0. Parses and validates each message, distinguishes a request from a notification by the presence of the id key (not by its value, because null is a valid id), and builds responses and errors with the standard codes -32700, -32600, -32601, -32602, and -32603.

  • servidor/protocolo.py, MCP. Initialization handshake with a state machine (NUEVAINICIALIZANDOLISTA): any method other than initialize or ping is rejected until notifications/initialized arrives. Version negotiation: if the client requests a version I support I return it, otherwise I return the most recent one I do support. tools/list, tools/call, and ping. Notifications I don't handle are silently ignored, because responding to a notification breaks the client.

  • servidor/registro.py. List of tools with their inputSchema and argument validation against it (types, required, enum). MAX_FILAS lives here.

I decided to separate JSON-RPC from MCP because they are two different levels of the protocol: JSON-RPC defines the shape of messages and MCP defines which methods exist and in what order. Separating them allowed me to test message validation without a session and the state machine without stdin.

The distinction I cared most about is in tools/call: if the tool does not exist or the arguments do not conform to the schema, it is a protocol error and is returned as a JSON-RPC error with -32602; if the tool exists and runs but the result is a business failure (nonexistent board, day without data), it is returned as a result with isError: true and a readable message, so that the model can explain it to the user.

The full trace of a real session, with the exact JSON of each message, is in docs/protocolo.md.

Repository structure

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.txt
F
license - not found
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP tool server providing SQLite database access for AI agents.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    MCP server enabling natural-language querying of SQLite databases via schema discovery, GraphRAG retrieval, and safely guarded read-only SQL execution.
  • F
    license
    Not graded
    quality
    B
    maintenance
    MCP 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.
  • A
    license
    A
    quality
    A
    maintenance
    MCP 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.
    18
    393
    Apache 2.0

View all related MCP servers

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.

View all MCP Connectors

Latest Blog Posts

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