Portable plugin MCP tools keep their names instead of a hash (64-char provider cap) - #119263
Merged
Merged
Conversation
…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.
૮ >ﻌ< ა ci reviewran on b7d2106 — fix(plugins): a portable MCP server is named what its mcp.js debug infoCI timingsCI timings · View report · View jobWall time 6m1s vs 5m44s (+4.9%). 8 job(s) slower, 4 faster, 1 unchanged.
|
…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`.
8 of 9 tasks
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()).
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.
The model can read a portable plugin's tool names again: a portable MCP server is named exactly what its
mcp.jsoncalls it, the same rule as aconfig.yamlserver.What the model saw
Every tool of a portable (Agent Plugins v1) package registered under a name like this:
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:
Seen live on the x64 desktop with the NVIDIA plugin: 12 tools, every one a hash,
agent.logfull ofexceeds 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-datadirectories 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-<slug>-<sha8>"] --> S1["<ns>__<server>"] --> T1["mcp__…__n_3fa9c2d1 (clamped)"] end subgraph after M2["mcp.json server name"] --> S2["<server>"] --> T2["mcp__nvidia_app__nvapp_client_get_driver_status"] endnvidia-appin a portable pluginagent-plugin-hermes-nvidia-72eb26b1__nvidia-appnvidia-appnvidia-appin the user'sconfig.yamlnvidia-appnvidia-app(unchanged; same rule now)nvapp_client_get_driver_statusplugin-data/directoryagent-plugin-<slug>-<sha8>config.yamlwins, then first-loaded plugin; warning names bothOne 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_componentsnow pins the relationship:server name equals the
mcp.jsonname, and a long vendor tool name reaches the wire without a hash suffix.Red on
main, green here.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 onmain.tests/hermes_cli/test_plugins.py97 pass,test_agent_plugins.py43,tests/tools/test_mcp_liveness*.py,tests/tui_gateway/test_plugins_manage_install.py.test_plugin_validate.pyhas one failure that is identical onmain(unrelated,test_portable_validation_fails_orphan_and_reports_availability).HERMES_HOME, real model turn: a portable plugin with serveracme-toolsand toolacme_client_get_driver_status_report. Onmainit registers asmcp__agent_plugin_acme_tools_88e7456f__acme_tools__acme_153862de; on this branch asmcp__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 withtool_search, called it, andreported that exact name.
Follow-ups (not here)
nvapp_client_…); that is the plugin author's naming.