Skip to main content
Glama

MCP Google Maps - stdio Edition

DOCUMENTACION_MCP.md19.6 kB
# 📚 Documentación del Protocolo MCP (Model Context Protocol) ## Índice 1. [¿Qué es MCP?](#qué-es-mcp) 2. [Arquitectura del Protocolo](#arquitectura-del-protocolo) 3. [Flujo de Comunicación](#flujo-de-comunicación) 4. [Formato de Mensajes](#formato-de-mensajes) 5. [Métodos del Protocolo](#métodos-del-protocolo) 6. [Ejemplo Práctico con Cliente](#ejemplo-práctico-con-cliente) 7. [Implementación de Herramientas](#implementación-de-herramientas) 8. [Manejo de Errores](#manejo-de-errores) --- ## ¿Qué es MCP? **Model Context Protocol (MCP)** es un protocolo estandarizado creado por Anthropic para permitir que modelos de lenguaje (LLMs) se conecten y utilicen herramientas externas de forma uniforme y segura. ### Características principales: - 🔌 **Protocolo abierto**: Basado en JSON-RPC 2.0 - 🔄 **Bidireccional**: Cliente y servidor pueden enviarse mensajes - 🛠️ **Extensible**: Permite crear herramientas personalizadas - 🔒 **Seguro**: Manejo de sesiones y autenticación - 📡 **Múltiples transportes**: HTTP/SSE, stdio, WebSocket --- ## Arquitectura del Protocolo ``` ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ Cliente MCP │◄──────────────────►│ Servidor MCP │ │ (LLM/App) │ JSON-RPC 2.0 │ (Herramientas) │ │ │ │ │ └─────────────────┘ └─────────────────┘ │ │ │ │ ┌────▼────┐ ┌─────▼──────┐ │ Session │ │ Tools │ │ Manager │ │ (geocode, │ └─────────┘ │ search, │ │ etc.) │ └────────────┘ ``` ### Componentes: 1. **Cliente MCP**: - Aplicación que quiere usar herramientas - Puede ser Claude Desktop, tu app, etc. - Inicia y mantiene la sesión 2. **Servidor MCP**: - Expone herramientas a través del protocolo - Maneja la lógica de negocio - Valida parámetros y ejecuta acciones 3. **Transporte**: - HTTP con Server-Sent Events (SSE) - stdio (entrada/salida estándar) - WebSocket --- ## Flujo de Comunicación ### 1. Inicialización de Sesión ``` Cliente Servidor │ │ │──────── initialize request ──────────►│ │ (capabilities, version) │ │ │ │◄──────── initialize response ─────────│ │ (server info, sessionId) │ │ │ │──────── initialized notification ────►│ │ │ ``` ### 2. Uso de Herramientas ``` Cliente Servidor │ │ │──────── tools/list request ──────────►│ │ │ │◄──────── tools/list response ─────────│ │ (available tools) │ │ │ │──────── tools/call request ──────────►│ │ (tool name + params) │ │ │ │◄──────── tools/call response ─────────│ │ (tool result) │ │ │ ``` ### 3. Cierre de Sesión ``` Cliente Servidor │ │ │──────── DELETE /mcp ─────────────────►│ │ (with session-id) │ │ │ │◄──────── 200 OK ──────────────────────│ │ │ ``` --- ## Formato de Mensajes Todos los mensajes siguen el estándar **JSON-RPC 2.0**: ### Request (Solicitud) ```json { "jsonrpc": "2.0", "method": "nombre_del_metodo", "params": { "param1": "valor1", "param2": "valor2" }, "id": 1 } ``` ### Response (Respuesta Exitosa) ```json { "jsonrpc": "2.0", "result": { "data": "resultado" }, "id": 1 } ``` ### Error Response (Respuesta con Error) ```json { "jsonrpc": "2.0", "error": { "code": -32602, "message": "Invalid params" }, "id": 1 } ``` ### Notification (Notificación - sin ID) ```json { "jsonrpc": "2.0", "method": "notifications/initialized", "params": {} } ``` --- ## Métodos del Protocolo ### Métodos de Inicialización #### 1. `initialize` **Propósito**: Iniciar una nueva sesión MCP **Request**: ```json { "jsonrpc": "2.0", "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": { "roots": { "listChanged": true }, "sampling": {} }, "clientInfo": { "name": "mi-cliente-mcp", "version": "1.0.0" } }, "id": 1 } ``` **Response**: ```json { "jsonrpc": "2.0", "result": { "protocolVersion": "2024-11-05", "capabilities": { "logging": {}, "tools": { "listChanged": true } }, "serverInfo": { "name": "MCP-Server", "version": "0.0.1" } }, "id": 1 } ``` **Headers importantes**: - `Content-Type: application/json` - `Accept: application/json, text/event-stream` - `mcp-session-id`: (recibido en response header) #### 2. `notifications/initialized` **Propósito**: Notificar al servidor que el cliente está listo **Request**: ```json { "jsonrpc": "2.0", "method": "notifications/initialized", "params": {} } ``` **No tiene response** (es una notificación) --- ### Métodos de Herramientas #### 3. `tools/list` **Propósito**: Obtener lista de herramientas disponibles **Request**: ```json { "jsonrpc": "2.0", "method": "tools/list", "params": {}, "id": 2 } ``` **Response**: ```json { "jsonrpc": "2.0", "result": { "tools": [ { "name": "search_nearby", "description": "Search for nearby places", "inputSchema": { "type": "object", "properties": { "center": { "type": "object", "description": "Search center point" }, "keyword": { "type": "string", "description": "Search keyword" } }, "required": ["center"] } } ] }, "id": 2 } ``` #### 4. `tools/call` **Propósito**: Ejecutar una herramienta específica **Request**: ```json { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "maps_geocode", "arguments": { "address": "Malecón 2000, Guayaquil, Ecuador" } }, "id": 3 } ``` **Response**: ```json { "jsonrpc": "2.0", "result": { "content": [ { "type": "text", "text": "{\"lat\": -2.1894128, \"lng\": -79.8844071}" } ] }, "id": 3 } ``` --- ### Métodos de Logging (Opcional) #### 5. `logging/setLevel` **Propósito**: Configurar nivel de logging **Request**: ```json { "jsonrpc": "2.0", "method": "logging/setLevel", "params": { "level": "debug" }, "id": 4 } ``` --- ## Ejemplo Práctico con Cliente ### Código Completo de Cliente JavaScript ```javascript import 'dotenv/config'; const API_KEY = process.env.GOOGLE_MAPS_API_KEY; const MCP_URL = "http://localhost:3000/mcp"; class MCPClient { constructor(serverUrl, apiKey) { this.serverUrl = serverUrl; this.apiKey = apiKey; this.sessionId = null; this.requestId = 0; } // Obtener siguiente ID de request getNextId() { return ++this.requestId; } // Hacer request al servidor async request(method, params = {}) { const payload = { jsonrpc: "2.0", method: method, params: params, id: this.getNextId() }; const headers = { "Content-Type": "application/json", "Accept": "application/json, text/event-stream", "X-Google-Maps-API-Key": this.apiKey }; // Agregar session ID si ya existe if (this.sessionId) { headers["mcp-session-id"] = this.sessionId; } const response = await fetch(this.serverUrl, { method: "POST", headers: headers, body: JSON.stringify(payload) }); // Capturar session ID en primera conexión if (!this.sessionId) { this.sessionId = response.headers.get("mcp-session-id"); } // Parsear respuesta SSE const text = await response.text(); return this.parseSSE(text); } // Parsear formato SSE parseSSE(text) { const lines = text.split('\n'); const events = []; let currentEvent = {}; for (const line of lines) { if (line.startsWith('event:')) { currentEvent.event = line.slice(6).trim(); } else if (line.startsWith('data:')) { try { currentEvent.data = JSON.parse(line.slice(5).trim()); } catch (e) { currentEvent.data = line.slice(5).trim(); } } else if (line === '' && currentEvent.event) { events.push(currentEvent); currentEvent = {}; } } // Retornar el mensaje principal const messageEvent = events.find(e => e.event === 'message'); return messageEvent ? messageEvent.data : null; } // Inicializar conexión MCP async initialize() { const response = await this.request("initialize", { protocolVersion: "2024-11-05", capabilities: { roots: { listChanged: true }, sampling: {} }, clientInfo: { name: "ejemplo-cliente-mcp", version: "1.0.0" } }); console.log("✅ Conexión inicializada"); console.log(" Session ID:", this.sessionId); console.log(" Server:", response.result.serverInfo.name); // Enviar notificación de inicialización await this.notify("notifications/initialized", {}); return response; } // Enviar notificación (sin esperar respuesta) async notify(method, params = {}) { const payload = { jsonrpc: "2.0", method: method, params: params }; await fetch(this.serverUrl, { method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/json, text/event-stream", "mcp-session-id": this.sessionId, "X-Google-Maps-API-Key": this.apiKey }, body: JSON.stringify(payload) }); } // Listar herramientas disponibles async listTools() { const response = await this.request("tools/list"); return response.result.tools; } // Llamar una herramienta async callTool(toolName, args) { const response = await this.request("tools/call", { name: toolName, arguments: args }); if (response.error) { throw new Error(`Tool error: ${response.error.message}`); } return response.result; } // Cerrar sesión async close() { if (!this.sessionId) return; await fetch(this.serverUrl, { method: "DELETE", headers: { "mcp-session-id": this.sessionId } }); console.log("✅ Sesión cerrada"); } } // Uso del cliente async function main() { const client = new MCPClient(MCP_URL, API_KEY); try { // 1. Inicializar await client.initialize(); // 2. Listar herramientas console.log("\n📋 Herramientas disponibles:"); const tools = await client.listTools(); tools.forEach(tool => { console.log(` - ${tool.name}: ${tool.description}`); }); // 3. Usar una herramienta console.log("\n🔍 Geocodificando dirección..."); const result = await client.callTool("maps_geocode", { address: "Malecón 2000, Guayaquil, Ecuador" }); console.log(" Resultado:", result.content[0].text); // 4. Cerrar sesión await client.close(); } catch (error) { console.error("❌ Error:", error.message); } } main(); ``` --- ## Implementación de Herramientas ### Estructura de una Herramienta Cada herramienta debe definir: 1. **Nombre**: Identificador único 2. **Descripción**: Qué hace la herramienta 3. **Schema**: Validación de parámetros (usando Zod) 4. **Action**: Función que ejecuta la herramienta ### Ejemplo: Herramienta de Geocodificación ```typescript import { z } from "zod"; const NAME = "maps_geocode"; const DESCRIPTION = "Convert addresses to coordinates"; const SCHEMA = { address: z.string().describe("Address to geocode"), region: z.string().optional().describe("Region bias") }; type GeocodeParams = z.infer<z.ZodObject<typeof SCHEMA>>; async function ACTION(params: GeocodeParams) { try { // Lógica de la herramienta const apiKey = getCurrentApiKey(); const client = new Client({ key: apiKey }); const response = await client.geocode({ params: { address: params.address } }); return { content: [{ type: "text", text: JSON.stringify(response.data.results[0].geometry.location) }], isError: false }; } catch (error) { return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } } export const Geocode = { NAME, DESCRIPTION, SCHEMA, ACTION }; ``` ### Registro de Herramientas ```typescript const tools: ToolConfig[] = [ { name: Geocode.NAME, description: Geocode.DESCRIPTION, schema: Geocode.SCHEMA, action: (params) => Geocode.ACTION(params) }, // ... más herramientas ]; const server = new BaseMcpServer("MCP-Server", tools); ``` --- ## Manejo de Errores ### Códigos de Error Estándar (JSON-RPC 2.0) | Código | Nombre | Descripción | |--------|--------|-------------| | -32700 | Parse error | JSON inválido | | -32600 | Invalid Request | Request no válido | | -32601 | Method not found | Método no existe | | -32602 | Invalid params | Parámetros inválidos | | -32603 | Internal error | Error interno del servidor | | -32000 | Server error | Error personalizado del servidor | ### Ejemplo de Error ```json { "jsonrpc": "2.0", "error": { "code": -32602, "message": "Invalid arguments for tool search_nearby", "data": { "validation_errors": [ { "code": "invalid_type", "expected": "object", "received": "undefined", "path": ["center"], "message": "Required" } ] } }, "id": 3 } ``` ### Manejo en Cliente ```javascript async callTool(toolName, args) { const response = await this.request("tools/call", { name: toolName, arguments: args }); if (response.error) { console.error("❌ Error de herramienta:"); console.error(" Código:", response.error.code); console.error(" Mensaje:", response.error.message); if (response.error.data) { console.error(" Detalles:", response.error.data); } throw new Error(response.error.message); } return response.result; } ``` --- ## Transporte HTTP + SSE ### ¿Por qué SSE (Server-Sent Events)? MCP usa SSE para permitir: - **Respuestas en tiempo real**: El servidor puede enviar múltiples mensajes - **Notificaciones del servidor**: Logging, progreso, etc. - **Streaming de datos**: Para respuestas grandes ### Formato SSE ``` event: message data: {"jsonrpc":"2.0","result":{...},"id":1} event: notification data: {"jsonrpc":"2.0","method":"notification/progress","params":{...}} ``` ### Headers Requeridos ```javascript headers: { "Content-Type": "application/json", "Accept": "application/json, text/event-stream", // ¡Importante! "mcp-session-id": "uuid-de-sesion" } ``` --- ## Buenas Prácticas ### 1. Manejo de Sesiones ```javascript // ✅ Bueno: Reusar sesión const client = new MCPClient(url, apiKey); await client.initialize(); await client.callTool("tool1", {}); await client.callTool("tool2", {}); await client.close(); // ❌ Malo: Crear nueva sesión para cada llamada for (const tool of tools) { const client = new MCPClient(url, apiKey); await client.initialize(); await client.callTool(tool, {}); await client.close(); } ``` ### 2. Validación de Parámetros ```javascript // ✅ Bueno: Validar antes de enviar const args = { center: { value: "Guayaquil, Ecuador", isCoordinates: false }, radius: 1000 }; // Validar que center existe if (!args.center || !args.center.value) { throw new Error("center es requerido"); } await client.callTool("search_nearby", args); ``` ### 3. Manejo de Errores ```javascript try { const result = await client.callTool("maps_geocode", { address: "dirección inválida" }); // Verificar si hubo error en el resultado if (result.isError) { console.error("La herramienta reportó un error"); } } catch (error) { // Error de protocolo o red console.error("Error de comunicación:", error); } ``` ### 4. Timeout ```javascript async requestWithTimeout(method, params, timeoutMs = 30000) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error("Request timeout")), timeoutMs); }); return Promise.race([ this.request(method, params), timeoutPromise ]); } ``` --- ## Depuración ### 1. Logs del Cliente ```javascript class MCPClient { constructor(serverUrl, apiKey, debug = false) { this.debug = debug; // ... } log(...args) { if (this.debug) { console.log("[MCP Client]", ...args); } } async request(method, params) { this.log("→ Request:", method, params); const response = await fetch(/* ... */); this.log("← Response:", response); return response; } } // Uso const client = new MCPClient(url, apiKey, true); // debug = true ``` ### 2. Inspeccionar Tráfico ```bash # Usar curl para probar manualmente curl -X POST http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "X-Google-Maps-API-Key: YOUR_KEY" \ -d '{ "jsonrpc": "2.0", "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0.0"} }, "id": 1 }' ``` --- ## Recursos Adicionales - **Especificación MCP**: https://spec.modelcontextprotocol.io/ - **SDK Oficial**: https://github.com/modelcontextprotocol/typescript-sdk - **Ejemplos**: https://github.com/modelcontextprotocol/servers --- ## Siguientes Pasos 1. ✅ Entender el protocolo básico (este documento) 2. ⏭️ Integración con Claude Desktop 3. ⏭️ Crear herramientas personalizadas 4. ⏭️ Implementar autenticación avanzada 5. ⏭️ Optimización y escalabilidad --- *Documentación generada para el proyecto mcp-google-map*

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/vicente-alvarado/mcp-google-map-stdio'

If you have feedback or need assistance with the MCP directory API, please join our Discord server