Skip to content

CallToolResult Serialization Format Violation #202

@bettercallsaulj

Description

@bettercallsaulj

What's the Issue

The handleCallTool function in mcp_server.cc was manually building a JSON-RPC response with an incorrect format that violates the MCP protocol specification.

Incorrect format produced:

{
  "content": "Hello, World!",
  "isError": false
}

Correct MCP format required:

{
  "content": [
    {
      "type": "text",
      "text": "Hello, World!"
    }
  ],
  "isError": false
}

The MCP specification requires content to be an array of content blocks, where each block has a type field (e.g., "text", "image") and corresponding data fields. The incorrect implementation was returning content as a plain string, causing MCP clients (like Claude.ai) to fail with "Error occurred during tool execution".

How to Reproduce

  1. Set up an MCP server using gopher-mcp/mcp-cpp-sdk
  2. Register a tool that returns text content:
    CallToolResult result;
    result.content.push_back(ExtendedContentBlock(TextContent("Hello")));
    result.isError = false;
    return result;
  3. Connect an MCP client (e.g., Claude.ai via MCP Inspector)
  4. Call the tool
  5. Expected: Tool returns successfully with content
  6. Actual: Client shows "Error occurred during tool execution" because it cannot parse the malformed response

How to Fix

Replace the manual response building with proper serialization using json::to_json():

Before (broken):

// Extract text content manually - WRONG
std::string content_text;
for (const auto& content_block : result.content) {
  if (holds_alternative<TextContent>(content_block)) {
    content_text += get<TextContent>(content_block).text;
  }
}

auto response_metadata = make<Metadata>()
    .add("content", content_text)
    .add("isError", result.isError)
    .build();

return jsonrpc::Response::success(request.id,
    jsonrpc::ResponseResult(response_metadata));

After (correct):

// Use proper MCP serialization
auto result_json = json::to_json(result);

return jsonrpc::Response::success(request.id,
    jsonrpc::ResponseResult(result_json));

The json::to_json(CallToolResult) function correctly serializes the result with:

  • content as an array of content blocks
  • Each block with proper type and data fields
  • isError boolean field

Files changed:

  • src/server/mcp_server.cc - Fix serialization in handleCallTool()

Tests added:

  • tests/json/test_mcp_serialization_extensive.cc
    • CallToolResultMcpFormatCompliance - Verifies single content block format
    • CallToolResultMultipleContentBlocks - Verifies multiple content blocks
    • CallToolResultWithError - Verifies error case serialization

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions