hermes - ✅(Solved) Fix Normalize nullable MCP tool schemas and avoid list_changed RPC races [1 pull requests, 1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#16764Fetched 2026-04-28 06:50:52
View on GitHub
Comments
1
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
labeled ×4commented ×1cross-referenced ×1

Fix Action

Fix / Workaround

A local patch adds/updates tests covering:

PR fix notes

PR #16766: fix(mcp): normalize nullable tool schemas

Description (problem / solution / changelog)

Summary

  • normalize MCP/Pydantic nullable anyOf/oneOf optional parameter schemas to provider-safe non-null branches
  • preserve nullable intent as nullable: true so runtime argument coercion can turn model-emitted "null" into JSON null / Python None
  • add Anthropic adapter normalization as a final guard before sending input_schema
  • serialize MCP client-initiated RPCs per server and make tools/list_changed refresh asynchronous to avoid stdio races/stale handlers
  • add regression coverage for nullable schema normalization, nullable argument coercion, and existing schema repair behavior

Fixes #16764

Test Plan

python -m pytest tests/agent/test_anthropic_adapter.py::TestConvertTools tests/tools/test_mcp_tool.py::TestSchemaConversion -o 'addopts=' -q
# 20 passed

python -m py_compile agent/anthropic_adapter.py tools/mcp_tool.py tests/agent/test_anthropic_adapter.py tests/tools/test_mcp_tool.py
# passed

python -m pytest tests/run_agent/test_tool_arg_coercion.py tests/tools/test_mcp_tool.py::TestSchemaConversion -o 'addopts=' -q
# 70 passed

python -m py_compile model_tools.py tools/mcp_tool.py tests/run_agent/test_tool_arg_coercion.py tests/tools/test_mcp_tool.py
# passed

python -m pytest tests/tools/test_mcp_tool.py tests/run_agent/test_tool_arg_coercion.py tests/tools/test_schema_sanitizer.py -o 'addopts=' -q
# 252 passed

Changed files

  • agent/anthropic_adapter.py (modified, +46/-1)
  • model_tools.py (modified, +33/-4)
  • tests/agent/test_anthropic_adapter.py (modified, +30/-0)
  • tests/run_agent/test_tool_arg_coercion.py (modified, +30/-1)
  • tests/tools/test_mcp_tool.py (modified, +188/-7)
  • tools/mcp_tool.py (modified, +129/-22)

Code Example

{
  "type": "object",
  "properties": {
    "workdir": {
      "anyOf": [{"type": "string"}, {"type": "null"}],
      "default": null,
      "description": "Optional working directory"
    }
  }
}

---

{
  "type": "object",
  "properties": {
    "workdir": {
      "type": "string",
      "default": null,
      "description": "Optional working directory"
    }
  }
}

---

python -m pytest tests/agent/test_anthropic_adapter.py::TestConvertTools tests/tools/test_mcp_tool.py::TestSchemaConversion -o 'addopts=' -q
# 20 passed

python -m py_compile agent/anthropic_adapter.py tools/mcp_tool.py tests/agent/test_anthropic_adapter.py tests/tools/test_mcp_tool.py
# passed
RAW_BUFFERClick to expand / collapse

Bug Description

MCP/Pydantic tool schemas that encode optional parameters as nullable unions can be rejected by some provider adapters, most visibly Anthropic, before the model can run any tool call.

Example shape emitted by MCP/Pydantic:

{
  "type": "object",
  "properties": {
    "workdir": {
      "anyOf": [{"type": "string"}, {"type": "null"}],
      "default": null,
      "description": "Optional working directory"
    }
  }
}

Anthropic tool input schemas should not receive the null branch. Optionality is already represented by the parent object's required list, so Hermes should normalize the schema to:

{
  "type": "object",
  "properties": {
    "workdir": {
      "type": "string",
      "default": null,
      "description": "Optional working directory"
    }
  }
}

During the same investigation, MCP tools/list_changed notifications can also race with active stdio JSON-RPC requests. The notification handler currently refreshes tools synchronously, which can interleave list_tools with normal call_tool/resource/prompt RPCs on the same stdio session and leave user-visible tool calls timing out.

Expected Behavior

  • Hermes accepts MCP/Pydantic optional nullable schemas and converts them into provider-safe tool schemas.
  • Optional fields remain optional via the parent required array.
  • MCP list refreshes should not wedge the session or transiently unregister tools while an agent turn is using them.

Actual Behavior

  • Nullable anyOf / oneOf branches can be forwarded into Anthropic input_schema.
  • Anthropic rejects the tool schema before the agent can execute tools.
  • Dynamic MCP tools/list_changed refreshes can run concurrently with user-triggered stdio RPCs, causing timeouts/stale tool races.

Reproduction Notes

  1. Register/use an MCP server whose tool input schema includes optional fields represented as anyOf: [{...}, {"type": "null"}] or oneOf: [{...}, {"type": "null"}].
  2. Use an Anthropic-backed model/tool adapter.
  3. Trigger a turn where Hermes sends that tool schema to the provider.
  4. Provider validation fails before tool execution.

For the stdio race, use an MCP server that emits tools/list_changed shortly after initialize while the agent is starting or calling another MCP tool. mongodb-mcp-server is one observed example.

Proposed Fix

  • Normalize MCP input schemas by collapsing nullable anyOf/oneOf unions to the single non-null branch, preserving metadata (title, description, default, examples).
  • Apply the same normalization in the Anthropic adapter as a final provider-side guard.
  • Serialize client-initiated MCP RPCs per server with a lock.
  • Handle tools/list_changed refresh asynchronously and avoid deregistering tools before re-registering replacements, reducing stale-handler windows.

Validation

A local patch adds/updates tests covering:

  • nullable optional field collapse in MCP schema normalization
  • nested nullable array item collapse
  • Anthropic tool conversion stripping nullable unions
  • existing MCP schema shape repair behavior

Local commands run:

python -m pytest tests/agent/test_anthropic_adapter.py::TestConvertTools tests/tools/test_mcp_tool.py::TestSchemaConversion -o 'addopts=' -q
# 20 passed

python -m py_compile agent/anthropic_adapter.py tools/mcp_tool.py tests/agent/test_anthropic_adapter.py tests/tools/test_mcp_tool.py
# passed

extent analysis

TL;DR

Normalize MCP input schemas by collapsing nullable anyOf/oneOf unions to the single non-null branch to prevent provider validation failures.

Guidance

  • Identify and update MCP input schemas to remove nullable unions, preserving metadata like title, description, default, and examples.
  • Apply schema normalization in the Anthropic adapter as a final provider-side guard to ensure compatibility.
  • Implement asynchronous handling of tools/list_changed refreshes to avoid deregistering tools before re-registering replacements, reducing stale-handler windows.
  • Serialize client-initiated MCP RPCs per server with a lock to prevent concurrent modifications.

Example

# Example of normalized schema
{
  "type": "object",
  "properties": {
    "workdir": {
      "type": "string",
      "default": null,
      "description": "Optional working directory"
    }
  }
}

Notes

The proposed fix involves normalizing MCP input schemas and applying the same normalization in the Anthropic adapter. Additionally, handling tools/list_changed refreshes asynchronously and serializing client-initiated MCP RPCs per server with a lock can help prevent concurrent modifications and reduce stale-handler windows.

Recommendation

Apply the proposed fix by normalizing MCP input schemas and implementing asynchronous handling of tools/list_changed refreshes, as this approach addresses the root cause of the issue and ensures compatibility with the Anthropic adapter.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING