This commit implements optional JSON Schema validation for tool outputs according to the MCP specification:
- Add MCP::Tool::OutputSchema class mirroring InputSchema functionality
- Support output_schema in class definitions, Tool.define, and OutputSchema objects
- Include validate_result method for runtime validation
- Update MCP::Client::Tool to track output schemas
- Add comprehensive test coverage with 249 passing tests
- Update README with dedicated Tool Output Schemas section
- Follow MCP spec requirements for server/client validation
Since the optional `title` becomes `title: null` when not provided, the following error occurs.
This occurs with Claude Code 1.0.100.
```console
[DEBUG] MCP server "example": Connection failed after 1591ms: [
{
"code": "invalid_type",
"expected": "string",
"received": "null",
"path": [
"serverInfo",
"title"
],
"message": "Expected string, received null"
}
]
```
By adding `compact`, the `title` key will be removed if the optional `title` keyword argument is not specified.
According to the specification, `destructive_hint`, `idempotent_hint`, `open_world_hint`,
and `read_only_hint` of `ToolAnnotations` are optional with default values:
https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations
This PR sets their default values in accordance with the specification.
Additionally, for readability, the hints have been reordered to follow the specification.
The tests related to `MCP::Tool::Annotations` will be extracted from the `MCP::ToolTest` test class
into a dedicated `MCP::Tool::AnnotationsTest` unit test class as a testing pair.
This improves test clarity, file level test independence, and file level execution speed.
It also ensures consistent placement as a testing pair under `MCP::Tool::*`:
```console
$ ls lib/mcp/tool/
annotations.rb input_schema.rb response.rb
$ ls test/mcp/tool/
annotations_test.rb input_schema_test.rb response_test.rb
```
Follow-up to #109.
The introduction of `title` in #109 is still a draft and is not supported in the latest specification, `Version 2025-06-18 (latest)`.
In this PR, the README.md will be updated to mention this point and restore the references to the removed `annotations.title`.
This aims to inform users that, as of `Version 2025-06-18 (latest)`, the use of `annotations.title` is considered stable.
Additionally, separate validation for these parameters could be added.
Please refer to the following comment regarding `annotations.title` and `title`:
https://github.com/modelcontextprotocol/modelcontextprotocol/pull/663#issuecomment-2963519737
Both the combination of positional argument and naming does not feel
like Ruby.
This change makes `is_error` argument a keyword argument called `error`.
Thanks to this, the usage is more self-explanatory:
```ruby
response = Response.new([], error: true)
```
When defining a tool without any arguments taken, it should be allowed
to not have to define the `input_schema` at all, and the default value
for such a scenario should be `{ type: "object" }` as per the schema.
16d91abb68/schema/2025-06-18/schema.ts (L896-L900)
Update send_notification method to properly construct JSON-RPC notification objects with jsonrpc version and method fields.
This ensures notifications conform to the JSON-RPC 2.0 specification when sent over HTTP transport.
https://www.jsonrpc.org/specification#notification
Another goal is to standardise the `send_notification` interface.
Before the revision, it was sufficient to pass the method and parameters in the case of stdio.
However, in the case of Streamable, a hash based on the JSON-RPC specification must be passed.
Currently, MCP has three versions: 2024-11-05, 2025-03-26, and 2025-06-18.
- https://modelcontextprotocol.io/specification/2024-11-05/basic/transports (stdio and HTTP with SSE)
- https://modelcontextprotocol.io/specification/2025-03-26/basic/transports (stdio and Streamable HTTP)
- https://modelcontextprotocol.io/specification/2025-06-18/basic/transports (stdio and Streamable HTTP)
The newer specification supports stdio and Streamable HTTP as transport mechanisms.
Only the oldest version, 2024-11-05, supports stdin and HTTP with SSE,
but that version is now considered legacy.
Given the current behavior of the Ruby SDK, it likely makes more sense to set
the default protocol version to the latest one, 2025-06-18, rather than the oldest, 2024-11-05.
Ideally, the implementation should adjust its behavior based on the version,
but this PR is limited to updating the default protocol version only.
## Motivation and Context
Currently, MCP supports the following three protocol versions:
"2025-06-18", "2025-03-26", and "2024-11-05".
https://modelcontextprotocol.io/introduction
As shown in the changes to `test/mcp/configuration_test.rb`,
the `protocol_version` value is not currently being validated,
which allows unexpected `protocol_version` values to be accepted.
This PR ensures that only supported protocol versions are accepted.
## How Has This Been Tested?
Existing tests have been updated and new tests have been added.
## Breaking Changes
None. If it becomes an issue, it means a value that was not originally intended has been used.
## Additional context
While one possible approach might be to fall back to a default protocol version
when an invalid `protocol_version` value is provided,
this PR opts to reject invalid `protocol_version` values explicitly.
The reasoning is that encouraging users to provide intentional,
valid input is preferable to silently falling back to a default,
which could lead to unexpected behavior.
The reason for the change is that a NameError occurs when a POST request is made while the stream is closed because ModelContextProtocol is undefined.
I have also added test cases to check the behaviour when IOError and Errno::EPIPE occurs.