mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
feat(mcp): adopt mcp__server__tool naming convention
Port from anomalyco/opencode#33533. Native MCP tools now register as mcp__<server>__<tool> (double-underscore delimiter) instead of mcp_<server>_<tool>, aligning with the convention used by Claude Code, Codex, and OpenCode. The double-underscore delimiter disambiguates the server/tool boundary even when either component contains underscores (the single-underscore form was ambiguous, which is why is_mcp_tool_parallel_safe already had to track provenance in a side-map). It also unifies native registration with the Anthropic-OAuth wire form (_MCP_TOOL_PREFIX = 'mcp__'), so the single->double promotion that path performed is now a no-op for native tools while still handling legacy replayed names. - tools/mcp_tool.py: add MCP_TOOL_NAME_PREFIX + mcp_prefixed_tool_name() helper; route _convert_mcp_schema, utility schemas, refresh stale-set, and the parallel-safe prefix gate through it - agent/transports/codex_event_projector.py: mirror convention in the deterministic call_id input for MCP server-executed tool calls - tests: update produced-name assertions to the new convention
This commit is contained in:
parent
5986cdd380
commit
e01f58ff1f
5 changed files with 171 additions and 152 deletions
|
|
@ -1681,8 +1681,7 @@ class MCPServerTask:
|
|||
# notifications. Tools absent from the fresh list are no longer
|
||||
# callable, so remove only those stale registry entries first.
|
||||
stale_tool_names = old_tool_names - {
|
||||
f"mcp_{sanitize_mcp_name_component(self.name)}_"
|
||||
f"{sanitize_mcp_name_component(tool.name)}"
|
||||
mcp_prefixed_tool_name(self.name, tool.name)
|
||||
for tool in new_mcp_tools
|
||||
}
|
||||
for tool_name in stale_tool_names:
|
||||
|
|
@ -3995,6 +3994,27 @@ def sanitize_mcp_name_component(value: str) -> str:
|
|||
return re.sub(r"[^A-Za-z0-9_]", "_", str(value or ""))
|
||||
|
||||
|
||||
# Native MCP tool-name prefix. Hermes uses the ``mcp__<server>__<tool>``
|
||||
# convention shared by Claude Code, Codex, and OpenCode (anomalyco/opencode
|
||||
# #33533). The double-underscore delimiter disambiguates the server/tool
|
||||
# boundary even when either component contains underscores, and matches the
|
||||
# naming models are trained on. It also aligns native registration with the
|
||||
# Anthropic-OAuth wire form (``_MCP_TOOL_PREFIX`` in anthropic_adapter.py),
|
||||
# removing the single->double rewrite that path previously had to perform.
|
||||
MCP_TOOL_NAME_PREFIX = "mcp__"
|
||||
_MCP_NAME_DELIM = "__"
|
||||
|
||||
|
||||
def mcp_prefixed_tool_name(server_name: str, tool_name: str) -> str:
|
||||
"""Build the registry/wire name for an MCP tool.
|
||||
|
||||
Produces ``mcp__<sanitizedServer>__<sanitizedTool>``.
|
||||
"""
|
||||
safe_server = sanitize_mcp_name_component(server_name)
|
||||
safe_tool = sanitize_mcp_name_component(tool_name)
|
||||
return f"{MCP_TOOL_NAME_PREFIX}{safe_server}{_MCP_NAME_DELIM}{safe_tool}"
|
||||
|
||||
|
||||
def _convert_mcp_schema(server_name: str, mcp_tool) -> dict:
|
||||
"""Convert an MCP tool listing to the Hermes registry schema format.
|
||||
|
||||
|
|
@ -4006,9 +4026,7 @@ def _convert_mcp_schema(server_name: str, mcp_tool) -> dict:
|
|||
Returns:
|
||||
A dict suitable for ``registry.register(schema=...)``.
|
||||
"""
|
||||
safe_tool_name = sanitize_mcp_name_component(mcp_tool.name)
|
||||
safe_server_name = sanitize_mcp_name_component(server_name)
|
||||
prefixed_name = f"mcp_{safe_server_name}_{safe_tool_name}"
|
||||
prefixed_name = mcp_prefixed_tool_name(server_name, mcp_tool.name)
|
||||
return {
|
||||
"name": prefixed_name,
|
||||
"description": mcp_tool.description or f"MCP tool {mcp_tool.name} from {server_name}",
|
||||
|
|
@ -4022,11 +4040,10 @@ def _build_utility_schemas(server_name: str) -> List[dict]:
|
|||
Returns a list of (schema, handler_factory_name) tuples encoded as dicts
|
||||
with keys: schema, handler_key.
|
||||
"""
|
||||
safe_name = sanitize_mcp_name_component(server_name)
|
||||
return [
|
||||
{
|
||||
"schema": {
|
||||
"name": f"mcp_{safe_name}_list_resources",
|
||||
"name": mcp_prefixed_tool_name(server_name, "list_resources"),
|
||||
"description": f"List available resources from MCP server '{server_name}'",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
|
|
@ -4037,7 +4054,7 @@ def _build_utility_schemas(server_name: str) -> List[dict]:
|
|||
},
|
||||
{
|
||||
"schema": {
|
||||
"name": f"mcp_{safe_name}_read_resource",
|
||||
"name": mcp_prefixed_tool_name(server_name, "read_resource"),
|
||||
"description": f"Read a resource by URI from MCP server '{server_name}'",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
|
|
@ -4054,7 +4071,7 @@ def _build_utility_schemas(server_name: str) -> List[dict]:
|
|||
},
|
||||
{
|
||||
"schema": {
|
||||
"name": f"mcp_{safe_name}_list_prompts",
|
||||
"name": mcp_prefixed_tool_name(server_name, "list_prompts"),
|
||||
"description": f"List available prompts from MCP server '{server_name}'",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
|
|
@ -4065,7 +4082,7 @@ def _build_utility_schemas(server_name: str) -> List[dict]:
|
|||
},
|
||||
{
|
||||
"schema": {
|
||||
"name": f"mcp_{safe_name}_get_prompt",
|
||||
"name": mcp_prefixed_tool_name(server_name, "get_prompt"),
|
||||
"description": f"Get a prompt by name from MCP server '{server_name}'",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
|
|
@ -4525,15 +4542,15 @@ def discover_mcp_tools() -> List[str]:
|
|||
def is_mcp_tool_parallel_safe(tool_name: str) -> bool:
|
||||
"""Check if an MCP tool belongs to a server that supports parallel tool calls.
|
||||
|
||||
MCP tool names follow the pattern ``mcp_{server}_{tool}``, but that string
|
||||
shape is ambiguous when server names contain underscores. Use the exact
|
||||
server provenance captured at registration time rather than prefix
|
||||
MCP tool names follow the pattern ``mcp__{server}__{tool}``, but that
|
||||
string shape is ambiguous when server names contain underscores. Use the
|
||||
exact server provenance captured at registration time rather than prefix
|
||||
matching, then check whether that server's config includes
|
||||
``supports_parallel_tool_calls: true``.
|
||||
|
||||
Returns False for non-MCP tools or tools from servers without the flag.
|
||||
"""
|
||||
if not tool_name.startswith("mcp_"):
|
||||
if not tool_name.startswith(MCP_TOOL_NAME_PREFIX):
|
||||
return False
|
||||
with _lock:
|
||||
server_name = _mcp_tool_server_names.get(tool_name)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue