mcp 0.10.0 → 0.12.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,6 +38,7 @@ 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 sampling (server-to-client LLM completion requests)
41
42
 
42
43
  ### Supported Methods
43
44
 
@@ -50,288 +51,10 @@ It implements the Model Context Protocol specification, handling model context r
50
51
  - `resources/list` - Lists all registered resources and their schemas
51
52
  - `resources/read` - Retrieves a specific resource by name
52
53
  - `resources/templates/list` - Lists all registered resource templates and their schemas
54
+ - `completion/complete` - Returns autocompletion suggestions for prompt arguments and resource URIs
55
+ - `sampling/createMessage` - Requests LLM completion from the client (server-to-client)
53
56
 
54
- ### Custom Methods
55
-
56
- The server allows you to define custom JSON-RPC methods beyond the standard MCP protocol methods using the `define_custom_method` method:
57
-
58
- ```ruby
59
- server = MCP::Server.new(name: "my_server")
60
-
61
- # Define a custom method that returns a result
62
- server.define_custom_method(method_name: "add") do |params|
63
- params[:a] + params[:b]
64
- end
65
-
66
- # Define a custom notification method (returns nil)
67
- server.define_custom_method(method_name: "notify") do |params|
68
- # Process notification
69
- nil
70
- end
71
- ```
72
-
73
- **Key Features:**
74
-
75
- - Accepts any method name as a string
76
- - Block receives the request parameters as a hash
77
- - Can handle both regular methods (with responses) and notifications
78
- - Prevents overriding existing MCP protocol methods
79
- - Supports instrumentation callbacks for monitoring
80
-
81
- **Usage Example:**
82
-
83
- ```ruby
84
- # Client request
85
- {
86
- "jsonrpc": "2.0",
87
- "id": 1,
88
- "method": "add",
89
- "params": { "a": 5, "b": 3 }
90
- }
91
-
92
- # Server response
93
- {
94
- "jsonrpc": "2.0",
95
- "id": 1,
96
- "result": 8
97
- }
98
- ```
99
-
100
- **Error Handling:**
101
-
102
- - Raises `MCP::Server::MethodAlreadyDefinedError` if trying to override an existing method
103
- - Supports the same exception reporting and instrumentation as standard methods
104
-
105
- ### Notifications
106
-
107
- The server supports sending notifications to clients when lists of tools, prompts, or resources change. This enables real-time updates without polling.
108
-
109
- #### Notification Methods
110
-
111
- The server provides the following notification methods:
112
-
113
- - `notify_tools_list_changed` - Send a notification when the tools list changes
114
- - `notify_prompts_list_changed` - Send a notification when the prompts list changes
115
- - `notify_resources_list_changed` - Send a notification when the resources list changes
116
- - `notify_log_message` - Send a structured logging notification message
117
-
118
- #### Session Scoping
119
-
120
- When using Streamable HTTP transport with multiple clients, each client connection gets its own session. Notifications are scoped as follows:
121
-
122
- - **`report_progress`** and **`notify_log_message`** called via `server_context` inside a tool handler are automatically sent only to the requesting client.
123
- No extra configuration is needed.
124
- - **`notify_tools_list_changed`**, **`notify_prompts_list_changed`**, and **`notify_resources_list_changed`** are always broadcast to all connected clients,
125
- as they represent server-wide state changes. These should be called on the `server` instance directly.
126
-
127
- #### Notification Format
128
-
129
- Notifications follow the JSON-RPC 2.0 specification and use these method names:
130
-
131
- - `notifications/tools/list_changed`
132
- - `notifications/prompts/list_changed`
133
- - `notifications/resources/list_changed`
134
- - `notifications/progress`
135
- - `notifications/message`
136
-
137
- ### Progress
138
-
139
- The MCP Ruby SDK supports progress tracking for long-running tool operations,
140
- following the [MCP Progress specification](https://modelcontextprotocol.io/specification/latest/server/utilities/progress).
141
-
142
- #### How Progress Works
143
-
144
- 1. **Client Request**: The client sends a `progressToken` in the `_meta` field when calling a tool
145
- 2. **Server Notification**: The server sends `notifications/progress` messages back to the client during tool execution
146
- 3. **Tool Integration**: Tools call `server_context.report_progress` to report incremental progress
147
-
148
- #### Server-Side: Tool with Progress
149
-
150
- Tools that accept a `server_context:` parameter can call `report_progress` on it.
151
- The server automatically wraps the context in an `MCP::ServerContext` instance that provides this method:
152
-
153
- ```ruby
154
- class LongRunningTool < MCP::Tool
155
- description "A tool that reports progress during execution"
156
- input_schema(
157
- properties: {
158
- count: { type: "integer" },
159
- },
160
- required: ["count"]
161
- )
162
-
163
- def self.call(count:, server_context:)
164
- count.times do |i|
165
- # Do work here.
166
- server_context.report_progress(i + 1, total: count, message: "Processing item #{i + 1}")
167
- end
168
-
169
- MCP::Tool::Response.new([{ type: "text", text: "Done" }])
170
- end
171
- end
172
- ```
173
-
174
- The `server_context.report_progress` method accepts:
175
-
176
- - `progress` (required) — current progress value (numeric)
177
- - `total:` (optional) — total expected value, so clients can display a percentage
178
- - `message:` (optional) — human-readable status message
179
-
180
- **Key Features:**
181
-
182
- - Tools report progress via `server_context.report_progress`
183
- - `report_progress` is a no-op when no `progressToken` was provided by the client
184
- - Supports both numeric and string progress tokens
185
-
186
- ### Logging
187
-
188
- 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).
189
-
190
- The `notifications/message` notification is used for structured logging between client and server.
191
-
192
- #### Log Levels
193
-
194
- The SDK supports 8 log levels with increasing severity:
195
-
196
- - `debug` - Detailed debugging information
197
- - `info` - General informational messages
198
- - `notice` - Normal but significant events
199
- - `warning` - Warning conditions
200
- - `error` - Error conditions
201
- - `critical` - Critical conditions
202
- - `alert` - Action must be taken immediately
203
- - `emergency` - System is unusable
204
-
205
- #### How Logging Works
206
-
207
- 1. **Client Configuration**: The client sends a `logging/setLevel` request to configure the minimum log level
208
- 2. **Server Filtering**: The server only sends log messages at the configured level or higher severity
209
- 3. **Notification Delivery**: Log messages are sent as `notifications/message` to the client
210
-
211
- For example, if the client sets the level to `"error"` (severity 4), the server will send messages with levels: `error`, `critical`, `alert`, and `emergency`.
212
-
213
- For more details, see the [MCP Logging specification](https://modelcontextprotocol.io/specification/latest/server/utilities/logging).
214
-
215
- **Usage Example:**
216
-
217
- ```ruby
218
- server = MCP::Server.new(name: "my_server")
219
- transport = MCP::Server::Transports::StdioTransport.new(server)
220
- server.transport = transport
221
-
222
- # The client first configures the logging level (on the client side):
223
- transport.send_request(
224
- request: {
225
- jsonrpc: "2.0",
226
- method: "logging/setLevel",
227
- params: { level: "info" },
228
- id: session_id # Unique request ID within the session
229
- }
230
- )
231
-
232
- # Send log messages at different severity levels
233
- server.notify_log_message(
234
- data: { message: "Application started successfully" },
235
- level: "info"
236
- )
237
-
238
- server.notify_log_message(
239
- data: { message: "Configuration file not found, using defaults" },
240
- level: "warning"
241
- )
242
-
243
- server.notify_log_message(
244
- data: {
245
- error: "Database connection failed",
246
- details: { host: "localhost", port: 5432 }
247
- },
248
- level: "error",
249
- logger: "DatabaseLogger" # Optional logger name
250
- )
251
- ```
252
-
253
- **Key Features:**
254
-
255
- - Supports 8 log levels (debug, info, notice, warning, error, critical, alert, emergency) based on https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging#log-levels
256
- - Server has capability `logging` to send log messages
257
- - Messages are only sent if a transport is configured
258
- - Messages are filtered based on the client's configured log level
259
- - If the log level hasn't been set by the client, no messages will be sent
260
-
261
- #### Transport Support
262
-
263
- - **stdio**: Notifications are sent as JSON-RPC 2.0 messages to stdout
264
- - **Streamable HTTP**: Notifications are sent as JSON-RPC 2.0 messages over HTTP with streaming (chunked transfer or SSE)
265
-
266
- #### Usage Example
267
-
268
- ```ruby
269
- server = MCP::Server.new(name: "my_server")
270
-
271
- # Default Streamable HTTP - session oriented
272
- transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
273
-
274
- server.transport = transport
275
-
276
- # When tools change, notify clients
277
- server.define_tool(name: "new_tool") { |**args| { result: "ok" } }
278
- server.notify_tools_list_changed
279
- ```
280
-
281
- You can use Stateless Streamable HTTP, where notifications are not supported and all calls are request/response interactions.
282
- This mode allows for easy multi-node deployment.
283
- Set `stateless: true` in `MCP::Server::Transports::StreamableHTTPTransport.new` (`stateless` defaults to `false`):
284
-
285
- ```ruby
286
- # Stateless Streamable HTTP - session-less
287
- transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)
288
- ```
289
-
290
- By default, sessions do not expire. To mitigate session hijacking risks, you can set a `session_idle_timeout` (in seconds).
291
- When configured, sessions that receive no HTTP requests for this duration are automatically expired and cleaned up:
292
-
293
- ```ruby
294
- # Session timeout of 30 minutes
295
- transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, session_idle_timeout: 1800)
296
- ```
297
-
298
- ### Unsupported Features (to be implemented in future versions)
299
-
300
- - Resource subscriptions
301
- - Completions
302
- - Elicitation
303
-
304
- ### Usage
305
-
306
- #### Rails Controller
307
-
308
- When added to a Rails controller on a route that handles POST requests, your server will be compliant with non-streaming
309
- [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports#streamable-http) transport
310
- requests.
311
-
312
- You can use `StreamableHTTPTransport#handle_request` to handle requests with proper HTTP
313
- status codes (e.g., 202 Accepted for notifications).
314
-
315
- ```ruby
316
- class McpController < ActionController::Base
317
- def create
318
- server = MCP::Server.new(
319
- name: "my_server",
320
- title: "Example Server Display Name",
321
- version: "1.0.0",
322
- instructions: "Use the tools of this server as a last resort",
323
- tools: [SomeTool, AnotherTool],
324
- prompts: [MyPrompt],
325
- server_context: { user_id: current_user.id },
326
- )
327
- transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
328
- server.transport = transport
329
- status, headers, body = transport.handle_request(request)
330
-
331
- render(json: body.first, status: status, headers: headers)
332
- end
333
- end
334
- ```
57
+ ### Usage
335
58
 
336
59
  #### Stdio Transport
337
60
 
@@ -380,6 +103,48 @@ $ ruby examples/stdio_server.rb
380
103
  {"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"example_tool","arguments":{"message":"Hello"}}}
381
104
  ```
382
105
 
106
+ #### Rails Controller
107
+
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.
111
+
112
+ You can use `StreamableHTTPTransport#handle_request` to handle requests with proper HTTP
113
+ status codes (e.g., 202 Accepted for notifications).
114
+
115
+ ```ruby
116
+ class McpController < ActionController::API
117
+ def create
118
+ server = MCP::Server.new(
119
+ name: "my_server",
120
+ title: "Example Server Display Name",
121
+ version: "1.0.0",
122
+ instructions: "Use the tools of this server as a last resort",
123
+ tools: [SomeTool, AnotherTool],
124
+ prompts: [MyPrompt],
125
+ server_context: { user_id: current_user.id },
126
+ )
127
+ # Since the `MCP-Session-Id` is not shared across requests, `stateless: true` is set.
128
+ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)
129
+ status, headers, body = transport.handle_request(request)
130
+
131
+ render(json: body.first, status: status, headers: headers)
132
+ end
133
+ end
134
+ ```
135
+
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
+
383
148
  ### Configuration
384
149
 
385
150
  The gem can be configured using the `MCP.configure` block:
@@ -930,119 +695,563 @@ end
930
695
  3. Using the `MCP::Server#define_prompt` method:
931
696
 
932
697
  ```ruby
933
- server = MCP::Server.new
934
- server.define_prompt(
935
- name: "my_prompt",
936
- description: "This prompt performs specific functionality...",
937
- arguments: [
938
- Prompt::Argument.new(
939
- name: "message",
940
- title: "Message Title",
941
- description: "Input message",
942
- required: true
943
- )
944
- ],
945
- meta: { version: "1.0", category: "example" }
946
- ) do |args, server_context:|
947
- Prompt::Result.new(
948
- description: "Response description",
949
- messages: [
950
- Prompt::Message.new(
951
- role: "user",
952
- content: Content::Text.new("User message")
953
- ),
954
- Prompt::Message.new(
955
- role: "assistant",
956
- content: Content::Text.new(args["message"])
957
- )
958
- ]
698
+ server = MCP::Server.new
699
+ server.define_prompt(
700
+ name: "my_prompt",
701
+ description: "This prompt performs specific functionality...",
702
+ arguments: [
703
+ Prompt::Argument.new(
704
+ name: "message",
705
+ title: "Message Title",
706
+ description: "Input message",
707
+ required: true
708
+ )
709
+ ],
710
+ meta: { version: "1.0", category: "example" }
711
+ ) do |args, server_context:|
712
+ Prompt::Result.new(
713
+ description: "Response description",
714
+ messages: [
715
+ Prompt::Message.new(
716
+ role: "user",
717
+ content: Content::Text.new("User message")
718
+ ),
719
+ Prompt::Message.new(
720
+ role: "assistant",
721
+ content: Content::Text.new(args["message"])
722
+ )
723
+ ]
724
+ )
725
+ end
726
+ ```
727
+
728
+ The server_context parameter is the server_context passed into the server and can be used to pass per request information,
729
+ e.g. around authentication state or user preferences.
730
+
731
+ ### Key Components
732
+
733
+ - `MCP::Prompt::Argument` - Defines input parameters for the prompt template with name, title, description, and required flag
734
+ - `MCP::Prompt::Message` - Represents a message in the conversation with a role and content
735
+ - `MCP::Prompt::Result` - The output of a prompt template containing description and messages
736
+ - `MCP::Content::Text` - Text content for messages
737
+
738
+ ### Usage
739
+
740
+ Register prompts with the MCP server:
741
+
742
+ ```ruby
743
+ server = MCP::Server.new(
744
+ name: "my_server",
745
+ prompts: [MyPrompt],
746
+ server_context: { user_id: current_user.id },
747
+ )
748
+ ```
749
+
750
+ The server will handle prompt listing and execution through the MCP protocol methods:
751
+
752
+ - `prompts/list` - Lists all registered prompts and their schemas
753
+ - `prompts/get` - Retrieves and executes a specific prompt with arguments
754
+
755
+ ### Resources
756
+
757
+ MCP spec includes [Resources](https://modelcontextprotocol.io/specification/latest/server/resources).
758
+
759
+ ### Reading Resources
760
+
761
+ The `MCP::Resource` class provides a way to register resources with the server.
762
+
763
+ ```ruby
764
+ resource = MCP::Resource.new(
765
+ uri: "https://example.com/my_resource",
766
+ name: "my-resource",
767
+ title: "My Resource",
768
+ description: "Lorem ipsum dolor sit amet",
769
+ mime_type: "text/html",
770
+ )
771
+
772
+ server = MCP::Server.new(
773
+ name: "my_server",
774
+ resources: [resource],
775
+ )
776
+ ```
777
+
778
+ The server must register a handler for the `resources/read` method to retrieve a resource dynamically.
779
+
780
+ ```ruby
781
+ server.resources_read_handler do |params|
782
+ [{
783
+ uri: params[:uri],
784
+ mimeType: "text/plain",
785
+ text: "Hello from example resource! URI: #{params[:uri]}"
786
+ }]
787
+ end
788
+ ```
789
+
790
+ otherwise `resources/read` requests will be a no-op.
791
+
792
+ ### Resource Templates
793
+
794
+ The `MCP::ResourceTemplate` class provides a way to register resource templates with the server.
795
+
796
+ ```ruby
797
+ resource_template = MCP::ResourceTemplate.new(
798
+ uri_template: "https://example.com/my_resource_template",
799
+ name: "my-resource-template",
800
+ title: "My Resource Template",
801
+ description: "Lorem ipsum dolor sit amet",
802
+ mime_type: "text/html",
803
+ )
804
+
805
+ server = MCP::Server.new(
806
+ name: "my_server",
807
+ resource_templates: [resource_template],
808
+ )
809
+ ```
810
+
811
+ ### Sampling
812
+
813
+ The Model Context Protocol allows servers to request LLM completions from clients through the `sampling/createMessage` method.
814
+ This enables servers to leverage the client's LLM capabilities without needing direct access to AI models.
815
+
816
+ **Key Concepts:**
817
+
818
+ - **Server-to-Client Request**: Unlike typical MCP methods (client to server), sampling is initiated by the server
819
+ - **Client Capability**: Clients must declare `sampling` capability during initialization
820
+ - **Tool Support**: When using tools in sampling requests, clients must declare `sampling.tools` capability
821
+ - **Human-in-the-Loop**: Clients can implement user approval before forwarding requests to LLMs
822
+
823
+ **Using Sampling in Tools:**
824
+
825
+ 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:
828
+
829
+ ```ruby
830
+ class SummarizeTool < MCP::Tool
831
+ description "Summarize text using LLM"
832
+ input_schema(
833
+ properties: {
834
+ text: { type: "string" }
835
+ },
836
+ required: ["text"]
837
+ )
838
+
839
+ def self.call(text:, server_context:)
840
+ result = server_context.create_sampling_message(
841
+ messages: [
842
+ { role: "user", content: { type: "text", text: "Please summarize: #{text}" } }
843
+ ],
844
+ max_tokens: 500
845
+ )
846
+
847
+ MCP::Tool::Response.new([{
848
+ type: "text",
849
+ text: result[:content][:text]
850
+ }])
851
+ end
852
+ end
853
+
854
+ server = MCP::Server.new(name: "my_server", tools: [SummarizeTool])
855
+ server.server_context = server
856
+ ```
857
+
858
+ **Parameters:**
859
+
860
+ Required:
861
+
862
+ - `messages:` (Array) - Array of message objects with `role` and `content`
863
+ - `max_tokens:` (Integer) - Maximum tokens in the response
864
+
865
+ Optional:
866
+
867
+ - `system_prompt:` (String) - System prompt for the LLM
868
+ - `model_preferences:` (Hash) - Model selection preferences (e.g., `{ intelligencePriority: 0.8 }`)
869
+ - `include_context:` (String) - Context inclusion: `"none"`, `"thisServer"`, or `"allServers"` (soft-deprecated)
870
+ - `temperature:` (Float) - Sampling temperature
871
+ - `stop_sequences:` (Array) - Sequences that stop generation
872
+ - `metadata:` (Hash) - Additional metadata
873
+ - `tools:` (Array) - Tools available to the LLM (requires `sampling.tools` capability)
874
+ - `tool_choice:` (Hash) - Tool selection mode (e.g., `{ mode: "auto" }`)
875
+
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
+ **Error Handling:**
954
+
955
+ - Raises `RuntimeError` if transport is not set
956
+ - Raises `RuntimeError` if client does not support `sampling` capability
957
+ - Raises `RuntimeError` if `tools` are used but client lacks `sampling.tools` capability
958
+ - Raises `StandardError` if client returns an error response
959
+
960
+ ### Notifications
961
+
962
+ The server supports sending notifications to clients when lists of tools, prompts, or resources change. This enables real-time updates without polling.
963
+
964
+ #### Notification Methods
965
+
966
+ The server provides the following notification methods:
967
+
968
+ - `notify_tools_list_changed` - Send a notification when the tools list changes
969
+ - `notify_prompts_list_changed` - Send a notification when the prompts list changes
970
+ - `notify_resources_list_changed` - Send a notification when the resources list changes
971
+ - `notify_log_message` - Send a structured logging notification message
972
+
973
+ #### Session Scoping
974
+
975
+ When using Streamable HTTP transport with multiple clients, each client connection gets its own session. Notifications are scoped as follows:
976
+
977
+ - **`report_progress`** and **`notify_log_message`** called via `server_context` inside a tool handler are automatically sent only to the requesting client.
978
+ No extra configuration is needed.
979
+ - **`notify_tools_list_changed`**, **`notify_prompts_list_changed`**, and **`notify_resources_list_changed`** are always broadcast to all connected clients,
980
+ as they represent server-wide state changes. These should be called on the `server` instance directly.
981
+
982
+ #### Notification Format
983
+
984
+ Notifications follow the JSON-RPC 2.0 specification and use these method names:
985
+
986
+ - `notifications/tools/list_changed`
987
+ - `notifications/prompts/list_changed`
988
+ - `notifications/resources/list_changed`
989
+ - `notifications/progress`
990
+ - `notifications/message`
991
+
992
+ ### Progress
993
+
994
+ The MCP Ruby SDK supports progress tracking for long-running tool operations,
995
+ following the [MCP Progress specification](https://modelcontextprotocol.io/specification/latest/server/utilities/progress).
996
+
997
+ #### How Progress Works
998
+
999
+ 1. **Client Request**: The client sends a `progressToken` in the `_meta` field when calling a tool
1000
+ 2. **Server Notification**: The server sends `notifications/progress` messages back to the client during tool execution
1001
+ 3. **Tool Integration**: Tools call `server_context.report_progress` to report incremental progress
1002
+
1003
+ #### Server-Side: Tool with Progress
1004
+
1005
+ Tools that accept a `server_context:` parameter can call `report_progress` on it.
1006
+ The server automatically wraps the context in an `MCP::ServerContext` instance that provides this method:
1007
+
1008
+ ```ruby
1009
+ class LongRunningTool < MCP::Tool
1010
+ description "A tool that reports progress during execution"
1011
+ input_schema(
1012
+ properties: {
1013
+ count: { type: "integer" },
1014
+ },
1015
+ required: ["count"]
959
1016
  )
1017
+
1018
+ def self.call(count:, server_context:)
1019
+ count.times do |i|
1020
+ # Do work here.
1021
+ server_context.report_progress(i + 1, total: count, message: "Processing item #{i + 1}")
1022
+ end
1023
+
1024
+ MCP::Tool::Response.new([{ type: "text", text: "Done" }])
1025
+ end
960
1026
  end
961
1027
  ```
962
1028
 
963
- The server_context parameter is the server_context passed into the server and can be used to pass per request information,
964
- e.g. around authentication state or user preferences.
1029
+ The `server_context.report_progress` method accepts:
965
1030
 
966
- ### Key Components
1031
+ - `progress` (required) — current progress value (numeric)
1032
+ - `total:` (optional) — total expected value, so clients can display a percentage
1033
+ - `message:` (optional) — human-readable status message
967
1034
 
968
- - `MCP::Prompt::Argument` - Defines input parameters for the prompt template with name, title, description, and required flag
969
- - `MCP::Prompt::Message` - Represents a message in the conversation with a role and content
970
- - `MCP::Prompt::Result` - The output of a prompt template containing description and messages
971
- - `MCP::Content::Text` - Text content for messages
1035
+ **Key Features:**
972
1036
 
973
- ### Usage
1037
+ - Tools report progress via `server_context.report_progress`
1038
+ - `report_progress` is a no-op when no `progressToken` was provided by the client
1039
+ - Supports both numeric and string progress tokens
974
1040
 
975
- Register prompts with the MCP server:
1041
+ ### Completions
1042
+
1043
+ MCP spec includes [Completions](https://modelcontextprotocol.io/specification/latest/server/utilities/completion),
1044
+ which enable servers to provide autocompletion suggestions for prompt arguments and resource URIs.
1045
+
1046
+ To enable completions, declare the `completions` capability and register a handler:
976
1047
 
977
1048
  ```ruby
978
1049
  server = MCP::Server.new(
979
1050
  name: "my_server",
980
- prompts: [MyPrompt],
981
- server_context: { user_id: current_user.id },
1051
+ prompts: [CodeReviewPrompt],
1052
+ resource_templates: [FileTemplate],
1053
+ capabilities: { completions: {} },
982
1054
  )
1055
+
1056
+ server.completion_handler do |params|
1057
+ ref = params[:ref]
1058
+ argument = params[:argument]
1059
+ value = argument[:value]
1060
+
1061
+ case ref[:type]
1062
+ when "ref/prompt"
1063
+ values = case argument[:name]
1064
+ when "language"
1065
+ ["python", "pytorch", "pyside"].select { |v| v.start_with?(value) }
1066
+ else
1067
+ []
1068
+ end
1069
+ { completion: { values: values, hasMore: false } }
1070
+ when "ref/resource"
1071
+ { completion: { values: [], hasMore: false } }
1072
+ end
1073
+ end
983
1074
  ```
984
1075
 
985
- The server will handle prompt listing and execution through the MCP protocol methods:
1076
+ The handler receives a `params` hash with:
986
1077
 
987
- - `prompts/list` - Lists all registered prompts and their schemas
988
- - `prompts/get` - Retrieves and executes a specific prompt with arguments
1078
+ - `ref` - The reference (`{ type: "ref/prompt", name: "..." }` or `{ type: "ref/resource", uri: "..." }`)
1079
+ - `argument` - The argument being completed (`{ name: "...", value: "..." }`)
1080
+ - `context` (optional) - Previously resolved arguments (`{ arguments: { ... } }`)
989
1081
 
990
- ### Resources
1082
+ The handler must return a hash with a `completion` key containing `values` (array of strings), and optionally `total` and `hasMore`.
1083
+ The SDK automatically enforces the 100-item limit per the MCP specification.
991
1084
 
992
- MCP spec includes [Resources](https://modelcontextprotocol.io/specification/latest/server/resources).
1085
+ The server validates that the referenced prompt, resource, or resource template is registered before calling the handler.
1086
+ Requests for unknown references return an error.
993
1087
 
994
- ### Reading Resources
1088
+ ### Logging
995
1089
 
996
- The `MCP::Resource` class provides a way to register resources with the server.
1090
+ 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).
1091
+
1092
+ The `notifications/message` notification is used for structured logging between client and server.
1093
+
1094
+ #### Log Levels
1095
+
1096
+ The SDK supports 8 log levels with increasing severity:
1097
+
1098
+ - `debug` - Detailed debugging information
1099
+ - `info` - General informational messages
1100
+ - `notice` - Normal but significant events
1101
+ - `warning` - Warning conditions
1102
+ - `error` - Error conditions
1103
+ - `critical` - Critical conditions
1104
+ - `alert` - Action must be taken immediately
1105
+ - `emergency` - System is unusable
1106
+
1107
+ #### How Logging Works
1108
+
1109
+ 1. **Client Configuration**: The client sends a `logging/setLevel` request to configure the minimum log level
1110
+ 2. **Server Filtering**: The server only sends log messages at the configured level or higher severity
1111
+ 3. **Notification Delivery**: Log messages are sent as `notifications/message` to the client
1112
+
1113
+ For example, if the client sets the level to `"error"` (severity 4), the server will send messages with levels: `error`, `critical`, `alert`, and `emergency`.
1114
+
1115
+ For more details, see the [MCP Logging specification](https://modelcontextprotocol.io/specification/latest/server/utilities/logging).
1116
+
1117
+ **Usage Example:**
997
1118
 
998
1119
  ```ruby
999
- resource = MCP::Resource.new(
1000
- uri: "https://example.com/my_resource",
1001
- name: "my-resource",
1002
- title: "My Resource",
1003
- description: "Lorem ipsum dolor sit amet",
1004
- mime_type: "text/html",
1120
+ server = MCP::Server.new(name: "my_server")
1121
+ transport = MCP::Server::Transports::StdioTransport.new(server)
1122
+
1123
+ # The client first configures the logging level (on the client side):
1124
+ transport.send_request(
1125
+ request: {
1126
+ jsonrpc: "2.0",
1127
+ method: "logging/setLevel",
1128
+ params: { level: "info" },
1129
+ id: session_id # Unique request ID within the session
1130
+ }
1005
1131
  )
1006
1132
 
1007
- server = MCP::Server.new(
1008
- name: "my_server",
1009
- resources: [resource],
1133
+ # Send log messages at different severity levels
1134
+ server.notify_log_message(
1135
+ data: { message: "Application started successfully" },
1136
+ level: "info"
1137
+ )
1138
+
1139
+ server.notify_log_message(
1140
+ data: { message: "Configuration file not found, using defaults" },
1141
+ level: "warning"
1142
+ )
1143
+
1144
+ server.notify_log_message(
1145
+ data: {
1146
+ error: "Database connection failed",
1147
+ details: { host: "localhost", port: 5432 }
1148
+ },
1149
+ level: "error",
1150
+ logger: "DatabaseLogger" # Optional logger name
1010
1151
  )
1011
1152
  ```
1012
1153
 
1013
- The server must register a handler for the `resources/read` method to retrieve a resource dynamically.
1154
+ **Key Features:**
1155
+
1156
+ - Supports 8 log levels (debug, info, notice, warning, error, critical, alert, emergency) based on https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging#log-levels
1157
+ - Server has capability `logging` to send log messages
1158
+ - Messages are only sent if a transport is configured
1159
+ - Messages are filtered based on the client's configured log level
1160
+ - If the log level hasn't been set by the client, no messages will be sent
1161
+
1162
+ #### Transport Support
1163
+
1164
+ - **stdio**: Notifications are sent as JSON-RPC 2.0 messages to stdout
1165
+ - **Streamable HTTP**: Notifications are sent as JSON-RPC 2.0 messages over HTTP with streaming (chunked transfer or SSE)
1166
+
1167
+ #### Usage Example
1014
1168
 
1015
1169
  ```ruby
1016
- server.resources_read_handler do |params|
1017
- [{
1018
- uri: params[:uri],
1019
- mimeType: "text/plain",
1020
- text: "Hello from example resource! URI: #{params[:uri]}"
1021
- }]
1170
+ server = MCP::Server.new(name: "my_server")
1171
+
1172
+ # Default Streamable HTTP - session oriented
1173
+ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
1174
+
1175
+ # When tools change, notify clients
1176
+ server.define_tool(name: "new_tool") { |**args| { result: "ok" } }
1177
+ server.notify_tools_list_changed
1178
+ ```
1179
+
1180
+ You can use Stateless Streamable HTTP, where notifications are not supported and all calls are request/response interactions.
1181
+ This mode allows for easy multi-node deployment.
1182
+ Set `stateless: true` in `MCP::Server::Transports::StreamableHTTPTransport.new` (`stateless` defaults to `false`):
1183
+
1184
+ ```ruby
1185
+ # Stateless Streamable HTTP - session-less
1186
+ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, stateless: true)
1187
+ ```
1188
+
1189
+ By default, sessions do not expire. To mitigate session hijacking risks, you can set a `session_idle_timeout` (in seconds).
1190
+ When configured, sessions that receive no HTTP requests for this duration are automatically expired and cleaned up:
1191
+
1192
+ ```ruby
1193
+ # Session timeout of 30 minutes
1194
+ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server, session_idle_timeout: 1800)
1195
+ ```
1196
+
1197
+ ### Advanced
1198
+
1199
+ #### Custom Methods
1200
+
1201
+ The server allows you to define custom JSON-RPC methods beyond the standard MCP protocol methods using the `define_custom_method` method:
1202
+
1203
+ ```ruby
1204
+ server = MCP::Server.new(name: "my_server")
1205
+
1206
+ # Define a custom method that returns a result
1207
+ server.define_custom_method(method_name: "add") do |params|
1208
+ params[:a] + params[:b]
1209
+ end
1210
+
1211
+ # Define a custom notification method (returns nil)
1212
+ server.define_custom_method(method_name: "notify") do |params|
1213
+ # Process notification
1214
+ nil
1022
1215
  end
1023
1216
  ```
1024
1217
 
1025
- otherwise `resources/read` requests will be a no-op.
1218
+ **Key Features:**
1026
1219
 
1027
- ### Resource Templates
1220
+ - Accepts any method name as a string
1221
+ - Block receives the request parameters as a hash
1222
+ - Can handle both regular methods (with responses) and notifications
1223
+ - Prevents overriding existing MCP protocol methods
1224
+ - Supports instrumentation callbacks for monitoring
1028
1225
 
1029
- The `MCP::ResourceTemplate` class provides a way to register resource templates with the server.
1226
+ **Usage Example:**
1030
1227
 
1031
1228
  ```ruby
1032
- resource_template = MCP::ResourceTemplate.new(
1033
- uri_template: "https://example.com/my_resource_template",
1034
- name: "my-resource-template",
1035
- title: "My Resource Template",
1036
- description: "Lorem ipsum dolor sit amet",
1037
- mime_type: "text/html",
1038
- )
1229
+ # Client request
1230
+ {
1231
+ "jsonrpc": "2.0",
1232
+ "id": 1,
1233
+ "method": "add",
1234
+ "params": { "a": 5, "b": 3 }
1235
+ }
1039
1236
 
1040
- server = MCP::Server.new(
1041
- name: "my_server",
1042
- resource_templates: [resource_template],
1043
- )
1237
+ # Server response
1238
+ {
1239
+ "jsonrpc": "2.0",
1240
+ "id": 1,
1241
+ "result": 8
1242
+ }
1044
1243
  ```
1045
1244
 
1245
+ **Error Handling:**
1246
+
1247
+ - Raises `MCP::Server::MethodAlreadyDefinedError` if trying to override an existing method
1248
+ - Supports the same exception reporting and instrumentation as standard methods
1249
+
1250
+ ### Unsupported Features (to be implemented in future versions)
1251
+
1252
+ - Resource subscriptions
1253
+ - Elicitation
1254
+
1046
1255
  ## Building an MCP Client
1047
1256
 
1048
1257
  The `MCP::Client` class provides an interface for interacting with MCP servers.
@@ -1056,6 +1265,7 @@ This class supports:
1056
1265
  - Resource reading via the `resources/read` method (`MCP::Client#read_resources`)
1057
1266
  - Prompt listing via the `prompts/list` method (`MCP::Client#prompts`)
1058
1267
  - Prompt retrieval via the `prompts/get` method (`MCP::Client#get_prompt`)
1268
+ - Completion requests via the `completion/complete` method (`MCP::Client#complete`)
1059
1269
  - Automatic JSON-RPC 2.0 message formatting
1060
1270
  - UUID request ID generation
1061
1271
 
@@ -1191,6 +1401,18 @@ client.tools # will make the call using Bearer auth
1191
1401
 
1192
1402
  You can add any custom headers needed for your authentication scheme, or for any other purpose. The client will include these headers on every request.
1193
1403
 
1404
+ #### Customizing the Faraday Connection
1405
+
1406
+ You can pass a block to `MCP::Client::HTTP.new` to customize the underlying Faraday connection.
1407
+ The block is called after the default middleware is configured, so you can add middleware or swap the HTTP adapter:
1408
+
1409
+ ```ruby
1410
+ http_transport = MCP::Client::HTTP.new(url: "https://api.example.com/mcp") do |faraday|
1411
+ faraday.use MyApp::Middleware::HttpRecorder
1412
+ faraday.adapter :typhoeus
1413
+ end
1414
+ ```
1415
+
1194
1416
  ### Tool Objects
1195
1417
 
1196
1418
  The client provides a wrapper class for tools returned by the server: