Conversation
## Motivation and Context
`MCP::Icon.new` checks an icon against the specification's `Icon` type, but the places that take icons never
saw that check when an application passed a Hash: `Server.new(icons:)`, the class-level `icons` of a tool,
prompt, resource, and resource template, and `Resource.new(icons:)` and `ResourceTemplate.new(icons:)` stored
whatever they were given, and `to_h` serialized each element with its own `to_h`. A Hash is a supported way
to give an icon (the SDK's own tests pass wire-shaped ones), and on that path `{ src: "x", sizes: "51x51" }`
reached the client as a JSON string, while a Hash written with the keyword names, such as
`{ mime_type: "image/png", src: "x" }`, was emitted with the key `mime_type`, which the wire `Icon` does not have.
The TypeScript SDK types those places as `Icon[]` and the Python SDK's models hold `list[Icon]`, so neither lets
an unchecked shape through there.
Every place that takes icons now passes them through `Icon.from_list`, which keeps `nil`, converts each element
of an Array with `Icon.from`, and refuses anything else. `Icon.from` returns an `Icon` as it is, builds one from
a Hash, and refuses any other object. A Hash may use the keyword names of `Icon.new` or the wire name `mimeType`,
as Symbols or Strings, so both spellings serialize the same way; a key outside the `Icon` type, or one member
given under two spellings, is refused rather than emitted. A rejected element is reported with its position
(`icons[1]: ...`) and the message `Icon.new` would give, at definition time, where the annotations of a tool
or resource are already built from a Hash the same way. The docs gain an Icons page describing `MCP::Icon`,
the Hash form, and where icons attach.
The page sits right after the Resources page in the navigation, so the pages that followed move down by one.
## How Has This Been Tested?
New tests in `test/mcp/icon_test.rb`, `test/mcp/tool_test.rb`, `test/mcp/prompt_test.rb`, `test/mcp/resource_test.rb`,
`test/mcp/resource_template_test.rb`, and `test/mcp/server_test.rb`. Against the previous library, a Hash icon with
a String `sizes` was serialized as given and a `mime_type:` key was emitted as `mime_type`.
## Breaking Changes
An element of `icons` that is neither an `MCP::Icon` nor a Hash is refused with `ArgumentError`, as is
an `icons` that is neither `nil` nor an Array; a Hash icon with a key that is not a Symbol or a String,
a key outside the `Icon` type, a member given under two spellings, or a value `MCP::Icon.new` rejects now raises
at definition time, and through `Server#icons=`, instead of being serialized. A Hash written with
the keyword names now serializes with the wire names. The `icons` readers of a server, tool, prompt, resource,
and resource template return a new, frozen Array of `MCP::Icon` instances where they returned the Array and
the Hashes as given, so a later change to the Array given is no longer seen, and adding to the Array read back
raises `FrozenError` instead of slipping past the checks.
atesgoral
approved these changes
Oct 1, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation and Context
MCP::Icon.newchecks an icon against the specification'sIcontype, but the places that take icons never saw that check when an application passed a Hash:Server.new(icons:), the class-leveliconsof a tool, prompt, resource, and resource template, andResource.new(icons:)andResourceTemplate.new(icons:)stored whatever they were given, andto_hserialized each element with its ownto_h. A Hash is a supported way to give an icon (the SDK's own tests pass wire-shaped ones), and on that path{ src: "x", sizes: "51x51" }reached the client as a JSON string, while a Hash written with the keyword names, such as{ mime_type: "image/png", src: "x" }, was emitted with the keymime_type, which the wireIcondoes not have. The TypeScript SDK types those places asIcon[]and the Python SDK's models holdlist[Icon], so neither lets an unchecked shape through there.Every place that takes icons now passes them through
Icon.from_list, which keepsnil, converts each element of an Array withIcon.from, and refuses anything else.Icon.fromreturns anIconas it is, builds one from a Hash, and refuses any other object. A Hash may use the keyword names ofIcon.newor the wire namemimeType, as Symbols or Strings, so both spellings serialize the same way; a key outside theIcontype, or one member given under two spellings, is refused rather than emitted. A rejected element is reported with its position (icons[1]: ...) and the messageIcon.newwould give, at definition time, where the annotations of a tool or resource are already built from a Hash the same way. The docs gain an Icons page describingMCP::Icon, the Hash form, and where icons attach.The page sits right after the Resources page in the navigation, so the pages that followed move down by one.
How Has This Been Tested?
New tests in
test/mcp/icon_test.rb,test/mcp/tool_test.rb,test/mcp/prompt_test.rb,test/mcp/resource_test.rb,test/mcp/resource_template_test.rb, andtest/mcp/server_test.rb. Against the previous library, a Hash icon with a Stringsizeswas serialized as given and amime_type:key was emitted asmime_type.Breaking Changes
An element of
iconsthat is neither anMCP::Iconnor a Hash is refused withArgumentError, as is aniconsthat is neithernilnor an Array; a Hash icon with a key that is not a Symbol or a String, a key outside theIcontype, a member given under two spellings, or a valueMCP::Icon.newrejects now raises at definition time, and throughServer#icons=, instead of being serialized. A Hash written with the keyword names now serializes with the wire names. Theiconsreaders of a server, tool, prompt, resource, and resource template return a new, frozen Array ofMCP::Iconinstances where they returned the Array and the Hashes as given, so a later change to the Array given is no longer seen, and adding to the Array read back raisesFrozenErrorinstead of slipping past the checks.Types of changes
Checklist