From bd416c7a5fee7875c41d826d152fbd101f924c5e Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Mon, 12 Jan 2026 09:39:18 +0900 Subject: [PATCH] Omit `icons` from responses when empty or nil Previously, the `icons` parameter was always included in responses (as an empty array) even when not configured. This differs from the response representation in MCP Ruby SDK 0.4.0. Since `icons` is optional, it should be omitted from responses when unused to reduce unnecessary context window usage. https://modelcontextprotocol.io/specification/2025-11-25/schema#implementation This fix applies to `Tool#to_h`, `Prompt#to_h`, `Resource#to_h`, `ResourceTemplate#to_h`, and `Server#server_info`. It doesn't seem critical, but if there are no other issues, it would be good to cut 0.5.1 bugfix release soon. --- lib/mcp/prompt.rb | 2 +- lib/mcp/resource.rb | 2 +- lib/mcp/resource_template.rb | 2 +- lib/mcp/server.rb | 2 +- lib/mcp/tool.rb | 2 +- test/mcp/prompt_test.rb | 19 ++++++++++++++ test/mcp/resource_template_test.rb | 40 ++++++++++++++++++++++++++++++ test/mcp/resource_test.rb | 40 ++++++++++++++++++++++++++++++ test/mcp/server_test.rb | 40 ++++++++++++++++++++++++++++++ test/mcp/tool_test.rb | 19 ++++++++++++++ 10 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 test/mcp/resource_template_test.rb create mode 100644 test/mcp/resource_test.rb diff --git a/lib/mcp/prompt.rb b/lib/mcp/prompt.rb index 9445cf7..128ee47 100644 --- a/lib/mcp/prompt.rb +++ b/lib/mcp/prompt.rb @@ -20,7 +20,7 @@ def to_h name: name_value, title: title_value, description: description_value, - icons: icons&.map(&:to_h), + icons: icons_value&.then { |icons| icons.empty? ? nil : icons.map(&:to_h) }, arguments: arguments_value&.map(&:to_h), _meta: meta_value, }.compact diff --git a/lib/mcp/resource.rb b/lib/mcp/resource.rb index 95b74b0..6f06d36 100644 --- a/lib/mcp/resource.rb +++ b/lib/mcp/resource.rb @@ -19,7 +19,7 @@ def to_h name: name, title: title, description: description, - icons: icons.map(&:to_h), + icons: icons&.then { |icons| icons.empty? ? nil : icons.map(&:to_h) }, mimeType: mime_type, }.compact end diff --git a/lib/mcp/resource_template.rb b/lib/mcp/resource_template.rb index bfb3c54..871600e 100644 --- a/lib/mcp/resource_template.rb +++ b/lib/mcp/resource_template.rb @@ -19,7 +19,7 @@ def to_h name: name, title: title, description: description, - icons: icons.map(&:to_h), + icons: icons&.then { |icons| icons.empty? ? nil : icons.map(&:to_h) }, mimeType: mime_type, }.compact end diff --git a/lib/mcp/server.rb b/lib/mcp/server.rb index 74c014d..e5b4711 100644 --- a/lib/mcp/server.rb +++ b/lib/mcp/server.rb @@ -290,7 +290,7 @@ def default_capabilities def server_info @server_info ||= { description: description, - icons: icons, + icons: icons&.then { |icons| icons.empty? ? nil : icons.map(&:to_h) }, name: name, title: title, version: version, diff --git a/lib/mcp/tool.rb b/lib/mcp/tool.rb index b1d2ad0..6c86ce5 100644 --- a/lib/mcp/tool.rb +++ b/lib/mcp/tool.rb @@ -21,7 +21,7 @@ def to_h name: name_value, title: title_value, description: description_value, - icons: icons&.map(&:to_h), + icons: icons_value&.then { |icons| icons.empty? ? nil : icons.map(&:to_h) }, inputSchema: input_schema_value.to_h, outputSchema: @output_schema_value&.to_h, annotations: annotations_value&.to_h, diff --git a/test/mcp/prompt_test.rb b/test/mcp/prompt_test.rb index 16bb584..a434e06 100644 --- a/test/mcp/prompt_test.rb +++ b/test/mcp/prompt_test.rb @@ -195,5 +195,24 @@ class NoArgumentsPrompt < Prompt assert_equal expected, prompt.to_h end + + test "#to_h does not have `:icons` key when icons is empty" do + prompt = Prompt.define( + name: "prompt_without_icons", + description: "a prompt without icons", + ) + + refute prompt.to_h.key?(:icons) + end + + test "#to_h does not have `:icons` key when icons is nil" do + prompt = Prompt.define( + name: "prompt_without_icons", + description: "a prompt without icons", + icons: nil, + ) + + refute prompt.to_h.key?(:icons) + end end end diff --git a/test/mcp/resource_template_test.rb b/test/mcp/resource_template_test.rb new file mode 100644 index 0000000..6b8a565 --- /dev/null +++ b/test/mcp/resource_template_test.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "test_helper" + +module MCP + class ResourceTemplateTest < ActiveSupport::TestCase + test "#to_h does not have `:icons` key when icons is empty" do + resource_template = ResourceTemplate.new( + uri_template: "file:///{path}", + name: "resource_template_without_icons", + description: "a resource template without icons", + ) + + refute resource_template.to_h.key?(:icons) + end + + test "#to_h does not have `:icons` key when icons is nil" do + resource_template = ResourceTemplate.new( + uri_template: "file:///{path}", + name: "resource_template_without_icons", + description: "a resource template without icons", + icons: nil, + ) + + refute resource_template.to_h.key?(:icons) + end + + test "#to_h includes icons when present" do + resource_template = ResourceTemplate.new( + uri_template: "file:///{path}", + name: "resource_template_with_icons", + description: "a resource template with icons", + icons: [Icon.new(mime_type: "image/png", sizes: ["48x48"], src: "https://example.com", theme: "light")], + ) + expected_icons = [{ mimeType: "image/png", sizes: ["48x48"], src: "https://example.com", theme: "light" }] + + assert_equal expected_icons, resource_template.to_h[:icons] + end + end +end diff --git a/test/mcp/resource_test.rb b/test/mcp/resource_test.rb new file mode 100644 index 0000000..9e78012 --- /dev/null +++ b/test/mcp/resource_test.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "test_helper" + +module MCP + class ResourceTest < ActiveSupport::TestCase + test "#to_h does not have `:icons` key when icons is empty" do + resource = Resource.new( + uri: "file:///test.txt", + name: "resource_without_icons", + description: "a resource without icons", + ) + + refute resource.to_h.key?(:icons) + end + + test "#to_h does not have `:icons` key when icons is nil" do + resource = Resource.new( + uri: "file:///test.txt", + name: "resource_without_icons", + description: "a resource without icons", + icons: nil, + ) + + refute resource.to_h.key?(:icons) + end + + test "#to_h includes icons when present" do + resource = Resource.new( + uri: "file:///test.txt", + name: "resource_with_icons", + description: "a resource with icons", + icons: [Icon.new(mime_type: "image/png", sizes: ["48x48"], src: "https://example.com", theme: "light")], + ) + expected_icons = [{ mimeType: "image/png", sizes: ["48x48"], src: "https://example.com", theme: "light" }] + + assert_equal expected_icons, resource.to_h[:icons] + end + end +end diff --git a/test/mcp/server_test.rb b/test/mcp/server_test.rb index b9ef6e5..0f54cae 100644 --- a/test/mcp/server_test.rb +++ b/test/mcp/server_test.rb @@ -895,6 +895,46 @@ class Example < Tool refute response[:result][:serverInfo].key?(:website_url) end + test "server response does not include icons when icons is empty" do + server = Server.new(name: "test_server") + request = { + jsonrpc: "2.0", + method: "initialize", + id: 1, + } + response = server.handle(request) + + refute response[:result][:serverInfo].key?(:icons) + end + + test "server response does not include icons when icons is nil" do + server = Server.new(name: "test_server", icons: nil) + request = { + jsonrpc: "2.0", + method: "initialize", + id: 1, + } + response = server.handle(request) + + refute response[:result][:serverInfo].key?(:icons) + end + + test "server response includes icons when icons is present" do + server = Server.new( + name: "test_server", + icons: [Icon.new(mime_type: "image/png", sizes: ["48x48"], src: "https://example.com", theme: "light")], + ) + request = { + jsonrpc: "2.0", + method: "initialize", + id: 1, + } + response = server.handle(request) + expected_icons = [{ mimeType: "image/png", sizes: ["48x48"], src: "https://example.com", theme: "light" }] + + assert_equal expected_icons, response[:result][:serverInfo][:icons] + end + test "server uses default version when not configured" do server = Server.new(name: "test_server") request = { diff --git a/test/mcp/tool_test.rb b/test/mcp/tool_test.rb index 3d8369c..ede3fbf 100644 --- a/test/mcp/tool_test.rb +++ b/test/mcp/tool_test.rb @@ -53,6 +53,25 @@ def call(message:, server_context: nil) refute tool.to_h.key?(:title) end + test "#to_h does not have `:icons` key when icons is empty" do + tool = Tool.define( + name: "tool_without_icons", + description: "a tool without icons", + ) + + refute tool.to_h.key?(:icons) + end + + test "#to_h does not have `:icons` key when icons is nil" do + tool = Tool.define( + name: "tool_without_icons", + description: "a tool without icons", + icons: nil, + ) + + refute tool.to_h.key?(:icons) + end + test "#to_h includes annotations when present" do tool = TestTool expected_annotations = {