Commit graph

459 commits

Author SHA1 Message Date
Koichi ITO
910d3ca0ec
Merge pull request #264 from koic/release_0_9_0 v0.9.0
Release 0.9.0
2026-03-20 18:31:22 +09:00
Koichi ITO
12922a6448 Release 0.9.0
Some new features and fixes have been added, and it seems like a good time to cut a release.
Subsequent feature proposals can continue to be incorporated into future releases.
2026-03-20 11:02:56 +09:00
Koichi ITO
09b4e30da6
Merge pull request #262 from koic/stdio_client_transport
Add `MCP::Client::Stdio` transport
2026-03-20 07:56:02 +09:00
Koichi ITO
21eef8df9a Add MCP::Client::Stdio transport
## Motivation and Context

The `MCP::Client` currently only supports HTTP transport (`MCP::Client::HTTP`).
To achieve parity with other MCP SDKs (Python, TypeScript, C#) which provide
stdio client transports for spawning and communicating with server subprocesses,
the Ruby SDK needs a stdio client transport.
https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#stdio

This enables building MCP clients that launch server processes and communicate
via stdin/stdout using newline-delimited JSON-RPC 2.0 messages.

## How Has This Been Tested?

Added tests for process spawning, protocol initialization handshake,
notification handling, error handling, and transport close lifecycle.

## Additional context

Enabled the `Lint/IncompatibleIoSelectWithFiberScheduler` cop in `.rubocop.yml`
to detect accidental use of `IO.select` instead of `IO#wait_readable`.
2026-03-19 12:41:27 +09:00
Koichi ITO
283c7a43e4
Merge pull request #260 from koic/use_missing_session_id_response_helper_in_handle_delete
Use `missing_session_id_response` method in `handle_delete`
2026-03-18 12:47:40 +09:00
Koichi ITO
75d03f334e Use missing_session_id_response method in handle_delete
## Motivation and Context

`handle_delete` had an inline error response for "Missing session ID" that
was identical to the existing `missing_session_id_response` helper method.
`handle_get` already uses this helper for the same purpose.

## How Has This Been Tested?

All existing tests pass. The behavior is unchanged.

## Breaking Change

None.
2026-03-18 12:17:23 +09:00
Koichi ITO
f4aab5cdf0
Merge pull request #258 from koic/use_mutex_protected_session_exists_predicate
Use mutex-protected `session_exists?` in `handle_regular_request`
2026-03-18 11:53:33 +09:00
Koichi ITO
be1bbc25c1 Use mutex-protected session_exists? in handle_regular_request
## Motivation and Context

`handle_regular_request` was checking `@sessions.key?(session_id)` directly
without holding `@mutex`, while concurrent threads could modify `@sessions`
via `cleanup_session` or `handle_delete`. This created a TOCTOU race where
the check could pass but the session could be deleted before subsequent use.

The class already provides a mutex-protected `session_exists?` helper, and
`handle_get` already uses it. This change makes `handle_regular_request`
consistent with `handle_get`.

## How Has This Been Tested?

Added a test that verifies `handle_regular_request` delegates to the
mutex-protected `session_exists?` helper instead of accessing
`@sessions` directly.

All existing tests pass.

## Breaking Change

None.
2026-03-18 04:31:08 +09:00
Koichi ITO
8823bd34ef
Merge pull request #257 from koic/return_404_for_invalid_session_id
Return 404 for invalid session ID in `handle_regular_request`
2026-03-18 04:26:57 +09:00
Koichi ITO
cf1d578433 Return 404 for invalid session ID in handle_regular_request
## Motivation and Context

The MCP specification requires that when a server receives a request containing
a session ID that is no longer valid, it MUST respond with HTTP 404 Not Found.
The `handle_get` path already returned 404 correctly, but `handle_regular_request`
(the POST path) was returning 400 "Invalid session ID" instead.

Ref: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management

> The server MAY terminate the session at any time, after which it MUST respond
> to requests containing that session ID with HTTP 404 Not Found.

## How Has This Been Tested?

Added a test for POST requests with an invalid session ID, verifying that the
response is 404 with "Session not found" error message. All existing tests pass.

## Breaking Change

POST requests with an invalid session ID now return HTTP 404 "Session not found"
instead of HTTP 400 "Invalid session ID". Clients that match on the 400 status
code or the old error message will need to be updated.

However, this change is considered a bug fix because it brings the behavior into compliance with
the MCP specification.
2026-03-17 02:29:30 +09:00
Koichi ITO
6e35d13479
Merge pull request #256 from koic/remove_dead_code_in_handle_regular_request
Remove dead code in `handle_regular_request`
2026-03-17 01:04:32 +09:00
Koichi ITO
628e5203d5
Merge pull request #255 from koic/use_autoload_to_defer_loading_of_unused_subsystems
Use `autoload` to defer loading of unused subsystems
2026-03-16 00:06:27 +09:00
Koichi ITO
95200b5467
Merge pull request #253 from koic/support_cors_and_accept_wildcard_for_browser_based_mcp_clients
Support CORS and Accept wildcard for browser-based MCP clients
2026-03-15 15:31:42 +09:00
Koichi ITO
9d9a1347d9 Remove dead code in handle_regular_request
Notifications never reach `handle_regular_request` because `handle_post`
checks `notification?(body)` first and returns 202 via `handle_accepted`.
Since only notifications cause `handle_json` to return `nil`, `response`
is always non-`nil` here.
2026-03-13 18:26:33 +09:00
Koichi ITO
0c729d0a2d Use autoload to defer loading of unused subsystems
## Motivation and Context

`lib/mcp.rb` currently declares all `require_relative` statements in one place.
As a result, Server and Client are both loaded, the stdio and Streamable HTTP
transports are both loaded, and Tool, Prompt, and Resource are all loaded,
regardless of which components are actually used.

These components are selected by the user depending on their use case,
so loading everything upfront is unnecessary.

Using `autoload` for selected constants defers loading until the constant
is actually referenced, so only the subsystems required by the user are loaded.

## How Has This Been Tested?

All existing tests pass unchanged.

## Breaking Change

None. This change only defers loading of unused components.
2026-03-12 14:08:27 +09:00
Koichi ITO
a2514a0ef2
Merge pull request #254 from koic/support_progress_notification_per_mcp_spec
Support progress notifications per MCP specification
2026-03-12 12:26:39 +09:00
Koichi ITO
066f49b7a7
Merge pull request #239 from koic/reduce_release_package_size
Reduce release package size
2026-03-11 02:24:30 +09:00
Koichi ITO
bbe637612b Support progress notifications per MCP specification
## Motivation and Context

The MCP specification defines `notifications/progress` for reporting incremental progress
during long-running tool operations. The Ruby SDK did not yet support this, so tools had no way to
send progress updates to clients. This caused the `tools-call-with-progress` conformance test to fail.

This adds an `MCP::ServerContext` wrapper that provides a `report_progress` method on
`server_context`. Tools call `server_context.report_progress(value, total:, message:)` to send
notifications back to the client. The method is a no-op when no `progressToken` was provided,
so tools can call it unconditionally without checking for a token.

This design is consistent with the TypeScript and Python SDKs, which provide progress
reporting through a context object rather than a separate parameter.

On the client side, `Client#call_tool` now accepts a `progress_token:` parameter that sets
`_meta.progressToken` in the request.

Ref: https://modelcontextprotocol.io/specification/latest/server/utilities/progress

## How Has This Been Tested?

Added comprehensive tests in `test/mcp/server_progress_test.rb` covering `notify_progress`,
tool integration with progress, block-defined tools, `**kwargs` tools, and no-op behavior
without a token.

Added client tests for `progress_token:` parameter inclusion and omission.

Added `test/mcp/server_context_test.rb` test for non-Hash `server_context` preservation
when `_meta` is present.

Conformance: `tools-call-with-progress` removed from expected failures.

## Breaking Change

`server_context` is now always an `MCP::ServerContext` instance when passed to tools,
even if the server has no context configured. Code that checks `server_context ?` to
detect a nil context will need to be updated, as `ServerContext` is always truthy.
`ServerContext` delegates method calls to the underlying context object via
`method_missing`, so `server_context[:key]` and other accessor patterns continue to work.

In practice, this is unlikely to affect users. A tool that declares `server_context:`
is opting in to use the context; if the server has no context configured, that is a
server configuration issue rather than something the tool should handle with a nil check.
2026-03-10 01:49:09 +09:00
Koichi ITO
e5d3b2c135 Support CORS and Accept wildcard for browser-based MCP clients
## Summary

When using browser-based MCP clients such as MCP Inspector, connections to
the example HTTP server failed due to two issues:

1. CORS preflight (OPTIONS) requests returned 405 Method Not Allowed,
causing browsers to block all cross-origin requests.
2. `Accept: */*` (commonly sent by browsers and fetch API) returned 406 Not Acceptable
because the transport required the Accept header to explicitly list `application/json`
and `text/event-stream`.

Add `rack-cors` middleware to the example HTTP servers so that preflight requests are handled correctly
and `Mcp-Session-Id` is exposed to browser clients. Also fix `validate_accept_header` in `StreamableHTTPTransport`
to treat `*/*` as satisfying all required Accept types.

Fixes #141.

## Repro Steps

### 1. Start the example server (Terminal 1)

```console
$ bundle exec ruby examples/streamable_http_server.rb
```

### 2. Start MCP Inspector (Terminal 2)

```console
$ npx @modelcontextprotocol/inspector
```

### 3. Open the Inspector Web UI (http://localhost:6274) in a browser

- Set Transport Type to "Streamable HTTP"
- Set URL to http://localhost:9393
- Disable the Authorization header toggle (the example server does not require authentication)
- Click "Connect"

Before this change, the connection fails due to CORS or 406 errors.
After this change, the connection succeeds and tools are listed.

These steps have been added to examples/README.md.
2026-03-09 10:45:53 +09:00
Koichi ITO
8a97cebc33
Merge pull request #252 from koic/fix_rails_controller_example_to_return_202_for_notifications
[Doc] Fix Rails controller example to return 202 for notifications
2026-03-08 03:06:19 +09:00
Koichi ITO
7e0a339681 [Doc] Fix Rails controller example to return 202 for notifications
The previous example used `Server#handle_json` directly, which does not control HTTP status codes.
Notifications such as `notifications/initialized` require 202 Accepted per the MCP spec,
but the example always returned 200.

Use `StreamableHTTPTransport#handle_request` instead, which returns the correct HTTP status code
for each request type.

- https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#sending-messages-to-the-server
- https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server

Closes #158
2026-03-06 03:21:25 +09:00
Koichi ITO
5d8f1bdf9f
Merge pull request #172 from erickreutz/add-meta-extraction-support
Add automatic _meta parameter extraction support
2026-03-05 02:45:05 +09:00
Eric Kreutzer
50cd86c01c
Add automatic _meta parameter extraction support 2026-03-04 10:11:07 -07:00
Koichi ITO
4bf168840e
Merge pull request #251 from modelcontextprotocol/dependabot/github_actions/actions/setup-node-6
Bump actions/setup-node from 4 to 6
2026-03-04 02:17:09 +09:00
Koichi ITO
7cede9c5fd
Merge pull request #250 from koic/release_0_8_0 v0.8.0
Release 0.8.0
2026-03-03 09:11:13 +09:00
dependabot[bot]
773345bd5d
Bump actions/setup-node from 4 to 6
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 22:05:03 +00:00
Koichi ITO
2da465c985 Release 0.8.0
Some fixes have been identified during the implementation of the conformance infrastructure.
It should be possible to proceed with a release at this stage.
2026-03-03 01:19:53 +09:00
Koichi ITO
fa9f374d73
Merge pull request #237 from koic/replace_assert_empty_with_assert_equal_using_explicit_literals
Replace `assert_empty` with `assert_equal` using explicit literals
2026-03-03 01:05:16 +09:00
Koichi ITO
37c3f0d991
Merge pull request #240 from koic/add_wildcard_gem_to_dot_gitignore
Add `*.gem` to .gitignore
2026-03-03 01:04:41 +09:00
Koichi ITO
ad45c10e13
Merge pull request #248 from koic/conformance
Support running MCP conformance tests against the Ruby SDK
2026-03-02 14:54:41 +09:00
Koichi ITO
13bbd2937d Support running MCP conformance tests against the Ruby SDK
## Motivation

To assess and track the Ruby SDK's compliance with the MCP
specification, integrate `@modelcontextprotocol/conformance` as the
standard evaluation harness. This makes it possible to measure
conformance objectively, catch regressions early, and produce the kind
of evaluation data needed for SDK tier assessments.

## Scope

Only server conformance is included at this point. Client conformance
requires protocol-level features that MCP::Client does not yet
support (initialize handshake, notifications, ping, etc.).

## Usage

Run conformance tests:

```console
bundle exec rake conformance
```

Start the conformance server in the foreground for iterating on a single
scenario without the per-run startup overhead:

```console
bundle exec rake conformance_server
```

## Default Rake Task (`bundle exec rake`)

It is configured to run in order of lower execution cost: `rubocop`, `test`, and then `conformance`.

## CI

`.github/workflows/conformance.yml` file has been added to run the conformance tests in CI.

## SDK Tier Report

The MCP SDK Tier system (https://modelcontextprotocol.io/community/sdk-tiers)
requires SDK maintainers to self-assess and report results to the SDK Working
Group via https://github.com/modelcontextprotocol/modelcontextprotocol/issues.

To generate a full tier assessment report, use the `/mcp-sdk-tier-audit`
slash command from the https://github.com/modelcontextprotocol/conformance repository with
the conformance server running:

```console
# Terminal 1 (this repository): start the conformance server
bundle exec rake conformance_server

# Terminal 2 (conformance repository): run the tier audit skill as a slash command in Claude Code
/mcp-sdk-tier-audit /path/to/modelcontextprotocol/ruby-sdk http://localhost:9292/mcp
```

The skill evaluates conformance pass rate, issue label taxonomy, triage
metrics, documentation coverage, and policy compliance, then produces a
markdown report suitable for tier advancement submissions.
2026-03-02 14:50:03 +09:00
Koichi ITO
5f16abfd96
Merge pull request #244 from koic/add_content_embedded_resource
Add `Content::EmbeddedResource` class for embedded resource content type
2026-03-02 01:35:37 +09:00
Koichi ITO
ba7a08aa04 Add Content::EmbeddedResource class for embedded resource content type
The MCP spec defines an embedded resource content type, but the SDK had
no corresponding class. Add `Content::EmbeddedResource` that wraps a
resource object and serializes it via `to_h`.

https://modelcontextprotocol.io/specification/2025-11-25/server/tools#embedded-resources
2026-03-02 01:33:05 +09:00
Koichi ITO
60f69351ee
Merge pull request #249 from koic/fix_econnreset_in_sse_notification
Handle `Errno::ECONNRESET` in SSE stream operations
2026-03-02 01:30:40 +09:00
Koichi ITO
2fc4f5e1d2
Merge pull request #243 from koic/add_content_audio
Add `Content::Audio` class for audio content type
2026-03-02 01:30:20 +09:00
Koichi ITO
29a32900fd
Merge pull request #246 from koic/fix_prompt_nil_arguments
Fix `Prompt#validate_arguments!` crash when arguments are `nil`
2026-03-01 18:38:14 +09:00
Koichi ITO
cbb81a836c Add Content::Audio class for audio content type
The MCP spec defines an audio content type, but the SDK only had
`Content::Text` and `Content::Image`. Add `Content::Audio` with the
same structure as `Content::Image`.

https://modelcontextprotocol.io/specification/2025-11-25/server/tools#audio-content
2026-03-01 17:30:17 +09:00
Koichi ITO
9c1c2491b1 Handle Errno::ECONNRESET in SSE stream operations
`Errno::ECONNRESET` is a `SystemCallError`, not an `IOError`, so it was
not caught by the existing `rescue IOError, Errno::EPIPE` clauses. When
a client disconnected with a TCP RST during SSE notification broadcast,
the unhandled exception aborted the entire broadcast loop, causing
notifications to be lost for all remaining sessions.

This caused the `tools-call-with-logging` conformance scenario to fail
in CI (#248), where stale sessions from prior scenarios triggered
`ECONNRESET` on the first notification write, aborting the broadcast
and losing log notifications.

Add `Errno::ECONNRESET` to all four rescue clauses in
`StreamableHTTPTransport`: `send_notification` (targeted and broadcast),
`send_response_to_stream`, and `send_keepalive_ping`.
2026-03-01 17:25:38 +09:00
Koichi ITO
200826c7fd
Merge pull request #247 from koic/fix_default_handler_return_values
Fix default handler return values to comply with MCP spec
2026-03-01 17:23:42 +09:00
Koichi ITO
ef5c76373b Fix Prompt#validate_arguments! crash when arguments are nil
`Prompt#validate_arguments!` called `required_args` which does `arguments_value.filter_map`,
crashing with `NoMethodError` when `arguments_value` is `nil` (the default for prompts without
declared arguments). Additionally, passing `nil` as args caused a crash on `args.keys`.

Fix by defaulting `arguments_value` to `[]` and guarding against `nil` args.
2026-03-01 17:21:41 +09:00
Koichi ITO
76ab822ad7
Merge pull request #245 from koic/fix_sse_response_status
Return 202 Accepted for SSE responses per MCP spec
2026-03-01 17:17:26 +09:00
Koichi ITO
145dc1f2c5
Merge pull request #242 from koic/support_ref_in_tool_schema
Allow `$ref` in `Tool::Schema` for protocol version 2025-11-25
2026-03-01 17:16:47 +09:00
Koichi ITO
c62902b1e8
Merge pull request #241 from koic/fix_content_image_to_h
Fix `Content::Image#to_h` to return `mimeType` (camelCase) per MCP spec
2026-03-01 17:16:25 +09:00
Koichi ITO
7b910bca23 Fix default handler return values to comply with MCP spec
The no-op handlers for `completion/complete`, `resources/subscribe`,
and `resources/unsubscribe` returned `nil`.
When a client accesses the expected response structure (e.g., `result[:completion][:values]`),
a `nil` result raises `NoMethodError`.

The MCP spec requires specific response structures even for no-op handlers.
Returning spec-compliant defaults ensures that clients receive valid responses without
requiring servers to explicitly override each handler.

`completion/complete` now returns `{ completion: { values: [], hasMore: false } }`.
ref: https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion

`resources/subscribe` and `resources/unsubscribe` now return `{}` (empty result).
ref: https://modelcontextprotocol.io/specification/2025-11-25/server/resources#subscriptions
2026-03-01 00:55:31 +09:00
Koichi ITO
a02a4572c4 Return 202 Accepted for SSE responses per MCP spec
`StreamableHTTPTransport#send_response_to_stream` returned 200 with
`{ accepted: true }` body, but the MCP spec requires 202 with empty body
when a response is sent via SSE stream. This has been a requirement since
Streamable HTTP was introduced in protocol version 2025-03-26.

- https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#sending-messages-to-the-server
- https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server
2026-02-28 17:59:54 +09:00
Koichi ITO
99cbefc610 Allow $ref in Tool::Schema for protocol version 2025-11-25
`Tool::Schema` rejected `$ref` with an `ArgumentError`.
Earlier MCP spec versions (2024-11-05 through 2025-06-18) did not define
a JSON Schema dialect or reference the `$ref` keyword. `inputSchema` was described
only as "JSON Schema defining expected parameters" with no further constraints:

- https://modelcontextprotocol.io/specification/2024-11-05/server/tools
- https://modelcontextprotocol.io/specification/2025-06-18/server/tools

The MCP spec 2025-11-25 introduced a "JSON Schema Usage" section that adopts
JSON Schema 2020-12 as the default dialect. Since `$ref` is a core keyword in 2020-12,
it is now allowed for protocol version 2025-11-25 and later.

- https://modelcontextprotocol.io/specification/2025-11-25/basic#json-schema-usage
- https://modelcontextprotocol.io/specification/2025-11-25/server/tools
- https://json-schema.org/draft/2020-12/release-notes

For backward compatibility with older protocol versions (2025-06-18 and earlier),
`Server#validate!` continues to raise `ArgumentError` when a tool input schema contains `$ref`.
This preserves the existing behavior for servers targeting those spec versions.
2026-02-27 14:33:36 +09:00
Koichi ITO
37563e59bd Fix Content::Image#to_h to return mimeType (camelCase) per MCP spec
This is a fix for a similar bug that remained, as in #235.

`Content::Image#to_h` returned `mime_type` (snake_case), but the MCP spec
requires `mimeType` (camelCase) for the MIME type field in image content.
2026-02-27 12:51:38 +09:00
Koichi ITO
bb94bdd6e3 Add *.gem to .gitignore
`gem build mcp.gemspec` outputs the built gem into the working directory (e.g. mcp-0.7.1.gem)
rather than pkg/, which is already ignored.
Without this entry, the file shows up as an untracked file and risks being accidentally
staged and committed during release preparation or local development.
2026-02-26 19:32:38 +09:00
Koichi ITO
912245dc11 Reduce release package size
The current release package contains files that are not required at runtime,
such as `.github`, `docs`, and `examples`.

```ruby
Dir.chdir(File.expand_path("..", __FILE__)) do
  %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
=>
[".gitattributes",
 ".github/dependabot.yml",
 ".github/workflows/ci.yml",
 ".github/workflows/release.yml",
 ".gitignore",
 ".rubocop.yml",
 "AGENTS.md",
 "CHANGELOG.md",
 "CODE_OF_CONDUCT.md",
 "Gemfile",
 "LICENSE",
(snip)
```

This PR removes unnecessary files where possible to reduce the size of the release package.
2026-02-25 19:05:02 +09:00
Koichi ITO
a44799b262 Replace assert_empty with assert_equal using explicit literals
Due to overly aggressive detection by RuboCop Minitest, the distinction between `[]` and `{}` was uniformly replaced with `assert_empty`,
making it impossible to differentiate between them.
As a result of https://github.com/rubocop/rubocop-minitest/issues/344, where the `Minitest/AssertEmptyLiteral` cop was disabled,
the return value has been expressed using explicit literal forms.
2026-02-24 16:09:34 +09:00