From df77c27d92659ed5e1bcc4b95ffc3fe62a2b4caf Mon Sep 17 00:00:00 2001 From: gophergogo Date: Tue, 30 Dec 2025 15:42:51 -0800 Subject: [PATCH 1/7] Add clarifying comment for ResponseResult variant usage (#144) Document that tools and prompts use vector types directly in the ResponseResult variant since they have simpler serialization needs. --- include/mcp/types.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/mcp/types.h b/include/mcp/types.h index 230fd6fb..22d9441e 100644 --- a/include/mcp/types.h +++ b/include/mcp/types.h @@ -493,6 +493,7 @@ struct Request { // Generic result type for responses // Note: ListResourcesResult is defined outside jsonrpc namespace but used here +// For tools and prompts, we use the vector types directly since they're simpler using ResponseResult = variant Date: Tue, 30 Dec 2025 15:43:04 -0800 Subject: [PATCH 2/7] Simplify server response serialization using ResponseResult variant (#144) Remove intermediate metadata conversion in handleListResources, handleListTools, and handleListPrompts. Return results directly through ResponseResult variant which supports ListResourcesResult, vector, and vector types. --- src/server/mcp_server.cc | 46 ++++++++-------------------------------- 1 file changed, 9 insertions(+), 37 deletions(-) diff --git a/src/server/mcp_server.cc b/src/server/mcp_server.cc index b6e9d5f4..b4389065 100644 --- a/src/server/mcp_server.cc +++ b/src/server/mcp_server.cc @@ -897,23 +897,11 @@ jsonrpc::Response McpServer::handleListResources( } } - // Get resources from resource manager + // Get resources from resource manager and return directly + // ResponseResult variant supports ListResourcesResult auto result = resource_manager_->listResources(cursor); - - // Convert to response - // TODO: Serialize ListResourcesResult to ResponseResult - auto response_metadata = - make() - .add("resourceCount", static_cast(result.resources.size())) - .build(); - - if (result.nextCursor.has_value()) { - // Add nextCursor to the response - response_metadata["nextCursor"] = result.nextCursor.value(); - } - return jsonrpc::Response::success(request.id, - jsonrpc::ResponseResult(response_metadata)); + jsonrpc::ResponseResult(result)); } jsonrpc::Response McpServer::handleReadResource(const jsonrpc::Request& request, @@ -1003,18 +991,11 @@ jsonrpc::Response McpServer::handleUnsubscribe(const jsonrpc::Request& request, jsonrpc::Response McpServer::handleListTools(const jsonrpc::Request& request, SessionContext& session) { - // Get tools from tool registry + // Get tools from tool registry and return tools vector directly + // ResponseResult variant supports std::vector auto result = tool_registry_->listTools(); - - // Convert to response - // TODO: Serialize ListToolsResult to ResponseResult - auto response_metadata = - make() - .add("toolCount", static_cast(result.tools.size())) - .build(); - return jsonrpc::Response::success(request.id, - jsonrpc::ResponseResult(response_metadata)); + jsonrpc::ResponseResult(result.tools)); } jsonrpc::Response McpServer::handleCallTool(const jsonrpc::Request& request, @@ -1102,20 +1083,11 @@ jsonrpc::Response McpServer::handleListPrompts(const jsonrpc::Request& request, } } - // Get prompts from prompt registry + // Get prompts from prompt registry and return prompts vector directly + // ResponseResult variant supports std::vector auto result = prompt_registry_->listPrompts(cursor); - - // Convert to response - // TODO: Serialize ListPromptsResult to ResponseResult - auto response_metadata = - make() - .add("prompts", - std::string("Prompts list placeholder")) // Simplified - avoid - // nested metadata - .build(); - return jsonrpc::Response::success(request.id, - jsonrpc::ResponseResult(response_metadata)); + jsonrpc::ResponseResult(result.prompts)); } jsonrpc::Response McpServer::handleGetPrompt(const jsonrpc::Request& request, From 938b45c655b4c9b2890990e132f9e1a846f18ff3 Mon Sep 17 00:00:00 2001 From: gophergogo Date: Tue, 30 Dec 2025 15:43:24 -0800 Subject: [PATCH 3/7] Fix client response parsing to extract from ResponseResult variant (#144) Update listResources and listTools methods to properly extract results from ResponseResult variant. listResources extracts ListResourcesResult directly, while listTools extracts vector and wraps it in ListToolsResult. --- src/client/mcp_client.cc | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/client/mcp_client.cc b/src/client/mcp_client.cc index 1064576f..3af2e8de 100644 --- a/src/client/mcp_client.cc +++ b/src/client/mcp_client.cc @@ -699,11 +699,18 @@ std::future McpClient::listResources( if (response.error.has_value()) { result_promise->set_exception(std::make_exception_ptr( std::runtime_error(response.error->message))); + } else if (response.result.has_value()) { + // Extract ListResourcesResult from response + // ResponseResult variant directly contains ListResourcesResult + if (holds_alternative(response.result.value())) { + result_promise->set_value( + get(response.result.value())); + } else { + // Fallback: return empty result if type doesn't match + result_promise->set_value(ListResourcesResult()); + } } else { - // Parse ListResourcesResult from response - ListResourcesResult result; - // TODO: Parse response into result structure - result_promise->set_value(result); + result_promise->set_value(ListResourcesResult()); } } catch (...) { result_promise->set_exception(std::current_exception()); @@ -915,11 +922,16 @@ std::future McpClient::listTools( if (response.error.has_value()) { result_promise->set_exception(std::make_exception_ptr( std::runtime_error(response.error->message))); - } else { - // Parse ListToolsResult from response + } else if (response.result.has_value()) { + // Extract tools vector from response and wrap in ListToolsResult + // ResponseResult variant contains std::vector ListToolsResult result; - // TODO: Parse response into result structure + if (holds_alternative>(response.result.value())) { + result.tools = get>(response.result.value()); + } result_promise->set_value(result); + } else { + result_promise->set_value(ListToolsResult()); } } catch (...) { result_promise->set_exception(std::current_exception()); From 2881b9f09fee31033102d787db443711603aab5b Mon Sep 17 00:00:00 2001 From: gophergogo Date: Tue, 30 Dec 2025 15:48:28 -0800 Subject: [PATCH 4/7] Fix callTool response parsing to extract content and isError (#144) Parse the Metadata response from server to extract the content string and isError flag. Convert content string to TextContent wrapped in ExtendedContentBlock for proper CallToolResult structure. --- src/client/mcp_client.cc | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/client/mcp_client.cc b/src/client/mcp_client.cc index 3af2e8de..520ac425 100644 --- a/src/client/mcp_client.cc +++ b/src/client/mcp_client.cc @@ -989,11 +989,30 @@ std::future McpClient::callTool( if (response.error.has_value()) { result_promise->set_exception(std::make_exception_ptr( std::runtime_error(response.error->message))); - } else { - // Parse CallToolResult from response + } else if (response.result.has_value()) { + // Extract CallToolResult from response + // Server returns Metadata with "content" (string) and "isError" (bool) CallToolResult result; - // TODO: Parse response into result structure + if (holds_alternative(response.result.value())) { + auto metadata = get(response.result.value()); + // Extract content string and convert to TextContent + auto content_it = metadata.find("content"); + if (content_it != metadata.end() && + holds_alternative(content_it->second)) { + result.content.push_back( + ExtendedContentBlock(TextContent( + get(content_it->second)))); + } + // Extract isError flag + auto error_it = metadata.find("isError"); + if (error_it != metadata.end() && + holds_alternative(error_it->second)) { + result.isError = get(error_it->second); + } + } result_promise->set_value(result); + } else { + result_promise->set_value(CallToolResult()); } } catch (...) { result_promise->set_exception(std::current_exception()); From 664968096b8beba79161a13c7e36ef02f3beb983 Mon Sep 17 00:00:00 2001 From: gophergogo Date: Tue, 30 Dec 2025 15:56:00 -0800 Subject: [PATCH 5/7] Fix ResponseResult deserialization for tools without inputSchema (#144) The Tool type only requires "name" field, inputSchema is optional per MCP spec. Changed detection logic from checking (name && inputSchema) to (name && !uri) to distinguish tools from resources. --- src/json/json_serialization.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/json/json_serialization.cc b/src/json/json_serialization.cc index 6ae5dbc2..101274d2 100644 --- a/src/json/json_serialization.cc +++ b/src/json/json_serialization.cc @@ -542,8 +542,9 @@ jsonrpc::ResponseResult deserialize_ResponseResult(const JsonValue& json) { } return jsonrpc::ResponseResult(blocks); } - } else if (first.contains("name") && first.contains("inputSchema")) { - // Array of Tools + } else if (first.contains("name") && !first.contains("uri")) { + // Array of Tools - tools have "name" but not "uri" + // inputSchema is optional per MCP spec std::vector tools; size_t size = json.size(); for (size_t i = 0; i < size; ++i) { From ca41bffe7efd3f005eccd50ae85c1c49ef73066f Mon Sep 17 00:00:00 2001 From: gophergogo Date: Tue, 30 Dec 2025 20:42:25 -0800 Subject: [PATCH 6/7] Fix callTool and getPrompt argument serialization (#144) Arguments were being flattened as "arguments.key" instead of nested "arguments" object. Server looks for params["arguments"] which failed. Now serialize arguments as JSON string to support nested object format that server expects. Also add debug output when callTool returns isError=true. --- examples/mcp/mcp_example_client.cc | 3 +++ src/client/mcp_client.cc | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/examples/mcp/mcp_example_client.cc b/examples/mcp/mcp_example_client.cc index 33b889a1..f9718b0a 100644 --- a/examples/mcp/mcp_example_client.cc +++ b/examples/mcp/mcp_example_client.cc @@ -288,6 +288,9 @@ void demonstrateFeatures(McpClient& client, bool verbose) { } } std::cerr << std::endl; + } else { + std::cerr << "[DEMO] Calculator returned error, content size: " + << call_result.content.size() << std::endl; } } catch (const std::exception& e) { std::cerr << "[ERROR] Tool call failed: " << e.what() << std::endl; diff --git a/src/client/mcp_client.cc b/src/client/mcp_client.cc index 520ac425..c9192c87 100644 --- a/src/client/mcp_client.cc +++ b/src/client/mcp_client.cc @@ -963,10 +963,11 @@ std::future McpClient::callTool( auto params = make_metadata(); params["name"] = name; if (arguments.has_value()) { - // Arguments is a Metadata object, merge it - for (const auto& arg : arguments.value()) { - params["arguments." + arg.first] = arg.second; - } + // Convert arguments to JSON string for nested object support + // Server expects "arguments" as a nested JSON object which is stored + // as a JSON string in Metadata since MetadataValue doesn't support nesting + auto args_json = json::metadataToJson(arguments.value()); + params["arguments"] = args_json.toString(); } auto params_ptr = std::make_shared(std::move(params)); @@ -1099,10 +1100,11 @@ std::future McpClient::getPrompt( auto params = make_metadata(); params["name"] = name; if (arguments.has_value()) { - // Arguments is a Metadata object, merge it - for (const auto& arg : arguments.value()) { - params["arguments." + arg.first] = arg.second; - } + // Convert arguments to JSON string for nested object support + // Server expects "arguments" as a nested JSON object which is stored + // as a JSON string in Metadata since MetadataValue doesn't support nesting + auto args_json = json::metadataToJson(arguments.value()); + params["arguments"] = args_json.toString(); } auto params_ptr = std::make_shared(std::move(params)); From 4de8dcd0a491841b23f99168ff166a0898fbb007 Mon Sep 17 00:00:00 2001 From: gophergogo Date: Tue, 30 Dec 2025 20:49:43 -0800 Subject: [PATCH 7/7] make format CPP code to apply clang-format (#144) --- src/client/mcp_client.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client/mcp_client.cc b/src/client/mcp_client.cc index c9192c87..0f81aa09 100644 --- a/src/client/mcp_client.cc +++ b/src/client/mcp_client.cc @@ -1000,9 +1000,8 @@ std::future McpClient::callTool( auto content_it = metadata.find("content"); if (content_it != metadata.end() && holds_alternative(content_it->second)) { - result.content.push_back( - ExtendedContentBlock(TextContent( - get(content_it->second)))); + result.content.push_back(ExtendedContentBlock( + TextContent(get(content_it->second)))); } // Extract isError flag auto error_it = metadata.find("isError");