Skip to content

feat: servidor MCP local y remoto con 13 tools para operar TravelMap con LLM#75

Draft
tucho235 wants to merge 19 commits intofabiomb:mainfrom
tucho235:feature/mcp
Draft

feat: servidor MCP local y remoto con 13 tools para operar TravelMap con LLM#75
tucho235 wants to merge 19 commits intofabiomb:mainfrom
tucho235:feature/mcp

Conversation

@tucho235
Copy link
Copy Markdown
Collaborator

Resumen

Implementación completa de un servidor MCP (Model Context Protocol) que permite operar TravelMap en lenguaje natural desde Claude Desktop, Claude Code, Cursor u otros clientes LLM compatibles.

Cambios principales

Servidor MCP (mcp/)

Infraestructura nueva desde cero con dos transportes:

Transporte Archivo Uso
stdio mcp/server.php proceso local (Claude Code / Claude Desktop)
HTTP mcp/http.php cliente remoto vía URL + Bearer API Key

*El stdio local me dio muchos problemas para que funcione correctamente, muchas veces intentó utilizar los modelos para hacer las operaciones, recomiendo el http.

Componentes internos: Dispatcher.php (registro y routing de tools), JsonRpc.php (framing NDJSON), Schema.php (validador JSON Schema), Logger.php (logs en logs/mcp.log), bootstrap.php (carga compartida de config, DB, helpers y modelos).

13 Tools en 4 módulos

ViajesTripTools.php

  • list_trips — lista con filtros de estado, orden y límite
  • search_trips — búsqueda por texto libre, tag o rango de fechas
  • get_trip — viaje completo con rutas, POIs, tags y links
  • create_trip — crea un viaje nuevo
  • update_trip — actualiza campos, tags y links (reemplazo completo)

RutasRouteTools.php

  • plan_route — calcula ruta terrestre via BRouter; guarda GeoJSON temporal
  • commit_route — persiste en BD la ruta calculada por plan_route
  • create_route — crea ruta desde GeoJSON o CSV BRouter externo
  • update_route — actualiza metadatos (sin cambiar geometría)

POIsPoiTools.php

  • search_pois — busca por texto, viaje o tipo
  • create_poi — crea POI; auto-rellena coords y fecha desde EXIF si se adjunta foto
  • update_poi — actualiza datos de un POI

LocalizaciónLocationTools.php

  • search_location — geocodifica un lugar por nombre via Nominatim (OSM)

Nuevos helpers extraídos

  • BRouterParser.php — parseo de CSV y GeoJSON de BRouter
  • BRouterClient.php — cliente HTTP para calcular rutas contra la API BRouter
  • ExifExtractor.php — extracción de coordenadas y fecha desde EXIF de fotos
  • Geocoder.php — geocodificación inversa y directa via Nominatim

admin/import_brouter.php fue simplificado al delegar en BRouterParser.

Autenticación por API Key para acceso remoto

  • Migración 024_mcp_api_key.php: añade columna mcp_api_key a users

  • api/mcp_apikey.php: endpoint para generar y revocar la clave

  • admin/user_form.php: nueva sección "Acceso MCP" en el formulario de usuario

  • includes/auth.php: validación de Bearer token tmk_<64hex>

  • src/models/User.php: método findByMcpApiKey()

    El transporte HTTP acepta también la cookie de sesión web como fallback.

Configuración y documentación

  • .mcp.json — configuración lista para Claude Code (stdio local)
  • docs/MCP.md — documentación completa de instalación y uso
  • docs/RFC_MCP.md — diseño y decisiones de arquitectura
  • mcp/README.md — guía de configuración rápida
  • mcp/tests/run_tests.sh — suite de tests de integración con fixtures reales

Cómo probar

# Tests de integración del servidor MCP
bash mcp/tests/run_tests.sh

# Acceso remoto: generar API Key desde Admin → Usuarios → editar

Migraciones necesarias

php install/migrations/024_mcp_api_key.php

Notas

- El servidor stdio no requiere autenticación (es un proceso local, de todas maneras me dio muchos problemas hacerlo funcionar local, recomiendo el remoto)
- El servidor HTTP protege todos los métodos; sin token válido devuelve 401
- plan_route + commit_route es un flujo en dos pasos deliberado: permite al LLM mostrar la ruta al usuario antes de persistirla

tucho235 and others added 12 commits April 15, 2026 01:16
- Nuevo servidor MCP en mcp/ (PHP, sin Composer, JSON-RPC 2.0 NDJSON)
- 12 tools: list/search/get/create trips, list/create routes (BRouter CSV),
  list/search/create POIs (auto-fill EXIF GPS+fecha, suggested_place Nominatim),
  import_photos_batch (hasta 50 fotos con interpolación GPS), get_stats
- Extracción de helpers reutilizables: BRouterParser, ExifExtractor, Geocoder
- FileHelper: nuevo saveImageFromBase64 con validación MIME+GD probe+realpath
- Trip::search y Point::search con LIKE escapado y filtros opcionales
- admin/import_brouter.php refactorizado para delegar en BRouterParser

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tools de edición

- bootstrap.php: cargar Link.php en lugar del eliminado PoiLink.php
- PoiTools: corregir new PoiLink() → new Link() en create_poi
- create_route: soporta ahora el campo links (igual que create_poi)
- Nuevas tools: update_trip, update_route, update_poi — patch parcial
  de cualquier campo excepto geometría/foto; links/tags se reemplazan
  completos cuando se incluyen en la llamada

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…API key auth y LocationTools

- Reemplaza StatsTools por LocationTools (search_location via Nominatim)
- Agrega transporte HTTP (mcp/http.php) además del stdio existente
- Implementa autenticación por API key: migración DB, modelo User, admin UI y endpoint api/mcp_apikey.php
- Agrega BRouterClient helper para cálculo de rutas via HTTP
- Refactoriza TripTools, RouteTools y PoiTools (lógica simplificada, nuevos params)
- Agrega .mcp.json para integración con Claude Code
- Documenta el servidor en docs/MCP.md y docs/RFC_MCP.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Faltaba el require_once y el register de LocationTools en http.php;
solo estaba en server.php (stdio), por eso el cliente remoto no veía search_location.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@GermanXander
Copy link
Copy Markdown
Contributor

ah bueno... On fire Mr. Tucho. 👏
Si tengo tiempo lo pruebo el finde

@fabiomb
Copy link
Copy Markdown
Owner

fabiomb commented Apr 25, 2026

Tucho, no lo pude testear todavía para el merge porque estuve enfermo estos días, cero programación, pero a partir de mañana espero estar mejor y ya sin fiebre 🥵

@fabiomb fabiomb linked an issue Apr 25, 2026 that may be closed by this pull request
@tucho235
Copy link
Copy Markdown
Collaborator Author

Tucho, no lo pude testear todavía para el merge porque estuve enfermo estos días, cero programación, pero a partir de mañana espero estar mejor y ya sin fiebre 🥵

See no problem, no hay apuro. Capaz alguien tiene alguna nueva idea, sugerencia, o corrección.

De hecho hoy mandé 1 push mas con 1 fix para que las rutas de vuelos no intente usar BRouter, sino que arme solamente con los 2 puntos de los aeropuertos.

…compara contra $tempDir . '/'

  (slash forward). La verificación siempre falla en Windows.
@fabiomb
Copy link
Copy Markdown
Owner

fabiomb commented May 2, 2026

le pude dar un poco de cariño y testear @tucho235
hice que Claude me planificara un viaje, medio falopa, encontró un bug de directorios en Windows que ya comiteé
Cosa que podríamos mejorar:
En RouteTools.php pusiste todos los prompts base en español, se que es una boludez, pero si los ponés en inglés consume menos tokens.

@fabiomb
Copy link
Copy Markdown
Owner

fabiomb commented May 2, 2026

Otro tema, la url que genera es medio falopa
ej:
http://192.168.1.1:32080/mcp/http.php

¿Puerto 32080? Eso dónde lo configura? mi webserver entrega sólo en el puerto 80, y esto lo maneja Apache, así que la url que genera es cualquiera

@tucho235
Copy link
Copy Markdown
Collaborator Author

tucho235 commented May 7, 2026

Nunca te hice ACK de esto. Sí, tengo que fixear estas cosas, cuando tenga un rato le dedico.

tucho235 and others added 2 commits May 8, 2026 23:12
Reemplaza la URL hardcodeada (192.168.3.195:32080) por BASE_URL configurado
en cada instalación, para que los snippets sean correctos en cualquier entorno.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tucho235
Copy link
Copy Markdown
Collaborator Author

tucho235 commented May 9, 2026

voy con el fix de la url 👍

Tool descriptions, parameter descriptions, error messages, hints and
comments were all in Spanish — now fully in English to reduce LLM token cost.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tucho235
Copy link
Copy Markdown
Collaborator Author

tucho235 commented May 9, 2026

los prompt 👍

tucho235 and others added 2 commits May 8, 2026 23:38
Tool descriptions, parameter descriptions, error messages, hints and
comments translated from Spanish to reduce LLM token cost.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@fabiomb
Copy link
Copy Markdown
Owner

fabiomb commented May 9, 2026

mañana reviso una vez más todo y le doy merge suicida

@fabiomb fabiomb requested a review from Copilot May 9, 2026 04:51
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Este PR incorpora un servidor MCP (Model Context Protocol) para operar TravelMap con LLMs mediante 2 transportes (stdio y HTTP), sumando 13 tools para gestionar viajes, rutas, POIs y geocodificación, además de soporte de API Key por usuario para acceso remoto.

Changes:

  • Nuevo servidor MCP en mcp/ (dispatcher, JSON-RPC NDJSON, validador de schema, logging y endpoints stdio/HTTP) + 13 tools.
  • Nuevos helpers reutilizables (BRouter, EXIF, Geocoder) y extensiones a modelos para soportar búsquedas y MCP API keys.
  • UI + API para generar API key y migración/DDL para persistirla.

Reviewed changes

Copilot reviewed 33 out of 37 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
src/models/User.php Agrega getters/setters y lookup por mcp_api_key + generación de API key.
src/models/Trip.php Incluye show_routes_in_timeline en listados y agrega Trip::search() para búsquedas.
src/models/Point.php Agrega Point::search() y amplía formatos aceptados para visit_date.
src/helpers/Geocoder.php Nuevo helper para geocodificación directa/inversa con cache y rate limit.
src/helpers/FileHelper.php Refactoriza post-proceso de imágenes y agrega guardado desde base64 con validaciones.
src/helpers/ExifExtractor.php Nuevo helper para leer EXIF GPS/fecha e interpolar GPS faltante.
src/helpers/BRouterParser.php Nuevo helper para parsear CSV de BRouter y emitir GeoJSON + métricas.
src/helpers/BRouterClient.php Nuevo cliente HTTP para calcular rutas vía API de BRouter y normalizar respuesta.
mcp/tools/TripTools.php Tools MCP para listar/buscar/obtener/crear/actualizar viajes.
mcp/tools/RouteTools.php Tools MCP para planificar/commit/crear/actualizar rutas (incl. BRouter y temporales).
mcp/tools/PoiTools.php Tools MCP para buscar/crear/actualizar POIs (incl. foto base64 + EXIF + geocoder).
mcp/tools/LocationTools.php Tool MCP para búsqueda de coordenadas por nombre (Nominatim).
mcp/tests/run_tests.sh Smoke tests de integración del servidor MCP + casos básicos de seguridad.
mcp/tests/fixtures/tiny.jpg.b64 Fixture base64 para test de subida de imagen.
mcp/tests/fixtures/sample.brouter.csv Fixture CSV para test de importación BRouter.
mcp/server.php Entry point MCP stdio (NDJSON) con routing JSON-RPC y manejo de errores.
mcp/Schema.php Validador minimal de JSON Schema para inputs de tools.
mcp/README.md Guía rápida de configuración y lista de tools.
mcp/Logger.php Logger MCP con sanitización de payloads grandes/binarios.
mcp/JsonRpc.php Framing NDJSON y helpers JSON-RPC para stdio.
mcp/http.php Endpoint MCP HTTP con auth Bearer/cookie y routing JSON-RPC.
mcp/Dispatcher.php Registro/routing de tools + validación de schema y empaquetado de respuestas.
mcp/bootstrap.php Bootstrap compartido de config/DB/helpers/modelos para servidor MCP.
mcp/.htaccess Restricciones de acceso web dentro de mcp/.
install/migrations/024_mcp_api_key.php Migración para agregar users.mcp_api_key + índice único.
includes/auth.php Agrega utilidades CSRF (csrf_token, csrf_verify).
docs/RFC_MCP.md Documento de arquitectura/decisiones para el servidor MCP.
docs/MCP.md Documentación de instalación/uso del MCP server y tools.
database.sql Actualiza schema base para incluir mcp_api_key e índice único.
backups/.htaccess Bloquea acceso web directo a backups.
backups/.gitkeep Mantiene el directorio backups/ en git.
backups/.gitignore Ignora archivos zip en backups.
assets/js/admin_mcp_apikey.js UI para ver/copiar/generar API key desde el admin con CSRF.
api/mcp_apikey.php API para leer/generar API key asociada a la sesión del usuario.
admin/user_form.php Añade sección “Acceso MCP” en el formulario de usuario + scripts de UI.
admin/import_brouter.php Simplifica import delegando parseo a BRouterParser.
.gitignore Ajustes de ignore (incl. logs/, .mcp.json, .claude/, etc.).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mcp/Schema.php
Comment on lines +33 to +36
if ($passing === 0) {
$errors[$path ?: 'root'] = 'Debe cumplir exactamente una de las condiciones oneOf';
}
// No retornamos aquí: seguimos validando el resto del schema
Comment thread mcp/Schema.php
}
break;
case 'integer':
if (!is_int($data) && !(is_numeric($data) && floor((float)$data) == (float)$data)) {
Comment thread mcp/http.php
Comment on lines +83 to +90
// Capa B: sesión web (cookie PHPSESSID)
if (!$authOk && !empty($_COOKIE['PHPSESSID'])) {
session_name('PHPSESSID');
session_start();
if (!empty($_SESSION['logged_in']) && !empty($_SESSION['user_id'])) {
$authOk = true;
}
}
Comment thread mcp/.htaccess
Comment on lines +3 to +5
<FilesMatch "^(?!http\.php$).*\.php$">
Require all denied
</FilesMatch>
Comment thread mcp/tools/RouteTools.php
Comment on lines +357 to +361
$tempDir = ROOT_PATH . '/uploads/mcp_temp';
if (!is_dir($tempDir)) {
mkdir($tempDir, 0750, true);
}
$tempFilename = 'route_' . uniqid('', true) . '.geojson';
Comment on lines +612 to +615
// 7. MIME check vía finfo
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeReal = finfo_file($finfo, $tmpFile);
finfo_close($finfo);
if ($body === false) {
return [
'success' => false,
'error' => 'No se pudo conectar con BRouter (' . self::DEFAULT_API_URL . '). Verificá la conexión a internet.',
Comment thread api/mcp_apikey.php
Comment on lines +5 to +8
* GET → devuelve la API key actual del usuario (null si no tiene).
* POST → genera y guarda una nueva API key. Requiere token CSRF en el
* header X-CSRF-Token o en el body JSON { "csrf_token": "..." }.
* Solo accesible para usuarios con sesión activa (admin).
Comment thread mcp/Schema.php
Comment on lines +188 to +192
private static function isIndexed(array $arr): bool
{
if (empty($arr)) return true;
return array_keys($arr) === range(0, count($arr) - 1);
}
Comment on lines +159 to +165
foreach ($images as $idx => &$img) {
if ($img['has_gps']) {
$withGps[$idx] = $img;
} elseif ($img['has_date'] && $img['timestamp'] !== null) {
$needsEstimate[$idx] = $img;
}
}
@fabiomb
Copy link
Copy Markdown
Owner

fabiomb commented May 9, 2026

bueno, el review de Copilot generó como 30 cositas, pequeñas, pero así de perfeccionista es 😁 no me mates

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP para TravelMap

4 participants