Skip to content

MCP-Anbindung ​

源码版本v2026.7.20

Verantwortung ​

MCP (Model Context Protocol) ist ein universelles Protokoll, um externe Werkzeuge (tools)/Datenquellen anzubinden. Hermes tritt sowohl als MCP-Client auf und konsumiert Werkzeuge externer MCP-Server (über mcp_tool in die ToolRegistry überbrückt), als auch als MCP-Server (mcp_serve.py), der die eigenen Werkzeuge anderen Agent-Clients zur Verfügung stellt. Diese Schicht ermöglicht es Hermes, Drittfähigkeiten aufzunehmen, ohne sie per Hand anzupassen.

Designmotiv ​

Warum MCP wählen statt ein eigenes Werkzeugprotokoll neu zu erfinden? MCP ist ein offener Standard, den Anthropic vorantreibt; Claude Code, Cursor und Codex docken bereits danach an — das Ökosystem existiert. Als Client kann Hermes Dutzende Community-MCP-Server wiederverwenden; als Server kann es von beliebigen MCP-Clients gesteuert werden — duale Interoperabilität geschenkt. Ein selbst gebautes Protokoll würde zwar zur internen ToolRegistry passen, kostet aber das ganze Ökosystem.

Schlüsseldateien ​

Datenfluss ​

  1. Beim Start (oder zur Laufzeit per Hot-Reload) wird die MCP-Konfiguration gelesen; mcp_tool verbindet sich mit dem externen MCP-Server.
  2. Die vom Server freigegebene Werkzeugliste wird abgerufen und jedes Werkzeug einzeln über registry.register:365 als ToolEntry registriert (Namespace-Isolation, um Konflikte mit eingebauten Werkzeugen zu vermeiden).
  3. Die Namespace-Zulassung wird von register_plugin_override_policy:316 gesteuert.
  4. Die Hauptschleife sammelt Schemata, dispatched (dispatch) tool_call und füllt Ergebnisse zurück wie bei eingebauten Werkzeugen.
  5. Reverse: Hermes kann über mcp_serve.py die eigenen Werkzeuge für andere MCP-Clients freigeben, damit Editoren/andere Agenten sie aufrufen.

Schlüsselcode ​

MCP-Werkzeuge in die ToolRegistry überbrücken ​

Nach erfolgreichem Verbinden wandelt _register_server_tools jedes MCP-Tool in ein Schema um und registriert es; der Handler bindet Servernamen und Timeout über einen Closure fest:

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"])

Das Präfixieren der Werkzeugnamen stellt sicher, dass mehrere Server sich nicht in die Quere kommen; gegen eingebaute Werkzeugsets erfolgt eine Kollisionserkennung: Kollidiert ein Name mit einem nicht mit mcp- beginnenden Werkzeugset, wird er übersprungen statt still ersetzt.

Werkzeugaufruf-Brücke ​

Wenn die Hauptschleife ein MCP-Werkzeug aufruft, leitet der Handler der Registry an diesen Closure weiter. Er prüft zuerst, ob der Breaker offen ist, holt dann die Verbindung (bei Bedarf mit stdio-Reconnect) und legt den synchronen Aufruf auf einem Hintergrund-Event-Loop ab, um auf die MCP-Antwort zu warten:

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)

Die Fehlermeldung enthält ausdrücklich «Do NOT retry this tool yet» — das ist für das Modell zum Lesen: es soll nicht ständig Iterationen (iterations) verbrennen, um es erneut zu versuchen, während der Breaker offen ist.

Reverse: Hermes selbst als MCP-Server freigeben ​

mcp_serve.py startet über FastMCP einen stdio-Server, registriert die Nachrichten-Brücken-Werkzeuge und macht sie für jeden MCP-Client list-/read-/sendbar:

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."""
    ...

Der @mcp.tool()-Dekorator registriert sofort — kein manuelles Schema; FastMCP leitet es aus den Typannotationen ab.

Grenzen und Fehler ​

  • MCP-Server abgestürzt: Der Kindprozess stirbt oder der HTTP-Endpunkt fällt aus; MCPServerTask verbindet sich automatisch neu; erreichten die Fehler einen Schwellwert, öffnet der Breaker, nachfolgende tool_calls liefern direkt einen Fehler; erst nach Ablauf der Abkühlzeit wird eine einzelne Sonde halb-offen durchgelassen.
  • stdio-Server-Wiederverwertung: Ein zu lange idlender Prozess wird wiederverwertet, um Ressourcen zu sparen; beim nächsten Aufruf weckt _request_lazy_reconnect zuerst auf und leitet dann weiter — das kostet wenige Dutzend Millisekunden bis Sekunden Latenz.
  • Werkzeugnamen-Konflikt: Präfixierte MCP-Werkzeuge, die mit eingebauten Werkzeugsets kollidieren, werden übersprungen; zwei MCP-Server mit gleichem Namen dürfen sich gegenseitig überschreiben, laufen über den both_mcp-Zweig und schreiben einen Debug-Log.

Zusammenfassung ​

MCP ist der «Fähigkeits-Bus» von Hermes. mcp_tool lässt externe Werkzeuge in den einheitlichen Werkzeugpool einfließen; mcp_serve.py macht Hermes selbst von außen aufrufbar. Weil alles über die ToolRegistry läuft, muss die Hauptschleife nicht wissen, ob ein Werkzeug eingebaut ist oder von MCP stammt — sie sieht nur Schema und Handler.