Skip to content

Acceso MCP

源码版本v2026.7.20

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

Flujo de datos

  1. En el arranque (o en carga caliente durante la ejecución) se lee la configuración MCP; mcp_tool se conecta al server MCP externo.
  2. Obtiene el listado de herramientas expuestas por el server y registra cada una vía registry.register:365 como un ToolEntry (con aislamiento de namespace, para no chocar con las herramientas internas).
  3. La admisión de namespace la controla register_plugin_override_policy:316.
  4. El bucle principal recoge el schema, despacha el tool_call y devuelve el resultado igual que con las herramientas internas.
  5. 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:

python
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:

python
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:

python
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, MCPServerTask reconecta solo; cuando los fallos acumulados superan el umbral, salta el circuit breaker y los siguientes tool_call devuelven 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.

Sitio de aprendizaje comunitario no oficial. Basado en el código fuente de NousResearch/hermes-agent (licencia MIT).