MCP Logistics — Spécification des outils & Docker Compose (France + Madagascar)
MCP Logistics — Spécification des outils & Docker Compose (France + Madagascar)
Ce document fournit :
- une spécification JSON des outils MCP de logistique (géocodage, itinéraire, matrices, optimisation),
- un docker-compose prêt pour un PoC avec OSRM (France + Madagascar), VROOM (VRP/TSP open‑source), GraphHopper (profils camion), Redis (cache), et un serveur MCP “logistics” (squelette).
Remarque : placez les fichiers .osm.pbf (extraits OSM) dans ./data/osm/ avant de lancer.
1) Spécification MCP (JSON)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"mcp_server": {
"name": "logistics",
"version": "0.1.0",
"auth": { "type": "bearer", "header": "Authorization" },
"tools": [
{
"name": "geocode",
"description": "Géocode une adresse ou un POI en (lat, lon). Peut utiliser Nominatim, Google Geocoding, etc.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"country_hint": {"type": "string", "description": "Code pays ISO‑3166‑1 alpha‑2, ex. FR, MG"},
"provider": {"type": "string", "enum": ["nominatim","google"], "default": "nominatim"}
},
"required": ["query"]
},
"output_schema": {
"type": "object",
"properties": {
"lat": {"type": "number"},
"lon": {"type": "number"},
"precision": {"type": "string", "enum": ["rooftop","range_interpolated","street","place"]},
"raw": {"type": "object"}
},
"required": ["lat","lon"]
}
},
{
"name": "route_plan",
"description": "Calcule un itinéraire A→B (ou avec étapes) et retourne distance, durée, ETA et géométrie.",
"input_schema": {
"type": "object",
"properties": {
"origin": {"type": "object", "properties": {"lat": {"type": "number"}, "lon": {"type": "number"}}, "required": ["lat","lon"]},
"destination": {"type": "object", "properties": {"lat": {"type": "number"}, "lon": {"type": "number"}}, "required": ["lat","lon"]},
"waypoints": {"type": "array", "items": {"type": "object", "properties": {"lat": {"type": "number"}, "lon": {"type": "number"}}, "required": ["lat","lon"]}},
"vehicle_profile": {"type": "string", "enum": ["car","van","truck","moto","bike","foot"], "default": "car"},
"departure_time": {"type": "string", "format": "date-time"},
"avoid": {"type": "array", "items": {"type": "string", "enum": ["tolls","ferries","unpaved","low_emission_zones"]}},
"router": {"type": "string", "enum": ["osrm-fr","osrm-mg","graphhopper"], "description": "Force un moteur; sinon auto"}
},
"required": ["origin","destination"]
},
"output_schema": {
"type": "object",
"properties": {
"distance_m": {"type": "integer"},
"duration_s": {"type": "integer"},
"eta_iso": {"type": "string"},
"geometry_polyline": {"type": "string"},
"steps": {"type": "array", "items": {"type": "object", "properties": {
"instr": {"type": "string"},
"distance_m": {"type": "integer"},
"duration_s": {"type": "integer"},
"road": {"type": "string"}
}}}
},
"required": ["distance_m","duration_s","geometry_polyline"]
}
},
{
"name": "distance_matrix",
"description": "Calcule une matrice Origins×Destinations (durées/distances).",
"input_schema": {
"type": "object",
"properties": {
"origins": {"type": "array", "minItems": 1, "items": {"type": "object", "properties": {"lat": {"type": "number"}, "lon": {"type": "number"}}, "required": ["lat","lon"]}},
"destinations": {"type": "array", "minItems": 1, "items": {"type": "object", "properties": {"lat": {"type": "number"}, "lon": {"type": "number"}}, "required": ["lat","lon"]}},
"vehicle_profile": {"type": "string", "enum": ["car","van","truck","moto","bike","foot"], "default": "car"},
"router": {"type": "string", "enum": ["osrm-fr","osrm-mg","graphhopper","google"]}
},
"required": ["origins","destinations"]
},
"output_schema": {
"type": "object",
"properties": {
"matrix_seconds": {"type": "array", "items": {"type": "array", "items": {"type": "integer"}}},
"matrix_meters": {"type": "array", "items": {"type": "array", "items": {"type": "integer"}}}
},
"required": ["matrix_seconds"]
}
},
{
"name": "optimize_stops",
"description": "Optimise l’ordre des arrêts (TSP/VRP). Utilise VROOM (self‑host) ou un provider externe.",
"input_schema": {
"type": "object",
"properties": {
"depot": {"type": "object", "properties": {"lat": {"type": "number"}, "lon": {"type": "number"}}, "required": ["lat","lon"]},
"stops": {"type": "array", "minItems": 1, "items": {"type": "object", "properties": {
"id": {"type": "string"},
"lat": {"type": "number"},
"lon": {"type": "number"},
"demand_kg": {"type": "number"},
"time_window": {"type": "array", "items": {"type": "string"}, "minItems": 2, "maxItems": 2}
}, "required": ["lat","lon"]}},
"vehicle_profile": {"type": "string", "enum": ["van","truck","car"], "default": "van"},
"capacity_kg": {"type": "number"},
"router": {"type": "string", "enum": ["vroom-osrm-fr","vroom-osrm-mg"]}
},
"required": ["depot","stops"]
},
"output_schema": {
"type": "object",
"properties": {
"ordered_stops": {"type": "array", "items": {"type": "object", "properties": {
"id": {"type": "string"},
"eta_iso": {"type": "string"},
"sequence": {"type": "integer"}
}}},
"total_distance_m": {"type": "integer"},
"total_duration_s": {"type": "integer"}
},
"required": ["ordered_stops"]
}
},
{
"name": "eta_live",
"description": "Donne une ETA (avec trafic si provider compatible).",
"input_schema": {
"type": "object",
"properties": {
"origin": {"type": "object", "properties": {"lat": {"type": "number"}, "lon": {"type": "number"}}, "required": ["lat","lon"]},
"destination": {"type": "object", "properties": {"lat": {"type": "number"}, "lon": {"type": "number"}}, "required": ["lat","lon"]},
"provider": {"type": "string", "enum": ["google","graphhopper"], "default": "google"},
"departure_time": {"type": "string", "format": "date-time"}
},
"required": ["origin","destination"]
},
"output_schema": {
"type": "object",
"properties": {
"eta_iso": {"type": "string"},
"duration_s": {"type": "integer"}
},
"required": ["eta_iso","duration_s"]
}
},
{
"name": "map_snapshot",
"description": "Génère une image statique (snapshot) d’un itinéraire ou point.",
"input_schema": {
"type": "object",
"properties": {
"geometry_polyline": {"type": "string"},
"provider": {"type": "string", "enum": ["staticmaps","google"], "default": "staticmaps"},
"size": {"type": "string", "pattern": "^\\d+x\\d+$", "default": "800x600"}
},
"required": ["geometry_polyline"]
},
"output_schema": {
"type": "object",
"properties": {"png_data_uri": {"type": "string"}}
}
},
{
"name": "geofence_check",
"description": "Teste si un point est dans une (ou plusieurs) zone(s) (polygones).",
"input_schema": {
"type": "object",
"properties": {
"point": {"type": "object", "properties": {"lat": {"type": "number"}, "lon": {"type": "number"}}, "required": ["lat","lon"]},
"polygons": {"type": "array", "items": {"type": "array", "items": {"type": "array", "items": {"type": "number"}}}}
},
"required": ["point","polygons"]
},
"output_schema": {
"type": "object",
"properties": {"inside": {"type": "boolean"}, "which_ids": {"type": "array", "items": {"type": "integer"}}},
"required": ["inside"]
}
}
],
"errors": [
{"code": "ROUTER_UNAVAILABLE", "message": "Le moteur de routage demandé est indisponible."},
{"code": "GEOCODING_FAILED", "message": "Échec du géocodage pour la requête."},
{"code": "NO_FEASIBLE_ROUTE", "message": "Aucun itinéraire réalisable avec les contraintes."},
{"code": "OPTIMIZATION_FAILED", "message": "Échec de l’optimisation TSP/VRP."},
{"code": "RATE_LIMIT", "message": "Limite d’appels atteinte."},
{"code": "INVALID_INPUT", "message": "Paramètres manquants ou invalides."}
]
}
}
2) docker-compose.yml (PoC France + Madagascar)
Pré‑requis :
- Placez france-latest.osm.pbf & madagascar-latest.osm.pbf dans ./data/osm/ (depuis geofabrik.de).
- Les conteneurs OSRM lanceront osrm-extract et osrm-customize au démarrage.
version: "3.9"
services:
osrm-fr:
image: osrm/osrm-backend:latest
container_name: osrm-fr
command: >
bash -lc "osrm-extract -p /opt/car.lua /data/osm/france-latest.osm.pbf &&
osrm-partition /data/osm/france-latest.osrm &&
osrm-customize /data/osm/france-latest.osrm &&
osrm-routed --algorithm mld /data/osm/france-latest.osrm"
volumes:
- ./data:/data
ports:
- "5000:5000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 20s
timeout: 5s
retries: 10
osrm-mg:
image: osrm/osrm-backend:latest
container_name: osrm-mg
command: >
bash -lc "osrm-extract -p /opt/car.lua /data/osm/madagascar-latest.osm.pbf &&
osrm-partition /data/osm/madagascar-latest.osrm &&
osrm-customize /data/osm/madagascar-latest.osrm &&
osrm-routed --algorithm mld -p 5001 /data/osm/madagascar-latest.osrm"
volumes:
- ./data:/data
ports:
- "5001:5001"
graphhopper:
image: ghcr.io/graphhopper/graphhopper:latest
container_name: graphhopper
environment:
- JAVA_OPTS=-Xmx6g -Xms6g
command: |
-Ddw.graphhopper.datareader.file=/data/osm/madagascar-latest.osm.pbf \
server config=/data/graphhopper/config.yml
volumes:
- ./data:/data
ports:
- "8989:8989"
vroom:
image: vroomvrp/vroom-docker:latest
container_name: vroom
environment:
- VROOM_ROUTER=osrm
- OSRM_PORT=5000
- OSRM_HOST=osrm-fr
depends_on:
- osrm-fr
ports:
- "3000:3000"
redis:
image: redis:7-alpine
container_name: redis
ports:
- "6379:6379"
mcp-logistics:
image: node:20-alpine
container_name: mcp-logistics
working_dir: /app
command: node dist/index.js
volumes:
- ./mcp-server:/app
environment:
- PORT=3030
- REDIS_URL=redis://redis:6379
- OSRM_FR_URL=http://osrm-fr:5000
- OSRM_MG_URL=http://osrm-mg:5001
- VROOM_URL=http://vroom:3000
- GRAPHHOPPER_URL=http://graphhopper:8989
- GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY:-}
depends_on:
- osrm-fr
- osrm-mg
- vroom
- graphhopper
- redis
ports:
- "3030:3030"
networks:
default:
name: logistics-net
3) data/graphhopper/config.yml (profils van & truck)
graphhopper:
graph.location: /data/graphhopper/gh
datareader.file: /data/osm/madagascar-latest.osm.pbf
prepare.ch.weightings: fastest
profiles:
- name: van
vehicle: car
weighting: fastest
turn_costs: true
- name: truck
vehicle: car
weighting: fastest
turn_costs: true
custom_model_file: /data/graphhopper/custom_models/truck.json
custom_models.directory: /data/graphhopper/custom_models
routing.non_ch:
enabled: true
server:
application_connectors:
- type: http
port: 8989
admin_connectors:
- type: http
port: 8990
data/graphhopper/custom_models/truck.json (exemple simplifié)
{
"priority": [
{"if": "road_class == MOTORWAY", "multiply_by": 0.9},
{"if": "tunnel == YES", "multiply_by": 0.8}
],
"speed": [
{"if": "road_class == MOTORWAY", "limit_to": 90},
{"if": "road_environment == URBAN", "limit_to": 50}
],
"areas": {
"restricted": {
"type": "FeatureCollection",
"features": []
}
},
"priority_area": [
{"if": "in_area('restricted')", "multiply_by": 0.5}
]
}
Pour des contraintes hauteur/poids, ajoutez des règles spécifiques si vos données OSM locales les exposent ; sinon, complétez via zones interdites (GeoJSON) ou utilisez Google Routes (Restrictions camion) côté eta_live.
4) Variables d’environnement (MCP server)
Créez mcp-server/.env (chargé au runtime) :
PORT=3030 REDIS_URL=redis://redis:6379 OSRM_FR_URL=http://osrm-fr:5000 OSRM_MG_URL=http://osrm-mg:5001 VROOM_URL=http://vroom:3000 GRAPHHOPPER_URL=http://graphhopper:8989 GOOGLE_MAPS_API_KEY=
5) Smoke tests (cURL)
OSRM France — route simple
curl "http://localhost:5000/route/v1/driving/2.3522,48.8566;5.3698,43.2965?overview=full&steps=true"
VROOM (optimisation) — mini TSP
curl -H "Content-Type: application/json" -d '{
"vehicles":[{"id":1,"start":[2.3522,48.8566]}],
"jobs":[{"id":11,"location":[2.295,48.873]}, {"id":12,"location":[2.36,48.85]}]
}' http://localhost:3000
GraphHopper — profil truck
curl "http://localhost:8989/route?profile=truck&point=48.8566,2.3522&point=43.2965,5.3698&points_encoded=false"
6) Notes d’intégration Odoo
-
Exposer ces outils MCP à un assistant appelé depuis :
- Odoo Sales/CRM → bouton « Calculer prix & ETA » (devis, livraison).
- Odoo Stock/Delivery → génération de tournée (VROOM), affectation livreur.
- WhatsApp bot → intention « devis transport » → geocode → distance_matrix → règle tarifaire.
- Tarification : créez une grille (€/km + palier poids) dans Odoo ; le MCP renvoie la distance/ETA, Odoo calcule le prix.
7) Checklist PoC
- PBF FR & MG dans ./data/osm/.
- docker compose up -d.
- Tester OSRM, VROOM, GraphHopper avec les cURL ci‑dessus.
- Appeler route_plan & optimize_stops via MCP (agent IA ou Postman).
- Brancher un bouton de test dans Odoo (devis/livraison) qui appelle route_plan.