advanced45 minLección 4 de 5

Servidores MCP, Plugins e Integración de Herramientas Externas

Explora la configuración de servidores MCP, integración de API externa, arquitectura de plugins, ámbitos de permisos de herramientas e implementaciones MCP personalizadas.

Servidores MCP, Plugins e Integración de Herramientas Externas

¿Qué es MCP?

El Model Context Protocol (MCP) es un estándar abierto que define cómo las aplicaciones LLM se comunican con herramientas externas y fuentes de datos. Utiliza JSON-RPC 2.0 como su protocolo de transporte.

ℹ️Note

MCP fue diseñado específicamente para patrones de interacción LLM-herramienta. A diferencia de las APIs REST que están diseñadas para humanos y operaciones CRUD, MCP utiliza un protocolo JSON-RPC bidireccional que soporta descubrimiento de herramientas, acceso a recursos y plantillas de prompt — primitivas que los LLMs entienden naturalmente.

100%

Intercambio de Protocolo JSON-RPC MCP

Cada interacción entre OpenCode y un servidor MCP sigue una conversación JSON-RPC 2.0 estructurada. Comprender este protocolo es esencial para depurar y construir servidores MCP personalizados.

100%
💡Tip

Al depurar problemas MCP, activa el registro verbose para ver los mensajes JSON-RPC brutos. Esto es invaluable para identificar esquemas de herramientas malformados, formatos de respuesta incorrectos o fallos de autenticación.

Ciclo de Vida del Servidor MCP

100%

Configuración del Servidor MCP

Los servidores MCP se configuran en la sección mcpServers de opencode.json:

json
{ "mcpServers": { "filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/home/usuario/proyectos" ], "env": { "NODE_ENV": "production" } }, "github": { "command": "node", "args": ["mcp-github-server.js"], "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" } }, "database": { "command": "python", "args": ["mcp-db-server.py"], "env": { "DATABASE_URL": "${DATABASE_URL}" } } } }
⚠️Warning

Los servidores MCP tienen acceso completo a las variables de entorno con las que se configuran. Nunca endurezcas secretos en opencode.json — siempre usa interpolación de variables de entorno (${VAR_NAME}). El bloque env se pasa directamente al proceso iniciado, y cualquier herramienta que se ejecute en ese proceso puede leer estos valores.


Conectando APIs Externas via MCP

Los servidores MCP envuelven APIs externas en interfaces de herramientas que los LLMs pueden llamar:

json
{ "mcpServers": { "slack": { "command": "python", "args": ["mcp-slack-server.py"], "env": { "SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}", "SLACK_SIGNING_SECRET": "${SLACK_SIGNING_SECRET}" } } } }
python
# mcp-slack-server.py # Servidor MCP que envuelve la API de Slack en herramientas invocables import os import httpx from mcp import Server server = Server("slack") @server.tool() async def send_message(channel: str, text: str) -> str: """Envía un mensaje a un canal de Slack""" async with httpx.AsyncClient() as client: resp = await client.post( f"https://slack.com/api/chat.postMessage", headers={ "Authorization": f"Bearer {os.environ['SLACK_BOT_TOKEN']}", "Content-Type": "application/json" }, json={"channel": channel, "text": text} ) data = resp.json() if not data.get("ok"): raise Exception(f"Error API de Slack: {data.get('error')}") return data["message"]["text"] @server.tool() async def list_channels(limit: int = 20) -> list: """Lista canales públicos en el workspace""" async with httpx.AsyncClient() as client: resp = await client.get( "https://slack.com/api/conversations.list", headers={"Authorization": f"Bearer {os.environ['SLACK_BOT_TOKEN']}"}, params={"limit": limit} ) return resp.json()["channels"] server.run()

Escribiendo Implementaciones de Servidor MCP

Un servidor MCP expone tres primitivas:

  • Tools: Funciones invocables que el LLM puede llamar
  • Resources: Datos de solo lectura que el LLM puede acceder
  • Prompts: Plantillas de prompt pre-escritas

Servidor MCP en TypeScript

typescript
// mcp-weather-server.ts import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; const server = new Server( { name: "weather-server", version: "1.0.0" }, { capabilities: { tools: {}, resources: {} } } ); // Define una herramienta con validación de entrada JSON Schema server.setRequestHandler("tools/list", async () => ({ tools: [{ name: "get_forecast", description: "Obtener pronóstico del tiempo para una ubicación", inputSchema: { type: "object", properties: { location: { type: "string", description: "Nombre de la ciudad o coordenadas" }, days: { type: "number", description: "Número de días de pronóstico", default: 3 } }, required: ["location"] } }] })); // Maneja la ejecución de herramientas con manejo de errores server.setRequestHandler("tools/call", async (request) => { const { name, arguments: args } = request.params; if (name === "get_forecast") { try { const data = await fetchWeather(args.location, args.days); return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Error al obtener pronóstico: ${error.message}` }], isError: true }; } } throw new Error(`Herramienta desconocida: ${name}`); }); const transport = new StdioServerTransport(); await server.connect(transport);

Servidor MCP en Python

python
# mcp-weather-server.py # Servidor MCP equivalente en Python import json import httpx from mcp import Server, StdioServerTransport server = Server("weather-server") @server.list_tools() async def list_tools(): return [ { "name": "get_forecast", "description": "Obtener pronóstico del tiempo para una ubicación", "inputSchema": { "type": "object", "properties": { "location": {"type": "string", "description": "Nombre de la ciudad"}, "days": {"type": "number", "description": "Días de pronóstico", "default": 3} }, "required": ["location"] } } ] @server.call_tool() async def call_tool(name: str, arguments: dict): if name == "get_forecast": async with httpx.AsyncClient() as client: resp = await client.get( f"https://api.weather.gov/points/{arguments['location']}/forecast" ) data = resp.json() return {"content": [{"type": "text", "text": json.dumps(data, indent=2)}]} async def main(): transport = StdioServerTransport() await server.connect(transport) if __name__ == "__main__": import asyncio asyncio.run(main())
📌Important

Los esquemas de herramientas definen el contrato entre el LLM y tu servidor. Siempre incluye campos description claros para cada parámetro — el LLM usa estas descripciones para determinar cómo completar los argumentos. Un parámetro mal descrito resultará en que el LLM pase valores incorrectos.


Arquitectura de Plugin

Los servidores MCP sirven como el sistema de plugins para OpenCode. Cualquier capacidad externa puede ser envuelta como un servidor MCP.

Comparación: Mecanismos de Transporte

Aspectostdio (subproceso)HTTP/SSE (remoto)
ProcesoIniciado por OpenCodeSe ejecuta independientemente
LatenciaBaja (IPC local)Más alta (I/O de red)
SeguridadAislamiento de proceso, localRequiere autenticación de red, TLS
DespliegueEmpaquetado con el proyectoServicio o contenedor en ejecución
Ciclo de vidaVinculado a la sesión OpenCodeDaemon independiente
Caso de usoHerramientas locales (fs, git)APIs remotas (Slack, GitHub, DB)
DepuraciónVerificar logs del servidorVerificar endpoints + red
EscalabilidadUno por sesiónMúltiples clientes
ComponenteRol
OpenCodeCliente MCP — inicia solicitudes
Servidor MCPPlugin — procesa solicitudes y devuelve
Transportestdin/stdout o HTTP/SSE
ProtocoloJSON-RPC 2.0
💡Tip

Usa transporte stdio para herramientas de desarrollo local que necesitan baja latencia (acceso a sistema de archivos, análisis de código). Usa HTTP/SSE para servicios compartidos que múltiples miembros del equipo necesitan acceder (bases de datos compartidas, APIs de equipo). Los servidores HTTP pueden desplegarse en contenedores Docker para entornos consistentes.


Ámbitos de Permiso de Herramientas

Cada herramienta MCP puede tener ámbitos de permiso definidos en la configuración:

json
{ "permissions": [ { "mcpServer": "filesystem", "tools": ["read", "write"], "allow": ["/home/usuario/proyectos/*"], "deny": ["/etc/**", "/home/usuario/.ssh/**"] }, { "mcpServer": "github", "tools": ["create_pr", "list_repos"], "allow": ["*"], "requireApproval": true } ] }
bash
# Probar conectividad del servidor MCP desde la línea de comandos echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | \ node mcp-weather-server.js # Salida esperada: respuesta JSON-RPC con definiciones de herramientas # ... | jq '.result.tools[].name'
⚠️Warning

Al configurar permisos para un servidor MCP, recuerda que el servidor se ejecuta como un proceso separado. Incluso si el sistema de permisos de OpenCode bloquea una llamada de herramienta, el proceso del servidor en sí sigue ejecutándose. Para servidores sensibles, implementa autenticación dentro del servidor como una medida de defensa en profundidad.


Preguntas de Práctica

Practice Question

Un servidor MCP necesita comunicarse con el cliente OpenCode. ¿Qué protocolo de transporte utilizan?

Practice Question

Un desarrollador está construyendo un servidor MCP para una API del clima. ¿Qué tres primitivas debe exponer el servidor al cliente OpenCode?

Practice Question

Un equipo necesita conectar su base de datos PostgreSQL a OpenCode usando un servidor MCP. El script del servidor es mcp-db-server.py y usa DATABASE_URL. ¿Cómo debería configurarse esto?

Practice Question

¿Cuál es la diferencia clave entre ejecutar un servidor MCP mediante stdin/stdout versus HTTP/SSE?

Practice Question

Una herramienta de servidor MCP tiene un parámetro sin campo de descripción en su inputSchema. ¿Cuál es la probable consecuencia?


**Conclusiones Clave**
  • MCP es un estándar abierto que usa JSON-RPC 2.0 para comunicación LLM-herramienta
  • Los servidores MCP exponen herramientas (invocables), recursos (datos legibles) y prompts (plantillas)
  • Los servidores se configuran en opencode.json bajo mcpServers con comando, args y env
  • Las APIs externas (Slack, GitHub, bases de datos) se envuelven como herramientas MCP
  • Los servidores MCP pueden usar stdio o HTTP/SSE como mecanismos de transporte
  • Los ámbitos de permiso controlan qué herramientas y rutas puede acceder cada servidor MCP
  • La interpolación de variables de entorno (${VAR_NAME}) previene la filtración de secretos
  • El intercambio de protocolo JSON-RPC sigue un ciclo de vida estructurado: inicializar, descubrir, ejecutar, apagar
  • Los esquemas de herramientas deben tener parámetros bien descritos para que el LLM los use correctamente
  • Los servidores MCP pueden implementarse en cualquier lenguaje (TypeScript, Python, Go, etc.)
Progreso80%