Skip to content

Commit e330168

Browse files
committed
Merge remote-tracking branch 'upstream/main' into add-basic-http-client-support
2 parents 91d3327 + eb0d9c0 commit e330168

File tree

6 files changed

+74
-32
lines changed

6 files changed

+74
-32
lines changed

README.md

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ class ApplicationController < ActionController::Base
160160
def index
161161
server = MCP::Server.new(
162162
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.
163164
version: "1.0.0",
164165
instructions: "Use the tools of this server as a last resort",
165166
tools: [SomeTool, AnotherTool],
@@ -216,6 +217,7 @@ You can run this script and then type in requests to the server at the command l
216217
$ ruby examples/stdio_server.rb
217218
{"jsonrpc":"2.0","id":"1","method":"ping"}
218219
{"jsonrpc":"2.0","id":"2","method":"tools/list"}
220+
{"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"example_tool","arguments":{"message":"Hello"}}}
219221
```
220222

221223
### Configuration
@@ -422,10 +424,10 @@ e.g. around authentication state.
422424

423425
Tools can include annotations that provide additional metadata about their behavior. The following annotations are supported:
424426

425-
- `destructive_hint`: Indicates if the tool performs destructive operations
426-
- `idempotent_hint`: Indicates if the tool's operations are idempotent
427-
- `open_world_hint`: Indicates if the tool operates in an open world context
428-
- `read_only_hint`: Indicates if the tool only reads data (doesn't modify state)
427+
- `destructive_hint`: Indicates if the tool performs destructive operations. Defaults to true
428+
- `idempotent_hint`: Indicates if the tool's operations are idempotent. Defaults to false
429+
- `open_world_hint`: Indicates if the tool operates in an open world context. Defaults to true
430+
- `read_only_hint`: Indicates if the tool only reads data (doesn't modify state). Defaults to false
429431
- `title`: A human-readable title for the tool
430432

431433
Annotations can be set either through the class definition using the `annotations` class method or when defining a tool using the `define` method.
@@ -560,6 +562,8 @@ This is to avoid potential issues with metric cardinality
560562

561563
MCP spec includes [Resources](https://modelcontextprotocol.io/specification/2025-06-18/server/resources).
562564

565+
### Reading Resources
566+
563567
The `MCP::Resource` class provides a way to register resources with the server.
564568

565569
```ruby
@@ -587,11 +591,29 @@ server.resources_read_handler do |params|
587591
text: "Hello from example resource! URI: #{params[:uri]}"
588592
}]
589593
end
590-
591594
```
592595

593596
otherwise `resources/read` requests will be a no-op.
594597

598+
### Resource Templates
599+
600+
The `MCP::ResourceTemplate` class provides a way to register resource templates with the server.
601+
602+
```ruby
603+
resource_template = MCP::ResourceTemplate.new(
604+
uri_template: "https://example.com/my_resource_template",
605+
name: "my-resource-template",
606+
title: "My Resource Template", # WARNING: This is a `Draft` and is not supported in the `Version 2025-06-18 (latest)` specification.
607+
description: "Lorem ipsum dolor sit amet",
608+
mime_type: "text/html",
609+
)
610+
611+
server = MCP::Server.new(
612+
name: "my_server",
613+
resource_templates: [resource_template],
614+
)
615+
```
616+
595617
## Building an MCP Client
596618

597619
The `MCP::Client` class provides an interface for interacting with MCP servers.

lib/mcp/server.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ def initialize(method_name)
3131

3232
include Instrumentation
3333

34-
attr_accessor :name, :version, :instructions, :tools, :prompts, :resources, :server_context, :configuration, :capabilities, :transport
34+
attr_accessor :name, :title, :version, :instructions, :tools, :prompts, :resources, :server_context, :configuration, :capabilities, :transport
3535

3636
def initialize(
3737
name: "model_context_protocol",
38+
title: nil,
3839
version: DEFAULT_VERSION,
3940
instructions: nil,
4041
tools: [],
@@ -47,6 +48,7 @@ def initialize(
4748
transport: nil
4849
)
4950
@name = name
51+
@title = title
5052
@version = version
5153
@instructions = instructions
5254
@tools = tools.to_h { |t| [t.name_value, t] }
@@ -218,6 +220,7 @@ def default_capabilities
218220
def server_info
219221
@server_info ||= {
220222
name:,
223+
title:,
221224
version:,
222225
}
223226
end

lib/mcp/tool/annotations.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
module MCP
44
class Tool
55
class Annotations
6-
attr_reader :title, :read_only_hint, :destructive_hint, :idempotent_hint, :open_world_hint
6+
attr_reader :destructive_hint, :idempotent_hint, :open_world_hint, :read_only_hint, :title
77

8-
def initialize(title: nil, read_only_hint: nil, destructive_hint: nil, idempotent_hint: nil, open_world_hint: nil)
8+
def initialize(destructive_hint: true, idempotent_hint: false, open_world_hint: true, read_only_hint: false, title: nil)
99
@title = title
1010
@read_only_hint = read_only_hint
1111
@destructive_hint = destructive_hint
@@ -15,11 +15,11 @@ def initialize(title: nil, read_only_hint: nil, destructive_hint: nil, idempoten
1515

1616
def to_h
1717
{
18-
title:,
19-
readOnlyHint: read_only_hint,
2018
destructiveHint: destructive_hint,
2119
idempotentHint: idempotent_hint,
2220
openWorldHint: open_world_hint,
21+
readOnlyHint: read_only_hint,
22+
title:,
2323
}.compact
2424
end
2525
end

test/mcp/server_test.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class ServerTest < ActiveSupport::TestCase
6969

7070
@server = Server.new(
7171
name: @server_name,
72+
title: "Example Server Display Name",
7273
version: "1.2.3",
7374
instructions: "Optional instructions for the client",
7475
tools: [@tool, @tool_that_raises],
@@ -140,6 +141,7 @@ class ServerTest < ActiveSupport::TestCase
140141
},
141142
serverInfo: {
142143
name: @server_name,
144+
title: "Example Server Display Name",
143145
version: "1.2.3",
144146
},
145147
instructions: "Optional instructions for the client",
@@ -769,6 +771,18 @@ def call(message:, server_context: nil)
769771
assert_equal Configuration::DEFAULT_PROTOCOL_VERSION, response[:result][:protocolVersion]
770772
end
771773

774+
test "server uses default title when not configured" do
775+
server = Server.new(name: "test_server")
776+
request = {
777+
jsonrpc: "2.0",
778+
method: "initialize",
779+
id: 1,
780+
}
781+
782+
response = server.handle(request)
783+
assert_nil response[:result][:serverInfo][:title]
784+
end
785+
772786
test "server uses default version when not configured" do
773787
server = Server.new(name: "test_server")
774788
request = {

test/mcp/tool/annotations_test.rb

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,31 @@ class Tool
77
class AnnotationsTest < ActiveSupport::TestCase
88
test "Tool::Annotations initializes with all properties" do
99
annotations = Tool::Annotations.new(
10-
title: "Test Tool",
11-
read_only_hint: true,
1210
destructive_hint: false,
1311
idempotent_hint: true,
1412
open_world_hint: false,
13+
read_only_hint: true,
14+
title: "Test Tool",
1515
)
1616

17-
assert_equal "Test Tool", annotations.title
18-
assert annotations.read_only_hint
1917
refute annotations.destructive_hint
2018
assert annotations.idempotent_hint
2119
refute annotations.open_world_hint
20+
assert annotations.read_only_hint
21+
assert_equal "Test Tool", annotations.title
2222
end
2323

2424
test "Tool::Annotations initializes with partial properties" do
2525
annotations = Tool::Annotations.new(
26-
title: "Test Tool",
2726
read_only_hint: true,
27+
title: "Test Tool",
2828
)
2929

30-
assert_equal "Test Tool", annotations.title
30+
assert annotations.destructive_hint
31+
refute annotations.idempotent_hint
32+
assert annotations.open_world_hint
3133
assert annotations.read_only_hint
32-
assert_nil annotations.destructive_hint
33-
assert_nil annotations.idempotent_hint
34-
assert_nil annotations.open_world_hint
34+
assert_equal "Test Tool", annotations.title
3535
end
3636

3737
test "Tool::Annotations#to_h omits nil values" do
@@ -41,34 +41,37 @@ class AnnotationsTest < ActiveSupport::TestCase
4141
)
4242

4343
expected = {
44-
title: "Test Tool",
44+
destructiveHint: true,
45+
idempotentHint: false,
46+
openWorldHint: true,
4547
readOnlyHint: true,
48+
title: "Test Tool",
4649
}
4750
assert_equal expected, annotations.to_h
4851
end
4952

5053
test "Tool::Annotations#to_h handles all properties" do
5154
annotations = Tool::Annotations.new(
52-
title: "Test Tool",
53-
read_only_hint: true,
5455
destructive_hint: false,
5556
idempotent_hint: true,
5657
open_world_hint: false,
58+
read_only_hint: true,
59+
title: "Test Tool",
5760
)
5861

5962
expected = {
60-
title: "Test Tool",
61-
readOnlyHint: true,
6263
destructiveHint: false,
6364
idempotentHint: true,
6465
openWorldHint: false,
66+
readOnlyHint: true,
67+
title: "Test Tool",
6568
}
6669
assert_equal expected, annotations.to_h
6770
end
6871

69-
test "Tool::Annotations#to_h returns empty hash when all values are nil" do
72+
test "Tool::Annotations#to_h returns hash with default hint values" do
7073
annotations = Tool::Annotations.new
71-
assert_empty annotations.to_h
74+
assert_equal({ destructiveHint: true, idempotentHint: false, openWorldHint: true, readOnlyHint: false }, annotations.to_h)
7275
end
7376
end
7477
end

test/mcp/tool_test.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ class TestTool < Tool
1010
description "a test tool for testing"
1111
input_schema({ properties: { message: { type: "string" } }, required: ["message"] })
1212
annotations(
13-
title: "Test Tool",
14-
read_only_hint: true,
1513
destructive_hint: false,
1614
idempotent_hint: true,
1715
open_world_hint: false,
16+
read_only_hint: true,
17+
title: "Test Tool",
1818
)
1919

2020
class << self
@@ -36,11 +36,11 @@ def call(message:, server_context: nil)
3636
test "#to_h includes annotations when present" do
3737
tool = TestTool
3838
expected_annotations = {
39-
title: "Test Tool",
40-
readOnlyHint: true,
4139
destructiveHint: false,
4240
idempotentHint: true,
4341
openWorldHint: false,
42+
readOnlyHint: true,
43+
title: "Test Tool",
4444
}
4545
assert_equal expected_annotations, tool.to_h[:annotations]
4646
end
@@ -142,15 +142,15 @@ class InputSchemaTool < Tool
142142
assert_equal "Mock Tool", tool.title
143143
assert_equal "a mock tool for testing", tool.description
144144
assert_equal tool.input_schema, Tool::InputSchema.new
145-
assert_equal({ readOnlyHint: true, title: "Mock Tool" }, tool.annotations_value.to_h)
145+
assert_equal({ destructiveHint: true, idempotentHint: false, openWorldHint: true, readOnlyHint: true, title: "Mock Tool" }, tool.annotations_value.to_h)
146146
end
147147

148148
test "Tool class method annotations can be set and retrieved" do
149149
class AnnotationsTestTool < Tool
150150
tool_name "annotations_test"
151151
annotations(
152-
title: "Annotations Test",
153152
read_only_hint: true,
153+
title: "Annotations Test",
154154
)
155155
end
156156

0 commit comments

Comments
 (0)