Set default values for ToolAnnotations hints
According to the specification, `destructive_hint`, `idempotent_hint`, `open_world_hint`, and `read_only_hint` of `ToolAnnotations` are optional with default values: https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations This PR sets their default values in accordance with the specification. Additionally, for readability, the hints have been reordered to follow the specification.
This commit is contained in:
parent
9486c63545
commit
cae30547d2
4 changed files with 33 additions and 30 deletions
|
|
@ -420,10 +420,10 @@ e.g. around authentication state.
|
||||||
|
|
||||||
Tools can include annotations that provide additional metadata about their behavior. The following annotations are supported:
|
Tools can include annotations that provide additional metadata about their behavior. The following annotations are supported:
|
||||||
|
|
||||||
- `destructive_hint`: Indicates if the tool performs destructive operations
|
- `destructive_hint`: Indicates if the tool performs destructive operations. Defaults to true
|
||||||
- `idempotent_hint`: Indicates if the tool's operations are idempotent
|
- `idempotent_hint`: Indicates if the tool's operations are idempotent. Defaults to false
|
||||||
- `open_world_hint`: Indicates if the tool operates in an open world context
|
- `open_world_hint`: Indicates if the tool operates in an open world context. Defaults to true
|
||||||
- `read_only_hint`: Indicates if the tool only reads data (doesn't modify state)
|
- `read_only_hint`: Indicates if the tool only reads data (doesn't modify state). Defaults to false
|
||||||
- `title`: A human-readable title for the tool
|
- `title`: A human-readable title for the tool
|
||||||
|
|
||||||
Annotations can be set either through the class definition using the `annotations` class method or when defining a tool using the `define` method.
|
Annotations can be set either through the class definition using the `annotations` class method or when defining a tool using the `define` method.
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
module MCP
|
module MCP
|
||||||
class Tool
|
class Tool
|
||||||
class Annotations
|
class Annotations
|
||||||
attr_reader :title, :read_only_hint, :destructive_hint, :idempotent_hint, :open_world_hint
|
attr_reader :destructive_hint, :idempotent_hint, :open_world_hint, :read_only_hint, :title
|
||||||
|
|
||||||
def initialize(title: nil, read_only_hint: nil, destructive_hint: nil, idempotent_hint: nil, open_world_hint: nil)
|
def initialize(destructive_hint: true, idempotent_hint: false, open_world_hint: true, read_only_hint: false, title: nil)
|
||||||
@title = title
|
@title = title
|
||||||
@read_only_hint = read_only_hint
|
@read_only_hint = read_only_hint
|
||||||
@destructive_hint = destructive_hint
|
@destructive_hint = destructive_hint
|
||||||
|
|
@ -15,11 +15,11 @@ module MCP
|
||||||
|
|
||||||
def to_h
|
def to_h
|
||||||
{
|
{
|
||||||
title:,
|
|
||||||
readOnlyHint: read_only_hint,
|
|
||||||
destructiveHint: destructive_hint,
|
destructiveHint: destructive_hint,
|
||||||
idempotentHint: idempotent_hint,
|
idempotentHint: idempotent_hint,
|
||||||
openWorldHint: open_world_hint,
|
openWorldHint: open_world_hint,
|
||||||
|
readOnlyHint: read_only_hint,
|
||||||
|
title:,
|
||||||
}.compact
|
}.compact
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -7,31 +7,31 @@ module MCP
|
||||||
class AnnotationsTest < ActiveSupport::TestCase
|
class AnnotationsTest < ActiveSupport::TestCase
|
||||||
test "Tool::Annotations initializes with all properties" do
|
test "Tool::Annotations initializes with all properties" do
|
||||||
annotations = Tool::Annotations.new(
|
annotations = Tool::Annotations.new(
|
||||||
title: "Test Tool",
|
|
||||||
read_only_hint: true,
|
|
||||||
destructive_hint: false,
|
destructive_hint: false,
|
||||||
idempotent_hint: true,
|
idempotent_hint: true,
|
||||||
open_world_hint: false,
|
open_world_hint: false,
|
||||||
|
read_only_hint: true,
|
||||||
|
title: "Test Tool",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert_equal "Test Tool", annotations.title
|
|
||||||
assert annotations.read_only_hint
|
|
||||||
refute annotations.destructive_hint
|
refute annotations.destructive_hint
|
||||||
assert annotations.idempotent_hint
|
assert annotations.idempotent_hint
|
||||||
refute annotations.open_world_hint
|
refute annotations.open_world_hint
|
||||||
|
assert annotations.read_only_hint
|
||||||
|
assert_equal "Test Tool", annotations.title
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Tool::Annotations initializes with partial properties" do
|
test "Tool::Annotations initializes with partial properties" do
|
||||||
annotations = Tool::Annotations.new(
|
annotations = Tool::Annotations.new(
|
||||||
title: "Test Tool",
|
|
||||||
read_only_hint: true,
|
read_only_hint: true,
|
||||||
|
title: "Test Tool",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert_equal "Test Tool", annotations.title
|
assert annotations.destructive_hint
|
||||||
|
refute annotations.idempotent_hint
|
||||||
|
assert annotations.open_world_hint
|
||||||
assert annotations.read_only_hint
|
assert annotations.read_only_hint
|
||||||
assert_nil annotations.destructive_hint
|
assert_equal "Test Tool", annotations.title
|
||||||
assert_nil annotations.idempotent_hint
|
|
||||||
assert_nil annotations.open_world_hint
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Tool::Annotations#to_h omits nil values" do
|
test "Tool::Annotations#to_h omits nil values" do
|
||||||
|
|
@ -41,34 +41,37 @@ module MCP
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
title: "Test Tool",
|
destructiveHint: true,
|
||||||
|
idempotentHint: false,
|
||||||
|
openWorldHint: true,
|
||||||
readOnlyHint: true,
|
readOnlyHint: true,
|
||||||
|
title: "Test Tool",
|
||||||
}
|
}
|
||||||
assert_equal expected, annotations.to_h
|
assert_equal expected, annotations.to_h
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Tool::Annotations#to_h handles all properties" do
|
test "Tool::Annotations#to_h handles all properties" do
|
||||||
annotations = Tool::Annotations.new(
|
annotations = Tool::Annotations.new(
|
||||||
title: "Test Tool",
|
|
||||||
read_only_hint: true,
|
|
||||||
destructive_hint: false,
|
destructive_hint: false,
|
||||||
idempotent_hint: true,
|
idempotent_hint: true,
|
||||||
open_world_hint: false,
|
open_world_hint: false,
|
||||||
|
read_only_hint: true,
|
||||||
|
title: "Test Tool",
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
title: "Test Tool",
|
|
||||||
readOnlyHint: true,
|
|
||||||
destructiveHint: false,
|
destructiveHint: false,
|
||||||
idempotentHint: true,
|
idempotentHint: true,
|
||||||
openWorldHint: false,
|
openWorldHint: false,
|
||||||
|
readOnlyHint: true,
|
||||||
|
title: "Test Tool",
|
||||||
}
|
}
|
||||||
assert_equal expected, annotations.to_h
|
assert_equal expected, annotations.to_h
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Tool::Annotations#to_h returns empty hash when all values are nil" do
|
test "Tool::Annotations#to_h returns hash with default hint values" do
|
||||||
annotations = Tool::Annotations.new
|
annotations = Tool::Annotations.new
|
||||||
assert_empty annotations.to_h
|
assert_equal({ destructiveHint: true, idempotentHint: false, openWorldHint: true, readOnlyHint: false }, annotations.to_h)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ module MCP
|
||||||
description "a test tool for testing"
|
description "a test tool for testing"
|
||||||
input_schema({ properties: { message: { type: "string" } }, required: ["message"] })
|
input_schema({ properties: { message: { type: "string" } }, required: ["message"] })
|
||||||
annotations(
|
annotations(
|
||||||
title: "Test Tool",
|
|
||||||
read_only_hint: true,
|
|
||||||
destructive_hint: false,
|
destructive_hint: false,
|
||||||
idempotent_hint: true,
|
idempotent_hint: true,
|
||||||
open_world_hint: false,
|
open_world_hint: false,
|
||||||
|
read_only_hint: true,
|
||||||
|
title: "Test Tool",
|
||||||
)
|
)
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
|
@ -36,11 +36,11 @@ module MCP
|
||||||
test "#to_h includes annotations when present" do
|
test "#to_h includes annotations when present" do
|
||||||
tool = TestTool
|
tool = TestTool
|
||||||
expected_annotations = {
|
expected_annotations = {
|
||||||
title: "Test Tool",
|
|
||||||
readOnlyHint: true,
|
|
||||||
destructiveHint: false,
|
destructiveHint: false,
|
||||||
idempotentHint: true,
|
idempotentHint: true,
|
||||||
openWorldHint: false,
|
openWorldHint: false,
|
||||||
|
readOnlyHint: true,
|
||||||
|
title: "Test Tool",
|
||||||
}
|
}
|
||||||
assert_equal expected_annotations, tool.to_h[:annotations]
|
assert_equal expected_annotations, tool.to_h[:annotations]
|
||||||
end
|
end
|
||||||
|
|
@ -142,15 +142,15 @@ module MCP
|
||||||
assert_equal "Mock Tool", tool.title
|
assert_equal "Mock Tool", tool.title
|
||||||
assert_equal "a mock tool for testing", tool.description
|
assert_equal "a mock tool for testing", tool.description
|
||||||
assert_equal tool.input_schema, Tool::InputSchema.new
|
assert_equal tool.input_schema, Tool::InputSchema.new
|
||||||
assert_equal({ readOnlyHint: true, title: "Mock Tool" }, tool.annotations_value.to_h)
|
assert_equal({ destructiveHint: true, idempotentHint: false, openWorldHint: true, readOnlyHint: true, title: "Mock Tool" }, tool.annotations_value.to_h)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Tool class method annotations can be set and retrieved" do
|
test "Tool class method annotations can be set and retrieved" do
|
||||||
class AnnotationsTestTool < Tool
|
class AnnotationsTestTool < Tool
|
||||||
tool_name "annotations_test"
|
tool_name "annotations_test"
|
||||||
annotations(
|
annotations(
|
||||||
title: "Annotations Test",
|
|
||||||
read_only_hint: true,
|
read_only_hint: true,
|
||||||
|
title: "Annotations Test",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue