feat(mcp): Native MCP client with HTTP transport, reconnection, and security - #301
Merged
Conversation
Connect to external MCP servers via stdio transport, discover their tools
at startup, and register them into the hermes-agent tool registry.
- New tools/mcp_tool.py: config loading, server connection via background
event loop, tool handler factories, discovery, and graceful shutdown
- model_tools.py: trigger MCP discovery after built-in tool imports
- cli.py: call shutdown_mcp_servers in _run_cleanup
- pyproject.toml: add mcp>=1.2.0 as optional dependency
- 27 unit tests covering config, schema conversion, handlers, registration,
SDK interaction, toolset injection, graceful fallback, and shutdown
Config format (in ~/.hermes/config.yaml):
mcp_servers:
filesystem:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
Ensures MCP subprocess connections are closed when the messaging gateway shuts down, preventing orphan processes.
Refactor MCP connections from AsyncExitStack to task-per-server architecture. Each server now runs as a long-lived asyncio Task with `async with stdio_client(...)`, ensuring anyio cancel-scope cleanup happens in the same Task that opened the connection.
When discover_mcp_tools() is called multiple times (e.g. direct call then model_tools import), return existing tool names instead of opening new connections that would orphan the previous ones.
Patch _servers to empty dict in tests that call discover_mcp_tools() with mocked config, preventing interference from real MCP connections that may exist when running within the full test suite.
- Add threading.Lock protecting all shared state (_servers, _mcp_loop, _mcp_thread)
- Fix deadlock in shutdown_mcp_servers: _stop_mcp_loop was called inside
a _lock block but also acquires _lock (non-reentrant)
- Fix race condition in _ensure_mcp_loop with concurrent callers
- Change idempotency to per-server (retry failed servers, skip connected)
- Dynamic toolset injection via startswith("hermes-") instead of hardcoded list
- Parallel shutdown via asyncio.gather instead of sequential loop
- Add tests for partial failure retry, parallel shutdown, dynamic injection
Authored by 0xbyt4. Adds MCP client with official SDK, direct tool registration, auto-injection into hermes-* toolsets, and graceful degradation.
Upgrades the MCP client implementation from PR #291 with: - HTTP/Streamable HTTP transport: support 'url' key in config for remote MCP servers (Notion, Slack, Sentry, Supabase, etc.) - Automatic reconnection with exponential backoff (1s-60s, 5 retries) when a server connection drops unexpectedly - Environment variable filtering: only pass safe vars (PATH, HOME, etc.) plus user-specified env to stdio subprocesses (prevents secret leaks) - Credential stripping: sanitize error messages before returning to the LLM (strips GitHub PATs, OpenAI keys, Bearer tokens, etc.) - Configurable per-server timeouts: 'timeout' and 'connect_timeout' keys - Fix shutdown race condition in servers_snapshot variable scoping Test coverage: 50 tests (up from 30), including new tests for env filtering, credential sanitization, HTTP config detection, reconnection logic, and configurable timeouts. All 1162 tests pass (1162 passed, 3 skipped, 0 failed).
- docs/mcp.md: Full MCP documentation covering prerequisites, configuration, transports (stdio + HTTP), security (env filtering, credential stripping), reconnection, troubleshooting, popular servers, and advanced usage - README.md: Add MCP section with quick config example and install instructions - cli-config.yaml.example: Add commented mcp_servers section with examples for stdio, HTTP, and authenticated server configs - docs/tools.md: Add MCP to Tool Categories table and MCP Tools section - skills/mcp/native-mcp/SKILL.md: Create native MCP client skill with full configuration reference, transport types, security, troubleshooting - skills/mcp/DESCRIPTION.md: Update category description to cover both native MCP client and mcporter bridge approaches
- Discovery is now parallel (asyncio.gather) instead of sequential, fixing the 60s shared timeout issue with multiple servers - Startup messages use print() so users see connection status even with default log levels (the 'tools' logger is set to ERROR) - Summary line shows total tools and failed servers count - Validate conflicting config: warn if both 'url' and 'command' are present (HTTP takes precedence) - Update TODO.md: mark MCP as implemented, list remaining work - Add test for conflicting config detection (51 tests total) All 1163 tests pass.
Banner integration: - MCP Servers section in CLI startup banner between Tools and Skills - Shows each server with transport type, tool count, connection status - Failed servers shown in red; section hidden when no MCP configured - Summary line includes MCP server count - Removed raw print() calls from discovery (banner handles display) /reload-mcp command: - New slash command in both CLI and gateway - Disconnects all MCP servers, re-reads config.yaml, reconnects - Reports what changed (added/removed/reconnected servers) - Allows adding/removing MCP servers without restarting Resources & Prompts support: - 4 utility tools registered per server: list_resources, read_resource, list_prompts, get_prompt - Exposes MCP Resources (data sources) and Prompts (templates) as tools - Proper parameter schemas (uri for read_resource, name for get_prompt) - Handles text and binary resource content - 23 new tests covering schemas, handlers, and registration Test coverage: 74 MCP tests total, 1186 tests pass overall.
- CLI: After reload, refreshes self.agent.tools and valid_tool_names so the model sees updated tools on its next API call - Both CLI and Gateway: Appends a [SYSTEM: ...] message at the END of conversation history explaining what changed (added/removed/ reconnected servers, tool count). This preserves prompt-cache for the system prompt and earlier messages — only the tail changes. - Gateway already creates a new AIAgent per message so tools refresh naturally; the injected message provides context for the model
After /reload-mcp updates self.agent.tools, immediately call _persist_session() so the session JSON file at ~/.hermes/sessions/ reflects the new tools list. Without this, the tools field in the session log would only update on the next conversation turn — if the user quit after reloading, the log would have stale tools.
angelburgosrosado
pushed a commit
to angelburgosrosado/hermes-agent
that referenced
this pull request
Apr 27, 2026
feat(mcp): Native MCP client with HTTP transport, reconnection, and security
angelburgosrosado
pushed a commit
to angelburgosrosado/hermes-agent
that referenced
this pull request
Apr 27, 2026
All remaining TODO items have covering issues: - Local Browser via CDP: NousResearch#374, NousResearch#493 - Signal Integration: NousResearch#405 - Plugin/Extension System: NousResearch#359 - MCP Client Improvements: NousResearch#581 (new) - Filesystem Checkpointing: NousResearch#452 Completed items (MCP core support) already shipped in PR NousResearch#301.
02356abc
pushed a commit
to 02356abc/hermes-agent
that referenced
this pull request
May 14, 2026
All remaining TODO items have covering issues: - Local Browser via CDP: NousResearch#374, NousResearch#493 - Signal Integration: NousResearch#405 - Plugin/Extension System: NousResearch#359 - MCP Client Improvements: NousResearch#581 (new) - Filesystem Checkpointing: NousResearch#452 Completed items (MCP core support) already shipped in PR NousResearch#301.
1 task
cpmidnite
pushed a commit
to cpmidnite/hermes-agent
that referenced
this pull request
Jun 3, 2026
cpmidnite
pushed a commit
to cpmidnite/hermes-agent
that referenced
this pull request
Jun 3, 2026
…size storms (React NousResearch#301) The /chat dashboard runs the Ink TUI bundle through a browser-side xterm.js PTY. The fit-addon emits rapid resize bursts (delivered as stdout.columns writes over the WS RESIZE escape), each invalidating the Yoga height cache mid-convergence. The useVirtualHistory layout effect re-measures on every height change (measuredHeightVersion is its own dep), so under the storm heights never settle within React's 25-rerender budget → NousResearch#301. Cap synchronous re-measure bumps (MAX_SYNC_MEASURE_BUMPS=8); past the cap, defer the bump via setTimeout(0) so React commits the frame and the storm settles before re-measuring. Budget resets on real layout changes (column resize) and when heights stabilize. The prior cmux fix debounced the cols SOURCE; this caps the convergence SINK, covering the xterm cadence that slipped through. Rebuilt dist/entry.js. 21/22 measurement tests pass (the 1 fail is a pre-existing estimatedMsgHeight width assertion, unrelated).
cpmidnite
pushed a commit
to cpmidnite/hermes-agent
that referenced
this pull request
Jun 3, 2026
The overlay already disables HERMES_TUI_GATEWAY_URL for browser embedded chat. Document that the same sidecar attach also presents as Minified React NousResearch#301 in addition to the older gateway-exited symptom.
7 tasks
kshitijk4poor
added a commit
that referenced
this pull request
Jun 28, 2026
…36658) Dashboard /chat spawns the TUI attached to the dashboard's in-memory gateway via HERMES_TUI_GATEWAY_URL. In that attach mode the already-running gateway replays `gateway.ready` (and `session.info`) the instant the socket connects, so those events land in GatewayClient.bufferedEvents *before* the consumer's mount-time subscribe effect (useMainApp.ts) calls drain(). drain() then emitted the buffered events synchronously, so the `gateway.ready` handler's patchUiState / setHistoryItems cascade ran while React was still inside the first commit — tripping "Too many re-renders" (Minified React error #301) and breaking Dashboard chat after `hermes update`. Spawn / inline / sidecar modes never hit this: their `gateway.ready` only arrives after the Python child boots, on a later async tick. Fix: drain() defers the replay to the next microtask AND keeps `subscribed` false until that microtask runs. Keeping `subscribed` false in the gap means any live event arriving before the flush keeps buffering (publish() pushes when !subscribed) instead of emitting synchronously and jumping ahead of the chronologically-earlier replayed events — the flush re-drains the buffer right after flipping `subscribed`, preserving FIFO order. A drainGeneration token (bumped in resetStartupState) makes a queued flush a no-op if the transport was reset/killed in the meantime, avoiding use-after-teardown and duplicate/reordered exits. Regression tests: (1) drain() does not dispatch buffered events synchronously; (2) a live event arriving in the post-drain / pre-microtask window still delivers BEHIND the earlier-buffered event (FIFO). Both are red against the old synchronous behavior, green with this fix. Same class of fix as #44528. Closes #36658
pai-scaffolde
pushed a commit
to pai-scaffolde/hermes-agent
that referenced
this pull request
Jun 28, 2026
…search#301 (NousResearch#36658) Dashboard /chat spawns the TUI attached to the dashboard's in-memory gateway via HERMES_TUI_GATEWAY_URL. In that attach mode the already-running gateway replays `gateway.ready` (and `session.info`) the instant the socket connects, so those events land in GatewayClient.bufferedEvents *before* the consumer's mount-time subscribe effect (useMainApp.ts) calls drain(). drain() then emitted the buffered events synchronously, so the `gateway.ready` handler's patchUiState / setHistoryItems cascade ran while React was still inside the first commit — tripping "Too many re-renders" (Minified React error NousResearch#301) and breaking Dashboard chat after `hermes update`. Spawn / inline / sidecar modes never hit this: their `gateway.ready` only arrives after the Python child boots, on a later async tick. Fix: drain() defers the replay to the next microtask AND keeps `subscribed` false until that microtask runs. Keeping `subscribed` false in the gap means any live event arriving before the flush keeps buffering (publish() pushes when !subscribed) instead of emitting synchronously and jumping ahead of the chronologically-earlier replayed events — the flush re-drains the buffer right after flipping `subscribed`, preserving FIFO order. A drainGeneration token (bumped in resetStartupState) makes a queued flush a no-op if the transport was reset/killed in the meantime, avoiding use-after-teardown and duplicate/reordered exits. Regression tests: (1) drain() does not dispatch buffered events synchronously; (2) a live event arriving in the post-drain / pre-microtask window still delivers BEHIND the earlier-buffered event (FIFO). Both are red against the old synchronous behavior, green with this fix. Same class of fix as NousResearch#44528. Closes NousResearch#36658
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
feat(mcp): Native MCP client with HTTP transport, reconnection, and security
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
All remaining TODO items have covering issues: - Local Browser via CDP: NousResearch#374, NousResearch#493 - Signal Integration: NousResearch#405 - Plugin/Extension System: NousResearch#359 - MCP Client Improvements: NousResearch#581 (new) - Filesystem Checkpointing: NousResearch#452 Completed items (MCP core support) already shipped in PR NousResearch#301.
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
…search#301 (NousResearch#36658) Dashboard /chat spawns the TUI attached to the dashboard's in-memory gateway via HERMES_TUI_GATEWAY_URL. In that attach mode the already-running gateway replays `gateway.ready` (and `session.info`) the instant the socket connects, so those events land in GatewayClient.bufferedEvents *before* the consumer's mount-time subscribe effect (useMainApp.ts) calls drain(). drain() then emitted the buffered events synchronously, so the `gateway.ready` handler's patchUiState / setHistoryItems cascade ran while React was still inside the first commit — tripping "Too many re-renders" (Minified React error NousResearch#301) and breaking Dashboard chat after `hermes update`. Spawn / inline / sidecar modes never hit this: their `gateway.ready` only arrives after the Python child boots, on a later async tick. Fix: drain() defers the replay to the next microtask AND keeps `subscribed` false until that microtask runs. Keeping `subscribed` false in the gap means any live event arriving before the flush keeps buffering (publish() pushes when !subscribed) instead of emitting synchronously and jumping ahead of the chronologically-earlier replayed events — the flush re-drains the buffer right after flipping `subscribed`, preserving FIFO order. A drainGeneration token (bumped in resetStartupState) makes a queued flush a no-op if the transport was reset/killed in the meantime, avoiding use-after-teardown and duplicate/reordered exits. Regression tests: (1) drain() does not dispatch buffered events synchronously; (2) a live event arriving in the post-drain / pre-microtask window still delivers BEHIND the earlier-buffered event (FIFO). Both are red against the old synchronous behavior, green with this fix. Same class of fix as NousResearch#44528. Closes NousResearch#36658
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
…teway-drain-microtask fix(tui): defer buffered gateway events to stop dashboard chat NousResearch#301 (NousResearch#36658)
Jasper6439
pushed a commit
to Jasper6439/hermes-agent
that referenced
this pull request
Jul 5, 2026
…search#301 (NousResearch#36658) Dashboard /chat spawns the TUI attached to the dashboard's in-memory gateway via HERMES_TUI_GATEWAY_URL. In that attach mode the already-running gateway replays `gateway.ready` (and `session.info`) the instant the socket connects, so those events land in GatewayClient.bufferedEvents *before* the consumer's mount-time subscribe effect (useMainApp.ts) calls drain(). drain() then emitted the buffered events synchronously, so the `gateway.ready` handler's patchUiState / setHistoryItems cascade ran while React was still inside the first commit — tripping "Too many re-renders" (Minified React error NousResearch#301) and breaking Dashboard chat after `hermes update`. Spawn / inline / sidecar modes never hit this: their `gateway.ready` only arrives after the Python child boots, on a later async tick. Fix: drain() defers the replay to the next microtask AND keeps `subscribed` false until that microtask runs. Keeping `subscribed` false in the gap means any live event arriving before the flush keeps buffering (publish() pushes when !subscribed) instead of emitting synchronously and jumping ahead of the chronologically-earlier replayed events — the flush re-drains the buffer right after flipping `subscribed`, preserving FIFO order. A drainGeneration token (bumped in resetStartupState) makes a queued flush a no-op if the transport was reset/killed in the meantime, avoiding use-after-teardown and duplicate/reordered exits. Regression tests: (1) drain() does not dispatch buffered events synchronously; (2) a live event arriving in the post-drain / pre-microtask window still delivers BEHIND the earlier-buffered event (FIFO). Both are red against the old synchronous behavior, green with this fix. Same class of fix as NousResearch#44528. Closes NousResearch#36658
habarmc1223-sudo
pushed a commit
to habarmc1223-sudo/hermes-agent-fluxmem
that referenced
this pull request
Jul 8, 2026
…search#301 (NousResearch#36658) Dashboard /chat spawns the TUI attached to the dashboard's in-memory gateway via HERMES_TUI_GATEWAY_URL. In that attach mode the already-running gateway replays `gateway.ready` (and `session.info`) the instant the socket connects, so those events land in GatewayClient.bufferedEvents *before* the consumer's mount-time subscribe effect (useMainApp.ts) calls drain(). drain() then emitted the buffered events synchronously, so the `gateway.ready` handler's patchUiState / setHistoryItems cascade ran while React was still inside the first commit — tripping "Too many re-renders" (Minified React error NousResearch#301) and breaking Dashboard chat after `hermes update`. Spawn / inline / sidecar modes never hit this: their `gateway.ready` only arrives after the Python child boots, on a later async tick. Fix: drain() defers the replay to the next microtask AND keeps `subscribed` false until that microtask runs. Keeping `subscribed` false in the gap means any live event arriving before the flush keeps buffering (publish() pushes when !subscribed) instead of emitting synchronously and jumping ahead of the chronologically-earlier replayed events — the flush re-drains the buffer right after flipping `subscribed`, preserving FIFO order. A drainGeneration token (bumped in resetStartupState) makes a queued flush a no-op if the transport was reset/killed in the meantime, avoiding use-after-teardown and duplicate/reordered exits. Regression tests: (1) drain() does not dispatch buffered events synchronously; (2) a live event arriving in the post-drain / pre-microtask window still delivers BEHIND the earlier-buffered event (FIFO). Both are red against the old synchronous behavior, green with this fix. Same class of fix as NousResearch#44528. Closes NousResearch#36658
habarmc1223-sudo
pushed a commit
to habarmc1223-sudo/hermes-agent-fluxmem
that referenced
this pull request
Jul 8, 2026
…teway-drain-microtask fix(tui): defer buffered gateway events to stop dashboard chat NousResearch#301 (NousResearch#36658)
santhreal
pushed a commit
to santhreal/hermes-agent
that referenced
this pull request
Jul 13, 2026
…search#301 (NousResearch#36658) Dashboard /chat spawns the TUI attached to the dashboard's in-memory gateway via HERMES_TUI_GATEWAY_URL. In that attach mode the already-running gateway replays `gateway.ready` (and `session.info`) the instant the socket connects, so those events land in GatewayClient.bufferedEvents *before* the consumer's mount-time subscribe effect (useMainApp.ts) calls drain(). drain() then emitted the buffered events synchronously, so the `gateway.ready` handler's patchUiState / setHistoryItems cascade ran while React was still inside the first commit — tripping "Too many re-renders" (Minified React error NousResearch#301) and breaking Dashboard chat after `hermes update`. Spawn / inline / sidecar modes never hit this: their `gateway.ready` only arrives after the Python child boots, on a later async tick. Fix: drain() defers the replay to the next microtask AND keeps `subscribed` false until that microtask runs. Keeping `subscribed` false in the gap means any live event arriving before the flush keeps buffering (publish() pushes when !subscribed) instead of emitting synchronously and jumping ahead of the chronologically-earlier replayed events — the flush re-drains the buffer right after flipping `subscribed`, preserving FIFO order. A drainGeneration token (bumped in resetStartupState) makes a queued flush a no-op if the transport was reset/killed in the meantime, avoiding use-after-teardown and duplicate/reordered exits. Regression tests: (1) drain() does not dispatch buffered events synchronously; (2) a live event arriving in the post-drain / pre-microtask window still delivers BEHIND the earlier-buffered event (FIFO). Both are red against the old synchronous behavior, green with this fix. Same class of fix as NousResearch#44528. Closes NousResearch#36658
santhreal
pushed a commit
to santhreal/hermes-agent
that referenced
this pull request
Jul 13, 2026
…teway-drain-microtask fix(tui): defer buffered gateway events to stop dashboard chat NousResearch#301 (NousResearch#36658)
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
…search#301 (NousResearch#36658) Dashboard /chat spawns the TUI attached to the dashboard's in-memory gateway via HERMES_TUI_GATEWAY_URL. In that attach mode the already-running gateway replays `gateway.ready` (and `session.info`) the instant the socket connects, so those events land in GatewayClient.bufferedEvents *before* the consumer's mount-time subscribe effect (useMainApp.ts) calls drain(). drain() then emitted the buffered events synchronously, so the `gateway.ready` handler's patchUiState / setHistoryItems cascade ran while React was still inside the first commit — tripping "Too many re-renders" (Minified React error NousResearch#301) and breaking Dashboard chat after `hermes update`. Spawn / inline / sidecar modes never hit this: their `gateway.ready` only arrives after the Python child boots, on a later async tick. Fix: drain() defers the replay to the next microtask AND keeps `subscribed` false until that microtask runs. Keeping `subscribed` false in the gap means any live event arriving before the flush keeps buffering (publish() pushes when !subscribed) instead of emitting synchronously and jumping ahead of the chronologically-earlier replayed events — the flush re-drains the buffer right after flipping `subscribed`, preserving FIFO order. A drainGeneration token (bumped in resetStartupState) makes a queued flush a no-op if the transport was reset/killed in the meantime, avoiding use-after-teardown and duplicate/reordered exits. Regression tests: (1) drain() does not dispatch buffered events synchronously; (2) a live event arriving in the post-drain / pre-microtask window still delivers BEHIND the earlier-buffered event (FIFO). Both are red against the old synchronous behavior, green with this fix. Same class of fix as NousResearch#44528. Closes NousResearch#36658
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
…teway-drain-microtask fix(tui): defer buffered gateway events to stop dashboard chat NousResearch#301 (NousResearch#36658)
leewenjie
pushed a commit
to leewenjie/hermes-agent
that referenced
this pull request
Aug 7, 2026
…search#301 (NousResearch#36658) Dashboard /chat spawns the TUI attached to the dashboard's in-memory gateway via HERMES_TUI_GATEWAY_URL. In that attach mode the already-running gateway replays `gateway.ready` (and `session.info`) the instant the socket connects, so those events land in GatewayClient.bufferedEvents *before* the consumer's mount-time subscribe effect (useMainApp.ts) calls drain(). drain() then emitted the buffered events synchronously, so the `gateway.ready` handler's patchUiState / setHistoryItems cascade ran while React was still inside the first commit — tripping "Too many re-renders" (Minified React error NousResearch#301) and breaking Dashboard chat after `hermes update`. Spawn / inline / sidecar modes never hit this: their `gateway.ready` only arrives after the Python child boots, on a later async tick. Fix: drain() defers the replay to the next microtask AND keeps `subscribed` false until that microtask runs. Keeping `subscribed` false in the gap means any live event arriving before the flush keeps buffering (publish() pushes when !subscribed) instead of emitting synchronously and jumping ahead of the chronologically-earlier replayed events — the flush re-drains the buffer right after flipping `subscribed`, preserving FIFO order. A drainGeneration token (bumped in resetStartupState) makes a queued flush a no-op if the transport was reset/killed in the meantime, avoiding use-after-teardown and duplicate/reordered exits. Regression tests: (1) drain() does not dispatch buffered events synchronously; (2) a live event arriving in the post-drain / pre-microtask window still delivers BEHIND the earlier-buffered event (FIFO). Both are red against the old synchronous behavior, green with this fix. Same class of fix as NousResearch#44528. Closes NousResearch#36658
leewenjie
pushed a commit
to leewenjie/hermes-agent
that referenced
this pull request
Aug 7, 2026
…teway-drain-microtask fix(tui): defer buffered gateway events to stop dashboard chat NousResearch#301 (NousResearch#36658)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MCP (Model Context Protocol) Client Support
Overview
Adds comprehensive native MCP client support to Hermes Agent. Users connect to any MCP-compatible server by adding a few lines to config.yaml — tools, resources, and prompts are automatically discovered and available to the agent.
Based on PR #291 by @0xbyt4 (merged with contributor credit preserved).
Quick Start
Install:
pip install hermes-agent[mcp]Features
Core:
command+args)url+headers)list_resources,read_resource,list_prompts,get_prompttools per serverasyncio.gathermcppackage (just skips MCP)Robustness:
url+commandin same servertimeoutandconnect_timeoutUX:
/reload-mcpcommand — disconnect, re-read config, reconnect (CLI + gateway). Add/remove servers without restartingN tools · N skills · N MCP serversUser Prerequisites
url)npx)npxauto-downloadsuvx)uvuvxauto-downloadsTesting
Files Changed (15 files, +3,705 / -27)
tools/mcp_tool.pytests/tools/test_mcp_tool.pydocs/mcp.mdskills/mcp/native-mcp/SKILL.mdhermes_cli/banner.pycli.py/reload-mcpcommand + shutdown hookgateway/run.py/reload-mcpcommand + shutdown hookcli-config.yaml.exampleREADME.mdTODO.mddocs/tools.mdskills/mcp/DESCRIPTION.mdmodel_tools.pydiscover_mcp_tools()hookpyproject.tomlmcp>=1.2.0optional dependencyuv.lockFuture Work
hermes mcpCLI subcommand (list/test/status)hermes toolsUI integration for MCP toolsets