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.
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.
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.
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
Configuración del Servidor MCP
Los servidores MCP se configuran en la sección mcpServers de opencode.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}"
}
}
}
}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:
{
"mcpServers": {
"slack": {
"command": "python",
"args": ["mcp-slack-server.py"],
"env": {
"SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}",
"SLACK_SIGNING_SECRET": "${SLACK_SIGNING_SECRET}"
}
}
}
}# 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
// 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
# 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())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
| Aspecto | stdio (subproceso) | HTTP/SSE (remoto) |
|---|---|---|
| Proceso | Iniciado por OpenCode | Se ejecuta independientemente |
| Latencia | Baja (IPC local) | Más alta (I/O de red) |
| Seguridad | Aislamiento de proceso, local | Requiere autenticación de red, TLS |
| Despliegue | Empaquetado con el proyecto | Servicio o contenedor en ejecución |
| Ciclo de vida | Vinculado a la sesión OpenCode | Daemon independiente |
| Caso de uso | Herramientas locales (fs, git) | APIs remotas (Slack, GitHub, DB) |
| Depuración | Verificar logs del servidor | Verificar endpoints + red |
| Escalabilidad | Uno por sesión | Múltiples clientes |
| Componente | Rol |
|---|---|
| OpenCode | Cliente MCP — inicia solicitudes |
| Servidor MCP | Plugin — procesa solicitudes y devuelve |
| Transporte | stdin/stdout o HTTP/SSE |
| Protocolo | JSON-RPC 2.0 |
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:
{
"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
}
]
}# 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'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
Un servidor MCP necesita comunicarse con el cliente OpenCode. ¿Qué protocolo de transporte utilizan?
Un desarrollador está construyendo un servidor MCP para una API del clima. ¿Qué tres primitivas debe exponer el servidor al cliente OpenCode?
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?
¿Cuál es la diferencia clave entre ejecutar un servidor MCP mediante stdin/stdout versus HTTP/SSE?
Una herramienta de servidor MCP tiene un parámetro sin campo de descripción en su inputSchema. ¿Cuál es la probable consecuencia?
- 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.jsonbajomcpServerscon 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.)