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.