mcp 0.1.0 → 0.3.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
@@ -1,6 +1,6 @@
1
- # Model Context Protocol
1
+ # MCP Ruby SDK [![Gem Version](https://img.shields.io/gem/v/mcp)](https://rubygems.org/gems/mcp) [![MIT licensed](https://img.shields.io/badge/license-MIT-green)](https://github.com/modelcontextprotocol/ruby-sdk/blob/main/LICENSE.txt) [![CI](https://github.com/modelcontextprotocol/ruby-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/modelcontextprotocol/ruby-sdk/actions/workflows/ci.yml)
2
2
 
3
- A Ruby gem for implementing Model Context Protocol servers
3
+ The official Ruby SDK for Model Context Protocol servers and clients.
4
4
 
5
5
  ## Installation
6
6
 
@@ -12,29 +12,35 @@ gem 'mcp'
12
12
 
13
13
  And then execute:
14
14
 
15
- ```bash
15
+ ```console
16
16
  $ bundle install
17
17
  ```
18
18
 
19
19
  Or install it yourself as:
20
20
 
21
- ```bash
21
+ ```console
22
22
  $ gem install mcp
23
23
  ```
24
24
 
25
- ## MCP Server
25
+ You may need to add additional dependencies depending on which features you wish to access.
26
+
27
+ ## Building an MCP Server
26
28
 
27
29
  The `MCP::Server` class is the core component that handles JSON-RPC requests and responses.
28
30
  It implements the Model Context Protocol specification, handling model context requests and responses.
29
31
 
30
32
  ### Key Features
33
+
31
34
  - Implements JSON-RPC 2.0 message handling
32
35
  - Supports protocol initialization and capability negotiation
33
36
  - Manages tool registration and invocation
34
37
  - Supports prompt registration and execution
35
38
  - Supports resource registration and retrieval
39
+ - Supports stdio & Streamable HTTP (including SSE) transports
40
+ - Supports notifications for list changes (tools, prompts, resources)
36
41
 
37
42
  ### Supported Methods
43
+
38
44
  - `initialize` - Initializes the protocol and returns server capabilities
39
45
  - `ping` - Simple health check
40
46
  - `tools/list` - Lists all registered tools and their schemas
@@ -45,31 +51,118 @@ It implements the Model Context Protocol specification, handling model context r
45
51
  - `resources/read` - Retrieves a specific resource by name
46
52
  - `resources/templates/list` - Lists all registered resource templates and their schemas
47
53
 
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 three 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
+
117
+ #### Notification Format
118
+
119
+ Notifications follow the JSON-RPC 2.0 specification and use these method names:
120
+
121
+ - `notifications/tools/list_changed`
122
+ - `notifications/prompts/list_changed`
123
+ - `notifications/resources/list_changed`
124
+
125
+ #### Transport Support
126
+
127
+ - **stdio**: Notifications are sent as JSON-RPC 2.0 messages to stdout
128
+ - **Streamable HTTP**: Notifications are sent as JSON-RPC 2.0 messages over HTTP with streaming (chunked transfer or SSE)
129
+
130
+ #### Usage Example
131
+
132
+ ```ruby
133
+ server = MCP::Server.new(name: "my_server")
134
+ transport = MCP::Transports::HTTP.new(server)
135
+ server.transport = transport
136
+
137
+ # When tools change, notify clients
138
+ server.define_tool(name: "new_tool") { |**args| { result: "ok" } }
139
+ server.notify_tools_list_changed
140
+ ```
141
+
48
142
  ### Unsupported Features ( to be implemented in future versions )
49
143
 
50
- - Notifications
51
144
  - Log Level
52
145
  - Resource subscriptions
53
146
  - Completions
54
- - Complete StreamableHTTP implementation with streaming responses
55
147
 
56
148
  ### Usage
57
149
 
58
150
  #### Rails Controller
59
151
 
60
152
  When added to a Rails controller on a route that handles POST requests, your server will be compliant with non-streaming
61
- [StreamableHTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) transport
153
+ [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) transport
62
154
  requests.
63
155
 
64
156
  You can use the `Server#handle_json` method to handle requests.
65
157
 
66
158
  ```ruby
67
159
  class ApplicationController < ActionController::Base
68
-
69
160
  def index
70
161
  server = MCP::Server.new(
71
162
  name: "my_server",
163
+ title: "Example Server Display Name", # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
72
164
  version: "1.0.0",
165
+ instructions: "Use the tools of this server as a last resort",
73
166
  tools: [SomeTool, AnotherTool],
74
167
  prompts: [MyPrompt],
75
168
  server_context: { user_id: current_user.id },
@@ -84,9 +177,8 @@ end
84
177
  If you want to build a local command-line application, you can use the stdio transport:
85
178
 
86
179
  ```ruby
87
- #!/usr/bin/env ruby
88
180
  require "mcp"
89
- require "mcp/transports/stdio"
181
+ require "mcp/server/transports/stdio_transport"
90
182
 
91
183
  # Create a simple tool
92
184
  class ExampleTool < MCP::Tool
@@ -115,20 +207,20 @@ server = MCP::Server.new(
115
207
  )
116
208
 
117
209
  # Create and start the transport
118
- transport = MCP::Transports::StdioTransport.new(server)
210
+ transport = MCP::Server::Transports::StdioTransport.new(server)
119
211
  transport.open
120
212
  ```
121
213
 
122
214
  You can run this script and then type in requests to the server at the command line.
123
215
 
124
- ```
125
- $ ./stdio_server.rb
126
- {"jsonrpc":"2.0","id":"1","result":"pong"}
127
- {"jsonrpc":"2.0","id":"2","result":["ExampleTool"]}
128
- {"jsonrpc":"2.0","id":"3","result":["ExampleTool"]}
216
+ ```console
217
+ $ ruby examples/stdio_server.rb
218
+ {"jsonrpc":"2.0","id":"1","method":"ping"}
219
+ {"jsonrpc":"2.0","id":"2","method":"tools/list"}
220
+ {"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"example_tool","arguments":{"message":"Hello"}}}
129
221
  ```
130
222
 
131
- ## Configuration
223
+ ### Configuration
132
224
 
133
225
  The gem can be configured using the `MCP.configure` block:
134
226
 
@@ -179,11 +271,13 @@ server = MCP::Server.new(
179
271
  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.
180
272
 
181
273
  **Type:**
274
+
182
275
  ```ruby
183
276
  server_context: { [String, Symbol] => Any }
184
277
  ```
185
278
 
186
279
  **Example:**
280
+
187
281
  ```ruby
188
282
  server = MCP::Server.new(
189
283
  name: "my_server",
@@ -203,6 +297,7 @@ The exception reporter receives:
203
297
  - `server_context`: The context hash provided to the server
204
298
 
205
299
  **Signature:**
300
+
206
301
  ```ruby
207
302
  exception_reporter = ->(exception, server_context) { ... }
208
303
  ```
@@ -219,12 +314,14 @@ The instrumentation callback receives a hash with the following possible keys:
219
314
  - `duration`: (Float) Duration of the call in seconds
220
315
 
221
316
  **Type:**
317
+
222
318
  ```ruby
223
319
  instrumentation_callback = ->(data) { ... }
224
320
  # where data is a Hash with keys as described above
225
321
  ```
226
322
 
227
323
  **Example:**
324
+
228
325
  ```ruby
229
326
  config.instrumentation_callback = ->(data) {
230
327
  puts "Instrumentation: #{data.inspect}"
@@ -233,19 +330,22 @@ config.instrumentation_callback = ->(data) {
233
330
 
234
331
  ### Server Protocol Version
235
332
 
236
- The server's protocol version can be overridden using the `protocol_version` class method:
333
+ The server's protocol version can be overridden using the `protocol_version` keyword argument:
237
334
 
238
335
  ```ruby
239
- MCP::Server.protocol_version = "2024-11-05"
336
+ configuration = MCP::Configuration.new(protocol_version: "2024-11-05")
337
+ MCP::Server.new(name: "test_server", configuration: configuration)
240
338
  ```
241
339
 
242
340
  This will make all new server instances use the specified protocol version instead of the default version. The protocol version can be reset to the default by setting it to `nil`:
243
341
 
244
342
  ```ruby
245
- MCP::Server.protocol_version = nil
343
+ MCP::Configuration.new(protocol_version: nil)
246
344
  ```
247
345
 
248
- Be sure to check the [MCP spec](https://spec.modelcontextprotocol.io/specification/2024-11-05/) for the protocol version to understand the supported features for the version being set.
346
+ If an invalid `protocol_version` value is set, an `ArgumentError` is raised.
347
+
348
+ Be sure to check the [MCP spec](https://modelcontextprotocol.io/specification/versioning) for the protocol version to understand the supported features for the version being set.
249
349
 
250
350
  ### Exception Reporting
251
351
 
@@ -267,16 +367,17 @@ When an exception occurs:
267
367
 
268
368
  If no exception reporter is configured, a default no-op reporter is used that silently ignores exceptions.
269
369
 
270
- ## Tools
370
+ ### Tools
271
371
 
272
- MCP spec includes [Tools](https://modelcontextprotocol.io/docs/concepts/tools) which provide functionality to LLM apps.
372
+ MCP spec includes [Tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) which provide functionality to LLM apps.
273
373
 
274
- This gem provides a `MCP::Tool` class that can be used to create tools in two ways:
374
+ This gem provides a `MCP::Tool` class that can be used to create tools in three ways:
275
375
 
276
376
  1. As a class definition:
277
377
 
278
378
  ```ruby
279
379
  class MyTool < MCP::Tool
380
+ title "My Tool" # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
280
381
  description "This tool performs specific functionality..."
281
382
  input_schema(
282
383
  properties: {
@@ -284,12 +385,20 @@ class MyTool < MCP::Tool
284
385
  },
285
386
  required: ["message"]
286
387
  )
388
+ output_schema(
389
+ properties: {
390
+ result: { type: "string" },
391
+ success: { type: "boolean" },
392
+ timestamp: { type: "string", format: "date-time" }
393
+ },
394
+ required: ["result", "success", "timestamp"]
395
+ )
287
396
  annotations(
288
- title: "My Tool",
289
397
  read_only_hint: true,
290
398
  destructive_hint: false,
291
399
  idempotent_hint: true,
292
- open_world_hint: false
400
+ open_world_hint: false,
401
+ title: "My Tool"
293
402
  )
294
403
 
295
404
  def self.call(message:, server_context:)
@@ -304,6 +413,23 @@ tool = MyTool
304
413
 
305
414
  ```ruby
306
415
  tool = MCP::Tool.define(
416
+ name: "my_tool",
417
+ title: "My Tool", # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
418
+ description: "This tool performs specific functionality...",
419
+ annotations: {
420
+ read_only_hint: true,
421
+ title: "My Tool"
422
+ }
423
+ ) do |args, server_context|
424
+ MCP::Tool::Response.new([{ type: "text", text: "OK" }])
425
+ end
426
+ ```
427
+
428
+ 3. By using the `ModelContextProtocol::Server#define_tool` method with a block:
429
+
430
+ ```ruby
431
+ server = ModelContextProtocol::Server.new
432
+ server.define_tool(
307
433
  name: "my_tool",
308
434
  description: "This tool performs specific functionality...",
309
435
  annotations: {
@@ -322,28 +448,128 @@ e.g. around authentication state.
322
448
 
323
449
  Tools can include annotations that provide additional metadata about their behavior. The following annotations are supported:
324
450
 
451
+ - `destructive_hint`: Indicates if the tool performs destructive operations. Defaults to true
452
+ - `idempotent_hint`: Indicates if the tool's operations are idempotent. Defaults to false
453
+ - `open_world_hint`: Indicates if the tool operates in an open world context. Defaults to true
454
+ - `read_only_hint`: Indicates if the tool only reads data (doesn't modify state). Defaults to false
325
455
  - `title`: A human-readable title for the tool
326
- - `read_only_hint`: Indicates if the tool only reads data (doesn't modify state)
327
- - `destructive_hint`: Indicates if the tool performs destructive operations
328
- - `idempotent_hint`: Indicates if the tool's operations are idempotent
329
- - `open_world_hint`: Indicates if the tool operates in an open world context
330
456
 
331
457
  Annotations can be set either through the class definition using the `annotations` class method or when defining a tool using the `define` method.
332
458
 
333
- ## Prompts
459
+ > [!NOTE]
460
+ > This **Tool Annotations** feature is supported starting from `protocol_version: '2025-03-26'`.
461
+
462
+ ### Tool Output Schemas
463
+
464
+ Tools can optionally define an `output_schema` to specify the expected structure of their results. This works similarly to how `input_schema` is defined and can be used in three ways:
465
+
466
+ 1. **Class definition with output_schema:**
467
+
468
+ ```ruby
469
+ class WeatherTool < MCP::Tool
470
+ tool_name "get_weather"
471
+ description "Get current weather for a location"
472
+
473
+ input_schema(
474
+ properties: {
475
+ location: { type: "string" },
476
+ units: { type: "string", enum: ["celsius", "fahrenheit"] }
477
+ },
478
+ required: ["location"]
479
+ )
480
+
481
+ output_schema(
482
+ properties: {
483
+ temperature: { type: "number" },
484
+ condition: { type: "string" },
485
+ humidity: { type: "integer" }
486
+ },
487
+ required: ["temperature", "condition", "humidity"]
488
+ )
489
+
490
+ def self.call(location:, units: "celsius", server_context:)
491
+ # Call weather API and structure the response
492
+ api_response = WeatherAPI.fetch(location, units)
493
+ weather_data = {
494
+ temperature: api_response.temp,
495
+ condition: api_response.description,
496
+ humidity: api_response.humidity_percent
497
+ }
498
+
499
+ output_schema.validate_result(weather_data)
500
+
501
+ MCP::Tool::Response.new([{
502
+ type: "text",
503
+ text: weather_data.to_json
504
+ }])
505
+ end
506
+ end
507
+ ```
508
+
509
+ 2. **Using Tool.define with output_schema:**
510
+
511
+ ```ruby
512
+ tool = MCP::Tool.define(
513
+ name: "calculate_stats",
514
+ description: "Calculate statistics for a dataset",
515
+ input_schema: {
516
+ properties: {
517
+ numbers: { type: "array", items: { type: "number" } }
518
+ },
519
+ required: ["numbers"]
520
+ },
521
+ output_schema: {
522
+ properties: {
523
+ mean: { type: "number" },
524
+ median: { type: "number" },
525
+ count: { type: "integer" }
526
+ },
527
+ required: ["mean", "median", "count"]
528
+ }
529
+ ) do |args, server_context|
530
+ # Calculate statistics and validate against schema
531
+ MCP::Tool::Response.new([{ type: "text", text: "Statistics calculated" }])
532
+ end
533
+ ```
534
+
535
+ 3. **Using OutputSchema objects:**
536
+
537
+ ```ruby
538
+ class DataTool < MCP::Tool
539
+ output_schema MCP::Tool::OutputSchema.new(
540
+ properties: {
541
+ success: { type: "boolean" },
542
+ data: { type: "object" }
543
+ },
544
+ required: ["success"]
545
+ )
546
+ end
547
+ ```
548
+
549
+ MCP spec for the [Output Schema](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema) specifies that:
550
+
551
+ - **Server Validation**: Servers MUST provide structured results that conform to the output schema
552
+ - **Client Validation**: Clients SHOULD validate structured results against the output schema
553
+ - **Better Integration**: Enables strict schema validation, type information, and improved developer experience
554
+ - **Backward Compatibility**: Tools returning structured content SHOULD also include serialized JSON in a TextContent block
555
+
556
+ The output schema follows standard JSON Schema format and helps ensure consistent data exchange between MCP servers and clients.
334
557
 
335
- MCP spec includes [Prompts](https://modelcontextprotocol.io/docs/concepts/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs.
558
+ ### Prompts
336
559
 
337
- The `MCP::Prompt` class provides two ways to create prompts:
560
+ MCP spec includes [Prompts](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs.
561
+
562
+ The `MCP::Prompt` class provides three ways to create prompts:
338
563
 
339
564
  1. As a class definition with metadata:
340
565
 
341
566
  ```ruby
342
567
  class MyPrompt < MCP::Prompt
343
568
  prompt_name "my_prompt" # Optional - defaults to underscored class name
569
+ title "My Prompt" # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
344
570
  description "This prompt performs specific functionality..."
345
571
  arguments [
346
- Prompt::Argument.new(
572
+ MCP::Prompt::Argument.new(
347
573
  name: "message",
348
574
  description: "Input message",
349
575
  required: true
@@ -352,16 +578,16 @@ class MyPrompt < MCP::Prompt
352
578
 
353
579
  class << self
354
580
  def template(args, server_context:)
355
- Prompt::Result.new(
581
+ MCP::Prompt::Result.new(
356
582
  description: "Response description",
357
583
  messages: [
358
- Prompt::Message.new(
584
+ MCP::Prompt::Message.new(
359
585
  role: "user",
360
- content: Content::Text.new("User message")
586
+ content: MCP::Content::Text.new("User message")
361
587
  ),
362
- Prompt::Message.new(
588
+ MCP::Prompt::Message.new(
363
589
  role: "assistant",
364
- content: Content::Text.new(args["message"])
590
+ content: MCP::Content::Text.new(args["message"])
365
591
  )
366
592
  ]
367
593
  )
@@ -376,6 +602,38 @@ prompt = MyPrompt
376
602
 
377
603
  ```ruby
378
604
  prompt = MCP::Prompt.define(
605
+ name: "my_prompt",
606
+ title: "My Prompt", # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
607
+ description: "This prompt performs specific functionality...",
608
+ arguments: [
609
+ MCP::Prompt::Argument.new(
610
+ name: "message",
611
+ description: "Input message",
612
+ required: true
613
+ )
614
+ ]
615
+ ) do |args, server_context:|
616
+ MCP::Prompt::Result.new(
617
+ description: "Response description",
618
+ messages: [
619
+ MCP::Prompt::Message.new(
620
+ role: "user",
621
+ content: MCP::Content::Text.new("User message")
622
+ ),
623
+ MCP::Prompt::Message.new(
624
+ role: "assistant",
625
+ content: MCP::Content::Text.new(args["message"])
626
+ )
627
+ ]
628
+ )
629
+ end
630
+ ```
631
+
632
+ 3. Using the `ModelContextProtocol::Server#define_protocol` method:
633
+
634
+ ```ruby
635
+ server = ModelContextProtocol::Server.new
636
+ server.define_protocol(
379
637
  name: "my_prompt",
380
638
  description: "This prompt performs specific functionality...",
381
639
  arguments: [
@@ -407,10 +665,10 @@ e.g. around authentication state or user preferences.
407
665
 
408
666
  ### Key Components
409
667
 
410
- - `Prompt::Argument` - Defines input parameters for the prompt template
411
- - `Prompt::Message` - Represents a message in the conversation with a role and content
412
- - `Prompt::Result` - The output of a prompt template containing description and messages
413
- - `Content::Text` - Text content for messages
668
+ - `MCP::Prompt::Argument` - Defines input parameters for the prompt template
669
+ - `MCP::Prompt::Message` - Represents a message in the conversation with a role and content
670
+ - `MCP::Prompt::Result` - The output of a prompt template containing description and messages
671
+ - `MCP::Content::Text` - Text content for messages
414
672
 
415
673
  ### Usage
416
674
 
@@ -438,32 +696,37 @@ To register a handler pass a proc/lambda to as `instrumentation_callback` into t
438
696
  MCP.configure do |config|
439
697
  config.instrumentation_callback = ->(data) {
440
698
  puts "Got instrumentation data #{data.inspect}"
441
- end
442
- }
699
+ }
700
+ end
443
701
  ```
444
702
 
445
703
  The data contains the following keys:
446
- `method`: the metod called, e.g. `ping`, `tools/list`, `tools/call` etc
447
- `tool_name`: the name of the tool called
448
- `prompt_name`: the name of the prompt called
449
- `resource_uri`: the uri of the resource called
450
- `error`: if looking up tools/prompts etc failed, e.g. `tool_not_found`
451
- `duration`: the duration of the call in seconds
704
+
705
+ - `method`: the method called, e.g. `ping`, `tools/list`, `tools/call` etc
706
+ - `tool_name`: the name of the tool called
707
+ - `prompt_name`: the name of the prompt called
708
+ - `resource_uri`: the uri of the resource called
709
+ - `error`: if looking up tools/prompts etc failed, e.g. `tool_not_found`
710
+ - `duration`: the duration of the call in seconds
452
711
 
453
712
  `tool_name`, `prompt_name` and `resource_uri` are only populated if a matching handler is registered.
454
713
  This is to avoid potential issues with metric cardinality
455
714
 
456
- ## Resources
715
+ ### Resources
716
+
717
+ MCP spec includes [Resources](https://modelcontextprotocol.io/specification/2025-06-18/server/resources).
457
718
 
458
- MCP spec includes [Resources](https://modelcontextprotocol.io/docs/concepts/resources)
719
+ ### Reading Resources
459
720
 
460
721
  The `MCP::Resource` class provides a way to register resources with the server.
461
722
 
462
723
  ```ruby
463
724
  resource = MCP::Resource.new(
464
- uri: "example.com/my_resource",
465
- mime_type: "text/plain",
466
- text: "Lorem ipsum dolor sit amet"
725
+ uri: "https://example.com/my_resource",
726
+ name: "my-resource",
727
+ title: "My Resource", # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
728
+ description: "Lorem ipsum dolor sit amet",
729
+ mime_type: "text/html",
467
730
  )
468
731
 
469
732
  server = MCP::Server.new(
@@ -479,13 +742,127 @@ server.resources_read_handler do |params|
479
742
  [{
480
743
  uri: params[:uri],
481
744
  mimeType: "text/plain",
482
- text: "Hello, world!",
745
+ text: "Hello from example resource! URI: #{params[:uri]}"
483
746
  }]
484
747
  end
748
+ ```
749
+
750
+ otherwise `resources/read` requests will be a no-op.
751
+
752
+ ### Resource Templates
753
+
754
+ The `MCP::ResourceTemplate` class provides a way to register resource templates with the server.
755
+
756
+ ```ruby
757
+ resource_template = MCP::ResourceTemplate.new(
758
+ uri_template: "https://example.com/my_resource_template",
759
+ name: "my-resource-template",
760
+ title: "My Resource Template", # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
761
+ description: "Lorem ipsum dolor sit amet",
762
+ mime_type: "text/html",
763
+ )
764
+
765
+ server = MCP::Server.new(
766
+ name: "my_server",
767
+ resource_templates: [resource_template],
768
+ )
769
+ ```
770
+
771
+ ## Building an MCP Client
772
+
773
+ The `MCP::Client` class provides an interface for interacting with MCP servers.
774
+
775
+ This class supports:
776
+
777
+ - Tool listing via the `tools/list` method
778
+ - Tool invocation via the `tools/call` method
779
+ - Automatic JSON-RPC 2.0 message formatting
780
+ - UUID request ID generation
781
+
782
+ Clients are initialized with a transport layer instance that handles the low-level communication mechanics.
783
+ Authorization is handled by the transport layer.
784
+
785
+ ## Transport Layer Interface
786
+
787
+ If the transport layer you need is not included in the gem, you can build and pass your own instances so long as they conform to the following interface:
788
+
789
+ ```ruby
790
+ class CustomTransport
791
+ # Sends a JSON-RPC request to the server and returns the raw response.
792
+ #
793
+ # @param request [Hash] A complete JSON-RPC request object.
794
+ # https://www.jsonrpc.org/specification#request_object
795
+ # @return [Hash] A hash modeling a JSON-RPC response object.
796
+ # https://www.jsonrpc.org/specification#response_object
797
+ def send_request(request:)
798
+ # Your transport-specific logic here
799
+ # - HTTP: POST to endpoint with JSON body
800
+ # - WebSocket: Send message over WebSocket
801
+ # - stdio: Write to stdout, read from stdin
802
+ # - etc.
803
+ end
804
+ end
805
+ ```
806
+
807
+ ### HTTP Transport Layer
808
+
809
+ Use the `MCP::Client::HTTP` transport to interact with MCP servers using simple HTTP requests.
485
810
 
811
+ You'll need to add `faraday` as a dependency in order to use the HTTP transport layer:
812
+
813
+ ```ruby
814
+ gem 'mcp'
815
+ gem 'faraday', '>= 2.0'
816
+ ```
817
+
818
+ Example usage:
819
+
820
+ ```ruby
821
+ http_transport = MCP::Client::HTTP.new(url: "https://api.example.com/mcp")
822
+ client = MCP::Client.new(transport: http_transport)
823
+
824
+ # List available tools
825
+ tools = client.tools
826
+ tools.each do |tool|
827
+ puts <<~TOOL_INFORMATION
828
+ Tool: #{tool.name}
829
+ Description: #{tool.description}
830
+ Input Schema: #{tool.input_schema}
831
+ TOOL_INFORMATION
832
+ end
833
+
834
+ # Call a specific tool
835
+ response = client.call_tool(
836
+ tool: tools.first,
837
+ arguments: { message: "Hello, world!" }
838
+ )
839
+ ```
840
+
841
+ #### HTTP Authorization
842
+
843
+ By default, the HTTP transport layer provides no authentication to the server, but you can provide custom headers if you need authentication. For example, to use Bearer token authentication:
844
+
845
+ ```ruby
846
+ http_transport = MCP::Client::HTTP.new(
847
+ url: "https://api.example.com/mcp",
848
+ headers: {
849
+ "Authorization" => "Bearer my_token"
850
+ }
851
+ )
852
+
853
+ client = MCP::Client.new(transport: http_transport)
854
+ client.tools # will make the call using Bearer auth
486
855
  ```
487
856
 
488
- otherwise 'resources/read' requests will be a no-op.
857
+ 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.
858
+
859
+ ### Tool Objects
860
+
861
+ The client provides a wrapper class for tools returned by the server:
862
+
863
+ - `MCP::Client::Tool` - Represents a single tool with its metadata
864
+
865
+ This class provides easy access to tool properties like name, description, input schema, and output schema.
489
866
 
490
867
  ## Releases
491
868
 
@@ -494,7 +871,8 @@ This gem is published to [RubyGems.org](https://rubygems.org/gems/mcp)
494
871
  Releases are triggered by PRs to the `main` branch updating the version number in `lib/mcp/version.rb`.
495
872
 
496
873
  1. **Update the version number** in `lib/mcp/version.rb`, following [semver](https://semver.org/)
497
- 2. **Create A PR and get approval from a maintainer**
498
- 3. **Merge your PR to the main branch** - This will automatically trigger the release workflow via GitHub Actions
874
+ 1. **Update CHANGELOG.md**, backfilling the changes since the last release if necessary, and adding a new section for the new version, clearing out the Unreleased section
875
+ 1. **Create a PR and get approval from a maintainer**
876
+ 1. **Merge your PR to the main branch** - This will automatically trigger the release workflow via GitHub Actions
499
877
 
500
878
  When changes are merged to the `main` branch, the GitHub Actions workflow (`.github/workflows/release.yml`) is triggered and the gem is published to RubyGems.