Skip to content

Portable plugin MCP tools keep their names instead of a hash (64-char provider cap) - #119263

Merged
alt-glitch merged 2 commits into
mainfrom
fix/portable-plugin-namespace
Sep 22, 2026
Merged

alt-glitch merged 2 commits into
mainfrom
fix/portable-plugin-namespace

Conversation

@alt-glitch

@alt-glitch alt-glitch commented Sep 22, 2026 •

Copy link
Copy Markdown

The model can read a portable plugin's tool names again: a portable MCP server is named exactly what its mcp.json calls it, the same rule as a config.yaml server.

What the model saw

Every tool of a portable (Agent Plugins v1) package registered under a name like this:

mcp__agent_plugin_hermes_nvidia_72eb26b1__nvidia_app__nvapp_client_get_driver_status   84 chars

Providers reject function names over 64 characters and fail the whole request, so the registry clamps long names
to 55 characters plus a hash. The clamp cut off the part that says what the tool does:

mcp__agent_plugin_hermes_nvidia_72eb26b1__nvidia_app__n_3fa9c2d1

Seen live on the x64 desktop with the NVIDIA plugin: 12 tools, every one a hash, agent.log full of
exceeds the 64-char provider limit; shortened to a deterministic hash-suffixed name.

Where the 43 extra characters came from

The loader named a portable plugin's MCP server after the plugin's skill namespace,
agent-plugin-<slug>-<sha8> (plugins_manifest.py::_portable_skill_namespace). That namespace exists for plugin-data
directories and skill names: it must be unique without any coordination between plugin authors, and it is written to
disk, so it carries a digest. Neither reason applies to the MCP server name. It only has to be unique among the
portable servers loaded in one process, and the loader already detects and refuses a duplicate.

Change

flowchart LR
  subgraph before
    K1[plugin key] --> N1["agent-plugin-&lt;slug&gt;-&lt;sha8&gt;"] --> S1["&lt;ns&gt;__&lt;server&gt;"] --> T1["mcp__…__n_3fa9c2d1 (clamped)"]
  end
  subgraph after
    M2["mcp.json server name"] --> S2["&lt;server&gt;"] --> T2["mcp__nvidia_app__nvapp_client_get_driver_status"]
  end
Loading
before after
server nvidia-app in a portable plugin agent-plugin-hermes-nvidia-72eb26b1__nvidia-app nvidia-app
server nvidia-app in the user's config.yaml nvidia-app nvidia-app (unchanged; same rule now)
wire tool name, nvapp_client_get_driver_status 84 chars → clamped, verb lost 47 chars, intact
skill names, plugin-data/ directory agent-plugin-<slug>-<sha8> unchanged
two sources naming a server the same impossible (digest) refused at load: config.yaml wins, then first-loaded plugin; warning names both

One function, plugins_manifest.py::portable_mcp_server_name, used by the loader and by the desktop card's server rows
(tui_gateway/methods_tools.py::_plugin_server_rows, which had recomputed the f-string on its own).

Applies to every portable plugin (~18 catalog entries describe themselves as portable), all of which were being
clamped. No migration: nothing persisted refers to a portable server's name. Plugin servers are merged at runtime, never
written to config.yaml; transcripts keep old names as history only.

Verification

  • tests/hermes_cli/test_plugins.py::test_enabled_portable_plugin_registers_components now pins the relationship:
    server name equals the mcp.json name, and a long vendor tool name reaches the wire without a hash suffix.
    Red on main, green here.
  • New test_two_portable_plugins_with_the_same_server_name_do_not_both_load: the clash the digest used to hide.
    Two enabled packages naming a server shared, one served, one skipped. Red on main.
  • Ran: tests/hermes_cli/test_plugins.py 97 pass, test_agent_plugins.py 43, tests/tools/test_mcp_liveness*.py,
    tests/tui_gateway/test_plugins_manage_install.py. test_plugin_validate.py has one failure that is identical on
    main (unrelated, test_portable_validation_fails_orphan_and_reports_availability).
  • Live spike, isolated HERMES_HOME, real model turn: a portable plugin with server acme-tools and tool
    acme_client_get_driver_status_report. On main it registers as
    mcp__agent_plugin_acme_tools_88e7456f__acme_tools__acme_153862de; on this branch as
    mcp__acme_tools__acme_client_get_driver_status_report. agent.log: MCP server 'acme-tools' (stdio): registered 1 tool(s): mcp__acme_tools__acme_client_get_driver_status_report; the model found it with tool_search, called it, and
    reported that exact name.
  • Not re-run on the Windows desktop against this branch; the NVIDIA run above is the "before".

Follow-ups (not here)

  • The vendor's own tool names repeat the product (nvapp_client_…); that is the plugin author's naming.
  • Plugin-data and skill namespaces keep the digest; changing them would need an on-disk migration.

…fit the 64-char cap

A portable plugin's MCP server was named `<skill_namespace>__<server>`, i.e.
`agent-plugin-<slug>-<sha8>__<server>`. That prefix is right for plugin-data and skill names
(collision-free without coordination, and persisted on disk) but it costs ~40 chars of every
`mcp__<server>__<tool>` name. Providers cap function names at 64, so the registry clamped every
tool of the NVIDIA plugin to a hash-suffixed stub with the verb cut off:
`mcp__agent_plugin_hermes_nvidia_72eb26b1__nvidia_app__n_3fa9c2d1`.

Server names only need to be unique among loaded portable servers, and the loader already refuses
a clash. `portable_mcp_server_name(key, server)` = `<plugin-slug>__<server>`, collapsed to
`<plugin-slug>` when the two match (the one-server package). The loader and the desktop card's
server rows both call it (the card recomputed the f-string on its own before). Skill and plugin-data
namespaces are unchanged; nothing persisted refers to the server name, so no migration.

Tests: the existing portable-load test now pins the relationship (server name = plugin slug +
server; a long vendor tool name reaches the wire unclamped), and a new test covers the clash the
digest used to hide: two enabled packages folding to one slug, second server skipped, first served.
Both red on main. Docs: developer-guide/plugins/index.md.
@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

ran on b7d2106 — fix(plugins): a portable MCP server is named what its mcp.js

debug info

CI timings

CI timings · View report · View job

Wall time 6m1s vs 5m44s (+4.9%). 8 job(s) slower, 4 faster, 1 unchanged.

  • Python tests / Run tests: +51.0s
  • Python lints / Windows footguns (blocking): +23.0s
  • Docs Site / docs-site-checks: +6.0s
  • OS-specific tests / macOS-only tests: +4.0s
  • OS-specific tests / Windows-only tests: +3.0s

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins comp/tui Terminal UI (ui-tui/ + tui_gateway/) tool/mcp MCP client and OAuth labels Sep 22, 2026
…it, nothing prepended

Drop the plugin segment too. A user's own config.yaml server named `nvidia-app` yields
`mcp__nvidia_app__<tool>`; a portable plugin's server of the same name now yields the same. Duplicates
are refused at load (config.yaml first, then first-loaded plugin) with a warning naming both owners.

Spike, isolated HERMES_HOME, real session: a portable plugin with server `acme-tools` and tool
`acme_client_get_driver_status_report` registers as
`mcp__acme_tools__acme_client_get_driver_status_report`; the model found it by tool_search, called it,
and reported that exact name. Same plugin on main: `mcp__agent_plugin_acme_tools_88e7456f__acme_tools__acme_153862de`.
@alt-glitch
alt-glitch merged commit 0761031 into main Sep 22, 2026
34 checks passed
@alt-glitch
alt-glitch deleted the fix/portable-plugin-namespace branch September 22, 2026 16:35
teknium1 added a commit that referenced this pull request Sep 22, 2026
teknium1 added a commit that referenced this pull request Sep 22, 2026
chelsealong added a commit to chelsealong/hermes-agent that referenced this pull request Sep 26, 2026
…search#119263 naming and the new mcp_rpc_helpers write-guard

- hermes_cli/mcp_config.py: keep _get_mcp_servers() native-only (main's
  tui_gateway RPC layer and dashboard now use its result as their own
  native baseline for plugin-ownership tracking, so merging portables
  into it there would silently mask which entries are plugin-owned).
  Add _get_visible_mcp_servers(), which reuses
  tui_gateway.mcp_rpc_helpers.server_configs_with_sources() for the
  merge, and route every hermes-mcp-subcommand visibility lookup
  through it instead.
- tui_gateway/methods_tools.py: main independently rewrote
  mcp.servers.* (add/list/set_api_key/remove/oauth.*) around
  _mcp_server_rows()/_mcp_plugin_write_error(), which already covers
  the exact bug our commit 4c7b0ed guarded against. Dropped our
  superseded patch for this file (now byte-identical to main).
- Updated portable-server test fixtures from the old
  agent-plugin-<slug>-<sha8>__<server> shape to the post-NousResearch#119263 plain
  mcp.json name, and added get_portable_mcp_server_plugins() to the
  fake PluginManager the tests use (now required by
  server_configs_with_sources()).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have tool/mcp MCP client and OAuth type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant