Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 99 additions & 13 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ A specialized HTTP server designed to test and demonstrate Server-Sent Events (S

**Available Tools:**

- `notification_tool` - Send custom SSE notifications with optional delays
- `notification_tool` - Sends progress notifications over SSE, with optional delays
- `echo` - Simple echo tool for basic testing

**Usage:**
Expand All @@ -124,14 +124,15 @@ An interactive client that connects to the SSE stream and provides a menu-driven

1. Start the SSE test server in one terminal:

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

2. Run the SSE test client in another terminal:
```console
$ ruby examples/streamable_http_client.rb
```

```console
$ ruby examples/streamable_http_client.rb
```

The client will:

Expand All @@ -155,6 +156,34 @@ $ bundle exec puma --port 9292

The MCP endpoint is available at `http://localhost:9292/mcp`. See [`rails/README.md`](rails/README.md) for a full curl-based walkthrough.

### 8. Modern Lifecycle HTTP Server / Client (`modern_http_server.rb`, `modern_http_client.rb`)

A server and client pair demonstrating the 2026-07-28 modern lifecycle (SEP-2575), which replaces the `initialize` handshake and per-session state with sessionless, self-contained requests.

**Features:**

- `server/discover` capability discovery before (or instead of) a handshake
- The per-request `_meta` envelope and `Mcp-Method` / `Mcp-Name` headers, stamped by the SDK automatically
- `resultType` stamping and SEP-2549 cache hints (`ttlMs`, `cacheScope`) on results
- A multi round-trip `deploy` tool (SEP-2322), resumed automatically by the client's elicitation handler
- The removal of legacy-only methods such as `ping`

**Usage:**

1. Start the server in one terminal:

```console
$ ruby examples/modern_http_server.rb
```

2. Run the client in another terminal:

```console
$ ruby examples/modern_http_client.rb
```

The same server still accepts the legacy `initialize` flow: the transport routes each request to the legacy or modern lifecycle by its `MCP-Protocol-Version` header.

### Testing with MCP Inspector

[MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) is a browser-based tool for testing and debugging MCP servers.
Expand Down Expand Up @@ -188,24 +217,78 @@ You can also test SSE functionality manually using cURL:

```console
SESSION_ID=$(curl -D - -s -o /dev/null http://localhost:9393 \
--json '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl-test","version":"1.0"}}}' | grep -i "Mcp-Session-Id:" | cut -d' ' -f2- | tr -d '\r')
-H "Accept: application/json, text/event-stream" \
--json '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl-test","version":"1.0"}}}' | grep -i "Mcp-Session-Id:" | cut -d' ' -f2- | tr -d '\r')
```

2. Connect to SSE stream (in one terminal):
2. Optionally connect the standalone SSE stream, which carries server-initiated messages that are not tied to a request (in another terminal):

```console
curl -i -N -H "Mcp-Session-Id: $SESSION_ID" http://localhost:9393
```

3. Trigger notifications (in another terminal):
3. Call the notification tool. The `notifications/progress` events (requested via the `progressToken` in `_meta`) and the final response arrive as SSE events on the POST response itself:

```console
# Send immediate notification
curl -i http://localhost:9393 \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $SESSION_ID" \
--json '{"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"notification_tool","arguments":{"message":"Hello from cURL!"}}}'
--json '{"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"notification_tool","arguments":{"message":"Hello from cURL!","delay":0.5},"_meta":{"progressToken":"curl-progress"}}}'
```

### Testing the modern lifecycle with cURL

The modern lifecycle (2026-07-28, SEP-2575) is sessionless: there is no `initialize` handshake and no `Mcp-Session-Id`. Start `examples/modern_http_server.rb` and walk it manually:

1. Probe capabilities with `server/discover`:

```console
curl -s http://localhost:9494 \
-H "Accept: application/json, text/event-stream" \
--json '{"jsonrpc":"2.0","id":0,"method":"server/discover"}'
```

2. List tools (the `MCP-Protocol-Version` header selects the era, `Mcp-Method` mirrors the method, and `params._meta` carries the SEP-2575 envelope):

```console
curl -s http://localhost:9494 \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/list" \
--json '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'
```

3. Call a tool (name-bearing methods additionally mirror the name in the `Mcp-Name` header):

```console
curl -s http://localhost:9494 \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: greet" \
--json '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"greet","arguments":{"name":"curl"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'
```

4. Observe a multi round-trip result (SEP-2322), then resume it by echoing the `requestState` back together with the answer. Declaring the `elicitation` capability in the envelope is required before the server may embed elicitation requests:

```console
curl -s http://localhost:9494 \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: deploy" \
--json '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"deploy","arguments":{"app":"storefront"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{"elicitation":{}}}}}'

curl -s http://localhost:9494 \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: deploy" \
--json '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"deploy","arguments":{"app":"storefront"},"inputResponses":{"environment":{"action":"accept","content":{"environment":"staging"}}},"requestState":"{\"app\":\"storefront\"}","_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{"elicitation":{}}}}}'
```

The same server still accepts the legacy flow from the previous sections, routed by the `MCP-Protocol-Version` header.

## Streamable HTTP Transport Details

### Protocol Flow
Expand Down Expand Up @@ -236,13 +319,15 @@ Initialize a session:

```console
curl -i http://localhost:9292 \
--json '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"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":"test","version":"1.0"}}}'
```

List tools (using the session ID from initialization):

```console
curl -i http://localhost:9292 \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: YOUR_SESSION_ID" \
--json '{"jsonrpc":"2.0","method":"tools/list","id":2}'
```
Expand All @@ -251,6 +336,7 @@ Call a tool:

```console
curl -i http://localhost:9292 \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: YOUR_SESSION_ID" \
--json '{"jsonrpc":"2.0","method":"tools/call","id":3,"params":{"name":"example_tool","arguments":{"a":5,"b":3}}}'
```
73 changes: 58 additions & 15 deletions examples/http_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
require "json"
require "uri"

# Simple HTTP client example for interacting with the MCP HTTP server
# Simple HTTP client example for interacting with the MCP HTTP server.
class MCPHTTPClient
def initialize(base_url = "http://localhost:9292")
@base_url = base_url
Expand All @@ -30,19 +30,51 @@ def send_request(method, params = nil, id = nil)

response = http.request(request)

# Store session ID if provided
# Store session ID if provided.
if response["Mcp-Session-Id"]
@session_id = response["Mcp-Session-Id"]
puts "Session ID: #{@session_id}"
end

JSON.parse(response.body)
parse_response_body(response)
end

# In the transport's default SSE mode, POST responses on an established session arrive as
# a Server-Sent Events stream whose `data:` line carries the JSON-RPC response; unwrap it
# before parsing.
def parse_response_body(response)
body = response.body

if response["Content-Type"]&.start_with?("text/event-stream")
data_lines = body.lines.select { |line| line.start_with?("data:") }
body = data_lines.map { |line| line.sub(/\Adata:\s*/, "") }.join
end

JSON.parse(body)
end

def send_notification(method, params = nil)
uri = URI(@base_url)
http = Net::HTTP.new(uri.host, uri.port)

request = Net::HTTP::Post.new(uri.path.empty? ? "/" : uri.path)
request["Content-Type"] = "application/json"
request["Mcp-Session-Id"] = @session_id if @session_id

# Notifications carry no `id` and receive no JSON-RPC response body.
request.body = {
jsonrpc: "2.0",
method: method,
params: params,
}.compact.to_json

http.request(request)
end

def initialize_session
puts "=== Initializing session ==="
result = send_request("initialize", {
protocolVersion: "2024-11-05",
protocolVersion: "2025-11-25",
capabilities: {},
clientInfo: {
name: "example_client",
Expand All @@ -54,6 +86,14 @@ def initialize_session
result
end

def notify_initialized
puts "=== Sending notifications/initialized ==="
response = send_notification("notifications/initialized")
puts "Response status: #{response.code} #{response.message}"

response
end

def ping
puts "=== Sending ping ==="
result = send_request("ping")
Expand Down Expand Up @@ -147,37 +187,40 @@ def main
client = MCPHTTPClient.new

begin
# Initialize session
# Initialize session.
client.initialize_session

# Test ping
# Complete the handshake.
client.notify_initialized

# Test ping.
client.ping

# List available tools
# List available tools.
client.list_tools

# Call the example_tool (note: snake_case name)
# Call the example_tool (note: snake_case name).
client.call_tool("example_tool", { a: 5, b: 3 })

# Call the echo tool
# Call the echo tool.
client.call_tool("echo", { message: "Hello from client!" })

# List prompts
# List prompts.
client.list_prompts

# Get a prompt (note: snake_case name)
# Get a prompt (note: snake_case name).
client.get_prompt("example_prompt", { message: "This is a test message" })

# List resources
# List resources.
client.list_resources

# Read a resource
client.read_resource("test_resource")
# Read a resource (the URI registered by http_server.rb).
client.read_resource("https://test_resource.invalid")
rescue => e
puts "Error: #{e.message}"
puts e.backtrace
ensure
# Clean up session
# Clean up session.
client.close_session
end
end
Expand Down
5 changes: 4 additions & 1 deletion examples/http_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def template(args, server_context:)
# Set up the server
server = MCP::Server.new(
name: "example_http_server",
version: "1.0.0",
tools: [ExampleTool],
prompts: [ExamplePrompt],
resources: [
Expand Down Expand Up @@ -169,7 +170,9 @@ def call(env)
Starting MCP HTTP server on http://localhost:9292
Use POST requests to initialize and send JSON-RPC commands
Example initialization:
curl -i http://localhost:9292 --json '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
curl -i http://localhost:9292 \\
-H "Accept: application/json, text/event-stream" \\
--json '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'

The server will return a session ID in the Mcp-Session-Id header.
Use this session ID for subsequent requests.
Expand Down
Loading