Acceso MCP
Responsabilidad
MCP (Model Context Protocol) es el protocolo estándar para conectar herramientas (tool)/fuentes de datos externas. Hermes actúa tanto como client MCP, consumiendo herramientas expuestas por servidores MCP externos (puenteadas a ToolRegistry vía mcp_tool), como como server MCP (mcp_serve.py), exponiendo sus propias herramientas a otros clientes agent. Esta capa permite a Hermes integrar capacidades de terceros de forma ilimitada.
Motivo de diseño
¿Por qué MCP en lugar de reinventar un protocolo de herramientas propio? MCP es un estándar abierto impulsado por Anthropic; Claude Code, Cursor y Codex ya lo han adoptado, así que el ecosistema está listo. Hermes como client puede reutilizar decenas de servers MCP comunitarios; como server puede ser accionado por cualquier cliente MCP — interop bidireccional gratis. Inventar un protocolo propio pegaría mejor con el ToolRegistry interno, pero se perdería todo un ecosistema.
Archivos clave
tools/mcp_tool.py— puente client MCP: registra en ToolRegistry las herramientas del server MCP externomcp_serve.py— Hermes como server MCP expone sus propias herramientas (raíz del repo)registry.register:365-436— las herramientas puenteadas por MCP terminan en la misma ruta de registro (registry)register_plugin_override_policy:316-365— controla la admisión de namespace de herramientas MCP/plugindirectorio optional-mcps/— implementaciones opcionales de servidores MCP
Flujo de datos
- En el arranque (o en carga caliente durante la ejecución) se lee la configuración MCP;
mcp_toolse conecta al server MCP externo. - Obtiene el listado de herramientas expuestas por el server y registra cada una vía
registry.register:365como unToolEntry(con aislamiento de namespace, para no chocar con las herramientas internas). - La admisión de namespace la controla
register_plugin_override_policy:316. - El bucle principal recoge el schema, despacha el
tool_cally devuelve el resultado igual que con las herramientas internas. - Sentido inverso: Hermes puede, vía
mcp_serve.py, exponer sus propias herramientas a otros clientes MCP, para que las invoquen editores u otros agentes.
Código clave
Puenteo de herramientas MCP a ToolRegistry
Tras conectar, _register_server_tools convierte cada MCP tool en un schema y la registra; el handler es una clausura que ata el nombre del server y el timeout:
for mcp_tool in server._tools:
if not _should_register(mcp_tool.name):
continue
schema = _convert_mcp_schema(name, mcp_tool)
tool_name_prefixed = schema["name"]
existing_toolset = registry.get_toolset_for_tool(tool_name_prefixed)
if existing_toolset and not existing_toolset.startswith("mcp-"):
logger.warning("MCP '%s': tool '%s' collides with built-in — skipping",
name, mcp_tool.name)
continue
registry.register(name=tool_name_prefixed, toolset=toolset_name, schema=schema,
handler=_make_tool_handler(name, mcp_tool.name, server.tool_timeout),
check_fn=_make_check_fn(name), is_async=False,
description=schema["description"])El nombre se prefija para que varios servers no colisionen entre sí; y el conflicto con herramientas internas se trata con honestidad: si el prefijo choca con un toolset no mcp-, se salta en lugar de reemplazar silenciosamente.
Puenteo de la llamada a herramienta
Cuando el bucle principal invoca una herramienta MCP, el handler del registro reenvía a esta clausura. Primero mira si el circuit breaker está abierto; luego toma la conexión (reconecta stdio si hace falta); después lanza la llamada síncrona al event loop en segundo plano y espera la respuesta MCP:
def _handler(args: dict, **kwargs) -> str:
if _server_error_counts.get(server_name, 0) >= _CIRCUIT_BREAKER_THRESHOLD:
age = time.monotonic() - _server_breaker_opened_at.get(server_name, 0.0)
if age < _CIRCUIT_BREAKER_COOLDOWN_SEC:
remaining = max(1, int(_CIRCUIT_BREAKER_COOLDOWN_SEC - age))
return json.dumps({"error": (
f"MCP server '{server_name}' unreachable after "
f"{_CIRCUIT_BREAKER_THRESHOLD} failures. Retry in ~{remaining}s. "
f"Do NOT retry this tool yet — use alternative approaches.")},
ensure_ascii=False)
server = _get_connected_server_for_call(server_name)
if not server:
_bump_server_error(server_name)
return json.dumps({"error": f"MCP server '{server_name}' is not connected"},
ensure_ascii=False)El mensaje de error incluye expresamente «Do NOT retry this tool yet» — va dirigido al modelo, para que no siga quemando iteraciones en un breaker abierto.
Inverso: exponer a Hermes como server MCP
mcp_serve.py levanta un server stdio con FastMCP y registra las herramientas del puente de mensajería. Cualquier cliente MCP puede hacer list/read/send de mensajes:
mcp = FastMCP("hermes", instructions=(
"Hermes Agent messaging bridge. Use these tools to interact with "
"conversations across Telegram, Discord, Slack, WhatsApp, Signal, Matrix."))
@mcp.tool()
def conversations_list(platform=None, limit=50, search=None) -> str:
"""List active messaging conversations across connected platforms."""
...El decorador @mcp.tool() registra solo con eso; no hace falta escribir el schema a mano — FastMCP lo infiere de las anotaciones de tipo.
Límites y fallos
- El server MCP se cae: si el subproceso muere o el endpoint HTTP cae,
MCPServerTaskreconecta solo; cuando los fallos acumulados superan el umbral, salta el circuit breaker y los siguientestool_calldevuelven error directamente. Tras un cooldown, el breaker semiabre y deja pasar una sonda. - Recogida de stdio server: un proceso ocioso demasiado tiempo se recoge para ahorrar recursos; la próxima llamada pasa por
_request_lazy_reconnect(despierta y reenvía), con una latencia de decenas de ms a segundos. - Colisión de nombres: las herramientas MCP se prefijan y, si chocan con un toolset interno, se saltan; dos servers MCP con el mismo nombre se permiten (rama
both_mcp) con un log a debug.
Resumen
MCP es el «bus de capacidades externas» de Hermes. mcp_tool hace que las herramientas externas entren al pool unificado de forma transparente; mcp_serve.py permite a su vez que Hermes sea invocado desde fuera. Al pasar todo por ToolRegistry, el bucle principal no necesita distinguir si una herramienta es interna o viene de MCP.