Skip to content

[Bug]: MCP sampling crashes on valid non-object JSON tool arguments #105551

Description

@Xuxyyy

Bug Description

When Hermes handles a server-initiated MCP sampling request with tools, the auxiliary LLM's tool-call arguments are parsed as JSON before Hermes builds an MCP ToolUseContent response.

If the arguments are syntactically valid JSON but the top-level value is not an object, such as [], null, 42, or true, Hermes passes a list or scalar into ToolUseContent.input. MCP SDK 2.0.0 requires that field to be a dictionary, so the sampling callback raises a Pydantic ValidationError and does not return the tool-use response to the MCP server.

Malformed JSON is already preserved as {"_raw": ...}. Valid non-object JSON falls through a gap in the same normalization path.

Steps to Reproduce

  1. Check out main at 6e2b8e070d28b1a3381a3fb290b6b8d6cce13cef.
  2. Configure an MCP sampling callback with tools enabled.
  3. Have the auxiliary LLM return a tool call whose arguments string is valid non-object JSON, for example [].
  4. Build the sampling tool-use response.

Minimal reproducer:

from mcp.types import ToolUseContent
from tools.mcp_tool_sampling import _parse_tool_call_arguments

raw = "[]"
ToolUseContent(
    type="tool_use",
    id="call_1",
    name="some_tool",
    input=_parse_tool_call_arguments("server", raw),
)

A focused callback-level regression test also reproduces all four inputs:

.venv/bin/pytest --confcutdir=tests/tools tests/tools/test_mcp_tool.py -q -k non_object_json_wrapped_as_raw

Result: four failures at tools/mcp_tool_sampling.py:153-155, each reporting that ToolUseContent.input must be a valid dictionary.

Expected Behavior

The sampling callback should always provide a dictionary to ToolUseContent.input. Valid JSON objects should remain objects. Valid non-object JSON should be preserved in a dictionary wrapper, consistent with the existing malformed-JSON behavior, so the MCP server receives a valid tool-use response.

Actual Behavior

The callback raises pydantic_core.ValidationError and fails before returning the MCP sampling response:

1 validation error for ToolUseContent
input
  Input should be a valid dictionary

Affected Component

Tools (MCP sampling)

Messaging Platform

N/A

Debug Report

Not applicable. This is a deterministic source-level reproduction against current main; it does not depend on local Hermes configuration, credentials, or runtime logs.

Environment

  • Operating system: macOS 26.5.2
  • Python: 3.11.15
  • Hermes: 0.21.1, commit 6e2b8e070d28b1a3381a3fb290b6b8d6cce13cef
  • MCP SDK: 2.0.0, as pinned by the repository

Root Cause Analysis

tools/mcp_tool_sampling.py:74-83 returns json.loads(args) directly for any syntactically valid JSON string. It checks whether non-string inputs are dictionaries, but it does not apply the same check to the parsed result.

tools/mcp_tool_sampling.py:153-155 then passes that value directly to ToolUseContent.input, whose MCP SDK 2.0.0 contract is dict[str, Any].

Proposed Fix

Parse string arguments, then return the parsed value only when it is a dictionary. Preserve all malformed or valid non-object inputs as {"_raw": original_value}. Add callback-level regression coverage for arrays, null, numbers, and booleans while keeping the existing valid-object and malformed-JSON behavior unchanged.

PR Offer

  • I'd like to fix this myself and submit a PR.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to havecomp/toolsTool registry, model_tools, toolsetstool/mcpMCP client and OAuthtype/bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions