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.
This commit is contained in:
Koichi ITO 2026-01-12 09:39:18 +09:00
commit bd416c7a5f
10 changed files with 163 additions and 5 deletions

View file

@ -20,7 +20,7 @@ module MCP
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

View file

@ -19,7 +19,7 @@ module MCP
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

View file

@ -19,7 +19,7 @@ module MCP
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

View file

@ -290,7 +290,7 @@ module MCP
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,

View file

@ -21,7 +21,7 @@ module MCP
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,

View file

@ -195,5 +195,24 @@ module MCP
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

View file

@ -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

40
test/mcp/resource_test.rb Normal file
View file

@ -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

View file

@ -895,6 +895,46 @@ module MCP
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 = {

View file

@ -53,6 +53,25 @@ module MCP
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 = {