Update examples for the 2026-07-28 era

## Motivation and Context

The runnable examples drifted from the SDK they ship with:

- POST requests whose `Accept` header does not cover both `application/json` and `text/event-stream` are answered 406,
  so the cURL walkthroughs  no longer worked as printed.
- `MCP::Client#connect` now defaults to `mode: :auto`, which adopts the sessionless modern lifecycle (2026-07-28)
  against a dual-era server; the Streamable HTTP client example requires a session and broke against its own server.
- POST responses on an established session arrive as an SSE stream, which the hand-rolled HTTP client did not parse.
- `notification_tool` claimed to send SSE notifications but only returned a `Tool::Response`.
- `http_client.rb` read a resource URI the server never registered and skipped `notifications/initialized`.

Handshake examples now offer the latest handshake protocol version (`2025-11-25`) instead of `2024-11-05`,
the Streamable HTTP client pins `mode: :legacy` for its session-based demo, `notification_tool` reports
real `notifications/progress` events requested through `_meta.progressToken`, and the interactive client streams
those events from the POST response so they are visible on screen.

The modern lifecycle itself had no runnable example, so a new server and client pair (`modern_http_server.rb`,
`modern_http_client.rb`) shows the sessionless flow end to end: `server/discover` capability discovery,
the per-request `_meta` envelope with the `Mcp-Method` and `Mcp-Name` headers (stamped by the SDK client automatically),
`resultType` stamping, SEP-2549 cache hints, a SEP-2322 multi round-trip `deploy` tool resumed automatically by
the client's elicitation handler, and the removal of legacy-only methods such as `ping`. The same server keeps serving
the legacy `initialize` flow, routed per request by the `MCP-Protocol-Version` header. `examples/README.md` documents
the pair and adds a cURL walkthrough covering the modern headers, the envelope, and the multi round-trip exchange.

## How Has This Been Tested?

- `ruby examples/stdio_client.rb` against `stdio_server.rb`
- `ruby examples/http_client.rb` against `http_server.rb`
- `ruby examples/streamable_http_client.rb` against `streamable_http_server.rb`, including the progress stream
- `ruby examples/modern_http_client.rb` against `modern_http_server.rb` (discover, modern adoption, cache hints,
  `resultType`, the multi round-trip resume, `ping` rejection)
- The cURL walkthroughs in the banners and `examples/README.md`, including the `requestState` echo and a legacy `initialize`
  against the modern example server

## Breaking Changes

None.
This commit is contained in:
Koichi ITO 2026-08-17 03:07:22 +09:00
commit bfc6e551a3

View file

@ -13,14 +13,16 @@ sse_logger.formatter = proc do |severity, datetime, _progname, msg|
"[SSE] #{severity} #{datetime.strftime("%H:%M:%S.%L")} - #{msg}\n"
end
# Tool that returns a response that will be sent via SSE if a stream is active
# Tool that emits `notifications/progress` events, delivered over the SSE stream.
# The client must request them by sending a `progressToken` in the request `_meta`.
class NotificationTool < MCP::Tool
tool_name "notification_tool"
description "Returns a notification message that will be sent via SSE if stream is active"
description "Sends the message back as progress notifications via SSE, then returns a summary"
input_schema(
properties: {
message: { type: "string", description: "Message to send via SSE" },
delay: { type: "number", description: "Delay in seconds before returning (optional)" },
steps: { type: "number", description: "Number of progress notifications to send (default: 3)" },
delay: { type: "number", description: "Delay in seconds between notifications (optional)" },
},
required: ["message"],
)
@ -28,14 +30,19 @@ class NotificationTool < MCP::Tool
class << self
attr_accessor :logger
def call(message:, delay: 0)
sleep(delay) if delay > 0
def call(message:, server_context:, steps: 3, delay: 0)
steps = steps.to_i.clamp(1, 10)
logger&.info("Returning notification message: #{message}")
steps.times do |i|
sleep(delay) if delay > 0
logger&.info("Reporting progress #{i + 1}/#{steps}: #{message}")
server_context.report_progress(i + 1, total: steps, message: message)
end
MCP::Tool::Response.new([{
type: "text",
text: "Notification: #{message} (timestamp: #{Time.now.iso8601})",
text: "Sent #{steps} progress notifications for: #{message}",
}])
end
end
@ -44,6 +51,7 @@ end
# Create the server
server = MCP::Server.new(
name: "sse_test_server",
version: "1.0.0",
tools: [NotificationTool],
prompts: [],
resources: [],
@ -149,29 +157,34 @@ puts <<~MESSAGE
Starting server on http://localhost:9393
Available Tools:
1. NotificationTool - Returns messages that are sent via SSE when stream is active"
1. notification_tool - Sends the message back as progress notifications via SSE
2. echo - Simple echo tool
Testing SSE:
1. Initialize session:
curl -i http://localhost:9393 \\
--json '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"sse-test","version":"1.0"}}}'
-H "Accept: application/json, text/event-stream" \\
--json '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"sse-test","version":"1.0"}}}'
2. Connect SSE stream (use the session ID from step 1):"
2. Connect SSE stream (use the session ID from step 1):
curl -i -N -H "Mcp-Session-Id: YOUR_SESSION_ID" http://localhost:9393
3. In another terminal, test tools (responses will be sent via SSE if stream is active):
Echo tool:
curl -i http://localhost:9393 -H "Mcp-Session-Id: YOUR_SESSION_ID" \\
-H "Accept: application/json, text/event-stream" \\
--json '{"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"echo","arguments":{"message":"Hello SSE!"}}}'
Notification tool (with 2 second delay):
Notification tool (progress notifications require a progressToken in _meta):
curl -i http://localhost:9393 -H "Mcp-Session-Id: YOUR_SESSION_ID" \\
--json '{"jsonrpc":"2.0","method":"tools/call","id":3,"params":{"name":"notification_tool","arguments":{"message":"Hello SSE!", "delay": 2}}}'
-H "Accept: application/json, text/event-stream" \\
--json '{"jsonrpc":"2.0","method":"tools/call","id":3,"params":{"name":"notification_tool","arguments":{"message":"Hello SSE!","delay":1},"_meta":{"progressToken":"curl-progress"}}}'
Note: When an SSE stream is active, tool responses will appear in the SSE stream and the POST request will return 202 Accepted with no body.
Note: Each POST is answered with an SSE stream of its own; request-scoped notifications
(such as the progress events above) appear on it before the final response.
The standalone GET stream carries server-initiated messages that are not tied to a request.
Press Ctrl+C to stop the server
MESSAGE