mcp 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -38,7 +38,9 @@ It implements the Model Context Protocol specification, handling model context r
38
38
  - Supports resource registration and retrieval
39
39
  - Supports stdio & Streamable HTTP (including SSE) transports
40
40
  - Supports notifications for list changes (tools, prompts, resources)
41
+ - Supports roots (server-to-client filesystem boundary queries)
41
42
  - Supports sampling (server-to-client LLM completion requests)
43
+ - Supports cursor-based pagination for list operations
42
44
 
43
45
  ### Supported Methods
44
46
 
@@ -51,8 +53,12 @@ It implements the Model Context Protocol specification, handling model context r
51
53
  - `resources/list` - Lists all registered resources and their schemas
52
54
  - `resources/read` - Retrieves a specific resource by name
53
55
  - `resources/templates/list` - Lists all registered resource templates and their schemas
56
+ - `resources/subscribe` - Subscribes to updates for a specific resource
57
+ - `resources/unsubscribe` - Unsubscribes from updates for a specific resource
54
58
  - `completion/complete` - Returns autocompletion suggestions for prompt arguments and resource URIs
59
+ - `roots/list` - Requests filesystem roots from the client (server-to-client)
55
60
  - `sampling/createMessage` - Requests LLM completion from the client (server-to-client)
61
+ - `elicitation/create` - Requests user input from the client (server-to-client)
56
62
 
57
63
  ### Usage
58
64
 
@@ -103,14 +109,56 @@ $ ruby examples/stdio_server.rb
103
109
  {"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"example_tool","arguments":{"message":"Hello"}}}
104
110
  ```
105
111
 
106
- #### Rails Controller
112
+ #### Streamable HTTP Transport
107
113
 
108
- When added to a Rails controller on a route that handles POST requests, your server will be compliant with non-streaming
109
- [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports#streamable-http) transport
110
- requests.
114
+ `MCP::Server::Transports::StreamableHTTPTransport` is a standard Rack app, so it can be mounted in any Rack-compatible framework.
115
+ The following examples show two common integration styles in Rails.
111
116
 
112
- You can use `StreamableHTTPTransport#handle_request` to handle requests with proper HTTP
113
- status codes (e.g., 202 Accepted for notifications).
117
+ > [!IMPORTANT]
118
+ > `MCP::Server::Transports::StreamableHTTPTransport` stores session and SSE stream state in memory,
119
+ > so it must run in a single process. Use a single-process server (e.g., Puma with `workers 0`).
120
+ > Multi-process configurations (Unicorn, or Puma with `workers > 0`) fork separate processes that
121
+ > do not share memory, which breaks session management and SSE connections.
122
+ >
123
+ > When running multiple server instances behind a load balancer, configure your load balancer to use
124
+ > sticky sessions (session affinity) so that requests with the same `Mcp-Session-Id` header are always
125
+ > routed to the same instance.
126
+ >
127
+ > Stateless mode (`stateless: true`) does not use sessions and works with any server configuration.
128
+
129
+ ##### Rails (mount)
130
+
131
+ `StreamableHTTPTransport` is a Rack app that can be mounted directly in Rails routes:
132
+
133
+ ```ruby
134
+ # config/routes.rb
135
+ server = MCP::Server.new(
136
+ name: "my_server",
137
+ title: "Example Server Display Name",
138
+ version: "1.0.0",
139
+ instructions: "Use the tools of this server as a last resort",
140
+ tools: [SomeTool, AnotherTool],
141
+ prompts: [MyPrompt],
142
+ )
143
+ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
144
+
145
+ Rails.application.routes.draw do
146
+ mount transport => "/mcp"
147
+ end
148
+ ```
149
+
150
+ `mount` directs all HTTP methods on `/mcp` to the transport. `StreamableHTTPTransport` internally dispatches
151
+ `POST` (client-to-server JSON-RPC messages, with responses optionally streamed via SSE),
152
+ `GET` (optional standalone SSE stream for server-to-client messages), and `DELETE` (session termination) per
153
+ the [MCP Streamable HTTP transport spec](https://modelcontextprotocol.io/specification/latest/basic/transports#streamable-http),
154
+ so no additional route configuration is needed.
155
+
156
+ ##### Rails (controller)
157
+
158
+ While the mount approach creates a single server at boot time, the controller approach creates a new server per request.
159
+ This allows you to customize tools, prompts, or configuration based on the request (e.g., different tools per route).
160
+
161
+ `StreamableHTTPTransport#handle_request` returns proper HTTP status codes (e.g., 202 Accepted for notifications):
114
162
 
115
163
  ```ruby
116
164
  class McpController < ActionController::API
@@ -133,18 +181,6 @@ class McpController < ActionController::API
133
181
  end
134
182
  ```
135
183
 
136
- > [!IMPORTANT]
137
- > `MCP::Server::Transports::StreamableHTTPTransport` stores session and SSE stream state in memory,
138
- > so it must run in a single process. Use a single-process server (e.g., Puma with `workers 0`).
139
- > Multi-process configurations (Unicorn, or Puma with `workers > 0`) fork separate processes that
140
- > do not share memory, which breaks session management and SSE connections.
141
- >
142
- > When running multiple server instances behind a load balancer, configure your load balancer to use
143
- > sticky sessions (session affinity) so that requests with the same `Mcp-Session-Id` header are always
144
- > routed to the same instance.
145
- >
146
- > Stateless mode (`stateless: true`) does not use sessions and works with any server configuration.
147
-
148
184
  ### Configuration
149
185
 
150
186
  The gem can be configured using the `MCP.configure` block:
@@ -159,15 +195,17 @@ MCP.configure do |config|
159
195
  end
160
196
  }
161
197
 
162
- config.instrumentation_callback = ->(data) {
163
- puts "Got instrumentation data #{data.inspect}"
198
+ config.around_request = ->(data, &request_handler) {
199
+ logger.info("Start: #{data[:method]}")
200
+ request_handler.call
201
+ logger.info("Done: #{data[:method]}, tool: #{data[:tool_name]}")
164
202
  }
165
203
  end
166
204
  ```
167
205
 
168
206
  or by creating an explicit configuration and passing it into the server.
169
207
  This is useful for systems where an application hosts more than one MCP server but
170
- they might require different instrumentation callbacks.
208
+ they might require different configurations.
171
209
 
172
210
  ```ruby
173
211
  configuration = MCP::Configuration.new
@@ -179,8 +217,10 @@ configuration.exception_reporter = ->(exception, server_context) {
179
217
  end
180
218
  }
181
219
 
182
- configuration.instrumentation_callback = ->(data) {
183
- puts "Got instrumentation data #{data.inspect}"
220
+ configuration.around_request = ->(data, &request_handler) {
221
+ logger.info("Start: #{data[:method]}")
222
+ request_handler.call
223
+ logger.info("Done: #{data[:method]}, tool: #{data[:tool_name]}")
184
224
  }
185
225
 
186
226
  server = MCP::Server.new(
@@ -193,7 +233,8 @@ server = MCP::Server.new(
193
233
 
194
234
  #### `server_context`
195
235
 
196
- The `server_context` is a user-defined hash that is passed into the server instance and made available to tools, prompts, and exception/instrumentation callbacks. It can be used to provide contextual information such as authentication state, user IDs, or request-specific data.
236
+ The `server_context` is a user-defined hash that is passed into the server instance and made available to tool and prompt calls.
237
+ It can be used to provide contextual information such as authentication state, user IDs, or request-specific data.
197
238
 
198
239
  **Type:**
199
240
 
@@ -210,7 +251,9 @@ server = MCP::Server.new(
210
251
  )
211
252
  ```
212
253
 
213
- This hash is then passed as the `server_context` argument to tool and prompt calls, and is included in exception and instrumentation callbacks.
254
+ This hash is then passed as the `server_context` keyword argument to tool and prompt calls.
255
+ Note that exception and instrumentation callbacks do not receive this user-defined hash.
256
+ See the relevant sections below for the arguments they receive.
214
257
 
215
258
  #### Request-specific `_meta` Parameter
216
259
 
@@ -263,7 +306,9 @@ end
263
306
  The exception reporter receives:
264
307
 
265
308
  - `exception`: The Ruby exception object that was raised
266
- - `server_context`: The context hash provided to the server
309
+ - `server_context`: A hash describing where the failure occurred (e.g., `{ request: <raw JSON-RPC request> }`
310
+ for request handling, `{ notification: "tools_list_changed" }` for notification delivery).
311
+ This is not the user-defined `server_context` passed to `Server.new`.
267
312
 
268
313
  **Signature:**
269
314
 
@@ -271,9 +316,67 @@ The exception reporter receives:
271
316
  exception_reporter = ->(exception, server_context) { ... }
272
317
  ```
273
318
 
274
- ##### Instrumentation Callback
319
+ ##### Around Request
320
+
321
+ The `around_request` hook wraps request handling, allowing you to execute code before and after each request.
322
+ This is useful for Application Performance Monitoring (APM) tracing, logging, or other observability needs.
323
+
324
+ The hook receives a `data` hash and a `request_handler` block. You must call `request_handler.call` to execute the request:
325
+
326
+ **Signature:**
275
327
 
276
- The instrumentation callback receives a hash with the following possible keys:
328
+ ```ruby
329
+ around_request = ->(data, &request_handler) { request_handler.call }
330
+ ```
331
+
332
+ **`data` availability by timing:**
333
+
334
+ - Before `request_handler.call`: `method`
335
+ - After `request_handler.call`: `tool_name`, `tool_arguments`, `prompt_name`, `resource_uri`, `error`, `client`
336
+ - Not available inside `around_request`: `duration` (added after `around_request` returns)
337
+
338
+ > [!NOTE]
339
+ > `tool_name`, `prompt_name` and `resource_uri` may only be populated for the corresponding request methods
340
+ > (`tools/call`, `prompts/get`, `resources/read`), and may not be set depending on how the request is handled
341
+ > (for example, `prompt_name` is not recorded when the prompt is not found).
342
+ > `duration` is added after `around_request` returns, so it is not visible from within the hook.
343
+
344
+ **Example:**
345
+
346
+ ```ruby
347
+ MCP.configure do |config|
348
+ config.around_request = ->(data, &request_handler) {
349
+ logger.info("Start: #{data[:method]}")
350
+ request_handler.call
351
+ logger.info("Done: #{data[:method]}, tool: #{data[:tool_name]}")
352
+ }
353
+ end
354
+ ```
355
+
356
+ ##### Instrumentation Callback (soft-deprecated)
357
+
358
+ > [!NOTE]
359
+ > `instrumentation_callback` is soft-deprecated. Use `around_request` instead.
360
+ >
361
+ > To migrate, wrap the call in `begin/ensure` so the callback still runs when the request fails:
362
+ >
363
+ > ```ruby
364
+ > # Before
365
+ > config.instrumentation_callback = ->(data) { log(data) }
366
+ >
367
+ > # After
368
+ > config.around_request = ->(data, &request_handler) do
369
+ > request_handler.call
370
+ > ensure
371
+ > log(data)
372
+ > end
373
+ > ```
374
+ >
375
+ > Note that `data[:duration]` is not available inside `around_request`.
376
+ > If you need it, measure elapsed time yourself within the hook, or keep using `instrumentation_callback`.
377
+
378
+ The instrumentation callback is called after each request finishes, whether successfully or with an error.
379
+ It receives a hash with the following possible keys:
277
380
 
278
381
  - `method`: (String) The protocol method called (e.g., "ping", "tools/list")
279
382
  - `tool_name`: (String, optional) The name of the tool called
@@ -284,25 +387,10 @@ The instrumentation callback receives a hash with the following possible keys:
284
387
  - `duration`: (Float) Duration of the call in seconds
285
388
  - `client`: (Hash, optional) Client information with `name` and `version` keys, from the initialize request
286
389
 
287
- > [!NOTE]
288
- > `tool_name`, `prompt_name` and `resource_uri` are only populated if a matching handler is registered.
289
- > This is to avoid potential issues with metric cardinality.
290
-
291
- **Type:**
390
+ **Signature:**
292
391
 
293
392
  ```ruby
294
393
  instrumentation_callback = ->(data) { ... }
295
- # where data is a Hash with keys as described above
296
- ```
297
-
298
- **Example:**
299
-
300
- ```ruby
301
- MCP.configure do |config|
302
- config.instrumentation_callback = ->(data) {
303
- puts "Instrumentation: #{data.inspect}"
304
- }
305
- end
306
394
  ```
307
395
 
308
396
  ### Server Protocol Version
@@ -808,6 +896,108 @@ server = MCP::Server.new(
808
896
  )
809
897
  ```
810
898
 
899
+ ### Roots
900
+
901
+ The Model Context Protocol allows servers to request filesystem roots from clients through the `roots/list` method.
902
+ Roots define the boundaries of where a server can operate, providing a list of directories and files the client has made available.
903
+
904
+ **Key Concepts:**
905
+
906
+ - **Server-to-Client Request**: Like sampling, roots listing is initiated by the server
907
+ - **Client Capability**: Clients must declare `roots` capability during initialization
908
+ - **Change Notifications**: Clients that support `roots.listChanged` send `notifications/roots/list_changed` when roots change
909
+
910
+ **Using Roots in Tools:**
911
+
912
+ Tools that accept a `server_context:` parameter can call `list_roots` on it.
913
+ The request is automatically routed to the correct client session:
914
+
915
+ ```ruby
916
+ class FileSearchTool < MCP::Tool
917
+ description "Search files within the client's project roots"
918
+ input_schema(
919
+ properties: {
920
+ query: { type: "string" }
921
+ },
922
+ required: ["query"]
923
+ )
924
+
925
+ def self.call(query:, server_context:)
926
+ roots = server_context.list_roots
927
+ root_uris = roots[:roots].map { |root| root[:uri] }
928
+
929
+ MCP::Tool::Response.new([{
930
+ type: "text",
931
+ text: "Searching in roots: #{root_uris.join(", ")}"
932
+ }])
933
+ end
934
+ end
935
+ ```
936
+
937
+ Result contains an array of root objects:
938
+
939
+ ```ruby
940
+ {
941
+ roots: [
942
+ { uri: "file:///home/user/projects/myproject", name: "My Project" },
943
+ { uri: "file:///home/user/repos/backend", name: "Backend Repository" }
944
+ ]
945
+ }
946
+ ```
947
+
948
+ **Handling Root Changes:**
949
+
950
+ Register a callback to be notified when the client's roots change:
951
+
952
+ ```ruby
953
+ server.roots_list_changed_handler do
954
+ puts "Client's roots have changed, tools will see updated roots on next call."
955
+ end
956
+ ```
957
+
958
+ **Error Handling:**
959
+
960
+ - Raises `RuntimeError` if client does not support `roots` capability
961
+ - Raises `StandardError` if client returns an error response
962
+
963
+ ### Resource Subscriptions
964
+
965
+ Resource subscriptions allow clients to monitor specific resources for changes.
966
+ When a subscribed resource is updated, the server sends a notification to the client.
967
+
968
+ The SDK does not track subscription state internally.
969
+ Server developers register handlers and manage their own subscription state.
970
+ Three methods are provided:
971
+
972
+ - `Server#resources_subscribe_handler` - registers a handler for `resources/subscribe` requests
973
+ - `Server#resources_unsubscribe_handler` - registers a handler for `resources/unsubscribe` requests
974
+ - `ServerContext#notify_resources_updated` - sends a `notifications/resources/updated` notification to the subscribing client
975
+
976
+ ```ruby
977
+ subscribed_uris = Set.new
978
+
979
+ server = MCP::Server.new(
980
+ name: "my_server",
981
+ resources: [my_resource],
982
+ capabilities: { resources: { subscribe: true } },
983
+ )
984
+
985
+ server.resources_subscribe_handler do |params|
986
+ subscribed_uris.add(params[:uri].to_s)
987
+ end
988
+
989
+ server.resources_unsubscribe_handler do |params|
990
+ subscribed_uris.delete(params[:uri].to_s)
991
+ end
992
+
993
+ server.define_tool(name: "update_resource") do |server_context:, **args|
994
+ if subscribed_uris.include?("test://my-resource")
995
+ server_context.notify_resources_updated(uri: "test://my-resource")
996
+ end
997
+ MCP::Tool::Response.new([MCP::Content::Text.new("Resource updated").to_h])
998
+ end
999
+ ```
1000
+
811
1001
  ### Sampling
812
1002
 
813
1003
  The Model Context Protocol allows servers to request LLM completions from clients through the `sampling/createMessage` method.
@@ -823,8 +1013,7 @@ This enables servers to leverage the client's LLM capabilities without needing d
823
1013
  **Using Sampling in Tools:**
824
1014
 
825
1015
  Tools that accept a `server_context:` parameter can call `create_sampling_message` on it.
826
- The request is automatically routed to the correct client session.
827
- Set `server.server_context = server` so that `server_context.create_sampling_message` delegates to the server:
1016
+ The request is automatically routed to the correct client session:
828
1017
 
829
1018
  ```ruby
830
1019
  class SummarizeTool < MCP::Tool
@@ -852,7 +1041,6 @@ class SummarizeTool < MCP::Tool
852
1041
  end
853
1042
 
854
1043
  server = MCP::Server.new(name: "my_server", tools: [SummarizeTool])
855
- server.server_context = server
856
1044
  ```
857
1045
 
858
1046
  **Parameters:**
@@ -873,86 +1061,8 @@ Optional:
873
1061
  - `tools:` (Array) - Tools available to the LLM (requires `sampling.tools` capability)
874
1062
  - `tool_choice:` (Hash) - Tool selection mode (e.g., `{ mode: "auto" }`)
875
1063
 
876
- **Direct Usage:**
877
-
878
- `Server#create_sampling_message` can also be called directly outside of tools:
879
-
880
- ```ruby
881
- result = server.create_sampling_message(
882
- messages: [
883
- { role: "user", content: { type: "text", text: "What is the capital of France?" } }
884
- ],
885
- max_tokens: 100,
886
- system_prompt: "You are a helpful assistant.",
887
- temperature: 0.7
888
- )
889
- ```
890
-
891
- Result contains the LLM response:
892
-
893
- ```ruby
894
- {
895
- role: "assistant",
896
- content: { type: "text", text: "The capital of France is Paris." },
897
- model: "claude-3-sonnet-20240307",
898
- stopReason: "endTurn"
899
- }
900
- ```
901
-
902
- For multi-client transports (e.g., `StreamableHTTPTransport`), use `server_context.create_sampling_message` inside tools
903
- to route the request to the correct client session.
904
-
905
- **Tool Use in Sampling:**
906
-
907
- When tools are provided in a sampling request, the LLM can call them during generation.
908
- The server must handle tool calls and continue the conversation with tool results:
909
-
910
- ```ruby
911
- result = server.create_sampling_message(
912
- messages: [
913
- { role: "user", content: { type: "text", text: "What's the weather in Paris?" } }
914
- ],
915
- max_tokens: 1000,
916
- tools: [
917
- {
918
- name: "get_weather",
919
- description: "Get weather for a city",
920
- inputSchema: {
921
- type: "object",
922
- properties: { city: { type: "string" } },
923
- required: ["city"]
924
- }
925
- }
926
- ],
927
- tool_choice: { mode: "auto" }
928
- )
929
-
930
- if result[:stopReason] == "toolUse"
931
- tool_results = result[:content].map do |tool_use|
932
- weather_data = get_weather(tool_use[:input][:city])
933
-
934
- {
935
- type: "tool_result",
936
- toolUseId: tool_use[:id],
937
- content: [{ type: "text", text: weather_data.to_json }]
938
- }
939
- end
940
-
941
- final_result = server.create_sampling_message(
942
- messages: [
943
- { role: "user", content: { type: "text", text: "What's the weather in Paris?" } },
944
- { role: "assistant", content: result[:content] },
945
- { role: "user", content: tool_results }
946
- ],
947
- max_tokens: 1000,
948
- tools: [...]
949
- )
950
- end
951
- ```
952
-
953
1064
  **Error Handling:**
954
1065
 
955
- - Raises `RuntimeError` if transport is not set
956
1066
  - Raises `RuntimeError` if client does not support `sampling` capability
957
1067
  - Raises `RuntimeError` if `tools` are used but client lacks `sampling.tools` capability
958
1068
  - Raises `StandardError` if client returns an error response
@@ -989,6 +1099,33 @@ Notifications follow the JSON-RPC 2.0 specification and use these method names:
989
1099
  - `notifications/progress`
990
1100
  - `notifications/message`
991
1101
 
1102
+ ### Ping
1103
+
1104
+ The MCP Ruby SDK supports the
1105
+ [MCP `ping` utility](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/ping),
1106
+ which allows either side of the connection to verify that the peer is still responsive.
1107
+ A `ping` request has no parameters, and the receiver MUST respond promptly with an empty result.
1108
+
1109
+ #### Server-Side
1110
+
1111
+ Servers respond to incoming `ping` requests automatically - no setup is required.
1112
+ Any `MCP::Server` instance replies with an empty result.
1113
+
1114
+ #### Client-Side
1115
+
1116
+ `MCP::Client` exposes `ping` to send a ping to the server:
1117
+
1118
+ ```ruby
1119
+ client = MCP::Client.new(transport: transport)
1120
+ client.ping # => {} on success
1121
+ ```
1122
+
1123
+ `#ping` raises `MCP::Client::ServerError` when the server returns a JSON-RPC error.
1124
+ It raises `MCP::Client::ValidationError` when the response `result` is missing or
1125
+ is not a Hash (matching the spec requirement that `result` be an object).
1126
+ Transport-level errors (for example, `MCP::Client::Stdio`'s `read_timeout:` firing)
1127
+ propagate as exceptions raised by the transport layer.
1128
+
992
1129
  ### Progress
993
1130
 
994
1131
  The MCP Ruby SDK supports progress tracking for long-running tool operations,
@@ -1085,6 +1222,108 @@ The SDK automatically enforces the 100-item limit per the MCP specification.
1085
1222
  The server validates that the referenced prompt, resource, or resource template is registered before calling the handler.
1086
1223
  Requests for unknown references return an error.
1087
1224
 
1225
+ ### Elicitation
1226
+
1227
+ The MCP Ruby SDK supports [elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation),
1228
+ which allows servers to request additional information from users through the client during tool execution.
1229
+
1230
+ Elicitation is a **server-to-client request**. The server sends a request and blocks until the user responds via the client.
1231
+
1232
+ #### Capabilities
1233
+
1234
+ Clients must declare the `elicitation` capability during initialization. The server checks this before sending any elicitation request
1235
+ and raises a `RuntimeError` if the client does not support it.
1236
+
1237
+ For URL mode support, the client must also declare `elicitation.url` capability.
1238
+
1239
+ #### Using Elicitation in Tools
1240
+
1241
+ Tools that accept a `server_context:` parameter can call `create_form_elicitation` on it:
1242
+
1243
+ ```ruby
1244
+ server.define_tool(name: "collect_info", description: "Collect user info") do |server_context:|
1245
+ result = server_context.create_form_elicitation(
1246
+ message: "Please provide your name",
1247
+ requested_schema: {
1248
+ type: "object",
1249
+ properties: { name: { type: "string" } },
1250
+ required: ["name"],
1251
+ },
1252
+ )
1253
+
1254
+ MCP::Tool::Response.new([{ type: "text", text: "Hello, #{result[:content][:name]}" }])
1255
+ end
1256
+ ```
1257
+
1258
+ #### Form Mode
1259
+
1260
+ Form mode collects structured data from the user directly through the MCP client:
1261
+
1262
+ ```ruby
1263
+ server.define_tool(name: "collect_contact", description: "Collect contact info") do |server_context:|
1264
+ result = server_context.create_form_elicitation(
1265
+ message: "Please provide your contact information",
1266
+ requested_schema: {
1267
+ type: "object",
1268
+ properties: {
1269
+ name: { type: "string", description: "Your full name" },
1270
+ email: { type: "string", format: "email", description: "Your email address" },
1271
+ },
1272
+ required: ["name", "email"],
1273
+ },
1274
+ )
1275
+
1276
+ text = case result[:action]
1277
+ when "accept"
1278
+ "Hello, #{result[:content][:name]} (#{result[:content][:email]})"
1279
+ when "decline"
1280
+ "User declined"
1281
+ when "cancel"
1282
+ "User cancelled"
1283
+ end
1284
+
1285
+ MCP::Tool::Response.new([{ type: "text", text: text }])
1286
+ end
1287
+ ```
1288
+
1289
+ #### URL Mode
1290
+
1291
+ URL mode directs the user to an external URL for out-of-band interactions such as OAuth flows:
1292
+
1293
+ ```ruby
1294
+ server.define_tool(name: "authorize_github", description: "Authorize GitHub") do |server_context:|
1295
+ elicitation_id = SecureRandom.uuid
1296
+
1297
+ result = server_context.create_url_elicitation(
1298
+ message: "Please authorize access to your GitHub account",
1299
+ url: "https://example.com/oauth/authorize?elicitation_id=#{elicitation_id}",
1300
+ elicitation_id: elicitation_id,
1301
+ )
1302
+
1303
+ server_context.notify_elicitation_complete(elicitation_id: elicitation_id)
1304
+
1305
+ MCP::Tool::Response.new([{ type: "text", text: "Authorization complete" }])
1306
+ end
1307
+ ```
1308
+
1309
+ #### URLElicitationRequiredError
1310
+
1311
+ When a tool cannot proceed until an out-of-band elicitation is completed, raise `MCP::Server::URLElicitationRequiredError`.
1312
+ This returns a JSON-RPC error with code `-32042` to the client:
1313
+
1314
+ ```ruby
1315
+ server.define_tool(name: "access_github", description: "Access GitHub") do |server_context:|
1316
+ raise MCP::Server::URLElicitationRequiredError.new([
1317
+ {
1318
+ mode: "url",
1319
+ elicitationId: SecureRandom.uuid,
1320
+ url: "https://example.com/oauth/authorize",
1321
+ message: "GitHub authorization is required.",
1322
+ },
1323
+ ])
1324
+ end
1325
+ ```
1326
+
1088
1327
  ### Logging
1089
1328
 
1090
1329
  The MCP Ruby SDK supports structured logging through the `notify_log_message` method, following the [MCP Logging specification](https://modelcontextprotocol.io/specification/latest/server/utilities/logging).
@@ -1186,6 +1425,22 @@ Set `stateless: true` in `MCP::Server::Transports::StreamableHTTPTransport.new`
1186
1425
  transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)
1187
1426
  ```
1188
1427
 
1428
+ You can enable JSON response mode, where the server returns `application/json` instead of `text/event-stream`.
1429
+ Set `enable_json_response: true` in `MCP::Server::Transports::StreamableHTTPTransport.new`:
1430
+
1431
+ ```ruby
1432
+ # JSON response mode
1433
+ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, enable_json_response: true)
1434
+ ```
1435
+
1436
+ In JSON response mode, the POST response is a single JSON object, so server-to-client messages
1437
+ that need to arrive during request processing are not supported:
1438
+ request-scoped notifications (`progress`, `log`) are silently dropped, and all server-to-client requests
1439
+ (`sampling/createMessage`, `roots/list`, `elicitation/create`) raise an error.
1440
+ Session-scoped standalone notifications (`resources/updated`, `elicitation/complete`) and
1441
+ broadcast notifications (`tools/list_changed`, etc.) still flow to clients connected to the GET SSE stream.
1442
+ This mode is suitable for simple tool servers that do not need server-initiated requests.
1443
+
1189
1444
  By default, sessions do not expire. To mitigate session hijacking risks, you can set a `session_idle_timeout` (in seconds).
1190
1445
  When configured, sessions that receive no HTTP requests for this duration are automatically expired and cleaned up:
1191
1446
 
@@ -1194,6 +1449,90 @@ When configured, sessions that receive no HTTP requests for this duration are au
1194
1449
  transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, session_idle_timeout: 1800)
1195
1450
  ```
1196
1451
 
1452
+ ### Pagination
1453
+
1454
+ The MCP Ruby SDK supports [pagination](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/pagination)
1455
+ for list operations that may return large result sets. Pagination uses string cursor tokens carrying a zero-based offset,
1456
+ treated as opaque by clients: the server decides page size, and the client follows `nextCursor` until the server omits it.
1457
+
1458
+ Pagination applies to `tools/list`, `prompts/list`, `resources/list`, and `resources/templates/list`.
1459
+
1460
+ #### Server-Side: Enabling Pagination
1461
+
1462
+ Pass `page_size:` to `MCP::Server.new` to split list responses into pages. When `page_size` is omitted (the default),
1463
+ list responses contain all items in a single response, preserving the pre-pagination behavior.
1464
+
1465
+ ```ruby
1466
+ server = MCP::Server.new(
1467
+ name: "my_server",
1468
+ tools: tools,
1469
+ page_size: 50,
1470
+ )
1471
+ ```
1472
+
1473
+ When `page_size` is set, list responses include a `nextCursor` field whenever more pages are available:
1474
+
1475
+ ```json
1476
+ {
1477
+ "jsonrpc": "2.0",
1478
+ "id": 1,
1479
+ "result": {
1480
+ "tools": [
1481
+ { "name": "example_tool" }
1482
+ ],
1483
+ "nextCursor": "50"
1484
+ }
1485
+ }
1486
+ ```
1487
+
1488
+ Invalid cursors (e.g. non-numeric, negative, or out-of-range) are rejected with JSON-RPC error code `-32602 (Invalid params)` per the MCP specification.
1489
+
1490
+ #### Client-Side: Iterating Pages
1491
+
1492
+ `MCP::Client` exposes `list_tools`, `list_prompts`, `list_resources`, and `list_resource_templates`.
1493
+ **Each call issues exactly one `*/list` JSON-RPC request and returns exactly one page** — not the full collection.
1494
+ The returned result object (`MCP::Client::ListToolsResult` etc.) exposes the page items and the next cursor as method accessors:
1495
+
1496
+ ```ruby
1497
+ client = MCP::Client.new(transport: transport)
1498
+
1499
+ cursor = nil
1500
+ loop do
1501
+ page = client.list_tools(cursor: cursor)
1502
+ page.tools.each { |tool| process(tool) }
1503
+ cursor = page.next_cursor
1504
+ break unless cursor
1505
+ end
1506
+ ```
1507
+
1508
+ The same pattern applies to `list_prompts` (`page.prompts`), `list_resources` (`page.resources`), and
1509
+ `list_resource_templates` (`page.resource_templates`). `next_cursor` is `nil` on the final page.
1510
+
1511
+ Because a single call returns a single page, how many items come back depends on the server's `page_size` configuration:
1512
+
1513
+ | Server `page_size` | `client.list_tools(cursor: nil)` |
1514
+ |--------------------|---------------------------------------------------------------------|
1515
+ | Not set (default) | Returns every item in one response. `next_cursor` is `nil`. |
1516
+ | Set to `N` | Returns the first `N` items. `next_cursor` is set for continuation. |
1517
+
1518
+ If your application needs the complete collection regardless of how the server is configured, either loop on
1519
+ `next_cursor` as shown above, or use the whole-collection methods described below.
1520
+
1521
+ #### Fetching the Complete Collection
1522
+
1523
+ `client.tools`, `client.resources`, `client.resource_templates`, and `client.prompts` auto-iterate
1524
+ through all pages and return a plain array of items, guaranteeing the full collection regardless
1525
+ of the server's `page_size` setting. When a server paginates, they issue multiple JSON-RPC round
1526
+ trips per call and break out of the pagination loop if the server returns the same `nextCursor`
1527
+ twice in a row as a safety measure.
1528
+
1529
+ ```ruby
1530
+ tools = client.tools # => Array<MCP::Client::Tool> of every tool on the server.
1531
+ ```
1532
+
1533
+ Use these when you want the complete list; use `list_tools(cursor:)` etc. when you need
1534
+ fine-grained iteration (e.g. to stream-process pages without loading everything into memory).
1535
+
1197
1536
  ### Advanced
1198
1537
 
1199
1538
  #### Custom Methods
@@ -1247,17 +1586,13 @@ end
1247
1586
  - Raises `MCP::Server::MethodAlreadyDefinedError` if trying to override an existing method
1248
1587
  - Supports the same exception reporting and instrumentation as standard methods
1249
1588
 
1250
- ### Unsupported Features (to be implemented in future versions)
1251
-
1252
- - Resource subscriptions
1253
- - Elicitation
1254
-
1255
1589
  ## Building an MCP Client
1256
1590
 
1257
1591
  The `MCP::Client` class provides an interface for interacting with MCP servers.
1258
1592
 
1259
1593
  This class supports:
1260
1594
 
1595
+ - Liveness check via the `ping` method (`MCP::Client#ping`)
1261
1596
  - Tool listing via the `tools/list` method (`MCP::Client#tools`)
1262
1597
  - Tool invocation via the `tools/call` method (`MCP::Client#call_tools`)
1263
1598
  - Resource listing via the `resources/list` method (`MCP::Client#resources`)
@@ -1344,11 +1679,12 @@ The stdio transport automatically handles:
1344
1679
 
1345
1680
  Use the `MCP::Client::HTTP` transport to interact with MCP servers using simple HTTP requests.
1346
1681
 
1347
- You'll need to add `faraday` as a dependency in order to use the HTTP transport layer:
1682
+ You'll need to add `faraday` as a dependency in order to use the HTTP transport layer. Add `event_stream_parser` as well if the server uses SSE (`text/event-stream`) responses:
1348
1683
 
1349
1684
  ```ruby
1350
1685
  gem 'mcp'
1351
1686
  gem 'faraday', '>= 2.0'
1687
+ gem 'event_stream_parser', '>= 1.0' # optional, required only for SSE responses
1352
1688
  ```
1353
1689
 
1354
1690
  Example usage: